mirror of
https://github.com/iNavFlight/inav.git
synced 2025-07-12 19:10:27 +03:00
4173 lines
151 KiB
C
4173 lines
151 KiB
C
/*
|
|
* This file is part of Cleanflight.
|
|
*
|
|
* Cleanflight is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Cleanflight is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include "common/log.h" //for MSP_SIMULATOR
|
|
#include "platform.h"
|
|
|
|
#include "blackbox/blackbox.h"
|
|
|
|
#include "build/debug.h"
|
|
#include "build/version.h"
|
|
|
|
#include "common/axis.h"
|
|
#include "common/color.h"
|
|
#include "common/maths.h"
|
|
#include "common/streambuf.h"
|
|
#include "common/bitarray.h"
|
|
#include "common/time.h"
|
|
#include "common/utils.h"
|
|
#include "programming/global_variables.h"
|
|
#include "programming/pid.h"
|
|
|
|
#include "config/parameter_group_ids.h"
|
|
|
|
#include "drivers/accgyro/accgyro.h"
|
|
#include "drivers/compass/compass.h"
|
|
#include "drivers/compass/compass_msp.h"
|
|
#include "drivers/barometer/barometer_msp.h"
|
|
#include "drivers/pitotmeter/pitotmeter_msp.h"
|
|
#include "sensors/battery_sensor_fake.h"
|
|
#include "drivers/bus_i2c.h"
|
|
#include "drivers/display.h"
|
|
#include "drivers/flash.h"
|
|
#include "drivers/osd.h"
|
|
#include "drivers/osd_symbols.h"
|
|
#include "drivers/pwm_mapping.h"
|
|
#include "drivers/sdcard/sdcard.h"
|
|
#include "drivers/serial.h"
|
|
#include "drivers/system.h"
|
|
#include "drivers/time.h"
|
|
#include "drivers/timer.h"
|
|
#include "drivers/vtx_common.h"
|
|
|
|
#include "fc/fc_core.h"
|
|
#include "fc/config.h"
|
|
#include "fc/controlrate_profile.h"
|
|
#include "fc/fc_msp.h"
|
|
#include "fc/fc_msp_box.h"
|
|
#include "fc/firmware_update.h"
|
|
#include "fc/rc_adjustments.h"
|
|
#include "fc/rc_controls.h"
|
|
#include "fc/rc_modes.h"
|
|
#include "fc/runtime_config.h"
|
|
#include "fc/settings.h"
|
|
|
|
#include "flight/failsafe.h"
|
|
#include "flight/imu.h"
|
|
#include "flight/mixer_profile.h"
|
|
#include "flight/mixer.h"
|
|
#include "flight/pid.h"
|
|
#include "flight/servos.h"
|
|
#include "flight/ez_tune.h"
|
|
|
|
#include "config/config_eeprom.h"
|
|
#include "config/feature.h"
|
|
|
|
#include "io/adsb.h"
|
|
#include "io/asyncfatfs/asyncfatfs.h"
|
|
#include "io/flashfs.h"
|
|
#include "io/gps.h"
|
|
#include "io/gps_ublox.h"
|
|
#include "io/opflow.h"
|
|
#include "io/rangefinder.h"
|
|
#include "io/ledstrip.h"
|
|
#include "io/osd.h"
|
|
#include "io/serial.h"
|
|
#include "io/serial_4way.h"
|
|
#include "io/vtx.h"
|
|
#include "io/vtx_string.h"
|
|
#include "io/gps_private.h" //for MSP_SIMULATOR
|
|
#include "io/headtracker_msp.h"
|
|
|
|
#include "io/osd/custom_elements.h"
|
|
|
|
#include "msp/msp.h"
|
|
#include "msp/msp_protocol.h"
|
|
#include "msp/msp_serial.h"
|
|
|
|
#include "navigation/navigation.h"
|
|
#include "navigation/navigation_private.h" //for MSP_SIMULATOR
|
|
#include "navigation/navigation_pos_estimator_private.h" //for MSP_SIMULATOR
|
|
|
|
#include "rx/rx.h"
|
|
#include "rx/msp.h"
|
|
#include "rx/srxl2.h"
|
|
#include "rx/crsf.h"
|
|
|
|
#include "scheduler/scheduler.h"
|
|
|
|
#include "sensors/boardalignment.h"
|
|
#include "sensors/sensors.h"
|
|
#include "sensors/diagnostics.h"
|
|
#include "sensors/battery.h"
|
|
#include "sensors/rangefinder.h"
|
|
#include "sensors/acceleration.h"
|
|
#include "sensors/barometer.h"
|
|
#include "sensors/pitotmeter.h"
|
|
#include "sensors/compass.h"
|
|
#include "sensors/gyro.h"
|
|
#include "sensors/opflow.h"
|
|
#include "sensors/temperature.h"
|
|
#include "sensors/esc_sensor.h"
|
|
|
|
#include "telemetry/telemetry.h"
|
|
|
|
#ifdef USE_HARDWARE_REVISION_DETECTION
|
|
#include "hardware_revision.h"
|
|
#endif
|
|
|
|
extern timeDelta_t cycleTime; // FIXME dependency on mw.c
|
|
|
|
static const char * const flightControllerIdentifier = INAV_IDENTIFIER; // 4 UPPER CASE alpha numeric characters that identify the flight controller.
|
|
static const char * const boardIdentifier = TARGET_BOARD_IDENTIFIER;
|
|
|
|
// from mixer.c
|
|
extern int16_t motor_disarmed[MAX_SUPPORTED_MOTORS];
|
|
|
|
static const char pidnames[] =
|
|
"ROLL;"
|
|
"PITCH;"
|
|
"YAW;"
|
|
"ALT;"
|
|
"Pos;"
|
|
"PosR;"
|
|
"NavR;"
|
|
"LEVEL;"
|
|
"MAG;"
|
|
"VEL;";
|
|
|
|
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_SUPPORTTED = 1
|
|
} mspSDCardFlags_e;
|
|
|
|
typedef enum {
|
|
MSP_FLASHFS_BIT_READY = 1,
|
|
MSP_FLASHFS_BIT_SUPPORTED = 2
|
|
} mspFlashfsFlags_e;
|
|
|
|
typedef enum {
|
|
MSP_PASSTHROUGH_SERIAL_ID = 0xFD,
|
|
MSP_PASSTHROUGH_SERIAL_FUNCTION_ID = 0xFE,
|
|
MSP_PASSTHROUGH_ESC_4WAY = 0xFF,
|
|
} mspPassthroughType_e;
|
|
|
|
static uint8_t mspPassthroughMode;
|
|
static uint8_t mspPassthroughArgument;
|
|
|
|
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);
|
|
if (!sbufReadU8Safe(&mspPassthroughArgument, src)) {
|
|
mspPassthroughArgument = 0;
|
|
}
|
|
}
|
|
|
|
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;
|
|
#endif
|
|
default:
|
|
sbufWriteU8(dst, 0);
|
|
}
|
|
}
|
|
|
|
static void mspRebootFn(serialPort_t *serialPort)
|
|
{
|
|
UNUSED(serialPort);
|
|
fcReboot(false);
|
|
}
|
|
|
|
static void serializeSDCardSummaryReply(sbuf_t *dst)
|
|
{
|
|
#ifdef USE_SDCARD
|
|
uint8_t flags = MSP_SDCARD_FLAG_SUPPORTTED;
|
|
uint8_t state;
|
|
|
|
sbufWriteU8(dst, flags);
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
sbufWriteU8(dst, state);
|
|
sbufWriteU8(dst, afatfs_getLastError());
|
|
// Write free space and total space in kilobytes
|
|
sbufWriteU32(dst, afatfs_getContiguousFreeSpace() / 1024);
|
|
sbufWriteU32(dst, sdcard_getMetadata()->numBlocks / 2); // Block size is half a kilobyte
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU32(dst, 0);
|
|
sbufWriteU32(dst, 0);
|
|
#endif
|
|
}
|
|
|
|
static void serializeDataflashSummaryReply(sbuf_t *dst)
|
|
{
|
|
#ifdef USE_FLASHFS
|
|
const flashGeometry_t *geometry = flashGetGeometry();
|
|
sbufWriteU8(dst, flashIsReady() ? 1 : 0);
|
|
sbufWriteU32(dst, geometry->sectors);
|
|
sbufWriteU32(dst, geometry->totalSize);
|
|
sbufWriteU32(dst, flashfsGetOffset()); // Effectively the current number of bytes stored on the volume
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU32(dst, 0);
|
|
sbufWriteU32(dst, 0);
|
|
sbufWriteU32(dst, 0);
|
|
#endif
|
|
}
|
|
|
|
#ifdef USE_FLASHFS
|
|
static void serializeDataflashReadReply(sbuf_t *dst, uint32_t address, uint16_t size)
|
|
{
|
|
// Check how much bytes we can read
|
|
const int bytesRemainingInBuf = sbufBytesRemaining(dst);
|
|
uint16_t readLen = (size > bytesRemainingInBuf) ? bytesRemainingInBuf : size;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Write address
|
|
sbufWriteU32(dst, address);
|
|
|
|
// Read into streambuf directly
|
|
const int bytesRead = flashfsReadAbs(address, sbufPtr(dst), readLen);
|
|
sbufAdvance(dst, bytesRead);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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 mspFcProcessOutCommand(uint16_t cmdMSP, sbuf_t *dst, mspPostProcessFnPtr *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, 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
|
|
// OSD support (for BF compatibility):
|
|
// 0 = no OSD
|
|
// 1 = OSD slave (not supported in INAV)
|
|
// 2 = OSD chip on board
|
|
#if defined(USE_OSD)
|
|
sbufWriteU8(dst, 2);
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
#endif
|
|
// Board communication capabilities (uint8)
|
|
// Bit 0: 1 iff the board has VCP
|
|
// Bit 1: 1 iff the board supports software serial
|
|
uint8_t commCapabilities = 0;
|
|
#ifdef USE_VCP
|
|
commCapabilities |= 1 << 0;
|
|
#endif
|
|
#if defined(USE_SOFTSERIAL1) || defined(USE_SOFTSERIAL2)
|
|
commCapabilities |= 1 << 1;
|
|
#endif
|
|
sbufWriteU8(dst, commCapabilities);
|
|
|
|
sbufWriteU8(dst, strlen(targetName));
|
|
sbufWriteData(dst, targetName, strlen(targetName));
|
|
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_SENSOR_STATUS:
|
|
sbufWriteU8(dst, isHardwareHealthy() ? 1 : 0);
|
|
sbufWriteU8(dst, getHwGyroStatus());
|
|
sbufWriteU8(dst, getHwAccelerometerStatus());
|
|
sbufWriteU8(dst, getHwCompassStatus());
|
|
sbufWriteU8(dst, getHwBarometerStatus());
|
|
sbufWriteU8(dst, getHwGPSStatus());
|
|
sbufWriteU8(dst, getHwRangefinderStatus());
|
|
sbufWriteU8(dst, getHwPitotmeterStatus());
|
|
sbufWriteU8(dst, getHwOpticalFlowStatus());
|
|
break;
|
|
|
|
case MSP_ACTIVEBOXES:
|
|
{
|
|
boxBitmask_t mspBoxModeFlags;
|
|
packBoxModeFlags(&mspBoxModeFlags);
|
|
sbufWriteData(dst, &mspBoxModeFlags, sizeof(mspBoxModeFlags));
|
|
}
|
|
break;
|
|
|
|
case MSP_STATUS_EX:
|
|
case MSP_STATUS:
|
|
{
|
|
boxBitmask_t mspBoxModeFlags;
|
|
packBoxModeFlags(&mspBoxModeFlags);
|
|
|
|
sbufWriteU16(dst, (uint16_t)cycleTime);
|
|
#ifdef USE_I2C
|
|
sbufWriteU16(dst, i2cGetErrorCounter());
|
|
#else
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
sbufWriteU16(dst, packSensorStatus());
|
|
sbufWriteData(dst, &mspBoxModeFlags, 4);
|
|
sbufWriteU8(dst, getConfigProfile());
|
|
|
|
if (cmdMSP == MSP_STATUS_EX) {
|
|
sbufWriteU16(dst, averageSystemLoadPercent);
|
|
sbufWriteU16(dst, armingFlags);
|
|
sbufWriteU8(dst, accGetCalibrationAxisFlags());
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MSP2_INAV_STATUS:
|
|
{
|
|
// Preserves full arming flags and box modes
|
|
boxBitmask_t mspBoxModeFlags;
|
|
packBoxModeFlags(&mspBoxModeFlags);
|
|
|
|
sbufWriteU16(dst, (uint16_t)cycleTime);
|
|
#ifdef USE_I2C
|
|
sbufWriteU16(dst, i2cGetErrorCounter());
|
|
#else
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
sbufWriteU16(dst, packSensorStatus());
|
|
sbufWriteU16(dst, averageSystemLoadPercent);
|
|
sbufWriteU8(dst, (getConfigBatteryProfile() << 4) | getConfigProfile());
|
|
sbufWriteU32(dst, armingFlags);
|
|
sbufWriteData(dst, &mspBoxModeFlags, sizeof(mspBoxModeFlags));
|
|
sbufWriteU8(dst, getConfigMixerProfile());
|
|
}
|
|
break;
|
|
|
|
case MSP_RAW_IMU:
|
|
{
|
|
for (int i = 0; i < 3; i++) {
|
|
sbufWriteU16(dst, (int16_t)lrintf(acc.accADCf[i] * 512));
|
|
}
|
|
for (int i = 0; i < 3; i++) {
|
|
sbufWriteU16(dst, gyroRateDps(i));
|
|
}
|
|
for (int i = 0; i < 3; i++) {
|
|
#ifdef USE_MAG
|
|
sbufWriteU16(dst, lrintf(mag.magADC[i]));
|
|
#else
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
}
|
|
}
|
|
break;
|
|
|
|
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, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 255); // used to be forwardFromChannel, not used anymore, send 0xff for compatibility reasons
|
|
sbufWriteU32(dst, 0); //Input reversing is not required since it can be done on mixer level
|
|
}
|
|
break;
|
|
case MSP2_INAV_SERVO_CONFIG:
|
|
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);
|
|
}
|
|
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);
|
|
sbufWriteU16(dst, customServoMixers(i)->rate);
|
|
sbufWriteU8(dst, customServoMixers(i)->speed);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 100);
|
|
sbufWriteU8(dst, 0);
|
|
}
|
|
break;
|
|
case MSP2_INAV_SERVO_MIXER:
|
|
for (int i = 0; i < MAX_SERVO_RULES; i++) {
|
|
sbufWriteU8(dst, customServoMixers(i)->targetChannel);
|
|
sbufWriteU8(dst, customServoMixers(i)->inputSource);
|
|
sbufWriteU16(dst, customServoMixers(i)->rate);
|
|
sbufWriteU8(dst, customServoMixers(i)->speed);
|
|
#ifdef USE_PROGRAMMING_FRAMEWORK
|
|
sbufWriteU8(dst, customServoMixers(i)->conditionId);
|
|
#else
|
|
sbufWriteU8(dst, -1);
|
|
#endif
|
|
}
|
|
if(MAX_MIXER_PROFILE_COUNT==1) break;
|
|
for (int i = 0; i < MAX_SERVO_RULES; i++) {
|
|
sbufWriteU8(dst, mixerServoMixersByIndex(nextMixerProfileIndex)[i].targetChannel);
|
|
sbufWriteU8(dst, mixerServoMixersByIndex(nextMixerProfileIndex)[i].inputSource);
|
|
sbufWriteU16(dst, mixerServoMixersByIndex(nextMixerProfileIndex)[i].rate);
|
|
sbufWriteU8(dst, mixerServoMixersByIndex(nextMixerProfileIndex)[i].speed);
|
|
#ifdef USE_PROGRAMMING_FRAMEWORK
|
|
sbufWriteU8(dst, mixerServoMixersByIndex(nextMixerProfileIndex)[i].conditionId);
|
|
#else
|
|
sbufWriteU8(dst, -1);
|
|
#endif
|
|
}
|
|
break;
|
|
#ifdef USE_PROGRAMMING_FRAMEWORK
|
|
case MSP2_INAV_LOGIC_CONDITIONS:
|
|
for (int i = 0; i < MAX_LOGIC_CONDITIONS; i++) {
|
|
sbufWriteU8(dst, logicConditions(i)->enabled);
|
|
sbufWriteU8(dst, logicConditions(i)->activatorId);
|
|
sbufWriteU8(dst, logicConditions(i)->operation);
|
|
sbufWriteU8(dst, logicConditions(i)->operandA.type);
|
|
sbufWriteU32(dst, logicConditions(i)->operandA.value);
|
|
sbufWriteU8(dst, logicConditions(i)->operandB.type);
|
|
sbufWriteU32(dst, logicConditions(i)->operandB.value);
|
|
sbufWriteU8(dst, logicConditions(i)->flags);
|
|
}
|
|
break;
|
|
case MSP2_INAV_LOGIC_CONDITIONS_STATUS:
|
|
for (int i = 0; i < MAX_LOGIC_CONDITIONS; i++) {
|
|
sbufWriteU32(dst, logicConditionGetValue(i));
|
|
}
|
|
break;
|
|
case MSP2_INAV_GVAR_STATUS:
|
|
for (int i = 0; i < MAX_GLOBAL_VARIABLES; i++) {
|
|
sbufWriteU32(dst, gvGet(i));
|
|
}
|
|
break;
|
|
case MSP2_INAV_PROGRAMMING_PID:
|
|
for (int i = 0; i < MAX_PROGRAMMING_PID_COUNT; i++) {
|
|
sbufWriteU8(dst, programmingPids(i)->enabled);
|
|
sbufWriteU8(dst, programmingPids(i)->setpoint.type);
|
|
sbufWriteU32(dst, programmingPids(i)->setpoint.value);
|
|
sbufWriteU8(dst, programmingPids(i)->measurement.type);
|
|
sbufWriteU32(dst, programmingPids(i)->measurement.value);
|
|
sbufWriteU16(dst, programmingPids(i)->gains.P);
|
|
sbufWriteU16(dst, programmingPids(i)->gains.I);
|
|
sbufWriteU16(dst, programmingPids(i)->gains.D);
|
|
sbufWriteU16(dst, programmingPids(i)->gains.FF);
|
|
}
|
|
break;
|
|
case MSP2_INAV_PROGRAMMING_PID_STATUS:
|
|
for (int i = 0; i < MAX_PROGRAMMING_PID_COUNT; i++) {
|
|
sbufWriteU32(dst, programmingPidGetOutput(i));
|
|
}
|
|
break;
|
|
#endif
|
|
case MSP2_COMMON_MOTOR_MIXER:
|
|
for (uint8_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
|
|
sbufWriteU16(dst, constrainf(primaryMotorMixer(i)->throttle + 2.0f, 0.0f, 4.0f) * 1000);
|
|
sbufWriteU16(dst, constrainf(primaryMotorMixer(i)->roll + 2.0f, 0.0f, 4.0f) * 1000);
|
|
sbufWriteU16(dst, constrainf(primaryMotorMixer(i)->pitch + 2.0f, 0.0f, 4.0f) * 1000);
|
|
sbufWriteU16(dst, constrainf(primaryMotorMixer(i)->yaw + 2.0f, 0.0f, 4.0f) * 1000);
|
|
}
|
|
if (MAX_MIXER_PROFILE_COUNT==1) break;
|
|
for (uint8_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
|
|
sbufWriteU16(dst, constrainf(mixerMotorMixersByIndex(nextMixerProfileIndex)[i].throttle + 2.0f, 0.0f, 4.0f) * 1000);
|
|
sbufWriteU16(dst, constrainf(mixerMotorMixersByIndex(nextMixerProfileIndex)[i].roll + 2.0f, 0.0f, 4.0f) * 1000);
|
|
sbufWriteU16(dst, constrainf(mixerMotorMixersByIndex(nextMixerProfileIndex)[i].pitch + 2.0f, 0.0f, 4.0f) * 1000);
|
|
sbufWriteU16(dst, constrainf(mixerMotorMixersByIndex(nextMixerProfileIndex)[i].yaw + 2.0f, 0.0f, 4.0f) * 1000);
|
|
}
|
|
break;
|
|
|
|
case MSP_MOTOR:
|
|
for (unsigned i = 0; i < 8; i++) {
|
|
sbufWriteU16(dst, i < MAX_SUPPORTED_MOTORS ? motor[i] : 0);
|
|
}
|
|
break;
|
|
|
|
case MSP_RC:
|
|
for (int i = 0; i < rxRuntimeConfig.channelCount; i++) {
|
|
sbufWriteU16(dst, rxGetChannelValue(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, lrintf(getEstimatedActualPosition(Z)));
|
|
sbufWriteU16(dst, lrintf(getEstimatedActualVelocity(Z)));
|
|
#if defined(USE_BARO)
|
|
sbufWriteU32(dst, baroGetLatestAltitude());
|
|
#else
|
|
sbufWriteU32(dst, 0);
|
|
#endif
|
|
break;
|
|
|
|
case MSP_SONAR_ALTITUDE:
|
|
#ifdef USE_RANGEFINDER
|
|
sbufWriteU32(dst, rangefinderGetLatestAltitude());
|
|
#else
|
|
sbufWriteU32(dst, 0);
|
|
#endif
|
|
break;
|
|
|
|
case MSP2_INAV_OPTICAL_FLOW:
|
|
#ifdef USE_OPFLOW
|
|
sbufWriteU8(dst, opflow.rawQuality);
|
|
sbufWriteU16(dst, RADIANS_TO_DEGREES(opflow.flowRate[X]));
|
|
sbufWriteU16(dst, RADIANS_TO_DEGREES(opflow.flowRate[Y]));
|
|
sbufWriteU16(dst, RADIANS_TO_DEGREES(opflow.bodyRate[X]));
|
|
sbufWriteU16(dst, RADIANS_TO_DEGREES(opflow.bodyRate[Y]));
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
break;
|
|
|
|
case MSP_ANALOG:
|
|
sbufWriteU8(dst, (uint8_t)constrain(getBatteryVoltage() / 10, 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 amperage in 0.01 A steps, range is -320A to 320A
|
|
break;
|
|
|
|
case MSP2_INAV_ANALOG:
|
|
// Bit 1: battery full, Bit 2: use capacity threshold, Bit 3-4: battery state, Bit 5-8: battery cell count
|
|
sbufWriteU8(dst, batteryWasFullWhenPluggedIn() | (batteryUsesCapacityThresholds() << 1) | (getBatteryState() << 2) | (getBatteryCellCount() << 4));
|
|
sbufWriteU16(dst, getBatteryVoltage());
|
|
sbufWriteU16(dst, getAmperage()); // send amperage in 0.01 A steps
|
|
sbufWriteU32(dst, getPower()); // power draw
|
|
sbufWriteU32(dst, getMAhDrawn()); // milliamp hours drawn from battery
|
|
sbufWriteU32(dst, getMWhDrawn()); // milliWatt hours drawn from battery
|
|
sbufWriteU32(dst, getBatteryRemainingCapacity());
|
|
sbufWriteU8(dst, calculateBatteryPercentage());
|
|
sbufWriteU16(dst, getRSSI());
|
|
break;
|
|
|
|
case MSP_LOOP_TIME:
|
|
sbufWriteU16(dst, gyroConfig()->looptime);
|
|
break;
|
|
|
|
case MSP_RC_TUNING:
|
|
sbufWriteU8(dst, 100); //rcRate8 kept for compatibity reasons, this setting is no longer used
|
|
sbufWriteU8(dst, currentControlRateProfile->stabilized.rcExpo8);
|
|
for (int i = 0 ; i < 3; i++) {
|
|
sbufWriteU8(dst, currentControlRateProfile->stabilized.rates[i]); // R,P,Y see flight_dynamics_index_t
|
|
}
|
|
sbufWriteU8(dst, currentControlRateProfile->throttle.dynPID);
|
|
sbufWriteU8(dst, currentControlRateProfile->throttle.rcMid8);
|
|
sbufWriteU8(dst, currentControlRateProfile->throttle.rcExpo8);
|
|
sbufWriteU16(dst, currentControlRateProfile->throttle.pa_breakpoint);
|
|
sbufWriteU8(dst, currentControlRateProfile->stabilized.rcYawExpo8);
|
|
break;
|
|
|
|
case MSP2_INAV_RATE_PROFILE:
|
|
// throttle
|
|
sbufWriteU8(dst, currentControlRateProfile->throttle.rcMid8);
|
|
sbufWriteU8(dst, currentControlRateProfile->throttle.rcExpo8);
|
|
sbufWriteU8(dst, currentControlRateProfile->throttle.dynPID);
|
|
sbufWriteU16(dst, currentControlRateProfile->throttle.pa_breakpoint);
|
|
|
|
// stabilized
|
|
sbufWriteU8(dst, currentControlRateProfile->stabilized.rcExpo8);
|
|
sbufWriteU8(dst, currentControlRateProfile->stabilized.rcYawExpo8);
|
|
for (uint8_t i = 0 ; i < 3; ++i) {
|
|
sbufWriteU8(dst, currentControlRateProfile->stabilized.rates[i]); // R,P,Y see flight_dynamics_index_t
|
|
}
|
|
|
|
// manual
|
|
sbufWriteU8(dst, currentControlRateProfile->manual.rcExpo8);
|
|
sbufWriteU8(dst, currentControlRateProfile->manual.rcYawExpo8);
|
|
for (uint8_t i = 0 ; i < 3; ++i) {
|
|
sbufWriteU8(dst, currentControlRateProfile->manual.rates[i]); // R,P,Y see flight_dynamics_index_t
|
|
}
|
|
break;
|
|
|
|
case MSP2_PID:
|
|
for (int i = 0; i < PID_ITEM_COUNT; i++) {
|
|
sbufWriteU8(dst, constrain(pidBank()->pid[i].P, 0, 255));
|
|
sbufWriteU8(dst, constrain(pidBank()->pid[i].I, 0, 255));
|
|
sbufWriteU8(dst, constrain(pidBank()->pid[i].D, 0, 255));
|
|
sbufWriteU8(dst, constrain(pidBank()->pid[i].FF, 0, 255));
|
|
}
|
|
#ifdef USE_EZ_TUNE
|
|
ezTuneUpdate();
|
|
#endif
|
|
break;
|
|
|
|
case MSP_PIDNAMES:
|
|
for (const char *c = pidnames; *c; c++) {
|
|
sbufWriteU8(dst, *c);
|
|
}
|
|
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 = findBoxByActiveBoxId(mac->modeId);
|
|
sbufWriteU8(dst, box ? box->permanentId : 0);
|
|
sbufWriteU8(dst, mac->auxChannelIndex);
|
|
sbufWriteU8(dst, mac->range.startStep);
|
|
sbufWriteU8(dst, mac->range.endStep);
|
|
}
|
|
break;
|
|
|
|
case MSP_ADJUSTMENT_RANGES:
|
|
for (int i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
|
|
const adjustmentRange_t *adjRange = adjustmentRanges(i);
|
|
sbufWriteU8(dst, adjRange->adjustmentIndex);
|
|
sbufWriteU8(dst, adjRange->auxChannelIndex);
|
|
sbufWriteU8(dst, adjRange->range.startStep);
|
|
sbufWriteU8(dst, adjRange->range.endStep);
|
|
sbufWriteU8(dst, adjRange->adjustmentFunction);
|
|
sbufWriteU8(dst, adjRange->auxSwitchChannelIndex);
|
|
}
|
|
break;
|
|
|
|
case MSP_BOXNAMES:
|
|
if (!serializeBoxNamesReply(dst)) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case MSP_BOXIDS:
|
|
serializeBoxReply(dst);
|
|
break;
|
|
|
|
case MSP_MISC:
|
|
sbufWriteU16(dst, PWM_RANGE_MIDDLE);
|
|
|
|
sbufWriteU16(dst, 0); // Was min_throttle
|
|
sbufWriteU16(dst, getMaxThrottle());
|
|
sbufWriteU16(dst, motorConfig()->mincommand);
|
|
|
|
sbufWriteU16(dst, currentBatteryProfile->failsafe_throttle);
|
|
|
|
#ifdef USE_GPS
|
|
sbufWriteU8(dst, gpsConfig()->provider); // gps_type
|
|
sbufWriteU8(dst, 0); // TODO gps_baudrate (an index, cleanflight uses a uint32_t
|
|
sbufWriteU8(dst, gpsConfig()->sbasMode); // gps_ubx_sbas
|
|
#else
|
|
sbufWriteU8(dst, 0); // gps_type
|
|
sbufWriteU8(dst, 0); // TODO gps_baudrate (an index, cleanflight uses a uint32_t
|
|
sbufWriteU8(dst, 0); // gps_ubx_sbas
|
|
#endif
|
|
sbufWriteU8(dst, 0); // multiwiiCurrentMeterOutput
|
|
sbufWriteU8(dst, rxConfig()->rssi_channel);
|
|
sbufWriteU8(dst, 0);
|
|
|
|
#ifdef USE_MAG
|
|
sbufWriteU16(dst, compassConfig()->mag_declination / 10);
|
|
#else
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
|
|
#ifdef USE_ADC
|
|
sbufWriteU8(dst, batteryMetersConfig()->voltage.scale / 10);
|
|
sbufWriteU8(dst, currentBatteryProfile->voltage.cellMin / 10);
|
|
sbufWriteU8(dst, currentBatteryProfile->voltage.cellMax / 10);
|
|
sbufWriteU8(dst, currentBatteryProfile->voltage.cellWarning / 10);
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
#endif
|
|
break;
|
|
|
|
case MSP2_INAV_MISC:
|
|
sbufWriteU16(dst, PWM_RANGE_MIDDLE);
|
|
|
|
sbufWriteU16(dst, 0); //Was min_throttle
|
|
sbufWriteU16(dst, getMaxThrottle());
|
|
sbufWriteU16(dst, motorConfig()->mincommand);
|
|
|
|
sbufWriteU16(dst, currentBatteryProfile->failsafe_throttle);
|
|
|
|
#ifdef USE_GPS
|
|
sbufWriteU8(dst, gpsConfig()->provider); // gps_type
|
|
sbufWriteU8(dst, 0); // TODO gps_baudrate (an index, cleanflight uses a uint32_t
|
|
sbufWriteU8(dst, gpsConfig()->sbasMode); // gps_ubx_sbas
|
|
#else
|
|
sbufWriteU8(dst, 0); // gps_type
|
|
sbufWriteU8(dst, 0); // TODO gps_baudrate (an index, cleanflight uses a uint32_t
|
|
sbufWriteU8(dst, 0); // gps_ubx_sbas
|
|
#endif
|
|
sbufWriteU8(dst, rxConfig()->rssi_channel);
|
|
|
|
#ifdef USE_MAG
|
|
sbufWriteU16(dst, compassConfig()->mag_declination / 10);
|
|
#else
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
|
|
#ifdef USE_ADC
|
|
sbufWriteU16(dst, batteryMetersConfig()->voltage.scale);
|
|
sbufWriteU8(dst, batteryMetersConfig()->voltageSource);
|
|
sbufWriteU8(dst, currentBatteryProfile->cells);
|
|
sbufWriteU16(dst, currentBatteryProfile->voltage.cellDetect);
|
|
sbufWriteU16(dst, currentBatteryProfile->voltage.cellMin);
|
|
sbufWriteU16(dst, currentBatteryProfile->voltage.cellMax);
|
|
sbufWriteU16(dst, currentBatteryProfile->voltage.cellWarning);
|
|
#else
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
|
|
sbufWriteU32(dst, currentBatteryProfile->capacity.value);
|
|
sbufWriteU32(dst, currentBatteryProfile->capacity.warning);
|
|
sbufWriteU32(dst, currentBatteryProfile->capacity.critical);
|
|
sbufWriteU8(dst, batteryMetersConfig()->capacity_unit);
|
|
break;
|
|
|
|
case MSP2_INAV_MISC2:
|
|
// Timers
|
|
sbufWriteU32(dst, micros() / 1000000); // On time (seconds)
|
|
sbufWriteU32(dst, getFlightTime()); // Flight time (seconds)
|
|
|
|
// Throttle
|
|
sbufWriteU8(dst, getThrottlePercent(true)); // Throttle Percent
|
|
sbufWriteU8(dst, navigationIsControllingThrottle() ? 1 : 0); // Auto Throttle Flag (0 or 1)
|
|
|
|
break;
|
|
|
|
case MSP2_INAV_BATTERY_CONFIG:
|
|
#ifdef USE_ADC
|
|
sbufWriteU16(dst, batteryMetersConfig()->voltage.scale);
|
|
sbufWriteU8(dst, batteryMetersConfig()->voltageSource);
|
|
sbufWriteU8(dst, currentBatteryProfile->cells);
|
|
sbufWriteU16(dst, currentBatteryProfile->voltage.cellDetect);
|
|
sbufWriteU16(dst, currentBatteryProfile->voltage.cellMin);
|
|
sbufWriteU16(dst, currentBatteryProfile->voltage.cellMax);
|
|
sbufWriteU16(dst, currentBatteryProfile->voltage.cellWarning);
|
|
#else
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
|
|
sbufWriteU16(dst, batteryMetersConfig()->current.offset);
|
|
sbufWriteU16(dst, batteryMetersConfig()->current.scale);
|
|
|
|
sbufWriteU32(dst, currentBatteryProfile->capacity.value);
|
|
sbufWriteU32(dst, currentBatteryProfile->capacity.warning);
|
|
sbufWriteU32(dst, currentBatteryProfile->capacity.critical);
|
|
sbufWriteU8(dst, batteryMetersConfig()->capacity_unit);
|
|
break;
|
|
|
|
#ifdef USE_GPS
|
|
case MSP_RAW_GPS:
|
|
sbufWriteU8(dst, gpsSol.fixType);
|
|
sbufWriteU8(dst, gpsSol.numSat);
|
|
sbufWriteU32(dst, gpsSol.llh.lat);
|
|
sbufWriteU32(dst, gpsSol.llh.lon);
|
|
sbufWriteU16(dst, gpsSol.llh.alt/100); // meters
|
|
sbufWriteU16(dst, gpsSol.groundSpeed);
|
|
sbufWriteU16(dst, gpsSol.groundCourse);
|
|
sbufWriteU16(dst, gpsSol.hdop);
|
|
break;
|
|
|
|
case MSP_COMP_GPS:
|
|
sbufWriteU16(dst, GPS_distanceToHome);
|
|
sbufWriteU16(dst, GPS_directionToHome);
|
|
sbufWriteU8(dst, gpsSol.flags.gpsHeartbeat ? 1 : 0);
|
|
break;
|
|
case MSP_NAV_STATUS:
|
|
sbufWriteU8(dst, NAV_Status.mode);
|
|
sbufWriteU8(dst, NAV_Status.state);
|
|
sbufWriteU8(dst, NAV_Status.activeWpAction);
|
|
sbufWriteU8(dst, NAV_Status.activeWpNumber);
|
|
sbufWriteU8(dst, NAV_Status.error);
|
|
//sbufWriteU16(dst, (int16_t)(target_bearing/100));
|
|
sbufWriteU16(dst, getHeadingHoldTarget());
|
|
break;
|
|
|
|
|
|
case MSP_GPSSVINFO:
|
|
/* Compatibility stub - return zero SVs */
|
|
sbufWriteU8(dst, 1);
|
|
|
|
// HDOP
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, gpsSol.hdop / 100);
|
|
sbufWriteU8(dst, gpsSol.hdop / 100);
|
|
break;
|
|
|
|
case MSP_GPSSTATISTICS:
|
|
sbufWriteU16(dst, gpsStats.lastMessageDt);
|
|
sbufWriteU32(dst, gpsStats.errors);
|
|
sbufWriteU32(dst, gpsStats.timeouts);
|
|
sbufWriteU32(dst, gpsStats.packetCount);
|
|
sbufWriteU16(dst, gpsSol.hdop);
|
|
sbufWriteU16(dst, gpsSol.eph);
|
|
sbufWriteU16(dst, gpsSol.epv);
|
|
break;
|
|
#endif
|
|
case MSP2_ADSB_VEHICLE_LIST:
|
|
#ifdef USE_ADSB
|
|
sbufWriteU8(dst, MAX_ADSB_VEHICLES);
|
|
sbufWriteU8(dst, ADSB_CALL_SIGN_MAX_LENGTH);
|
|
|
|
for(uint8_t i = 0; i < MAX_ADSB_VEHICLES; i++){
|
|
|
|
adsbVehicle_t *adsbVehicle = findVehicle(i);
|
|
|
|
for(uint8_t ii = 0; ii < ADSB_CALL_SIGN_MAX_LENGTH; ii++){
|
|
sbufWriteU8(dst, adsbVehicle->vehicleValues.callsign[ii]);
|
|
}
|
|
|
|
sbufWriteU32(dst, adsbVehicle->vehicleValues.icao);
|
|
sbufWriteU32(dst, adsbVehicle->vehicleValues.lat);
|
|
sbufWriteU32(dst, adsbVehicle->vehicleValues.lon);
|
|
sbufWriteU32(dst, adsbVehicle->vehicleValues.alt);
|
|
sbufWriteU16(dst, (uint16_t)CENTIDEGREES_TO_DEGREES(adsbVehicle->vehicleValues.heading));
|
|
sbufWriteU8(dst, adsbVehicle->vehicleValues.tslc);
|
|
sbufWriteU8(dst, adsbVehicle->vehicleValues.emitterType);
|
|
sbufWriteU8(dst, adsbVehicle->ttl);
|
|
}
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
#endif
|
|
break;
|
|
case MSP_DEBUG:
|
|
// output some useful QA statistics
|
|
// debug[x] = ((hse_value / 1000000) * 1000) + (SystemCoreClock / 1000000); // XX0YY [crystal clock : core clock]
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
sbufWriteU16(dst, debug[i]); // 4 variables are here for general monitoring purpose
|
|
}
|
|
break;
|
|
|
|
case MSP2_INAV_DEBUG:
|
|
for (int i = 0; i < DEBUG32_VALUE_COUNT; i++) {
|
|
sbufWriteU32(dst, debug[i]); // 8 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:
|
|
sbufWriteU32(dst, featureMask());
|
|
break;
|
|
|
|
case MSP_BOARD_ALIGNMENT:
|
|
sbufWriteU16(dst, boardAlignment()->rollDeciDegrees);
|
|
sbufWriteU16(dst, boardAlignment()->pitchDeciDegrees);
|
|
sbufWriteU16(dst, boardAlignment()->yawDeciDegrees);
|
|
break;
|
|
|
|
case MSP_VOLTAGE_METER_CONFIG:
|
|
#ifdef USE_ADC
|
|
sbufWriteU8(dst, batteryMetersConfig()->voltage.scale / 10);
|
|
sbufWriteU8(dst, currentBatteryProfile->voltage.cellMin / 10);
|
|
sbufWriteU8(dst, currentBatteryProfile->voltage.cellMax / 10);
|
|
sbufWriteU8(dst, currentBatteryProfile->voltage.cellWarning / 10);
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
#endif
|
|
break;
|
|
|
|
case MSP_CURRENT_METER_CONFIG:
|
|
sbufWriteU16(dst, batteryMetersConfig()->current.scale);
|
|
sbufWriteU16(dst, batteryMetersConfig()->current.offset);
|
|
sbufWriteU8(dst, batteryMetersConfig()->current.type);
|
|
sbufWriteU16(dst, constrain(currentBatteryProfile->capacity.value, 0, 0xFFFF));
|
|
break;
|
|
|
|
case MSP_MIXER:
|
|
sbufWriteU8(dst, 3); // mixerMode no longer supported, send 3 (QuadX) as fallback
|
|
break;
|
|
|
|
|
|
case MSP_RX_CONFIG:
|
|
sbufWriteU8(dst, rxConfig()->serialrx_provider);
|
|
sbufWriteU16(dst, rxConfig()->maxcheck);
|
|
sbufWriteU16(dst, PWM_RANGE_MIDDLE);
|
|
sbufWriteU16(dst, rxConfig()->mincheck);
|
|
#ifdef USE_SPEKTRUM_BIND
|
|
sbufWriteU8(dst, rxConfig()->spektrum_sat_bind);
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
#endif
|
|
sbufWriteU16(dst, rxConfig()->rx_min_usec);
|
|
sbufWriteU16(dst, rxConfig()->rx_max_usec);
|
|
sbufWriteU8(dst, 0); // for compatibility with betaflight (rcInterpolation)
|
|
sbufWriteU8(dst, 0); // for compatibility with betaflight (rcInterpolationInterval)
|
|
sbufWriteU16(dst, 0); // for compatibility with betaflight (airModeActivateThreshold)
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU32(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0); // for compatibility with betaflight (fpvCamAngleDegrees)
|
|
sbufWriteU8(dst, rxConfig()->receiverType);
|
|
break;
|
|
|
|
case MSP_FAILSAFE_CONFIG:
|
|
sbufWriteU8(dst, failsafeConfig()->failsafe_delay);
|
|
sbufWriteU8(dst, failsafeConfig()->failsafe_off_delay);
|
|
sbufWriteU16(dst, currentBatteryProfile->failsafe_throttle);
|
|
sbufWriteU8(dst, 0); // was failsafe_kill_switch
|
|
sbufWriteU16(dst, failsafeConfig()->failsafe_throttle_low_delay);
|
|
sbufWriteU8(dst, failsafeConfig()->failsafe_procedure);
|
|
sbufWriteU8(dst, failsafeConfig()->failsafe_recovery_delay);
|
|
sbufWriteU16(dst, failsafeConfig()->failsafe_fw_roll_angle);
|
|
sbufWriteU16(dst, failsafeConfig()->failsafe_fw_pitch_angle);
|
|
sbufWriteU16(dst, failsafeConfig()->failsafe_fw_yaw_rate);
|
|
sbufWriteU16(dst, failsafeConfig()->failsafe_stick_motion_threshold);
|
|
sbufWriteU16(dst, failsafeConfig()->failsafe_min_distance);
|
|
sbufWriteU8(dst, failsafeConfig()->failsafe_min_distance_procedure);
|
|
break;
|
|
|
|
case MSP_RSSI_CONFIG:
|
|
sbufWriteU8(dst, rxConfig()->rssi_channel);
|
|
break;
|
|
|
|
case MSP_RX_MAP:
|
|
sbufWriteData(dst, rxConfig()->rcmap, MAX_MAPPABLE_RX_INPUTS);
|
|
break;
|
|
|
|
case MSP2_COMMON_SERIAL_CONFIG:
|
|
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].peripheral_baudrateIndex);
|
|
}
|
|
break;
|
|
|
|
#ifdef USE_LED_STRIP
|
|
case MSP_LED_COLORS:
|
|
for (int i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
|
|
const hsvColor_t *color = &ledStripConfig()->colors[i];
|
|
sbufWriteU16(dst, color->h);
|
|
sbufWriteU8(dst, color->s);
|
|
sbufWriteU8(dst, color->v);
|
|
}
|
|
break;
|
|
|
|
case MSP_LED_STRIP_CONFIG:
|
|
for (int i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
|
|
const ledConfig_t *ledConfig = &ledStripConfig()->ledConfigs[i];
|
|
|
|
uint32_t legacyLedConfig = ledConfig->led_position;
|
|
int shiftCount = 8;
|
|
legacyLedConfig |= ledConfig->led_function << shiftCount;
|
|
shiftCount += 4;
|
|
legacyLedConfig |= (ledConfig->led_overlay & 0x3F) << (shiftCount);
|
|
shiftCount += 6;
|
|
legacyLedConfig |= (ledConfig->led_color) << (shiftCount);
|
|
shiftCount += 4;
|
|
legacyLedConfig |= (ledConfig->led_direction) << (shiftCount);
|
|
shiftCount += 6;
|
|
legacyLedConfig |= (ledConfig->led_params) << (shiftCount);
|
|
|
|
sbufWriteU32(dst, legacyLedConfig);
|
|
}
|
|
break;
|
|
|
|
case MSP2_INAV_LED_STRIP_CONFIG_EX:
|
|
for (int i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
|
|
const ledConfig_t *ledConfig = &ledStripConfig()->ledConfigs[i];
|
|
sbufWriteDataSafe(dst, ledConfig, sizeof(ledConfig_t));
|
|
}
|
|
break;
|
|
|
|
|
|
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, ledStripConfig()->modeColors[i].color[j]);
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
|
|
sbufWriteU8(dst, LED_MODE_COUNT);
|
|
sbufWriteU8(dst, j);
|
|
sbufWriteU8(dst, ledStripConfig()->specialColors.color[j]);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case MSP_DATAFLASH_SUMMARY:
|
|
serializeDataflashSummaryReply(dst);
|
|
break;
|
|
|
|
case MSP_BLACKBOX_CONFIG:
|
|
sbufWriteU8(dst, 0); // API no longer supported
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
break;
|
|
|
|
case MSP2_BLACKBOX_CONFIG:
|
|
#ifdef USE_BLACKBOX
|
|
sbufWriteU8(dst, 1); //Blackbox supported
|
|
sbufWriteU8(dst, blackboxConfig()->device);
|
|
sbufWriteU16(dst, blackboxConfig()->rate_num);
|
|
sbufWriteU16(dst, blackboxConfig()->rate_denom);
|
|
sbufWriteU32(dst,blackboxConfig()->includeFlags);
|
|
#else
|
|
sbufWriteU8(dst, 0); // Blackbox not supported
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
break;
|
|
|
|
case MSP_SDCARD_SUMMARY:
|
|
serializeSDCardSummaryReply(dst);
|
|
break;
|
|
|
|
#if defined (USE_DJI_HD_OSD) || defined (USE_MSP_DISPLAYPORT)
|
|
case MSP_BATTERY_STATE:
|
|
// Battery characteristics
|
|
sbufWriteU8(dst, constrain(getBatteryCellCount(), 0, 255));
|
|
sbufWriteU16(dst, currentBatteryProfile->capacity.value);
|
|
|
|
// Battery state
|
|
sbufWriteU8(dst, constrain(getBatteryVoltage() / 10, 0, 255)); // in 0.1V steps
|
|
sbufWriteU16(dst, constrain(getMAhDrawn(), 0, 0xFFFF));
|
|
sbufWriteU16(dst, constrain(getAmperage(), -0x8000, 0x7FFF));
|
|
|
|
// Battery alerts - used values match Betaflight's/DJI's
|
|
sbufWriteU8(dst, getBatteryState());
|
|
|
|
// Additional battery voltage field (in 0.01V steps)
|
|
sbufWriteU16(dst, getBatteryVoltage());
|
|
break;
|
|
#endif
|
|
|
|
case MSP_OSD_CONFIG:
|
|
#ifdef USE_OSD
|
|
sbufWriteU8(dst, OSD_DRIVER_MAX7456); // OSD supported
|
|
// send video system (AUTO/PAL/NTSC)
|
|
sbufWriteU8(dst, osdConfig()->video_system);
|
|
sbufWriteU8(dst, osdConfig()->units);
|
|
sbufWriteU8(dst, osdConfig()->rssi_alarm);
|
|
sbufWriteU16(dst, currentBatteryProfile->capacity.warning);
|
|
sbufWriteU16(dst, osdConfig()->time_alarm);
|
|
sbufWriteU16(dst, osdConfig()->alt_alarm);
|
|
sbufWriteU16(dst, osdConfig()->dist_alarm);
|
|
sbufWriteU16(dst, osdConfig()->neg_alt_alarm);
|
|
for (int i = 0; i < OSD_ITEM_COUNT; i++) {
|
|
sbufWriteU16(dst, osdLayoutsConfig()->item_pos[0][i]);
|
|
}
|
|
#else
|
|
sbufWriteU8(dst, OSD_DRIVER_NONE); // OSD not supported
|
|
#endif
|
|
break;
|
|
|
|
case MSP_3D:
|
|
sbufWriteU16(dst, reversibleMotorsConfig()->deadband_low);
|
|
sbufWriteU16(dst, reversibleMotorsConfig()->deadband_high);
|
|
sbufWriteU16(dst, reversibleMotorsConfig()->neutral);
|
|
break;
|
|
|
|
case MSP_RC_DEADBAND:
|
|
sbufWriteU8(dst, rcControlsConfig()->deadband);
|
|
sbufWriteU8(dst, rcControlsConfig()->yaw_deadband);
|
|
sbufWriteU8(dst, rcControlsConfig()->alt_hold_deadband);
|
|
sbufWriteU16(dst, rcControlsConfig()->mid_throttle_deadband);
|
|
break;
|
|
|
|
case MSP_SENSOR_ALIGNMENT:
|
|
sbufWriteU8(dst, 0); // was gyroConfig()->gyro_align
|
|
sbufWriteU8(dst, 0); // was accelerometerConfig()->acc_align
|
|
#ifdef USE_MAG
|
|
sbufWriteU8(dst, compassConfig()->mag_align);
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
#endif
|
|
#ifdef USE_OPFLOW
|
|
sbufWriteU8(dst, opticalFlowConfig()->opflow_align);
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
#endif
|
|
break;
|
|
|
|
case MSP_ADVANCED_CONFIG:
|
|
sbufWriteU8(dst, 1); // gyroConfig()->gyroSyncDenominator
|
|
sbufWriteU8(dst, 1); // BF: masterConfig.pid_process_denom
|
|
sbufWriteU8(dst, 1); // BF: motorConfig()->useUnsyncedPwm
|
|
sbufWriteU8(dst, motorConfig()->motorPwmProtocol);
|
|
sbufWriteU16(dst, motorConfig()->motorPwmRate);
|
|
sbufWriteU16(dst, servoConfig()->servoPwmRate);
|
|
sbufWriteU8(dst, 0);
|
|
break;
|
|
|
|
case MSP_FILTER_CONFIG :
|
|
sbufWriteU8(dst, gyroConfig()->gyro_main_lpf_hz);
|
|
sbufWriteU16(dst, pidProfile()->dterm_lpf_hz);
|
|
sbufWriteU16(dst, pidProfile()->yaw_lpf_hz);
|
|
sbufWriteU16(dst, 0); //Was gyroConfig()->gyro_notch_hz
|
|
sbufWriteU16(dst, 1); //Was gyroConfig()->gyro_notch_cutoff
|
|
sbufWriteU16(dst, 0); //BF: pidProfile()->dterm_notch_hz
|
|
sbufWriteU16(dst, 1); //pidProfile()->dterm_notch_cutoff
|
|
|
|
sbufWriteU16(dst, 0); //BF: masterConfig.gyro_soft_notch_hz_2
|
|
sbufWriteU16(dst, 1); //BF: masterConfig.gyro_soft_notch_cutoff_2
|
|
|
|
sbufWriteU16(dst, accelerometerConfig()->acc_notch_hz);
|
|
sbufWriteU16(dst, accelerometerConfig()->acc_notch_cutoff);
|
|
|
|
sbufWriteU16(dst, 0); //Was gyroConfig()->gyro_stage2_lowpass_hz
|
|
break;
|
|
|
|
case MSP_PID_ADVANCED:
|
|
sbufWriteU16(dst, 0); // pidProfile()->rollPitchItermIgnoreRate
|
|
sbufWriteU16(dst, 0); // pidProfile()->yawItermIgnoreRate
|
|
sbufWriteU16(dst, 0); //pidProfile()->yaw_p_limit
|
|
sbufWriteU8(dst, 0); //BF: pidProfile()->deltaMethod
|
|
sbufWriteU8(dst, 0); //BF: pidProfile()->vbatPidCompensation
|
|
sbufWriteU8(dst, 0); //BF: pidProfile()->setpointRelaxRatio
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU16(dst, 0); //Was pidsum limit
|
|
sbufWriteU8(dst, 0); //BF: pidProfile()->itermThrottleGain
|
|
|
|
/*
|
|
* To keep compatibility on MSP frame length level with Betaflight, axis axisAccelerationLimitYaw
|
|
* limit will be sent and received in [dps / 10]
|
|
*/
|
|
sbufWriteU16(dst, constrain(pidProfile()->axisAccelerationLimitRollPitch / 10, 0, 65535));
|
|
sbufWriteU16(dst, constrain(pidProfile()->axisAccelerationLimitYaw / 10, 0, 65535));
|
|
break;
|
|
|
|
case MSP_INAV_PID:
|
|
sbufWriteU8(dst, 0); //Legacy, no longer in use async processing value
|
|
sbufWriteU16(dst, 0); //Legacy, no longer in use async processing value
|
|
sbufWriteU16(dst, 0); //Legacy, no longer in use async processing value
|
|
sbufWriteU8(dst, pidProfile()->heading_hold_rate_limit);
|
|
sbufWriteU8(dst, HEADING_HOLD_ERROR_LPF_FREQ);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU8(dst, GYRO_LPF_256HZ);
|
|
sbufWriteU8(dst, accelerometerConfig()->acc_lpf_hz);
|
|
sbufWriteU8(dst, 0); //reserved
|
|
sbufWriteU8(dst, 0); //reserved
|
|
sbufWriteU8(dst, 0); //reserved
|
|
sbufWriteU8(dst, 0); //reserved
|
|
break;
|
|
|
|
case MSP_SENSOR_CONFIG:
|
|
sbufWriteU8(dst, accelerometerConfig()->acc_hardware);
|
|
#ifdef USE_BARO
|
|
sbufWriteU8(dst, barometerConfig()->baro_hardware);
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
#endif
|
|
#ifdef USE_MAG
|
|
sbufWriteU8(dst, compassConfig()->mag_hardware);
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
#endif
|
|
#ifdef USE_PITOT
|
|
sbufWriteU8(dst, pitotmeterConfig()->pitot_hardware);
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
#endif
|
|
#ifdef USE_RANGEFINDER
|
|
sbufWriteU8(dst, rangefinderConfig()->rangefinder_hardware);
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
#endif
|
|
#ifdef USE_OPFLOW
|
|
sbufWriteU8(dst, opticalFlowConfig()->opflow_hardware);
|
|
#else
|
|
sbufWriteU8(dst, 0);
|
|
#endif
|
|
break;
|
|
|
|
case MSP_NAV_POSHOLD:
|
|
sbufWriteU8(dst, navConfig()->general.flags.user_control_mode);
|
|
sbufWriteU16(dst, navConfig()->general.max_auto_speed);
|
|
sbufWriteU16(dst, mixerConfig()->platformType == PLATFORM_AIRPLANE ? navConfig()->fw.max_auto_climb_rate : navConfig()->mc.max_auto_climb_rate);
|
|
sbufWriteU16(dst, navConfig()->general.max_manual_speed);
|
|
sbufWriteU16(dst, mixerConfig()->platformType == PLATFORM_AIRPLANE ? navConfig()->fw.max_manual_climb_rate : navConfig()->mc.max_manual_climb_rate);
|
|
sbufWriteU8(dst, navConfig()->mc.max_bank_angle);
|
|
sbufWriteU8(dst, navConfig()->mc.althold_throttle_type);
|
|
sbufWriteU16(dst, currentBatteryProfile->nav.mc.hover_throttle);
|
|
break;
|
|
|
|
case MSP_RTH_AND_LAND_CONFIG:
|
|
sbufWriteU16(dst, navConfig()->general.min_rth_distance);
|
|
sbufWriteU8(dst, navConfig()->general.flags.rth_climb_first);
|
|
sbufWriteU8(dst, navConfig()->general.flags.rth_climb_ignore_emerg);
|
|
sbufWriteU8(dst, navConfig()->general.flags.rth_tail_first);
|
|
sbufWriteU8(dst, navConfig()->general.flags.rth_allow_landing);
|
|
sbufWriteU8(dst, navConfig()->general.flags.rth_alt_control_mode);
|
|
sbufWriteU16(dst, navConfig()->general.rth_abort_threshold);
|
|
sbufWriteU16(dst, navConfig()->general.rth_altitude);
|
|
sbufWriteU16(dst, navConfig()->general.land_minalt_vspd);
|
|
sbufWriteU16(dst, navConfig()->general.land_maxalt_vspd);
|
|
sbufWriteU16(dst, navConfig()->general.land_slowdown_minalt);
|
|
sbufWriteU16(dst, navConfig()->general.land_slowdown_maxalt);
|
|
sbufWriteU16(dst, navConfig()->general.emerg_descent_rate);
|
|
break;
|
|
|
|
case MSP_FW_CONFIG:
|
|
sbufWriteU16(dst, currentBatteryProfile->nav.fw.cruise_throttle);
|
|
sbufWriteU16(dst, currentBatteryProfile->nav.fw.min_throttle);
|
|
sbufWriteU16(dst, currentBatteryProfile->nav.fw.max_throttle);
|
|
sbufWriteU8(dst, navConfig()->fw.max_bank_angle);
|
|
sbufWriteU8(dst, navConfig()->fw.max_climb_angle);
|
|
sbufWriteU8(dst, navConfig()->fw.max_dive_angle);
|
|
sbufWriteU8(dst, currentBatteryProfile->nav.fw.pitch_to_throttle);
|
|
sbufWriteU16(dst, navConfig()->fw.loiter_radius);
|
|
break;
|
|
|
|
case MSP_CALIBRATION_DATA:
|
|
sbufWriteU8(dst, accGetCalibrationAxisFlags());
|
|
sbufWriteU16(dst, accelerometerConfig()->accZero.raw[X]);
|
|
sbufWriteU16(dst, accelerometerConfig()->accZero.raw[Y]);
|
|
sbufWriteU16(dst, accelerometerConfig()->accZero.raw[Z]);
|
|
sbufWriteU16(dst, accelerometerConfig()->accGain.raw[X]);
|
|
sbufWriteU16(dst, accelerometerConfig()->accGain.raw[Y]);
|
|
sbufWriteU16(dst, accelerometerConfig()->accGain.raw[Z]);
|
|
|
|
#ifdef USE_MAG
|
|
sbufWriteU16(dst, compassConfig()->magZero.raw[X]);
|
|
sbufWriteU16(dst, compassConfig()->magZero.raw[Y]);
|
|
sbufWriteU16(dst, compassConfig()->magZero.raw[Z]);
|
|
#else
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
|
|
#ifdef USE_OPFLOW
|
|
sbufWriteU16(dst, opticalFlowConfig()->opflow_scale * 256);
|
|
#else
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
|
|
#ifdef USE_MAG
|
|
sbufWriteU16(dst, compassConfig()->magGain[X]);
|
|
sbufWriteU16(dst, compassConfig()->magGain[Y]);
|
|
sbufWriteU16(dst, compassConfig()->magGain[Z]);
|
|
#else
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
|
|
break;
|
|
|
|
case MSP_POSITION_ESTIMATION_CONFIG:
|
|
|
|
sbufWriteU16(dst, positionEstimationConfig()->w_z_baro_p * 100); // inav_w_z_baro_p float as value * 100
|
|
sbufWriteU16(dst, positionEstimationConfig()->w_z_gps_p * 100); // 2 inav_w_z_gps_p float as value * 100
|
|
sbufWriteU16(dst, positionEstimationConfig()->w_z_gps_v * 100); // 2 inav_w_z_gps_v float as value * 100
|
|
sbufWriteU16(dst, positionEstimationConfig()->w_xy_gps_p * 100); // 2 inav_w_xy_gps_p float as value * 100
|
|
sbufWriteU16(dst, positionEstimationConfig()->w_xy_gps_v * 100); // 2 inav_w_xy_gps_v float as value * 100
|
|
sbufWriteU8(dst, gpsConfigMutable()->gpsMinSats); // 1
|
|
sbufWriteU8(dst, 1); // 1 inav_use_gps_velned ON/OFF
|
|
|
|
break;
|
|
|
|
case MSP_REBOOT:
|
|
if (!ARMING_FLAG(ARMED)) {
|
|
if (mspPostProcessFn) {
|
|
*mspPostProcessFn = mspRebootFn;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MSP_WP_GETINFO:
|
|
sbufWriteU8(dst, 0); // Reserved for waypoint capabilities
|
|
sbufWriteU8(dst, NAV_MAX_WAYPOINTS); // Maximum number of waypoints supported
|
|
sbufWriteU8(dst, isWaypointListValid()); // Is current mission valid
|
|
sbufWriteU8(dst, getWaypointCount()); // Number of waypoints in current mission
|
|
break;
|
|
|
|
case MSP_TX_INFO:
|
|
sbufWriteU8(dst, getRSSISource());
|
|
uint8_t rtcDateTimeIsSet = 0;
|
|
dateTime_t dt;
|
|
if (rtcGetDateTime(&dt)) {
|
|
rtcDateTimeIsSet = 1;
|
|
}
|
|
sbufWriteU8(dst, rtcDateTimeIsSet);
|
|
break;
|
|
|
|
case MSP_RTC:
|
|
{
|
|
int32_t seconds = 0;
|
|
uint16_t millis = 0;
|
|
rtcTime_t t;
|
|
if (rtcGet(&t)) {
|
|
seconds = rtcTimeGetSeconds(&t);
|
|
millis = rtcTimeGetMillis(&t);
|
|
}
|
|
sbufWriteU32(dst, (uint32_t)seconds);
|
|
sbufWriteU16(dst, millis);
|
|
}
|
|
break;
|
|
|
|
case MSP_VTX_CONFIG:
|
|
#ifdef USE_VTX_CONTROL
|
|
{
|
|
vtxDevice_t *vtxDevice = vtxCommonDevice();
|
|
if (vtxDevice) {
|
|
|
|
uint8_t deviceType = vtxCommonGetDeviceType(vtxDevice);
|
|
|
|
// Return band, channel and power from vtxSettingsConfig_t
|
|
// since the VTX might be configured but temporarily offline.
|
|
uint8_t pitmode = 0;
|
|
vtxCommonGetPitMode(vtxDevice, &pitmode);
|
|
|
|
sbufWriteU8(dst, deviceType);
|
|
sbufWriteU8(dst, vtxSettingsConfig()->band);
|
|
sbufWriteU8(dst, vtxSettingsConfig()->channel);
|
|
sbufWriteU8(dst, vtxSettingsConfig()->power);
|
|
sbufWriteU8(dst, pitmode);
|
|
|
|
// Betaflight < 4 doesn't send these fields
|
|
sbufWriteU8(dst, vtxCommonDeviceIsReady(vtxDevice) ? 1 : 0);
|
|
sbufWriteU8(dst, vtxSettingsConfig()->lowPowerDisarm);
|
|
// future extensions here...
|
|
}
|
|
else {
|
|
sbufWriteU8(dst, VTXDEV_UNKNOWN); // no VTX configured
|
|
}
|
|
}
|
|
#else
|
|
sbufWriteU8(dst, VTXDEV_UNKNOWN); // no VTX configured
|
|
#endif
|
|
break;
|
|
|
|
case MSP_NAME:
|
|
{
|
|
const char *name = systemConfig()->craftName;
|
|
while (*name) {
|
|
sbufWriteU8(dst, *name++);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MSP2_COMMON_TZ:
|
|
sbufWriteU16(dst, (uint16_t)timeConfig()->tz_offset);
|
|
sbufWriteU8(dst, (uint8_t)timeConfig()->tz_automatic_dst);
|
|
break;
|
|
|
|
case MSP2_INAV_AIR_SPEED:
|
|
#ifdef USE_PITOT
|
|
sbufWriteU32(dst, getAirspeedEstimate());
|
|
#else
|
|
sbufWriteU32(dst, 0);
|
|
#endif
|
|
break;
|
|
|
|
case MSP2_INAV_MIXER:
|
|
sbufWriteU8(dst, mixerConfig()->motorDirectionInverted);
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, mixerConfig()->motorstopOnLow);
|
|
sbufWriteU8(dst, mixerConfig()->platformType);
|
|
sbufWriteU8(dst, mixerConfig()->hasFlaps);
|
|
sbufWriteU16(dst, mixerConfig()->appliedMixerPreset);
|
|
sbufWriteU8(dst, MAX_SUPPORTED_MOTORS);
|
|
sbufWriteU8(dst, MAX_SUPPORTED_SERVOS);
|
|
break;
|
|
|
|
#if defined(USE_OSD)
|
|
case MSP2_INAV_OSD_ALARMS:
|
|
sbufWriteU8(dst, osdConfig()->rssi_alarm);
|
|
sbufWriteU16(dst, osdConfig()->time_alarm);
|
|
sbufWriteU16(dst, osdConfig()->alt_alarm);
|
|
sbufWriteU16(dst, osdConfig()->dist_alarm);
|
|
sbufWriteU16(dst, osdConfig()->neg_alt_alarm);
|
|
sbufWriteU16(dst, osdConfig()->gforce_alarm * 1000);
|
|
sbufWriteU16(dst, (int16_t)(osdConfig()->gforce_axis_alarm_min * 1000));
|
|
sbufWriteU16(dst, (int16_t)(osdConfig()->gforce_axis_alarm_max * 1000));
|
|
sbufWriteU8(dst, osdConfig()->current_alarm);
|
|
sbufWriteU16(dst, osdConfig()->imu_temp_alarm_min);
|
|
sbufWriteU16(dst, osdConfig()->imu_temp_alarm_max);
|
|
#ifdef USE_BARO
|
|
sbufWriteU16(dst, osdConfig()->baro_temp_alarm_min);
|
|
sbufWriteU16(dst, osdConfig()->baro_temp_alarm_max);
|
|
#else
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
#ifdef USE_ADSB
|
|
sbufWriteU16(dst, osdConfig()->adsb_distance_warning);
|
|
sbufWriteU16(dst, osdConfig()->adsb_distance_alert);
|
|
#else
|
|
sbufWriteU16(dst, 0);
|
|
sbufWriteU16(dst, 0);
|
|
#endif
|
|
break;
|
|
|
|
case MSP2_INAV_OSD_PREFERENCES:
|
|
sbufWriteU8(dst, osdConfig()->video_system);
|
|
sbufWriteU8(dst, osdConfig()->main_voltage_decimals);
|
|
sbufWriteU8(dst, osdConfig()->ahi_reverse_roll);
|
|
sbufWriteU8(dst, osdConfig()->crosshairs_style);
|
|
sbufWriteU8(dst, osdConfig()->left_sidebar_scroll);
|
|
sbufWriteU8(dst, osdConfig()->right_sidebar_scroll);
|
|
sbufWriteU8(dst, osdConfig()->sidebar_scroll_arrows);
|
|
sbufWriteU8(dst, osdConfig()->units);
|
|
sbufWriteU8(dst, osdConfig()->stats_energy_unit);
|
|
break;
|
|
|
|
#endif
|
|
|
|
case MSP2_INAV_OUTPUT_MAPPING:
|
|
for (uint8_t i = 0; i < timerHardwareCount; ++i)
|
|
if (!(timerHardware[i].usageFlags & (TIM_USE_PPM | TIM_USE_PWM))) {
|
|
sbufWriteU8(dst, timerHardware[i].usageFlags);
|
|
}
|
|
break;
|
|
|
|
// Obsolete, replaced by MSP2_INAV_OUTPUT_MAPPING_EXT2
|
|
case MSP2_INAV_OUTPUT_MAPPING_EXT:
|
|
for (uint8_t i = 0; i < timerHardwareCount; ++i)
|
|
if (!(timerHardware[i].usageFlags & (TIM_USE_PPM | TIM_USE_PWM))) {
|
|
#if defined(SITL_BUILD)
|
|
sbufWriteU8(dst, i);
|
|
#else
|
|
sbufWriteU8(dst, timer2id(timerHardware[i].tim));
|
|
#endif
|
|
// usageFlags is u32, cuts out the higher 24bits
|
|
sbufWriteU8(dst, timerHardware[i].usageFlags);
|
|
}
|
|
break;
|
|
case MSP2_INAV_OUTPUT_MAPPING_EXT2:
|
|
{
|
|
#if !defined(SITL_BUILD) && defined(WS2811_PIN)
|
|
ioTag_t led_tag = IO_TAG(WS2811_PIN);
|
|
#endif
|
|
for (uint8_t i = 0; i < timerHardwareCount; ++i)
|
|
|
|
if (!(timerHardware[i].usageFlags & (TIM_USE_PPM | TIM_USE_PWM))) {
|
|
#if defined(SITL_BUILD)
|
|
sbufWriteU8(dst, i);
|
|
#else
|
|
sbufWriteU8(dst, timer2id(timerHardware[i].tim));
|
|
#endif
|
|
sbufWriteU32(dst, timerHardware[i].usageFlags);
|
|
#if defined(SITL_BUILD) || !defined(WS2811_PIN)
|
|
sbufWriteU8(dst, 0);
|
|
#else
|
|
// Extra label to help identify repurposed PINs.
|
|
// Eventually, we can try to add more labels for PPM pins, etc.
|
|
sbufWriteU8(dst, timerHardware[i].tag == led_tag ? PIN_LABEL_LED : PIN_LABEL_NONE);
|
|
#endif
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case MSP2_INAV_MC_BRAKING:
|
|
#ifdef USE_MR_BRAKING_MODE
|
|
sbufWriteU16(dst, navConfig()->mc.braking_speed_threshold);
|
|
sbufWriteU16(dst, navConfig()->mc.braking_disengage_speed);
|
|
sbufWriteU16(dst, navConfig()->mc.braking_timeout);
|
|
sbufWriteU8(dst, navConfig()->mc.braking_boost_factor);
|
|
sbufWriteU16(dst, navConfig()->mc.braking_boost_timeout);
|
|
sbufWriteU16(dst, navConfig()->mc.braking_boost_speed_threshold);
|
|
sbufWriteU16(dst, navConfig()->mc.braking_boost_disengage_speed);
|
|
sbufWriteU8(dst, navConfig()->mc.braking_bank_angle);
|
|
#endif
|
|
break;
|
|
|
|
#ifdef USE_TEMPERATURE_SENSOR
|
|
case MSP2_INAV_TEMP_SENSOR_CONFIG:
|
|
for (uint8_t index = 0; index < MAX_TEMP_SENSORS; ++index) {
|
|
const tempSensorConfig_t *sensorConfig = tempSensorConfig(index);
|
|
sbufWriteU8(dst, sensorConfig->type);
|
|
for (uint8_t addrIndex = 0; addrIndex < 8; ++addrIndex)
|
|
sbufWriteU8(dst, ((uint8_t *)&sensorConfig->address)[addrIndex]);
|
|
sbufWriteU16(dst, sensorConfig->alarm_min);
|
|
sbufWriteU16(dst, sensorConfig->alarm_max);
|
|
sbufWriteU8(dst, sensorConfig->osdSymbol);
|
|
for (uint8_t labelIndex = 0; labelIndex < TEMPERATURE_LABEL_LEN; ++labelIndex)
|
|
sbufWriteU8(dst, sensorConfig->label[labelIndex]);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_TEMPERATURE_SENSOR
|
|
case MSP2_INAV_TEMPERATURES:
|
|
for (uint8_t index = 0; index < MAX_TEMP_SENSORS; ++index) {
|
|
int16_t temperature;
|
|
bool valid = getSensorTemperature(index, &temperature);
|
|
sbufWriteU16(dst, valid ? temperature : -1000);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_ESC_SENSOR
|
|
case MSP2_INAV_ESC_RPM:
|
|
{
|
|
uint8_t motorCount = getMotorCount();
|
|
|
|
for (uint8_t i = 0; i < motorCount; i++){
|
|
const escSensorData_t *escState = getEscTelemetry(i); //Get ESC telemetry
|
|
sbufWriteU32(dst, escState->rpm);
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_EZ_TUNE
|
|
|
|
case MSP2_INAV_EZ_TUNE:
|
|
{
|
|
sbufWriteU8(dst, ezTune()->enabled);
|
|
sbufWriteU16(dst, ezTune()->filterHz);
|
|
sbufWriteU8(dst, ezTune()->axisRatio);
|
|
sbufWriteU8(dst, ezTune()->response);
|
|
sbufWriteU8(dst, ezTune()->damping);
|
|
sbufWriteU8(dst, ezTune()->stability);
|
|
sbufWriteU8(dst, ezTune()->aggressiveness);
|
|
sbufWriteU8(dst, ezTune()->rate);
|
|
sbufWriteU8(dst, ezTune()->expo);
|
|
sbufWriteU8(dst, ezTune()->snappiness);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_RATE_DYNAMICS
|
|
|
|
case MSP2_INAV_RATE_DYNAMICS:
|
|
{
|
|
sbufWriteU8(dst, currentControlRateProfile->rateDynamics.sensitivityCenter);
|
|
sbufWriteU8(dst, currentControlRateProfile->rateDynamics.sensitivityEnd);
|
|
sbufWriteU8(dst, currentControlRateProfile->rateDynamics.correctionCenter);
|
|
sbufWriteU8(dst, currentControlRateProfile->rateDynamics.correctionEnd);
|
|
sbufWriteU8(dst, currentControlRateProfile->rateDynamics.weightCenter);
|
|
sbufWriteU8(dst, currentControlRateProfile->rateDynamics.weightEnd);
|
|
}
|
|
break;
|
|
|
|
#endif
|
|
#ifdef USE_PROGRAMMING_FRAMEWORK
|
|
case MSP2_INAV_CUSTOM_OSD_ELEMENTS:
|
|
sbufWriteU8(dst, MAX_CUSTOM_ELEMENTS);
|
|
sbufWriteU8(dst, OSD_CUSTOM_ELEMENT_TEXT_SIZE - 1);
|
|
|
|
for (int i = 0; i < MAX_CUSTOM_ELEMENTS; i++) {
|
|
const osdCustomElement_t *customElement = osdCustomElements(i);
|
|
for (int ii = 0; ii < CUSTOM_ELEMENTS_PARTS; ii++) {
|
|
sbufWriteU8(dst, customElement->part[ii].type);
|
|
sbufWriteU16(dst, customElement->part[ii].value);
|
|
}
|
|
sbufWriteU8(dst, customElement->visibility.type);
|
|
sbufWriteU16(dst, customElement->visibility.value);
|
|
for (int ii = 0; ii < OSD_CUSTOM_ELEMENT_TEXT_SIZE - 1; ii++) {
|
|
sbufWriteU8(dst, customElement->osdCustomElementText[ii]);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_SAFE_HOME
|
|
static mspResult_e mspFcSafeHomeOutCommand(sbuf_t *dst, sbuf_t *src)
|
|
{
|
|
const uint8_t safe_home_no = sbufReadU8(src); // get the home number
|
|
if(safe_home_no < MAX_SAFE_HOMES) {
|
|
sbufWriteU8(dst, safe_home_no);
|
|
sbufWriteU8(dst, safeHomeConfig(safe_home_no)->enabled);
|
|
sbufWriteU32(dst, safeHomeConfig(safe_home_no)->lat);
|
|
sbufWriteU32(dst, safeHomeConfig(safe_home_no)->lon);
|
|
return MSP_RESULT_ACK;
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_FW_AUTOLAND
|
|
static mspResult_e mspFwApproachOutCommand(sbuf_t *dst, sbuf_t *src)
|
|
{
|
|
const uint8_t idx = sbufReadU8(src);
|
|
if(idx < MAX_FW_LAND_APPOACH_SETTINGS) {
|
|
sbufWriteU8(dst, idx);
|
|
sbufWriteU32(dst, fwAutolandApproachConfig(idx)->approachAlt);
|
|
sbufWriteU32(dst, fwAutolandApproachConfig(idx)->landAlt);
|
|
sbufWriteU8(dst, fwAutolandApproachConfig(idx)->approachDirection);
|
|
sbufWriteU16(dst, fwAutolandApproachConfig(idx)->landApproachHeading1);
|
|
sbufWriteU16(dst, fwAutolandApproachConfig(idx)->landApproachHeading2);
|
|
sbufWriteU8(dst, fwAutolandApproachConfig(idx)->isSeaLevelRef);
|
|
return MSP_RESULT_ACK;
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static mspResult_e mspFcLogicConditionCommand(sbuf_t *dst, sbuf_t *src) {
|
|
const uint8_t idx = sbufReadU8(src);
|
|
if (idx < MAX_LOGIC_CONDITIONS) {
|
|
sbufWriteU8(dst, logicConditions(idx)->enabled);
|
|
sbufWriteU8(dst, logicConditions(idx)->activatorId);
|
|
sbufWriteU8(dst, logicConditions(idx)->operation);
|
|
sbufWriteU8(dst, logicConditions(idx)->operandA.type);
|
|
sbufWriteU32(dst, logicConditions(idx)->operandA.value);
|
|
sbufWriteU8(dst, logicConditions(idx)->operandB.type);
|
|
sbufWriteU32(dst, logicConditions(idx)->operandB.value);
|
|
sbufWriteU8(dst, logicConditions(idx)->flags);
|
|
return MSP_RESULT_ACK;
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
}
|
|
|
|
static void mspFcWaypointOutCommand(sbuf_t *dst, sbuf_t *src)
|
|
{
|
|
const uint8_t msp_wp_no = sbufReadU8(src); // get the wp number
|
|
navWaypoint_t msp_wp;
|
|
getWaypoint(msp_wp_no, &msp_wp);
|
|
sbufWriteU8(dst, msp_wp_no); // wp_no
|
|
sbufWriteU8(dst, msp_wp.action); // action (WAYPOINT)
|
|
sbufWriteU32(dst, msp_wp.lat); // lat
|
|
sbufWriteU32(dst, msp_wp.lon); // lon
|
|
sbufWriteU32(dst, msp_wp.alt); // altitude (cm)
|
|
sbufWriteU16(dst, msp_wp.p1); // P1
|
|
sbufWriteU16(dst, msp_wp.p2); // P2
|
|
sbufWriteU16(dst, msp_wp.p3); // P3
|
|
sbufWriteU8(dst, msp_wp.flag); // flags
|
|
}
|
|
|
|
#ifdef USE_FLASHFS
|
|
static void mspFcDataFlashReadCommand(sbuf_t *dst, sbuf_t *src)
|
|
{
|
|
const unsigned int dataSize = sbufBytesRemaining(src);
|
|
uint16_t readLength;
|
|
|
|
const uint32_t readAddress = sbufReadU32(src);
|
|
|
|
// Request payload:
|
|
// uint32_t - address to read from
|
|
// uint16_t - size of block to read (optional)
|
|
if (dataSize == sizeof(uint32_t) + sizeof(uint16_t)) {
|
|
readLength = sbufReadU16(src);
|
|
}
|
|
else {
|
|
readLength = 128;
|
|
}
|
|
|
|
serializeDataflashReadReply(dst, readAddress, readLength);
|
|
}
|
|
#endif
|
|
|
|
static mspResult_e mspFcProcessInCommand(uint16_t cmdMSP, sbuf_t *src)
|
|
{
|
|
uint8_t tmp_u8;
|
|
uint16_t tmp_u16;
|
|
|
|
const unsigned int dataSize = sbufBytesRemaining(src);
|
|
|
|
switch (cmdMSP) {
|
|
case MSP_SELECT_SETTING:
|
|
if (sbufReadU8Safe(&tmp_u8, src) && (!ARMING_FLAG(ARMED)))
|
|
setConfigProfileAndWriteEEPROM(tmp_u8);
|
|
else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_HEAD:
|
|
if (sbufReadU16Safe(&tmp_u16, src))
|
|
updateHeadingHoldTarget(tmp_u16);
|
|
else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
#ifdef USE_RX_MSP
|
|
case MSP_SET_RAW_RC:
|
|
{
|
|
uint8_t channelCount = dataSize / sizeof(uint16_t);
|
|
if ((channelCount > MAX_SUPPORTED_RC_CHANNEL_COUNT) || (dataSize > channelCount * sizeof(uint16_t))) {
|
|
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);
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case MSP_SET_LOOP_TIME:
|
|
if (sbufReadU16Safe(&tmp_u16, src))
|
|
gyroConfigMutable()->looptime = tmp_u16;
|
|
else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_SET_PID:
|
|
if (dataSize == PID_ITEM_COUNT * 4) {
|
|
for (int i = 0; i < PID_ITEM_COUNT; i++) {
|
|
pidBankMutable()->pid[i].P = sbufReadU8(src);
|
|
pidBankMutable()->pid[i].I = sbufReadU8(src);
|
|
pidBankMutable()->pid[i].D = sbufReadU8(src);
|
|
pidBankMutable()->pid[i].FF = sbufReadU8(src);
|
|
}
|
|
schedulePidGainsUpdate();
|
|
navigationUsePIDs();
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_MODE_RANGE:
|
|
sbufReadU8Safe(&tmp_u8, src);
|
|
if ((dataSize == 5) && (tmp_u8 < MAX_MODE_ACTIVATION_CONDITION_COUNT)) {
|
|
modeActivationCondition_t *mac = modeActivationConditionsMutable(tmp_u8);
|
|
tmp_u8 = sbufReadU8(src);
|
|
const box_t *box = findBoxByPermanentId(tmp_u8);
|
|
if (box) {
|
|
mac->modeId = box->boxId;
|
|
mac->auxChannelIndex = sbufReadU8(src);
|
|
mac->range.startStep = sbufReadU8(src);
|
|
mac->range.endStep = sbufReadU8(src);
|
|
|
|
updateUsedModeActivationConditionFlags();
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
|
|
case MSP_SET_ADJUSTMENT_RANGE:
|
|
sbufReadU8Safe(&tmp_u8, src);
|
|
if ((dataSize == 7) && (tmp_u8 < MAX_ADJUSTMENT_RANGE_COUNT)) {
|
|
adjustmentRange_t *adjRange = adjustmentRangesMutable(tmp_u8);
|
|
tmp_u8 = sbufReadU8(src);
|
|
if (tmp_u8 < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
|
|
adjRange->adjustmentIndex = tmp_u8;
|
|
adjRange->auxChannelIndex = sbufReadU8(src);
|
|
adjRange->range.startStep = sbufReadU8(src);
|
|
adjRange->range.endStep = sbufReadU8(src);
|
|
adjRange->adjustmentFunction = sbufReadU8(src);
|
|
adjRange->auxSwitchChannelIndex = sbufReadU8(src);
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
|
|
case MSP_SET_RC_TUNING:
|
|
if ((dataSize == 10) || (dataSize == 11)) {
|
|
sbufReadU8(src); //Read rcRate8, kept for protocol compatibility reasons
|
|
// need to cast away const to set controlRateProfile
|
|
((controlRateConfig_t*)currentControlRateProfile)->stabilized.rcExpo8 = sbufReadU8(src);
|
|
for (int i = 0; i < 3; i++) {
|
|
tmp_u8 = sbufReadU8(src);
|
|
if (i == FD_YAW) {
|
|
((controlRateConfig_t*)currentControlRateProfile)->stabilized.rates[i] = constrain(tmp_u8, SETTING_YAW_RATE_MIN, SETTING_YAW_RATE_MAX);
|
|
}
|
|
else {
|
|
((controlRateConfig_t*)currentControlRateProfile)->stabilized.rates[i] = constrain(tmp_u8, SETTING_CONSTANT_ROLL_PITCH_RATE_MIN, SETTING_CONSTANT_ROLL_PITCH_RATE_MAX);
|
|
}
|
|
}
|
|
tmp_u8 = sbufReadU8(src);
|
|
((controlRateConfig_t*)currentControlRateProfile)->throttle.dynPID = MIN(tmp_u8, SETTING_TPA_RATE_MAX);
|
|
((controlRateConfig_t*)currentControlRateProfile)->throttle.rcMid8 = sbufReadU8(src);
|
|
((controlRateConfig_t*)currentControlRateProfile)->throttle.rcExpo8 = sbufReadU8(src);
|
|
((controlRateConfig_t*)currentControlRateProfile)->throttle.pa_breakpoint = sbufReadU16(src);
|
|
if (dataSize > 10) {
|
|
((controlRateConfig_t*)currentControlRateProfile)->stabilized.rcYawExpo8 = sbufReadU8(src);
|
|
}
|
|
|
|
schedulePidGainsUpdate();
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
|
|
case MSP2_INAV_SET_RATE_PROFILE:
|
|
if (dataSize == 15) {
|
|
controlRateConfig_t *currentControlRateProfile_p = (controlRateConfig_t*)currentControlRateProfile; // need to cast away const to set controlRateProfile
|
|
|
|
// throttle
|
|
currentControlRateProfile_p->throttle.rcMid8 = sbufReadU8(src);
|
|
currentControlRateProfile_p->throttle.rcExpo8 = sbufReadU8(src);
|
|
currentControlRateProfile_p->throttle.dynPID = sbufReadU8(src);
|
|
currentControlRateProfile_p->throttle.pa_breakpoint = sbufReadU16(src);
|
|
|
|
// stabilized
|
|
currentControlRateProfile_p->stabilized.rcExpo8 = sbufReadU8(src);
|
|
currentControlRateProfile_p->stabilized.rcYawExpo8 = sbufReadU8(src);
|
|
for (uint8_t i = 0; i < 3; ++i) {
|
|
tmp_u8 = sbufReadU8(src);
|
|
if (i == FD_YAW) {
|
|
currentControlRateProfile_p->stabilized.rates[i] = constrain(tmp_u8, SETTING_YAW_RATE_MIN, SETTING_YAW_RATE_MAX);
|
|
} else {
|
|
currentControlRateProfile_p->stabilized.rates[i] = constrain(tmp_u8, SETTING_CONSTANT_ROLL_PITCH_RATE_MIN, SETTING_CONSTANT_ROLL_PITCH_RATE_MAX);
|
|
}
|
|
}
|
|
|
|
// manual
|
|
currentControlRateProfile_p->manual.rcExpo8 = sbufReadU8(src);
|
|
currentControlRateProfile_p->manual.rcYawExpo8 = sbufReadU8(src);
|
|
for (uint8_t i = 0; i < 3; ++i) {
|
|
tmp_u8 = sbufReadU8(src);
|
|
if (i == FD_YAW) {
|
|
currentControlRateProfile_p->manual.rates[i] = constrain(tmp_u8, SETTING_YAW_RATE_MIN, SETTING_YAW_RATE_MAX);
|
|
} else {
|
|
currentControlRateProfile_p->manual.rates[i] = constrain(tmp_u8, SETTING_CONSTANT_ROLL_PITCH_RATE_MIN, SETTING_CONSTANT_ROLL_PITCH_RATE_MAX);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
|
|
case MSP_SET_MISC:
|
|
if (dataSize == 22) {
|
|
sbufReadU16(src); // midrc
|
|
|
|
sbufReadU16(src); //Was min_throttle
|
|
sbufReadU16(src); //Was maxThrottle
|
|
motorConfigMutable()->mincommand = constrain(sbufReadU16(src), 0, PWM_RANGE_MAX);
|
|
|
|
currentBatteryProfileMutable->failsafe_throttle = constrain(sbufReadU16(src), PWM_RANGE_MIN, PWM_RANGE_MAX);
|
|
|
|
#ifdef USE_GPS
|
|
gpsConfigMutable()->provider = sbufReadU8(src); // gps_type
|
|
sbufReadU8(src); // gps_baudrate
|
|
gpsConfigMutable()->sbasMode = sbufReadU8(src); // gps_ubx_sbas
|
|
#else
|
|
sbufReadU8(src); // gps_type
|
|
sbufReadU8(src); // gps_baudrate
|
|
sbufReadU8(src); // gps_ubx_sbas
|
|
#endif
|
|
sbufReadU8(src); // multiwiiCurrentMeterOutput
|
|
tmp_u8 = sbufReadU8(src);
|
|
if (tmp_u8 <= MAX_SUPPORTED_RC_CHANNEL_COUNT) {
|
|
rxConfigMutable()->rssi_channel = tmp_u8;
|
|
rxUpdateRSSISource(); // Changing rssi_channel might change the RSSI source
|
|
}
|
|
sbufReadU8(src);
|
|
|
|
#ifdef USE_MAG
|
|
compassConfigMutable()->mag_declination = sbufReadU16(src) * 10;
|
|
#else
|
|
sbufReadU16(src);
|
|
#endif
|
|
|
|
#ifdef USE_ADC
|
|
batteryMetersConfigMutable()->voltage.scale = sbufReadU8(src) * 10;
|
|
currentBatteryProfileMutable->voltage.cellMin = sbufReadU8(src) * 10; // vbatlevel_warn1 in MWC2.3 GUI
|
|
currentBatteryProfileMutable->voltage.cellMax = sbufReadU8(src) * 10; // vbatlevel_warn2 in MWC2.3 GUI
|
|
currentBatteryProfileMutable->voltage.cellWarning = sbufReadU8(src) * 10; // vbatlevel when buzzer starts to alert
|
|
#else
|
|
sbufReadU8(src);
|
|
sbufReadU8(src);
|
|
sbufReadU8(src);
|
|
sbufReadU8(src);
|
|
#endif
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_INAV_SET_MISC:
|
|
if (dataSize == 41) {
|
|
sbufReadU16(src); // midrc
|
|
|
|
sbufReadU16(src); // was min_throttle
|
|
sbufReadU16(src); // was maxThrottle
|
|
motorConfigMutable()->mincommand = constrain(sbufReadU16(src), 0, PWM_RANGE_MAX);
|
|
|
|
currentBatteryProfileMutable->failsafe_throttle = constrain(sbufReadU16(src), PWM_RANGE_MIN, PWM_RANGE_MAX);
|
|
|
|
#ifdef USE_GPS
|
|
gpsConfigMutable()->provider = sbufReadU8(src); // gps_type
|
|
sbufReadU8(src); // gps_baudrate
|
|
gpsConfigMutable()->sbasMode = sbufReadU8(src); // gps_ubx_sbas
|
|
#else
|
|
sbufReadU8(src); // gps_type
|
|
sbufReadU8(src); // gps_baudrate
|
|
sbufReadU8(src); // gps_ubx_sbas
|
|
#endif
|
|
|
|
tmp_u8 = sbufReadU8(src);
|
|
if (tmp_u8 <= MAX_SUPPORTED_RC_CHANNEL_COUNT)
|
|
rxConfigMutable()->rssi_channel = tmp_u8;
|
|
|
|
#ifdef USE_MAG
|
|
compassConfigMutable()->mag_declination = sbufReadU16(src) * 10;
|
|
#else
|
|
sbufReadU16(src);
|
|
#endif
|
|
|
|
#ifdef USE_ADC
|
|
batteryMetersConfigMutable()->voltage.scale = sbufReadU16(src);
|
|
batteryMetersConfigMutable()->voltageSource = sbufReadU8(src);
|
|
currentBatteryProfileMutable->cells = sbufReadU8(src);
|
|
currentBatteryProfileMutable->voltage.cellDetect = sbufReadU16(src);
|
|
currentBatteryProfileMutable->voltage.cellMin = sbufReadU16(src);
|
|
currentBatteryProfileMutable->voltage.cellMax = sbufReadU16(src);
|
|
currentBatteryProfileMutable->voltage.cellWarning = sbufReadU16(src);
|
|
#else
|
|
sbufReadU16(src);
|
|
sbufReadU8(src);
|
|
sbufReadU8(src);
|
|
sbufReadU16(src);
|
|
sbufReadU16(src);
|
|
sbufReadU16(src);
|
|
sbufReadU16(src);
|
|
#endif
|
|
|
|
currentBatteryProfileMutable->capacity.value = sbufReadU32(src);
|
|
currentBatteryProfileMutable->capacity.warning = sbufReadU32(src);
|
|
currentBatteryProfileMutable->capacity.critical = sbufReadU32(src);
|
|
batteryMetersConfigMutable()->capacity_unit = sbufReadU8(src);
|
|
if ((batteryMetersConfig()->voltageSource != BAT_VOLTAGE_RAW) && (batteryMetersConfig()->voltageSource != BAT_VOLTAGE_SAG_COMP)) {
|
|
batteryMetersConfigMutable()->voltageSource = BAT_VOLTAGE_RAW;
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
if ((batteryMetersConfig()->capacity_unit != BAT_CAPACITY_UNIT_MAH) && (batteryMetersConfig()->capacity_unit != BAT_CAPACITY_UNIT_MWH)) {
|
|
batteryMetersConfigMutable()->capacity_unit = BAT_CAPACITY_UNIT_MAH;
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_INAV_SET_BATTERY_CONFIG:
|
|
if (dataSize == 29) {
|
|
#ifdef USE_ADC
|
|
batteryMetersConfigMutable()->voltage.scale = sbufReadU16(src);
|
|
batteryMetersConfigMutable()->voltageSource = sbufReadU8(src);
|
|
currentBatteryProfileMutable->cells = sbufReadU8(src);
|
|
currentBatteryProfileMutable->voltage.cellDetect = sbufReadU16(src);
|
|
currentBatteryProfileMutable->voltage.cellMin = sbufReadU16(src);
|
|
currentBatteryProfileMutable->voltage.cellMax = sbufReadU16(src);
|
|
currentBatteryProfileMutable->voltage.cellWarning = sbufReadU16(src);
|
|
#else
|
|
sbufReadU16(src);
|
|
sbufReadU8(src);
|
|
sbufReadU8(src);
|
|
sbufReadU16(src);
|
|
sbufReadU16(src);
|
|
sbufReadU16(src);
|
|
sbufReadU16(src);
|
|
#endif
|
|
|
|
batteryMetersConfigMutable()->current.offset = sbufReadU16(src);
|
|
batteryMetersConfigMutable()->current.scale = sbufReadU16(src);
|
|
|
|
currentBatteryProfileMutable->capacity.value = sbufReadU32(src);
|
|
currentBatteryProfileMutable->capacity.warning = sbufReadU32(src);
|
|
currentBatteryProfileMutable->capacity.critical = sbufReadU32(src);
|
|
batteryMetersConfigMutable()->capacity_unit = sbufReadU8(src);
|
|
if ((batteryMetersConfig()->voltageSource != BAT_VOLTAGE_RAW) && (batteryMetersConfig()->voltageSource != BAT_VOLTAGE_SAG_COMP)) {
|
|
batteryMetersConfigMutable()->voltageSource = BAT_VOLTAGE_RAW;
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
if ((batteryMetersConfig()->capacity_unit != BAT_CAPACITY_UNIT_MAH) && (batteryMetersConfig()->capacity_unit != BAT_CAPACITY_UNIT_MWH)) {
|
|
batteryMetersConfigMutable()->capacity_unit = BAT_CAPACITY_UNIT_MAH;
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_MOTOR:
|
|
if (dataSize == 8 * sizeof(uint16_t)) {
|
|
for (int i = 0; i < 8; i++) {
|
|
const int16_t disarmed = sbufReadU16(src);
|
|
if (i < MAX_SUPPORTED_MOTORS) {
|
|
motor_disarmed[i] = disarmed;
|
|
}
|
|
}
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_SERVO_CONFIGURATION:
|
|
if (dataSize != (1 + 14)) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
tmp_u8 = sbufReadU8(src);
|
|
if (tmp_u8 >= MAX_SUPPORTED_SERVOS) {
|
|
return MSP_RESULT_ERROR;
|
|
} else {
|
|
servoParamsMutable(tmp_u8)->min = sbufReadU16(src);
|
|
servoParamsMutable(tmp_u8)->max = sbufReadU16(src);
|
|
servoParamsMutable(tmp_u8)->middle = sbufReadU16(src);
|
|
servoParamsMutable(tmp_u8)->rate = sbufReadU8(src);
|
|
sbufReadU8(src);
|
|
sbufReadU8(src);
|
|
sbufReadU8(src); // used to be forwardFromChannel, ignored
|
|
sbufReadU32(src); // used to be reversedSources
|
|
servoComputeScalingFactors(tmp_u8);
|
|
}
|
|
break;
|
|
|
|
case MSP2_INAV_SET_SERVO_CONFIG:
|
|
if (dataSize != 8) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
tmp_u8 = sbufReadU8(src);
|
|
if (tmp_u8 >= MAX_SUPPORTED_SERVOS) {
|
|
return MSP_RESULT_ERROR;
|
|
} else {
|
|
servoParamsMutable(tmp_u8)->min = sbufReadU16(src);
|
|
servoParamsMutable(tmp_u8)->max = sbufReadU16(src);
|
|
servoParamsMutable(tmp_u8)->middle = sbufReadU16(src);
|
|
servoParamsMutable(tmp_u8)->rate = sbufReadU8(src);
|
|
servoComputeScalingFactors(tmp_u8);
|
|
}
|
|
break;
|
|
|
|
case MSP_SET_SERVO_MIX_RULE:
|
|
sbufReadU8Safe(&tmp_u8, src);
|
|
if ((dataSize == 9) && (tmp_u8 < MAX_SERVO_RULES)) {
|
|
customServoMixersMutable(tmp_u8)->targetChannel = sbufReadU8(src);
|
|
customServoMixersMutable(tmp_u8)->inputSource = sbufReadU8(src);
|
|
customServoMixersMutable(tmp_u8)->rate = sbufReadU16(src);
|
|
customServoMixersMutable(tmp_u8)->speed = sbufReadU8(src);
|
|
sbufReadU16(src); //Read 2bytes for min/max and ignore it
|
|
sbufReadU8(src); //Read 1 byte for `box` and ignore it
|
|
loadCustomServoMixer();
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_INAV_SET_SERVO_MIXER:
|
|
sbufReadU8Safe(&tmp_u8, src);
|
|
if ((dataSize == 7) && (tmp_u8 < MAX_SERVO_RULES)) {
|
|
customServoMixersMutable(tmp_u8)->targetChannel = sbufReadU8(src);
|
|
customServoMixersMutable(tmp_u8)->inputSource = sbufReadU8(src);
|
|
customServoMixersMutable(tmp_u8)->rate = sbufReadU16(src);
|
|
customServoMixersMutable(tmp_u8)->speed = sbufReadU8(src);
|
|
#ifdef USE_PROGRAMMING_FRAMEWORK
|
|
customServoMixersMutable(tmp_u8)->conditionId = sbufReadU8(src);
|
|
#else
|
|
sbufReadU8(src);
|
|
#endif
|
|
loadCustomServoMixer();
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
#ifdef USE_PROGRAMMING_FRAMEWORK
|
|
case MSP2_INAV_SET_LOGIC_CONDITIONS:
|
|
sbufReadU8Safe(&tmp_u8, src);
|
|
if ((dataSize == 15) && (tmp_u8 < MAX_LOGIC_CONDITIONS)) {
|
|
logicConditionsMutable(tmp_u8)->enabled = sbufReadU8(src);
|
|
logicConditionsMutable(tmp_u8)->activatorId = sbufReadU8(src);
|
|
logicConditionsMutable(tmp_u8)->operation = sbufReadU8(src);
|
|
logicConditionsMutable(tmp_u8)->operandA.type = sbufReadU8(src);
|
|
logicConditionsMutable(tmp_u8)->operandA.value = sbufReadU32(src);
|
|
logicConditionsMutable(tmp_u8)->operandB.type = sbufReadU8(src);
|
|
logicConditionsMutable(tmp_u8)->operandB.value = sbufReadU32(src);
|
|
logicConditionsMutable(tmp_u8)->flags = sbufReadU8(src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_INAV_SET_PROGRAMMING_PID:
|
|
sbufReadU8Safe(&tmp_u8, src);
|
|
if ((dataSize == 20) && (tmp_u8 < MAX_PROGRAMMING_PID_COUNT)) {
|
|
programmingPidsMutable(tmp_u8)->enabled = sbufReadU8(src);
|
|
programmingPidsMutable(tmp_u8)->setpoint.type = sbufReadU8(src);
|
|
programmingPidsMutable(tmp_u8)->setpoint.value = sbufReadU32(src);
|
|
programmingPidsMutable(tmp_u8)->measurement.type = sbufReadU8(src);
|
|
programmingPidsMutable(tmp_u8)->measurement.value = sbufReadU32(src);
|
|
programmingPidsMutable(tmp_u8)->gains.P = sbufReadU16(src);
|
|
programmingPidsMutable(tmp_u8)->gains.I = sbufReadU16(src);
|
|
programmingPidsMutable(tmp_u8)->gains.D = sbufReadU16(src);
|
|
programmingPidsMutable(tmp_u8)->gains.FF = sbufReadU16(src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
#endif
|
|
case MSP2_COMMON_SET_MOTOR_MIXER:
|
|
sbufReadU8Safe(&tmp_u8, src);
|
|
if ((dataSize == 9) && (tmp_u8 < MAX_SUPPORTED_MOTORS)) {
|
|
primaryMotorMixerMutable(tmp_u8)->throttle = constrainf(sbufReadU16(src) / 1000.0f, 0.0f, 4.0f) - 2.0f;
|
|
primaryMotorMixerMutable(tmp_u8)->roll = constrainf(sbufReadU16(src) / 1000.0f, 0.0f, 4.0f) - 2.0f;
|
|
primaryMotorMixerMutable(tmp_u8)->pitch = constrainf(sbufReadU16(src) / 1000.0f, 0.0f, 4.0f) - 2.0f;
|
|
primaryMotorMixerMutable(tmp_u8)->yaw = constrainf(sbufReadU16(src) / 1000.0f, 0.0f, 4.0f) - 2.0f;
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_3D:
|
|
if (dataSize == 6) {
|
|
reversibleMotorsConfigMutable()->deadband_low = sbufReadU16(src);
|
|
reversibleMotorsConfigMutable()->deadband_high = sbufReadU16(src);
|
|
reversibleMotorsConfigMutable()->neutral = sbufReadU16(src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_RC_DEADBAND:
|
|
if (dataSize == 5) {
|
|
rcControlsConfigMutable()->deadband = sbufReadU8(src);
|
|
rcControlsConfigMutable()->yaw_deadband = sbufReadU8(src);
|
|
rcControlsConfigMutable()->alt_hold_deadband = sbufReadU8(src);
|
|
rcControlsConfigMutable()->mid_throttle_deadband = sbufReadU16(src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_RESET_CURR_PID:
|
|
PG_RESET_CURRENT(pidProfile);
|
|
break;
|
|
|
|
case MSP_SET_SENSOR_ALIGNMENT:
|
|
if (dataSize == 4) {
|
|
sbufReadU8(src); // was gyroConfigMutable()->gyro_align
|
|
sbufReadU8(src); // was accelerometerConfigMutable()->acc_align
|
|
#ifdef USE_MAG
|
|
compassConfigMutable()->mag_align = sbufReadU8(src);
|
|
#else
|
|
sbufReadU8(src);
|
|
#endif
|
|
#ifdef USE_OPFLOW
|
|
opticalFlowConfigMutable()->opflow_align = sbufReadU8(src);
|
|
#else
|
|
sbufReadU8(src);
|
|
#endif
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_ADVANCED_CONFIG:
|
|
if (dataSize == 9) {
|
|
sbufReadU8(src); // gyroConfig()->gyroSyncDenominator
|
|
sbufReadU8(src); // BF: masterConfig.pid_process_denom
|
|
sbufReadU8(src); // BF: motorConfig()->useUnsyncedPwm
|
|
motorConfigMutable()->motorPwmProtocol = sbufReadU8(src);
|
|
motorConfigMutable()->motorPwmRate = sbufReadU16(src);
|
|
servoConfigMutable()->servoPwmRate = sbufReadU16(src);
|
|
sbufReadU8(src); //Was gyroSync
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_FILTER_CONFIG :
|
|
if (dataSize >= 5) {
|
|
gyroConfigMutable()->gyro_main_lpf_hz = sbufReadU8(src);
|
|
pidProfileMutable()->dterm_lpf_hz = constrain(sbufReadU16(src), 0, 500);
|
|
pidProfileMutable()->yaw_lpf_hz = constrain(sbufReadU16(src), 0, 255);
|
|
if (dataSize >= 9) {
|
|
sbufReadU16(src); //Was gyroConfigMutable()->gyro_notch_hz
|
|
sbufReadU16(src); //Was gyroConfigMutable()->gyro_notch_cutoff
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
if (dataSize >= 13) {
|
|
sbufReadU16(src);
|
|
sbufReadU16(src);
|
|
pidInitFilters();
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
if (dataSize >= 17) {
|
|
sbufReadU16(src); // Was gyroConfigMutable()->gyro_soft_notch_hz_2
|
|
sbufReadU16(src); // Was gyroConfigMutable()->gyro_soft_notch_cutoff_2
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
|
|
if (dataSize >= 21) {
|
|
accelerometerConfigMutable()->acc_notch_hz = constrain(sbufReadU16(src), 0, 255);
|
|
accelerometerConfigMutable()->acc_notch_cutoff = constrain(sbufReadU16(src), 1, 255);
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
|
|
if (dataSize >= 22) {
|
|
sbufReadU16(src); //Was gyro_stage2_lowpass_hz
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_PID_ADVANCED:
|
|
if (dataSize == 17) {
|
|
sbufReadU16(src); // pidProfileMutable()->rollPitchItermIgnoreRate
|
|
sbufReadU16(src); // pidProfileMutable()->yawItermIgnoreRate
|
|
sbufReadU16(src); //pidProfile()->yaw_p_limit
|
|
|
|
sbufReadU8(src); //BF: pidProfileMutable()->deltaMethod
|
|
sbufReadU8(src); //BF: pidProfileMutable()->vbatPidCompensation
|
|
sbufReadU8(src); //BF: pidProfileMutable()->setpointRelaxRatio
|
|
sbufReadU8(src);
|
|
sbufReadU16(src); // Was pidsumLimit
|
|
sbufReadU8(src); //BF: pidProfileMutable()->itermThrottleGain
|
|
|
|
/*
|
|
* To keep compatibility on MSP frame length level with Betaflight, axis axisAccelerationLimitYaw
|
|
* limit will be sent and received in [dps / 10]
|
|
*/
|
|
pidProfileMutable()->axisAccelerationLimitRollPitch = sbufReadU16(src) * 10;
|
|
pidProfileMutable()->axisAccelerationLimitYaw = sbufReadU16(src) * 10;
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_INAV_PID:
|
|
if (dataSize == 15) {
|
|
sbufReadU8(src); //Legacy, no longer in use async processing value
|
|
sbufReadU16(src); //Legacy, no longer in use async processing value
|
|
sbufReadU16(src); //Legacy, no longer in use async processing value
|
|
pidProfileMutable()->heading_hold_rate_limit = sbufReadU8(src);
|
|
sbufReadU8(src); //HEADING_HOLD_ERROR_LPF_FREQ
|
|
sbufReadU16(src); //Legacy yaw_jump_prevention_limit
|
|
sbufReadU8(src); // was gyro lpf
|
|
accelerometerConfigMutable()->acc_lpf_hz = sbufReadU8(src);
|
|
sbufReadU8(src); //reserved
|
|
sbufReadU8(src); //reserved
|
|
sbufReadU8(src); //reserved
|
|
sbufReadU8(src); //reserved
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_SENSOR_CONFIG:
|
|
if (dataSize == 6) {
|
|
accelerometerConfigMutable()->acc_hardware = sbufReadU8(src);
|
|
#ifdef USE_BARO
|
|
barometerConfigMutable()->baro_hardware = sbufReadU8(src);
|
|
#else
|
|
sbufReadU8(src);
|
|
#endif
|
|
#ifdef USE_MAG
|
|
compassConfigMutable()->mag_hardware = sbufReadU8(src);
|
|
#else
|
|
sbufReadU8(src);
|
|
#endif
|
|
#ifdef USE_PITOT
|
|
pitotmeterConfigMutable()->pitot_hardware = sbufReadU8(src);
|
|
#else
|
|
sbufReadU8(src);
|
|
#endif
|
|
#ifdef USE_RANGEFINDER
|
|
rangefinderConfigMutable()->rangefinder_hardware = sbufReadU8(src);
|
|
#else
|
|
sbufReadU8(src); // rangefinder hardware
|
|
#endif
|
|
#ifdef USE_OPFLOW
|
|
opticalFlowConfigMutable()->opflow_hardware = sbufReadU8(src);
|
|
#else
|
|
sbufReadU8(src); // optical flow hardware
|
|
#endif
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_NAV_POSHOLD:
|
|
if (dataSize == 13) {
|
|
navConfigMutable()->general.flags.user_control_mode = sbufReadU8(src);
|
|
navConfigMutable()->general.max_auto_speed = sbufReadU16(src);
|
|
if (mixerConfig()->platformType == PLATFORM_AIRPLANE) {
|
|
navConfigMutable()->fw.max_auto_climb_rate = sbufReadU16(src);
|
|
} else {
|
|
navConfigMutable()->mc.max_auto_climb_rate = sbufReadU16(src);
|
|
}
|
|
navConfigMutable()->general.max_manual_speed = sbufReadU16(src);
|
|
if (mixerConfig()->platformType == PLATFORM_AIRPLANE) {
|
|
navConfigMutable()->fw.max_manual_climb_rate = sbufReadU16(src);
|
|
} else {
|
|
navConfigMutable()->mc.max_manual_climb_rate = sbufReadU16(src);
|
|
}
|
|
navConfigMutable()->mc.max_bank_angle = sbufReadU8(src);
|
|
navConfigMutable()->mc.althold_throttle_type = sbufReadU8(src);
|
|
currentBatteryProfileMutable->nav.mc.hover_throttle = sbufReadU16(src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_RTH_AND_LAND_CONFIG:
|
|
if (dataSize == 21) {
|
|
navConfigMutable()->general.min_rth_distance = sbufReadU16(src);
|
|
navConfigMutable()->general.flags.rth_climb_first = sbufReadU8(src);
|
|
navConfigMutable()->general.flags.rth_climb_ignore_emerg = sbufReadU8(src);
|
|
navConfigMutable()->general.flags.rth_tail_first = sbufReadU8(src);
|
|
navConfigMutable()->general.flags.rth_allow_landing = sbufReadU8(src);
|
|
navConfigMutable()->general.flags.rth_alt_control_mode = sbufReadU8(src);
|
|
navConfigMutable()->general.rth_abort_threshold = sbufReadU16(src);
|
|
navConfigMutable()->general.rth_altitude = sbufReadU16(src);
|
|
navConfigMutable()->general.land_minalt_vspd = sbufReadU16(src);
|
|
navConfigMutable()->general.land_maxalt_vspd = sbufReadU16(src);
|
|
navConfigMutable()->general.land_slowdown_minalt = sbufReadU16(src);
|
|
navConfigMutable()->general.land_slowdown_maxalt = sbufReadU16(src);
|
|
navConfigMutable()->general.emerg_descent_rate = sbufReadU16(src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_FW_CONFIG:
|
|
if (dataSize == 12) {
|
|
currentBatteryProfileMutable->nav.fw.cruise_throttle = sbufReadU16(src);
|
|
currentBatteryProfileMutable->nav.fw.min_throttle = sbufReadU16(src);
|
|
currentBatteryProfileMutable->nav.fw.max_throttle = sbufReadU16(src);
|
|
navConfigMutable()->fw.max_bank_angle = sbufReadU8(src);
|
|
navConfigMutable()->fw.max_climb_angle = sbufReadU8(src);
|
|
navConfigMutable()->fw.max_dive_angle = sbufReadU8(src);
|
|
currentBatteryProfileMutable->nav.fw.pitch_to_throttle = sbufReadU8(src);
|
|
navConfigMutable()->fw.loiter_radius = sbufReadU16(src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_CALIBRATION_DATA:
|
|
if (dataSize >= 18) {
|
|
accelerometerConfigMutable()->accZero.raw[X] = sbufReadU16(src);
|
|
accelerometerConfigMutable()->accZero.raw[Y] = sbufReadU16(src);
|
|
accelerometerConfigMutable()->accZero.raw[Z] = sbufReadU16(src);
|
|
accelerometerConfigMutable()->accGain.raw[X] = sbufReadU16(src);
|
|
accelerometerConfigMutable()->accGain.raw[Y] = sbufReadU16(src);
|
|
accelerometerConfigMutable()->accGain.raw[Z] = sbufReadU16(src);
|
|
|
|
#ifdef USE_MAG
|
|
compassConfigMutable()->magZero.raw[X] = sbufReadU16(src);
|
|
compassConfigMutable()->magZero.raw[Y] = sbufReadU16(src);
|
|
compassConfigMutable()->magZero.raw[Z] = sbufReadU16(src);
|
|
#else
|
|
sbufReadU16(src);
|
|
sbufReadU16(src);
|
|
sbufReadU16(src);
|
|
#endif
|
|
#ifdef USE_OPFLOW
|
|
if (dataSize >= 20) {
|
|
opticalFlowConfigMutable()->opflow_scale = sbufReadU16(src) / 256.0f;
|
|
}
|
|
#endif
|
|
#ifdef USE_MAG
|
|
if (dataSize >= 22) {
|
|
compassConfigMutable()->magGain[X] = sbufReadU16(src);
|
|
compassConfigMutable()->magGain[Y] = sbufReadU16(src);
|
|
compassConfigMutable()->magGain[Z] = sbufReadU16(src);
|
|
}
|
|
#else
|
|
if (dataSize >= 22) {
|
|
sbufReadU16(src);
|
|
sbufReadU16(src);
|
|
sbufReadU16(src);
|
|
}
|
|
#endif
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_POSITION_ESTIMATION_CONFIG:
|
|
if (dataSize == 12) {
|
|
positionEstimationConfigMutable()->w_z_baro_p = constrainf(sbufReadU16(src) / 100.0f, 0.0f, 10.0f);
|
|
positionEstimationConfigMutable()->w_z_gps_p = constrainf(sbufReadU16(src) / 100.0f, 0.0f, 10.0f);
|
|
positionEstimationConfigMutable()->w_z_gps_v = constrainf(sbufReadU16(src) / 100.0f, 0.0f, 10.0f);
|
|
positionEstimationConfigMutable()->w_xy_gps_p = constrainf(sbufReadU16(src) / 100.0f, 0.0f, 10.0f);
|
|
positionEstimationConfigMutable()->w_xy_gps_v = constrainf(sbufReadU16(src) / 100.0f, 0.0f, 10.0f);
|
|
gpsConfigMutable()->gpsMinSats = constrain(sbufReadU8(src), 5, 10);
|
|
sbufReadU8(src); // was positionEstimationConfigMutable()->use_gps_velned
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_RESET_CONF:
|
|
if (!ARMING_FLAG(ARMED)) {
|
|
suspendRxSignal();
|
|
resetEEPROM();
|
|
writeEEPROM();
|
|
readEEPROM();
|
|
resumeRxSignal();
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_ACC_CALIBRATION:
|
|
if (!ARMING_FLAG(ARMED))
|
|
accStartCalibration();
|
|
else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_MAG_CALIBRATION:
|
|
if (!ARMING_FLAG(ARMED))
|
|
ENABLE_STATE(CALIBRATE_MAG);
|
|
else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
#ifdef USE_OPFLOW
|
|
case MSP2_INAV_OPFLOW_CALIBRATION:
|
|
if (!ARMING_FLAG(ARMED))
|
|
opflowStartCalibration();
|
|
else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
#endif
|
|
|
|
case MSP_EEPROM_WRITE:
|
|
if (!ARMING_FLAG(ARMED)) {
|
|
suspendRxSignal();
|
|
writeEEPROM();
|
|
readEEPROM();
|
|
resumeRxSignal();
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
#ifdef USE_BLACKBOX
|
|
case MSP2_SET_BLACKBOX_CONFIG:
|
|
// Don't allow config to be updated while Blackbox is logging
|
|
if ((dataSize == 9) && blackboxMayEditConfig()) {
|
|
blackboxConfigMutable()->device = sbufReadU8(src);
|
|
blackboxConfigMutable()->rate_num = sbufReadU16(src);
|
|
blackboxConfigMutable()->rate_denom = sbufReadU16(src);
|
|
blackboxConfigMutable()->includeFlags = sbufReadU32(src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_OSD
|
|
case MSP_SET_OSD_CONFIG:
|
|
sbufReadU8Safe(&tmp_u8, src);
|
|
// set all the other settings
|
|
if ((int8_t)tmp_u8 == -1) {
|
|
if (dataSize >= 10) {
|
|
osdConfigMutable()->video_system = sbufReadU8(src);
|
|
osdConfigMutable()->units = sbufReadU8(src);
|
|
osdConfigMutable()->rssi_alarm = sbufReadU8(src);
|
|
currentBatteryProfileMutable->capacity.warning = sbufReadU16(src);
|
|
osdConfigMutable()->time_alarm = sbufReadU16(src);
|
|
osdConfigMutable()->alt_alarm = sbufReadU16(src);
|
|
// Won't be read if they weren't provided
|
|
sbufReadU16Safe(&osdConfigMutable()->dist_alarm, src);
|
|
sbufReadU16Safe(&osdConfigMutable()->neg_alt_alarm, src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
} else {
|
|
// set a position setting
|
|
if ((dataSize >= 3) && (tmp_u8 < OSD_ITEM_COUNT)) // tmp_u8 == addr
|
|
osdLayoutsConfigMutable()->item_pos[0][tmp_u8] = sbufReadU16(src);
|
|
else
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
// Either a element position change or a units change needs
|
|
// a full redraw, since an element can change size significantly
|
|
// and the old position or the now unused space due to the
|
|
// size change need to be erased.
|
|
osdStartFullRedraw();
|
|
break;
|
|
|
|
case MSP_OSD_CHAR_WRITE:
|
|
if (dataSize >= 55) {
|
|
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();
|
|
if (osdDisplayPort) {
|
|
displayWriteFontCharacter(osdDisplayPort, addr, &chr);
|
|
}
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
#endif // USE_OSD
|
|
|
|
#ifdef USE_VTX_CONTROL
|
|
case MSP_SET_VTX_CONFIG:
|
|
if (dataSize >= 2) {
|
|
vtxDevice_t *vtxDevice = vtxCommonDevice();
|
|
if (vtxDevice) {
|
|
if (vtxCommonGetDeviceType(vtxDevice) != VTXDEV_UNKNOWN) {
|
|
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;
|
|
|
|
if(vtxSettingsConfig()->band != newBand || vtxSettingsConfig()->channel != newChannel) {
|
|
vtxCommonSetBandAndChannel(vtxDevice, newBand, newChannel);
|
|
}
|
|
|
|
vtxSettingsConfigMutable()->band = newBand;
|
|
vtxSettingsConfigMutable()->channel = newChannel;
|
|
}
|
|
|
|
if (sbufBytesRemaining(src) > 1) {
|
|
uint8_t newPower = sbufReadU8(src);
|
|
uint8_t currentPower = 0;
|
|
vtxCommonGetPowerIndex(vtxDevice, ¤tPower);
|
|
if (newPower != currentPower) {
|
|
vtxCommonSetPowerByIndex(vtxDevice, newPower);
|
|
vtxSettingsConfigMutable()->power = newPower;
|
|
}
|
|
|
|
// Delegate pitmode to vtx directly
|
|
const uint8_t newPitmode = sbufReadU8(src);
|
|
uint8_t currentPitmode = 0;
|
|
vtxCommonGetPitMode(vtxDevice, ¤tPitmode);
|
|
if (currentPitmode != newPitmode) {
|
|
vtxCommonSetPitMode(vtxDevice, newPitmode);
|
|
}
|
|
|
|
if (sbufBytesRemaining(src) > 0) {
|
|
vtxSettingsConfigMutable()->lowPowerDisarm = sbufReadU8(src);
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////
|
|
// this code is taken from BF, it's hack for HDZERO VTX MSP frame
|
|
// API version 1.42 - this parameter kept separate since clients may already be supplying
|
|
if (sbufBytesRemaining(src) >= 2) {
|
|
sbufReadU16(src); //skip pitModeFreq
|
|
}
|
|
|
|
// API version 1.42 - extensions for non-encoded versions of the band, channel or frequency
|
|
if (sbufBytesRemaining(src) >= 4) {
|
|
uint8_t newBand = sbufReadU8(src);
|
|
const uint8_t newChannel = sbufReadU8(src);
|
|
vtxSettingsConfigMutable()->band = newBand;
|
|
vtxSettingsConfigMutable()->channel = newChannel;
|
|
}
|
|
|
|
/* if (sbufBytesRemaining(src) >= 4) {
|
|
sbufRead8(src); // freq_l
|
|
sbufRead8(src); // freq_h
|
|
sbufRead8(src); // band count
|
|
sbufRead8(src); // channel count
|
|
}*/
|
|
// //////////////////////////////////////////////////////////
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_FLASHFS
|
|
case MSP_DATAFLASH_ERASE:
|
|
flashfsEraseCompletely();
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_GPS
|
|
case MSP_SET_RAW_GPS:
|
|
if (dataSize == 14) {
|
|
gpsSol.fixType = sbufReadU8(src);
|
|
if (gpsSol.fixType) {
|
|
ENABLE_STATE(GPS_FIX);
|
|
} else {
|
|
DISABLE_STATE(GPS_FIX);
|
|
}
|
|
gpsSol.flags.validVelNE = false;
|
|
gpsSol.flags.validVelD = false;
|
|
gpsSol.flags.validEPE = false;
|
|
gpsSol.flags.validTime = false;
|
|
gpsSol.numSat = sbufReadU8(src);
|
|
gpsSol.llh.lat = sbufReadU32(src);
|
|
gpsSol.llh.lon = sbufReadU32(src);
|
|
gpsSol.llh.alt = 100 * sbufReadU16(src); // require cm
|
|
gpsSol.groundSpeed = sbufReadU16(src);
|
|
gpsSol.velNED[X] = 0;
|
|
gpsSol.velNED[Y] = 0;
|
|
gpsSol.velNED[Z] = 0;
|
|
gpsSol.eph = 100;
|
|
gpsSol.epv = 100;
|
|
// Feed data to navigation
|
|
sensorsSet(SENSOR_GPS);
|
|
onNewGPSData();
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
#endif
|
|
|
|
case MSP_SET_WP:
|
|
if (dataSize == 21) {
|
|
|
|
const uint8_t msp_wp_no = sbufReadU8(src); // get the waypoint number
|
|
navWaypoint_t msp_wp;
|
|
msp_wp.action = sbufReadU8(src); // action
|
|
msp_wp.lat = sbufReadU32(src); // lat
|
|
msp_wp.lon = sbufReadU32(src); // lon
|
|
msp_wp.alt = sbufReadU32(src); // to set altitude (cm)
|
|
msp_wp.p1 = sbufReadU16(src); // P1
|
|
msp_wp.p2 = sbufReadU16(src); // P2
|
|
msp_wp.p3 = sbufReadU16(src); // P3
|
|
msp_wp.flag = sbufReadU8(src); // future: to set nav flag
|
|
setWaypoint(msp_wp_no, &msp_wp);
|
|
|
|
#ifdef USE_FW_AUTOLAND
|
|
static uint8_t mmIdx = 0, fwAppraochStartIdx = 8;
|
|
#ifdef USE_SAFE_HOME
|
|
fwAppraochStartIdx = MAX_SAFE_HOMES;
|
|
#endif
|
|
if (msp_wp_no == 0) {
|
|
mmIdx = 0;
|
|
} else if (msp_wp.flag == NAV_WP_FLAG_LAST) {
|
|
mmIdx++;
|
|
}
|
|
resetFwAutolandApproach(fwAppraochStartIdx + mmIdx);
|
|
#endif
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
|
|
break;
|
|
case MSP2_COMMON_SET_RADAR_POS:
|
|
if (dataSize == 19) {
|
|
const uint8_t msp_radar_no = MIN(sbufReadU8(src), RADAR_MAX_POIS - 1); // Radar poi number, 0 to 3
|
|
radar_pois[msp_radar_no].state = sbufReadU8(src); // 0=undefined, 1=armed, 2=lost
|
|
radar_pois[msp_radar_no].gps.lat = sbufReadU32(src); // lat 10E7
|
|
radar_pois[msp_radar_no].gps.lon = sbufReadU32(src); // lon 10E7
|
|
radar_pois[msp_radar_no].gps.alt = sbufReadU32(src); // altitude (cm)
|
|
radar_pois[msp_radar_no].heading = sbufReadU16(src); // °
|
|
radar_pois[msp_radar_no].speed = sbufReadU16(src); // cm/s
|
|
radar_pois[msp_radar_no].lq = sbufReadU8(src); // Link quality, from 0 to 4
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_FEATURE:
|
|
if (dataSize == 4) {
|
|
featureClearAll();
|
|
featureSet(sbufReadU32(src)); // features bitmap
|
|
rxUpdateRSSISource(); // For FEATURE_RSSI_ADC
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_BOARD_ALIGNMENT:
|
|
if (dataSize == 6) {
|
|
boardAlignmentMutable()->rollDeciDegrees = sbufReadU16(src);
|
|
boardAlignmentMutable()->pitchDeciDegrees = sbufReadU16(src);
|
|
boardAlignmentMutable()->yawDeciDegrees = sbufReadU16(src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_VOLTAGE_METER_CONFIG:
|
|
if (dataSize == 4) {
|
|
#ifdef USE_ADC
|
|
batteryMetersConfigMutable()->voltage.scale = sbufReadU8(src) * 10;
|
|
currentBatteryProfileMutable->voltage.cellMin = sbufReadU8(src) * 10;
|
|
currentBatteryProfileMutable->voltage.cellMax = sbufReadU8(src) * 10;
|
|
currentBatteryProfileMutable->voltage.cellWarning = sbufReadU8(src) * 10;
|
|
#else
|
|
sbufReadU8(src);
|
|
sbufReadU8(src);
|
|
sbufReadU8(src);
|
|
sbufReadU8(src);
|
|
#endif
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_CURRENT_METER_CONFIG:
|
|
if (dataSize == 7) {
|
|
batteryMetersConfigMutable()->current.scale = sbufReadU16(src);
|
|
batteryMetersConfigMutable()->current.offset = sbufReadU16(src);
|
|
batteryMetersConfigMutable()->current.type = sbufReadU8(src);
|
|
currentBatteryProfileMutable->capacity.value = sbufReadU16(src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_MIXER:
|
|
if (dataSize == 1) {
|
|
sbufReadU8(src); //This is ignored, no longer supporting mixerMode
|
|
mixerUpdateStateFlags(); // Required for correct preset functionality
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_RX_CONFIG:
|
|
if (dataSize == 24) {
|
|
rxConfigMutable()->serialrx_provider = sbufReadU8(src);
|
|
rxConfigMutable()->maxcheck = sbufReadU16(src);
|
|
sbufReadU16(src); // midrc
|
|
rxConfigMutable()->mincheck = sbufReadU16(src);
|
|
#ifdef USE_SPEKTRUM_BIND
|
|
rxConfigMutable()->spektrum_sat_bind = sbufReadU8(src);
|
|
#else
|
|
sbufReadU8(src);
|
|
#endif
|
|
rxConfigMutable()->rx_min_usec = sbufReadU16(src);
|
|
rxConfigMutable()->rx_max_usec = sbufReadU16(src);
|
|
sbufReadU8(src); // for compatibility with betaflight (rcInterpolation)
|
|
sbufReadU8(src); // for compatibility with betaflight (rcInterpolationInterval)
|
|
sbufReadU16(src); // for compatibility with betaflight (airModeActivateThreshold)
|
|
sbufReadU8(src);
|
|
sbufReadU32(src);
|
|
sbufReadU8(src);
|
|
sbufReadU8(src); // for compatibility with betaflight (fpvCamAngleDegrees)
|
|
rxConfigMutable()->receiverType = sbufReadU8(src); // Won't be modified if buffer is not large enough
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_FAILSAFE_CONFIG:
|
|
if (dataSize == 20) {
|
|
failsafeConfigMutable()->failsafe_delay = sbufReadU8(src);
|
|
failsafeConfigMutable()->failsafe_off_delay = sbufReadU8(src);
|
|
currentBatteryProfileMutable->failsafe_throttle = sbufReadU16(src);
|
|
sbufReadU8(src); // was failsafe_kill_switch
|
|
failsafeConfigMutable()->failsafe_throttle_low_delay = sbufReadU16(src);
|
|
failsafeConfigMutable()->failsafe_procedure = sbufReadU8(src);
|
|
failsafeConfigMutable()->failsafe_recovery_delay = sbufReadU8(src);
|
|
failsafeConfigMutable()->failsafe_fw_roll_angle = (int16_t)sbufReadU16(src);
|
|
failsafeConfigMutable()->failsafe_fw_pitch_angle = (int16_t)sbufReadU16(src);
|
|
failsafeConfigMutable()->failsafe_fw_yaw_rate = (int16_t)sbufReadU16(src);
|
|
failsafeConfigMutable()->failsafe_stick_motion_threshold = sbufReadU16(src);
|
|
failsafeConfigMutable()->failsafe_min_distance = sbufReadU16(src);
|
|
failsafeConfigMutable()->failsafe_min_distance_procedure = sbufReadU8(src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_RSSI_CONFIG:
|
|
sbufReadU8Safe(&tmp_u8, src);
|
|
if ((dataSize == 1) && (tmp_u8 <= MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
|
|
rxConfigMutable()->rssi_channel = tmp_u8;
|
|
rxUpdateRSSISource();
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
|
|
case MSP_SET_RX_MAP:
|
|
if (dataSize == MAX_MAPPABLE_RX_INPUTS) {
|
|
for (int i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
|
|
rxConfigMutable()->rcmap[i] = sbufReadU8(src);
|
|
}
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_COMMON_SET_SERIAL_CONFIG:
|
|
{
|
|
uint8_t portConfigSize = sizeof(uint8_t) + sizeof(uint32_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 = serialFindPortConfiguration(identifier);
|
|
if (!portConfig) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
|
|
portConfig->identifier = identifier;
|
|
portConfig->functionMask = sbufReadU32(src);
|
|
portConfig->msp_baudrateIndex = constrain(sbufReadU8(src), BAUD_MIN, BAUD_MAX);
|
|
portConfig->gps_baudrateIndex = constrain(sbufReadU8(src), BAUD_MIN, BAUD_MAX);
|
|
portConfig->telemetry_baudrateIndex = constrain(sbufReadU8(src), BAUD_MIN, BAUD_MAX);
|
|
portConfig->peripheral_baudrateIndex = constrain(sbufReadU8(src), BAUD_MIN, BAUD_MAX);
|
|
}
|
|
}
|
|
break;
|
|
|
|
#ifdef USE_LED_STRIP
|
|
case MSP_SET_LED_COLORS:
|
|
if (dataSize == LED_CONFIGURABLE_COLOR_COUNT * 4) {
|
|
for (int i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
|
|
hsvColor_t *color = &ledStripConfigMutable()->colors[i];
|
|
color->h = sbufReadU16(src);
|
|
color->s = sbufReadU8(src);
|
|
color->v = sbufReadU8(src);
|
|
}
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_LED_STRIP_CONFIG:
|
|
if (dataSize == (1 + sizeof(uint32_t))) {
|
|
tmp_u8 = sbufReadU8(src);
|
|
if (tmp_u8 >= LED_MAX_STRIP_LENGTH) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
ledConfig_t *ledConfig = &ledStripConfigMutable()->ledConfigs[tmp_u8];
|
|
|
|
uint32_t legacyConfig = sbufReadU32(src);
|
|
|
|
ledConfig->led_position = legacyConfig & 0xFF;
|
|
ledConfig->led_function = (legacyConfig >> 8) & 0xF;
|
|
ledConfig->led_overlay = (legacyConfig >> 12) & 0x3F;
|
|
ledConfig->led_color = (legacyConfig >> 18) & 0xF;
|
|
ledConfig->led_direction = (legacyConfig >> 22) & 0x3F;
|
|
ledConfig->led_params = (legacyConfig >> 28) & 0xF;
|
|
|
|
reevaluateLedConfig();
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_INAV_SET_LED_STRIP_CONFIG_EX:
|
|
if (dataSize == (1 + sizeof(ledConfig_t))) {
|
|
tmp_u8 = sbufReadU8(src);
|
|
if (tmp_u8 >= LED_MAX_STRIP_LENGTH) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
ledConfig_t *ledConfig = &ledStripConfigMutable()->ledConfigs[tmp_u8];
|
|
sbufReadDataSafe(src, ledConfig, sizeof(ledConfig_t));
|
|
reevaluateLedConfig();
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_LED_STRIP_MODECOLOR:
|
|
if (dataSize == 3) {
|
|
ledModeIndex_e modeIdx = sbufReadU8(src);
|
|
int funIdx = sbufReadU8(src);
|
|
int color = sbufReadU8(src);
|
|
|
|
if (!setModeColor(modeIdx, funIdx, color))
|
|
return MSP_RESULT_ERROR;
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
#endif
|
|
|
|
#ifdef NAV_NON_VOLATILE_WAYPOINT_STORAGE
|
|
case MSP_WP_MISSION_LOAD:
|
|
sbufReadU8Safe(NULL, src); // Mission ID (reserved)
|
|
if ((dataSize != 1) || (!loadNonVolatileWaypointList(false)))
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_WP_MISSION_SAVE:
|
|
sbufReadU8Safe(NULL, src); // Mission ID (reserved)
|
|
if ((dataSize != 1) || (!saveNonVolatileWaypointList()))
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
#endif
|
|
|
|
case MSP_SET_RTC:
|
|
if (dataSize == 6) {
|
|
// 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);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP_SET_TX_INFO:
|
|
{
|
|
// This message will be sent while the aircraft is
|
|
// armed. Better to guard ourselves against potentially
|
|
// malformed requests.
|
|
uint8_t rssi;
|
|
if (sbufReadU8Safe(&rssi, src)) {
|
|
setRSSIFromMSP(rssi);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MSP_SET_NAME:
|
|
if (dataSize <= MAX_NAME_LENGTH) {
|
|
char *name = systemConfigMutable()->craftName;
|
|
int len = MIN(MAX_NAME_LENGTH, (int)dataSize);
|
|
sbufReadData(src, name, len);
|
|
memset(&name[len], '\0', (MAX_NAME_LENGTH + 1) - len);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_COMMON_SET_TZ:
|
|
if (dataSize == 2)
|
|
timeConfigMutable()->tz_offset = (int16_t)sbufReadU16(src);
|
|
else if (dataSize == 3) {
|
|
timeConfigMutable()->tz_offset = (int16_t)sbufReadU16(src);
|
|
timeConfigMutable()->tz_automatic_dst = (uint8_t)sbufReadU8(src);
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_INAV_SET_MIXER:
|
|
if (dataSize == 9) {
|
|
mixerConfigMutable()->motorDirectionInverted = sbufReadU8(src);
|
|
sbufReadU8(src); // Was yaw_jump_prevention_limit
|
|
mixerConfigMutable()->motorstopOnLow = sbufReadU8(src);
|
|
mixerConfigMutable()->platformType = sbufReadU8(src);
|
|
mixerConfigMutable()->hasFlaps = sbufReadU8(src);
|
|
mixerConfigMutable()->appliedMixerPreset = sbufReadU16(src);
|
|
sbufReadU8(src); //Read and ignore MAX_SUPPORTED_MOTORS
|
|
sbufReadU8(src); //Read and ignore MAX_SUPPORTED_SERVOS
|
|
mixerUpdateStateFlags();
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
#if defined(USE_OSD)
|
|
case MSP2_INAV_OSD_SET_LAYOUT_ITEM:
|
|
{
|
|
uint8_t layout;
|
|
if (!sbufReadU8Safe(&layout, src)) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
uint8_t item;
|
|
if (!sbufReadU8Safe(&item, src)) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
if (!sbufReadU16Safe(&osdLayoutsConfigMutable()->item_pos[layout][item], src)) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
// If the layout is not already overriden and it's different
|
|
// than the layout for the item that was just configured,
|
|
// override it for 10 seconds.
|
|
bool overridden;
|
|
int activeLayout = osdGetActiveLayout(&overridden);
|
|
if (activeLayout != layout && !overridden) {
|
|
osdOverrideLayout(layout, 10000);
|
|
} else {
|
|
osdStartFullRedraw();
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case MSP2_INAV_OSD_SET_ALARMS:
|
|
{
|
|
if (dataSize == 24) {
|
|
osdConfigMutable()->rssi_alarm = sbufReadU8(src);
|
|
osdConfigMutable()->time_alarm = sbufReadU16(src);
|
|
osdConfigMutable()->alt_alarm = sbufReadU16(src);
|
|
osdConfigMutable()->dist_alarm = sbufReadU16(src);
|
|
osdConfigMutable()->neg_alt_alarm = sbufReadU16(src);
|
|
tmp_u16 = sbufReadU16(src);
|
|
osdConfigMutable()->gforce_alarm = tmp_u16 / 1000.0f;
|
|
tmp_u16 = sbufReadU16(src);
|
|
osdConfigMutable()->gforce_axis_alarm_min = (int16_t)tmp_u16 / 1000.0f;
|
|
tmp_u16 = sbufReadU16(src);
|
|
osdConfigMutable()->gforce_axis_alarm_max = (int16_t)tmp_u16 / 1000.0f;
|
|
osdConfigMutable()->current_alarm = sbufReadU8(src);
|
|
osdConfigMutable()->imu_temp_alarm_min = sbufReadU16(src);
|
|
osdConfigMutable()->imu_temp_alarm_max = sbufReadU16(src);
|
|
#ifdef USE_BARO
|
|
osdConfigMutable()->baro_temp_alarm_min = sbufReadU16(src);
|
|
osdConfigMutable()->baro_temp_alarm_max = sbufReadU16(src);
|
|
#endif
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
|
|
break;
|
|
|
|
case MSP2_INAV_OSD_SET_PREFERENCES:
|
|
{
|
|
if (dataSize == 9) {
|
|
osdConfigMutable()->video_system = sbufReadU8(src);
|
|
osdConfigMutable()->main_voltage_decimals = sbufReadU8(src);
|
|
osdConfigMutable()->ahi_reverse_roll = sbufReadU8(src);
|
|
osdConfigMutable()->crosshairs_style = sbufReadU8(src);
|
|
osdConfigMutable()->left_sidebar_scroll = sbufReadU8(src);
|
|
osdConfigMutable()->right_sidebar_scroll = sbufReadU8(src);
|
|
osdConfigMutable()->sidebar_scroll_arrows = sbufReadU8(src);
|
|
osdConfigMutable()->units = sbufReadU8(src);
|
|
osdConfigMutable()->stats_energy_unit = sbufReadU8(src);
|
|
osdStartFullRedraw();
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
|
|
break;
|
|
#endif
|
|
|
|
case MSP2_INAV_SET_MC_BRAKING:
|
|
#ifdef USE_MR_BRAKING_MODE
|
|
if (dataSize == 14) {
|
|
navConfigMutable()->mc.braking_speed_threshold = sbufReadU16(src);
|
|
navConfigMutable()->mc.braking_disengage_speed = sbufReadU16(src);
|
|
navConfigMutable()->mc.braking_timeout = sbufReadU16(src);
|
|
navConfigMutable()->mc.braking_boost_factor = sbufReadU8(src);
|
|
navConfigMutable()->mc.braking_boost_timeout = sbufReadU16(src);
|
|
navConfigMutable()->mc.braking_boost_speed_threshold = sbufReadU16(src);
|
|
navConfigMutable()->mc.braking_boost_disengage_speed = sbufReadU16(src);
|
|
navConfigMutable()->mc.braking_bank_angle = sbufReadU8(src);
|
|
} else
|
|
#endif
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_INAV_SELECT_BATTERY_PROFILE:
|
|
if (!ARMING_FLAG(ARMED) && sbufReadU8Safe(&tmp_u8, src)) {
|
|
setConfigBatteryProfileAndWriteEEPROM(tmp_u8);
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
|
|
case MSP2_INAV_SELECT_MIXER_PROFILE:
|
|
if (!ARMING_FLAG(ARMED) && sbufReadU8Safe(&tmp_u8, src)) {
|
|
setConfigMixerProfileAndWriteEEPROM(tmp_u8);
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
|
|
#ifdef USE_TEMPERATURE_SENSOR
|
|
case MSP2_INAV_SET_TEMP_SENSOR_CONFIG:
|
|
if (dataSize == sizeof(tempSensorConfig_t) * MAX_TEMP_SENSORS) {
|
|
for (uint8_t index = 0; index < MAX_TEMP_SENSORS; ++index) {
|
|
tempSensorConfig_t *sensorConfig = tempSensorConfigMutable(index);
|
|
sensorConfig->type = sbufReadU8(src);
|
|
for (uint8_t addrIndex = 0; addrIndex < 8; ++addrIndex)
|
|
((uint8_t *)&sensorConfig->address)[addrIndex] = sbufReadU8(src);
|
|
sensorConfig->alarm_min = sbufReadU16(src);
|
|
sensorConfig->alarm_max = sbufReadU16(src);
|
|
tmp_u8 = sbufReadU8(src);
|
|
sensorConfig->osdSymbol = tmp_u8 > TEMP_SENSOR_SYM_COUNT ? 0 : tmp_u8;
|
|
for (uint8_t labelIndex = 0; labelIndex < TEMPERATURE_LABEL_LEN; ++labelIndex)
|
|
sensorConfig->label[labelIndex] = toupper(sbufReadU8(src));
|
|
}
|
|
} else
|
|
return MSP_RESULT_ERROR;
|
|
break;
|
|
#endif
|
|
|
|
#ifdef MSP_FIRMWARE_UPDATE
|
|
case MSP2_INAV_FWUPDT_PREPARE:
|
|
if (!firmwareUpdatePrepare(sbufReadU32(src))) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
case MSP2_INAV_FWUPDT_STORE:
|
|
if (!firmwareUpdateStore(sbufPtr(src), sbufBytesRemaining(src))) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
case MSP2_INAV_FWUPDT_EXEC:
|
|
firmwareUpdateExec(sbufReadU8(src));
|
|
return MSP_RESULT_ERROR; // will only be reached if the update is not ready
|
|
break;
|
|
case MSP2_INAV_FWUPDT_ROLLBACK_PREPARE:
|
|
if (!firmwareUpdateRollbackPrepare()) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
case MSP2_INAV_FWUPDT_ROLLBACK_EXEC:
|
|
firmwareUpdateRollbackExec();
|
|
return MSP_RESULT_ERROR; // will only be reached if the rollback is not ready
|
|
break;
|
|
#endif
|
|
#ifdef USE_SAFE_HOME
|
|
case MSP2_INAV_SET_SAFEHOME:
|
|
if (dataSize == 10) {
|
|
uint8_t i;
|
|
if (!sbufReadU8Safe(&i, src) || i >= MAX_SAFE_HOMES) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
safeHomeConfigMutable(i)->enabled = sbufReadU8(src);
|
|
safeHomeConfigMutable(i)->lat = sbufReadU32(src);
|
|
safeHomeConfigMutable(i)->lon = sbufReadU32(src);
|
|
#ifdef USE_FW_AUTOLAND
|
|
resetFwAutolandApproach(i);
|
|
#endif
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_FW_AUTOLAND
|
|
case MSP2_INAV_SET_FW_APPROACH:
|
|
if (dataSize == 15) {
|
|
uint8_t i;
|
|
if (!sbufReadU8Safe(&i, src) || i >= MAX_FW_LAND_APPOACH_SETTINGS) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
fwAutolandApproachConfigMutable(i)->approachAlt = sbufReadU32(src);
|
|
fwAutolandApproachConfigMutable(i)->landAlt = sbufReadU32(src);
|
|
fwAutolandApproachConfigMutable(i)->approachDirection = sbufReadU8(src);
|
|
|
|
int16_t head1 = 0, head2 = 0;
|
|
if (sbufReadI16Safe(&head1, src)) {
|
|
fwAutolandApproachConfigMutable(i)->landApproachHeading1 = head1;
|
|
}
|
|
if (sbufReadI16Safe(&head2, src)) {
|
|
fwAutolandApproachConfigMutable(i)->landApproachHeading2 = head2;
|
|
}
|
|
fwAutolandApproachConfigMutable(i)->isSeaLevelRef = sbufReadU8(src);
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
#endif
|
|
case MSP2_INAV_GPS_UBLOX_COMMAND:
|
|
if(dataSize < 8 || !isGpsUblox()) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
|
|
gpsUbloxSendCommand(src->ptr, dataSize, 0);
|
|
break;
|
|
|
|
#ifdef USE_EZ_TUNE
|
|
|
|
case MSP2_INAV_EZ_TUNE_SET:
|
|
{
|
|
|
|
if (dataSize < 10 || dataSize > 11) {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
|
|
ezTuneMutable()->enabled = sbufReadU8(src);
|
|
ezTuneMutable()->filterHz = sbufReadU16(src);
|
|
ezTuneMutable()->axisRatio = sbufReadU8(src);
|
|
ezTuneMutable()->response = sbufReadU8(src);
|
|
ezTuneMutable()->damping = sbufReadU8(src);
|
|
ezTuneMutable()->stability = sbufReadU8(src);
|
|
ezTuneMutable()->aggressiveness = sbufReadU8(src);
|
|
ezTuneMutable()->rate = sbufReadU8(src);
|
|
ezTuneMutable()->expo = sbufReadU8(src);
|
|
|
|
if (dataSize == 11) {
|
|
ezTuneMutable()->snappiness = sbufReadU8(src);
|
|
}
|
|
ezTuneUpdate();
|
|
}
|
|
break;
|
|
|
|
#endif
|
|
|
|
#ifdef USE_RATE_DYNAMICS
|
|
|
|
case MSP2_INAV_SET_RATE_DYNAMICS:
|
|
|
|
if (dataSize == 6) {
|
|
((controlRateConfig_t*)currentControlRateProfile)->rateDynamics.sensitivityCenter = sbufReadU8(src);
|
|
((controlRateConfig_t*)currentControlRateProfile)->rateDynamics.sensitivityEnd = sbufReadU8(src);
|
|
((controlRateConfig_t*)currentControlRateProfile)->rateDynamics.correctionCenter = sbufReadU8(src);
|
|
((controlRateConfig_t*)currentControlRateProfile)->rateDynamics.correctionEnd = sbufReadU8(src);
|
|
((controlRateConfig_t*)currentControlRateProfile)->rateDynamics.weightCenter = sbufReadU8(src);
|
|
((controlRateConfig_t*)currentControlRateProfile)->rateDynamics.weightEnd = sbufReadU8(src);
|
|
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
|
|
break;
|
|
|
|
#endif
|
|
#ifdef USE_PROGRAMMING_FRAMEWORK
|
|
case MSP2_INAV_SET_CUSTOM_OSD_ELEMENTS:
|
|
sbufReadU8Safe(&tmp_u8, src);
|
|
if ((dataSize == (OSD_CUSTOM_ELEMENT_TEXT_SIZE - 1) + (MAX_CUSTOM_ELEMENTS * 3) + 4) && (tmp_u8 < MAX_CUSTOM_ELEMENTS)) {
|
|
for (int i = 0; i < CUSTOM_ELEMENTS_PARTS; i++) {
|
|
osdCustomElementsMutable(tmp_u8)->part[i].type = sbufReadU8(src);
|
|
osdCustomElementsMutable(tmp_u8)->part[i].value = sbufReadU16(src);
|
|
}
|
|
osdCustomElementsMutable(tmp_u8)->visibility.type = sbufReadU8(src);
|
|
osdCustomElementsMutable(tmp_u8)->visibility.value = sbufReadU16(src);
|
|
for (int i = 0; i < OSD_CUSTOM_ELEMENT_TEXT_SIZE - 1; i++) {
|
|
osdCustomElementsMutable(tmp_u8)->osdCustomElementText[i] = sbufReadU8(src);
|
|
}
|
|
osdCustomElementsMutable(tmp_u8)->osdCustomElementText[OSD_CUSTOM_ELEMENT_TEXT_SIZE - 1] = '\0';
|
|
} else{
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
|
|
break;
|
|
|
|
case MSP2_BETAFLIGHT_BIND:
|
|
if (rxConfig()->receiverType == RX_TYPE_SERIAL) {
|
|
switch (rxConfig()->serialrx_provider) {
|
|
default:
|
|
return MSP_RESULT_ERROR;
|
|
#if defined(USE_SERIALRX_SRXL2)
|
|
case SERIALRX_SRXL2:
|
|
srxl2Bind();
|
|
break;
|
|
#endif
|
|
#if defined(USE_SERIALRX_CRSF)
|
|
case SERIALRX_CRSF:
|
|
crsfBind();
|
|
break;
|
|
#endif
|
|
}
|
|
} else {
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return MSP_RESULT_ERROR;
|
|
}
|
|
return MSP_RESULT_ACK;
|
|
}
|
|
#endif
|
|
|
|
static const setting_t *mspReadSetting(sbuf_t *src)
|
|
{
|
|
char name[SETTING_MAX_NAME_LENGTH];
|
|
uint16_t id;
|
|
uint8_t c;
|
|
size_t s = 0;
|
|
while (1) {
|
|
if (!sbufReadU8Safe(&c, src)) {
|
|
return NULL;
|
|
}
|
|
name[s++] = c;
|
|
if (c == '\0') {
|
|
if (s == 1) {
|
|
// Payload starts with a zero, setting index
|
|
// as uint16_t follows
|
|
if (sbufReadU16Safe(&id, src)) {
|
|
return settingGet(id);
|
|
}
|
|
return NULL;
|
|
}
|
|
break;
|
|
}
|
|
if (s == SETTING_MAX_NAME_LENGTH) {
|
|
// Name is too long
|
|
return NULL;
|
|
}
|
|
}
|
|
return settingFind(name);
|
|
}
|
|
|
|
static bool mspSettingCommand(sbuf_t *dst, sbuf_t *src)
|
|
{
|
|
const setting_t *setting = mspReadSetting(src);
|
|
if (!setting) {
|
|
return false;
|
|
}
|
|
|
|
const void *ptr = settingGetValuePointer(setting);
|
|
size_t size = settingGetValueSize(setting);
|
|
sbufWriteDataSafe(dst, ptr, size);
|
|
return true;
|
|
}
|
|
|
|
static bool mspSetSettingCommand(sbuf_t *dst, sbuf_t *src)
|
|
{
|
|
UNUSED(dst);
|
|
|
|
const setting_t *setting = mspReadSetting(src);
|
|
if (!setting) {
|
|
return false;
|
|
}
|
|
|
|
setting_min_t min = settingGetMin(setting);
|
|
setting_max_t max = settingGetMax(setting);
|
|
|
|
void *ptr = settingGetValuePointer(setting);
|
|
switch (SETTING_TYPE(setting)) {
|
|
case VAR_UINT8:
|
|
{
|
|
uint8_t val;
|
|
if (!sbufReadU8Safe(&val, src)) {
|
|
return false;
|
|
}
|
|
if (val > max) {
|
|
return false;
|
|
}
|
|
*((uint8_t*)ptr) = val;
|
|
}
|
|
break;
|
|
case VAR_INT8:
|
|
{
|
|
int8_t val;
|
|
if (!sbufReadI8Safe(&val, src)) {
|
|
return false;
|
|
}
|
|
if (val < min || val > (int8_t)max) {
|
|
return false;
|
|
}
|
|
*((int8_t*)ptr) = val;
|
|
}
|
|
break;
|
|
case VAR_UINT16:
|
|
{
|
|
uint16_t val;
|
|
if (!sbufReadU16Safe(&val, src)) {
|
|
return false;
|
|
}
|
|
if (val > max) {
|
|
return false;
|
|
}
|
|
*((uint16_t*)ptr) = val;
|
|
}
|
|
break;
|
|
case VAR_INT16:
|
|
{
|
|
int16_t val;
|
|
if (!sbufReadI16Safe(&val, src)) {
|
|
return false;
|
|
}
|
|
if (val < min || val > (int16_t)max) {
|
|
return false;
|
|
}
|
|
*((int16_t*)ptr) = val;
|
|
}
|
|
break;
|
|
case VAR_UINT32:
|
|
{
|
|
uint32_t val;
|
|
if (!sbufReadU32Safe(&val, src)) {
|
|
return false;
|
|
}
|
|
if (val > max) {
|
|
return false;
|
|
}
|
|
*((uint32_t*)ptr) = val;
|
|
}
|
|
break;
|
|
case VAR_FLOAT:
|
|
{
|
|
float val;
|
|
if (!sbufReadDataSafe(src, &val, sizeof(float))) {
|
|
return false;
|
|
}
|
|
if (val < (float)min || val > (float)max) {
|
|
return false;
|
|
}
|
|
*((float*)ptr) = val;
|
|
}
|
|
break;
|
|
case VAR_STRING:
|
|
{
|
|
settingSetString(setting, (const char*)sbufPtr(src), sbufBytesRemaining(src));
|
|
}
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool mspSettingInfoCommand(sbuf_t *dst, sbuf_t *src)
|
|
{
|
|
const setting_t *setting = mspReadSetting(src);
|
|
if (!setting) {
|
|
return false;
|
|
}
|
|
|
|
char name_buf[SETTING_MAX_WORD_LENGTH+1];
|
|
settingGetName(setting, name_buf);
|
|
sbufWriteDataSafe(dst, name_buf, strlen(name_buf) + 1);
|
|
|
|
// Parameter Group ID
|
|
sbufWriteU16(dst, settingGetPgn(setting));
|
|
|
|
// Type, section and mode
|
|
sbufWriteU8(dst, SETTING_TYPE(setting));
|
|
sbufWriteU8(dst, SETTING_SECTION(setting));
|
|
sbufWriteU8(dst, SETTING_MODE(setting));
|
|
|
|
// Min as int32_t
|
|
int32_t min = settingGetMin(setting);
|
|
sbufWriteU32(dst, (uint32_t)min);
|
|
// Max as uint32_t
|
|
uint32_t max = settingGetMax(setting);
|
|
sbufWriteU32(dst, max);
|
|
|
|
// Absolute setting index
|
|
sbufWriteU16(dst, settingGetIndex(setting));
|
|
|
|
// If the setting is profile based, send the current one
|
|
// and the count, both as uint8_t. For MASTER_VALUE, we
|
|
// send two zeroes, so the MSP client can assume there
|
|
// will always be two bytes.
|
|
switch (SETTING_SECTION(setting)) {
|
|
case MASTER_VALUE:
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, 0);
|
|
break;
|
|
case EZ_TUNE_VALUE:
|
|
FALLTHROUGH;
|
|
case PROFILE_VALUE:
|
|
FALLTHROUGH;
|
|
case CONTROL_RATE_VALUE:
|
|
sbufWriteU8(dst, getConfigProfile());
|
|
sbufWriteU8(dst, MAX_PROFILE_COUNT);
|
|
break;
|
|
case BATTERY_CONFIG_VALUE:
|
|
sbufWriteU8(dst, getConfigBatteryProfile());
|
|
sbufWriteU8(dst, MAX_BATTERY_PROFILE_COUNT);
|
|
break;
|
|
case MIXER_CONFIG_VALUE:
|
|
sbufWriteU8(dst, getConfigMixerProfile());
|
|
sbufWriteU8(dst, MAX_MIXER_PROFILE_COUNT);
|
|
break;
|
|
}
|
|
|
|
// If the setting uses a table, send each possible string (null terminated)
|
|
if (SETTING_MODE(setting) == MODE_LOOKUP) {
|
|
for (int ii = (int)min; ii <= (int)max; ii++) {
|
|
const char *name = settingLookupValueName(setting, ii);
|
|
sbufWriteDataSafe(dst, name, strlen(name) + 1);
|
|
}
|
|
}
|
|
|
|
// Finally, include the setting value. This way resource constrained callers
|
|
// (e.g. a script in the radio) don't need to perform another call to retrieve
|
|
// the value.
|
|
const void *ptr = settingGetValuePointer(setting);
|
|
size_t size = settingGetValueSize(setting);
|
|
sbufWriteDataSafe(dst, ptr, size);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool mspParameterGroupsCommand(sbuf_t *dst, sbuf_t *src)
|
|
{
|
|
uint16_t first;
|
|
uint16_t last;
|
|
uint16_t start;
|
|
uint16_t end;
|
|
|
|
if (sbufReadU16Safe(&first, src)) {
|
|
last = first;
|
|
} else {
|
|
first = PG_ID_FIRST;
|
|
last = PG_ID_LAST;
|
|
}
|
|
|
|
for (int ii = first; ii <= last; ii++) {
|
|
if (settingsGetParameterGroupIndexes(ii, &start, &end)) {
|
|
sbufWriteU16(dst, ii);
|
|
sbufWriteU16(dst, start);
|
|
sbufWriteU16(dst, end);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#ifdef USE_SIMULATOR
|
|
bool isOSDTypeSupportedBySimulator(void)
|
|
{
|
|
#ifdef USE_OSD
|
|
displayPort_t *osdDisplayPort = osdGetDisplayPort();
|
|
return (!!osdDisplayPort && !!osdDisplayPort->vTable->readChar);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void mspWriteSimulatorOSD(sbuf_t *dst)
|
|
{
|
|
//RLE encoding
|
|
//scan displayBuffer iteratively
|
|
//no more than 80+3+2 bytes output in single run
|
|
//0 and 255 are special symbols
|
|
//255 [char] - font bank switch
|
|
//0 [flags,count] [char] - font bank switch, blink switch and character repeat
|
|
//original 0 is sent as 32
|
|
//original 0xff, 0x100 and 0x1ff are forcibly sent inside command 0
|
|
|
|
static uint8_t osdPos_y = 0;
|
|
static uint8_t osdPos_x = 0;
|
|
|
|
//indicate new format hitl 1.4.0
|
|
sbufWriteU8(dst, 255);
|
|
|
|
if (isOSDTypeSupportedBySimulator())
|
|
{
|
|
displayPort_t *osdDisplayPort = osdGetDisplayPort();
|
|
|
|
sbufWriteU8(dst, osdDisplayPort->rows);
|
|
sbufWriteU8(dst, osdDisplayPort->cols);
|
|
|
|
sbufWriteU8(dst, osdPos_y);
|
|
sbufWriteU8(dst, osdPos_x);
|
|
|
|
int bytesCount = 0;
|
|
|
|
uint16_t c = 0;
|
|
textAttributes_t attr = 0;
|
|
bool highBank = false;
|
|
bool blink = false;
|
|
int count = 0;
|
|
|
|
int processedRows = osdDisplayPort->rows;
|
|
|
|
while (bytesCount < 80) //whole response should be less 155 bytes at worst.
|
|
{
|
|
bool blink1;
|
|
uint16_t lastChar;
|
|
|
|
count = 0;
|
|
while ( true )
|
|
{
|
|
displayReadCharWithAttr(osdDisplayPort, osdPos_x, osdPos_y, &c, &attr);
|
|
if (c == 0) c = 32;
|
|
|
|
//REVIEW: displayReadCharWithAttr() should return mode with _TEXT_ATTRIBUTES_BLINK_BIT !
|
|
//for max7456 it returns mode with MAX7456_MODE_BLINK instead (wrong)
|
|
//because max7456ReadChar() does not decode from MAX7456_MODE_BLINK to _TEXT_ATTRIBUTES_BLINK_BIT
|
|
//it should!
|
|
|
|
//bool blink2 = TEXT_ATTRIBUTES_HAVE_BLINK(attr);
|
|
bool blink2 = attr & (1<<4); //MAX7456_MODE_BLINK
|
|
|
|
if (count == 0)
|
|
{
|
|
lastChar = c;
|
|
blink1 = blink2;
|
|
}
|
|
else if ((lastChar != c) || (blink2 != blink1) || (count == 63))
|
|
{
|
|
break;
|
|
}
|
|
|
|
count++;
|
|
|
|
osdPos_x++;
|
|
if (osdPos_x == osdDisplayPort->cols)
|
|
{
|
|
osdPos_x = 0;
|
|
osdPos_y++;
|
|
processedRows--;
|
|
if (osdPos_y == osdDisplayPort->rows)
|
|
{
|
|
osdPos_y = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8_t cmd = 0;
|
|
uint8_t lastCharLow = (uint8_t)(lastChar & 0xff);
|
|
if (blink1 != blink)
|
|
{
|
|
cmd |= 128;//switch blink attr
|
|
blink = blink1;
|
|
}
|
|
|
|
bool highBank1 = lastChar > 255;
|
|
if (highBank1 != highBank)
|
|
{
|
|
cmd |= 64;//switch bank attr
|
|
highBank = highBank1;
|
|
}
|
|
|
|
if (count == 1 && cmd == 64)
|
|
{
|
|
sbufWriteU8(dst, 255); //short command for bank switch with char following
|
|
sbufWriteU8(dst, lastChar & 0xff);
|
|
bytesCount += 2;
|
|
}
|
|
else if ((count > 2) || (cmd !=0) || (lastChar == 255) || (lastChar == 0x100) || (lastChar == 0x1ff))
|
|
{
|
|
cmd |= count; //long command for blink/bank switch and symbol repeat
|
|
sbufWriteU8(dst, 0);
|
|
sbufWriteU8(dst, cmd);
|
|
sbufWriteU8(dst, lastCharLow);
|
|
bytesCount += 3;
|
|
}
|
|
else if (count == 2) //cmd == 0 here
|
|
{
|
|
sbufWriteU8(dst, lastCharLow);
|
|
sbufWriteU8(dst, lastCharLow);
|
|
bytesCount+=2;
|
|
}
|
|
else
|
|
{
|
|
sbufWriteU8(dst, lastCharLow);
|
|
bytesCount++;
|
|
}
|
|
|
|
if ( processedRows <= 0 )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
sbufWriteU8(dst, 0); //command 0 with length=0 -> stop
|
|
sbufWriteU8(dst, 0);
|
|
}
|
|
else
|
|
{
|
|
sbufWriteU8(dst, 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool mspFCProcessInOutCommand(uint16_t cmdMSP, sbuf_t *dst, sbuf_t *src, mspResult_e *ret)
|
|
{
|
|
uint8_t tmp_u8;
|
|
const unsigned int dataSize = sbufBytesRemaining(src);
|
|
|
|
switch (cmdMSP) {
|
|
|
|
case MSP_WP:
|
|
mspFcWaypointOutCommand(dst, src);
|
|
*ret = MSP_RESULT_ACK;
|
|
break;
|
|
|
|
#if defined(USE_FLASHFS)
|
|
case MSP_DATAFLASH_READ:
|
|
mspFcDataFlashReadCommand(dst, src);
|
|
*ret = MSP_RESULT_ACK;
|
|
break;
|
|
#endif
|
|
|
|
case MSP2_COMMON_SETTING:
|
|
*ret = mspSettingCommand(dst, src) ? MSP_RESULT_ACK : MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_COMMON_SET_SETTING:
|
|
*ret = mspSetSettingCommand(dst, src) ? MSP_RESULT_ACK : MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_COMMON_SETTING_INFO:
|
|
*ret = mspSettingInfoCommand(dst, src) ? MSP_RESULT_ACK : MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
case MSP2_COMMON_PG_LIST:
|
|
*ret = mspParameterGroupsCommand(dst, src) ? MSP_RESULT_ACK : MSP_RESULT_ERROR;
|
|
break;
|
|
|
|
#if defined(USE_OSD)
|
|
case MSP2_INAV_OSD_LAYOUTS:
|
|
if (sbufBytesRemaining(src) >= 1) {
|
|
uint8_t layout = sbufReadU8(src);
|
|
if (layout >= OSD_LAYOUT_COUNT) {
|
|
*ret = MSP_RESULT_ERROR;
|
|
break;
|
|
}
|
|
if (sbufBytesRemaining(src) >= 2) {
|
|
// Asking for an specific item in a layout
|
|
uint16_t item = sbufReadU16(src);
|
|
if (item >= OSD_ITEM_COUNT) {
|
|
*ret = MSP_RESULT_ERROR;
|
|
break;
|
|
}
|
|
sbufWriteU16(dst, osdLayoutsConfig()->item_pos[layout][item]);
|
|
} else {
|
|
// Asking for an specific layout
|
|
for (unsigned ii = 0; ii < OSD_ITEM_COUNT; ii++) {
|
|
sbufWriteU16(dst, osdLayoutsConfig()->item_pos[layout][ii]);
|
|
}
|
|
}
|
|
} else {
|
|
// Return the number of layouts and items
|
|
sbufWriteU8(dst, OSD_LAYOUT_COUNT);
|
|
sbufWriteU8(dst, OSD_ITEM_COUNT);
|
|
}
|
|
*ret = MSP_RESULT_ACK;
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_PROGRAMMING_FRAMEWORK
|
|
case MSP2_INAV_LOGIC_CONDITIONS_SINGLE:
|
|
*ret = mspFcLogicConditionCommand(dst, src);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SAFE_HOME
|
|
case MSP2_INAV_SAFEHOME:
|
|
*ret = mspFcSafeHomeOutCommand(dst, src);
|
|
break;
|
|
#endif
|
|
#ifdef USE_FW_AUTOLAND
|
|
case MSP2_INAV_FW_APPROACH:
|
|
*ret = mspFwApproachOutCommand(dst, src);
|
|
break;
|
|
#endif
|
|
#ifdef USE_SIMULATOR
|
|
case MSP_SIMULATOR:
|
|
tmp_u8 = sbufReadU8(src); // Get the Simulator MSP version
|
|
|
|
// Check the MSP version of simulator
|
|
if (tmp_u8 != SIMULATOR_MSP_VERSION) {
|
|
break;
|
|
}
|
|
|
|
simulatorData.flags = sbufReadU8(src);
|
|
|
|
if (!SIMULATOR_HAS_OPTION(HITL_ENABLE)) {
|
|
|
|
if (ARMING_FLAG(SIMULATOR_MODE_HITL)) { // Just once
|
|
DISABLE_ARMING_FLAG(SIMULATOR_MODE_HITL);
|
|
|
|
#ifdef USE_BARO
|
|
if ( requestedSensors[SENSOR_INDEX_BARO] != BARO_NONE ) {
|
|
baroStartCalibration();
|
|
}
|
|
#endif
|
|
#ifdef USE_MAG
|
|
DISABLE_STATE(COMPASS_CALIBRATED);
|
|
compassInit();
|
|
#endif
|
|
simulatorData.flags = HITL_RESET_FLAGS;
|
|
// Review: Many states were affected. Reboot?
|
|
|
|
disarm(DISARM_SWITCH); // Disarm to prevent motor output!!!
|
|
}
|
|
} else {
|
|
if (!ARMING_FLAG(SIMULATOR_MODE_HITL)) { // Just once
|
|
#ifdef USE_BARO
|
|
if ( requestedSensors[SENSOR_INDEX_BARO] != BARO_NONE ) {
|
|
sensorsSet(SENSOR_BARO);
|
|
setTaskEnabled(TASK_BARO, true);
|
|
DISABLE_ARMING_FLAG(ARMING_DISABLED_HARDWARE_FAILURE);
|
|
baroStartCalibration();
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_MAG
|
|
if (compassConfig()->mag_hardware != MAG_NONE) {
|
|
sensorsSet(SENSOR_MAG);
|
|
ENABLE_STATE(COMPASS_CALIBRATED);
|
|
DISABLE_ARMING_FLAG(ARMING_DISABLED_HARDWARE_FAILURE);
|
|
mag.magADC[X] = 800;
|
|
mag.magADC[Y] = 0;
|
|
mag.magADC[Z] = 0;
|
|
}
|
|
#endif
|
|
ENABLE_ARMING_FLAG(SIMULATOR_MODE_HITL);
|
|
ENABLE_STATE(ACCELEROMETER_CALIBRATED);
|
|
LOG_DEBUG(SYSTEM, "Simulator enabled");
|
|
}
|
|
|
|
if (dataSize >= 14) {
|
|
|
|
if (feature(FEATURE_GPS) && SIMULATOR_HAS_OPTION(HITL_HAS_NEW_GPS_DATA)) {
|
|
gpsSolDRV.fixType = sbufReadU8(src);
|
|
gpsSolDRV.hdop = gpsSolDRV.fixType == GPS_NO_FIX ? 9999 : 100;
|
|
gpsSolDRV.numSat = sbufReadU8(src);
|
|
|
|
if (gpsSolDRV.fixType != GPS_NO_FIX) {
|
|
gpsSolDRV.flags.validVelNE = true;
|
|
gpsSolDRV.flags.validVelD = true;
|
|
gpsSolDRV.flags.validEPE = true;
|
|
gpsSolDRV.flags.validTime = false;
|
|
|
|
gpsSolDRV.llh.lat = sbufReadU32(src);
|
|
gpsSolDRV.llh.lon = sbufReadU32(src);
|
|
gpsSolDRV.llh.alt = sbufReadU32(src);
|
|
gpsSolDRV.groundSpeed = (int16_t)sbufReadU16(src);
|
|
gpsSolDRV.groundCourse = (int16_t)sbufReadU16(src);
|
|
|
|
gpsSolDRV.velNED[X] = (int16_t)sbufReadU16(src);
|
|
gpsSolDRV.velNED[Y] = (int16_t)sbufReadU16(src);
|
|
gpsSolDRV.velNED[Z] = (int16_t)sbufReadU16(src);
|
|
|
|
gpsSolDRV.eph = 100;
|
|
gpsSolDRV.epv = 100;
|
|
} else {
|
|
sbufAdvance(src, sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) * 3);
|
|
}
|
|
// Feed data to navigation
|
|
gpsProcessNewDriverData();
|
|
gpsProcessNewSolutionData(false);
|
|
} else {
|
|
sbufAdvance(src, sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) * 3);
|
|
}
|
|
|
|
if (!SIMULATOR_HAS_OPTION(HITL_USE_IMU)) {
|
|
attitude.values.roll = (int16_t)sbufReadU16(src);
|
|
attitude.values.pitch = (int16_t)sbufReadU16(src);
|
|
attitude.values.yaw = (int16_t)sbufReadU16(src);
|
|
} else {
|
|
sbufAdvance(src, sizeof(uint16_t) * XYZ_AXIS_COUNT);
|
|
}
|
|
|
|
// Get the acceleration in 1G units
|
|
acc.accADCf[X] = ((int16_t)sbufReadU16(src)) / 1000.0f;
|
|
acc.accADCf[Y] = ((int16_t)sbufReadU16(src)) / 1000.0f;
|
|
acc.accADCf[Z] = ((int16_t)sbufReadU16(src)) / 1000.0f;
|
|
acc.accVibeSq[X] = 0.0f;
|
|
acc.accVibeSq[Y] = 0.0f;
|
|
acc.accVibeSq[Z] = 0.0f;
|
|
|
|
// Get the angular velocity in DPS
|
|
gyro.gyroADCf[X] = ((int16_t)sbufReadU16(src)) / 16.0f;
|
|
gyro.gyroADCf[Y] = ((int16_t)sbufReadU16(src)) / 16.0f;
|
|
gyro.gyroADCf[Z] = ((int16_t)sbufReadU16(src)) / 16.0f;
|
|
|
|
if (sensors(SENSOR_BARO)) {
|
|
baro.baroPressure = (int32_t)sbufReadU32(src);
|
|
baro.baroTemperature = DEGREES_TO_CENTIDEGREES(SIMULATOR_BARO_TEMP);
|
|
} else {
|
|
sbufAdvance(src, sizeof(uint32_t));
|
|
}
|
|
|
|
if (sensors(SENSOR_MAG)) {
|
|
mag.magADC[X] = ((int16_t)sbufReadU16(src)) / 20; // 16000 / 20 = 800uT
|
|
mag.magADC[Y] = ((int16_t)sbufReadU16(src)) / 20; //note that mag failure is simulated by setting all readings to zero
|
|
mag.magADC[Z] = ((int16_t)sbufReadU16(src)) / 20;
|
|
} else {
|
|
sbufAdvance(src, sizeof(uint16_t) * XYZ_AXIS_COUNT);
|
|
}
|
|
|
|
if (SIMULATOR_HAS_OPTION(HITL_EXT_BATTERY_VOLTAGE)) {
|
|
simulatorData.vbat = sbufReadU8(src);
|
|
} else {
|
|
simulatorData.vbat = SIMULATOR_FULL_BATTERY;
|
|
}
|
|
|
|
if (SIMULATOR_HAS_OPTION(HITL_AIRSPEED)) {
|
|
simulatorData.airSpeed = sbufReadU16(src);
|
|
} else {
|
|
if (SIMULATOR_HAS_OPTION(HITL_EXTENDED_FLAGS)) {
|
|
sbufReadU16(src);
|
|
}
|
|
}
|
|
|
|
if (SIMULATOR_HAS_OPTION(HITL_EXTENDED_FLAGS)) {
|
|
simulatorData.flags |= ((uint16_t)sbufReadU8(src)) << 8;
|
|
}
|
|
} else {
|
|
DISABLE_STATE(GPS_FIX);
|
|
}
|
|
}
|
|
|
|
sbufWriteU16(dst, (uint16_t)simulatorData.input[INPUT_STABILIZED_ROLL]);
|
|
sbufWriteU16(dst, (uint16_t)simulatorData.input[INPUT_STABILIZED_PITCH]);
|
|
sbufWriteU16(dst, (uint16_t)simulatorData.input[INPUT_STABILIZED_YAW]);
|
|
sbufWriteU16(dst, (uint16_t)(ARMING_FLAG(ARMED) ? simulatorData.input[INPUT_STABILIZED_THROTTLE] : -500));
|
|
|
|
simulatorData.debugIndex++;
|
|
if (simulatorData.debugIndex == 8) {
|
|
simulatorData.debugIndex = 0;
|
|
}
|
|
|
|
tmp_u8 = simulatorData.debugIndex |
|
|
((mixerConfig()->platformType == PLATFORM_AIRPLANE) ? 128 : 0) |
|
|
(ARMING_FLAG(ARMED) ? 64 : 0) |
|
|
(!feature(FEATURE_OSD) ? 32: 0) |
|
|
(!isOSDTypeSupportedBySimulator() ? 16 : 0);
|
|
|
|
sbufWriteU8(dst, tmp_u8);
|
|
sbufWriteU32(dst, debug[simulatorData.debugIndex]);
|
|
|
|
sbufWriteU16(dst, attitude.values.roll);
|
|
sbufWriteU16(dst, attitude.values.pitch);
|
|
sbufWriteU16(dst, attitude.values.yaw);
|
|
|
|
mspWriteSimulatorOSD(dst);
|
|
|
|
*ret = MSP_RESULT_ACK;
|
|
break;
|
|
#endif
|
|
#ifndef SITL_BUILD
|
|
case MSP2_INAV_TIMER_OUTPUT_MODE:
|
|
if (dataSize == 0) {
|
|
for (int i = 0; i < HARDWARE_TIMER_DEFINITION_COUNT; ++i) {
|
|
sbufWriteU8(dst, i);
|
|
sbufWriteU8(dst, timerOverrides(i)->outputMode);
|
|
}
|
|
*ret = MSP_RESULT_ACK;
|
|
} else if(dataSize == 1) {
|
|
uint8_t timer = sbufReadU8(src);
|
|
if(timer < HARDWARE_TIMER_DEFINITION_COUNT) {
|
|
sbufWriteU8(dst, timer);
|
|
sbufWriteU8(dst, timerOverrides(timer)->outputMode);
|
|
*ret = MSP_RESULT_ACK;
|
|
} else {
|
|
*ret = MSP_RESULT_ERROR;
|
|
}
|
|
} else {
|
|
*ret = MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
case MSP2_INAV_SET_TIMER_OUTPUT_MODE:
|
|
if(dataSize == 2) {
|
|
uint8_t timer = sbufReadU8(src);
|
|
uint8_t outputMode = sbufReadU8(src);
|
|
if(timer < HARDWARE_TIMER_DEFINITION_COUNT) {
|
|
timerOverridesMutable(timer)->outputMode = outputMode;
|
|
*ret = MSP_RESULT_ACK;
|
|
} else {
|
|
*ret = MSP_RESULT_ERROR;
|
|
}
|
|
} else {
|
|
*ret = MSP_RESULT_ERROR;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
// Not handled
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static mspResult_e mspProcessSensorCommand(uint16_t cmdMSP, sbuf_t *src)
|
|
{
|
|
int dataSize = sbufBytesRemaining(src);
|
|
UNUSED(dataSize);
|
|
|
|
switch (cmdMSP) {
|
|
#if defined(USE_RANGEFINDER_MSP)
|
|
case MSP2_SENSOR_RANGEFINDER:
|
|
mspRangefinderReceiveNewData(sbufPtr(src));
|
|
break;
|
|
#endif
|
|
|
|
#if defined(USE_OPFLOW_MSP)
|
|
case MSP2_SENSOR_OPTIC_FLOW:
|
|
mspOpflowReceiveNewData(sbufPtr(src));
|
|
break;
|
|
#endif
|
|
|
|
#if defined(USE_GPS_PROTO_MSP)
|
|
case MSP2_SENSOR_GPS:
|
|
mspGPSReceiveNewData(sbufPtr(src));
|
|
break;
|
|
#endif
|
|
|
|
#if defined(USE_MAG_MSP)
|
|
case MSP2_SENSOR_COMPASS:
|
|
mspMagReceiveNewData(sbufPtr(src));
|
|
break;
|
|
#endif
|
|
|
|
#if defined(USE_BARO_MSP)
|
|
case MSP2_SENSOR_BAROMETER:
|
|
mspBaroReceiveNewData(sbufPtr(src));
|
|
break;
|
|
#endif
|
|
|
|
#if defined(USE_PITOT_MSP)
|
|
case MSP2_SENSOR_AIRSPEED:
|
|
mspPitotmeterReceiveNewData(sbufPtr(src));
|
|
break;
|
|
#endif
|
|
|
|
#if (defined(USE_HEADTRACKER) && defined(USE_HEADTRACKER_MSP))
|
|
case MSP2_SENSOR_HEADTRACKER:
|
|
mspHeadTrackerReceiverNewData(sbufPtr(src), dataSize);
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
return MSP_RESULT_NO_REPLY;
|
|
}
|
|
|
|
/*
|
|
* Returns MSP_RESULT_ACK, MSP_RESULT_ERROR or MSP_RESULT_NO_REPLY
|
|
*/
|
|
mspResult_e mspFcProcessCommand(mspPacket_t *cmd, mspPacket_t *reply, mspPostProcessFnPtr *mspPostProcessFn)
|
|
{
|
|
mspResult_e ret = MSP_RESULT_ACK;
|
|
sbuf_t *dst = &reply->buf;
|
|
sbuf_t *src = &cmd->buf;
|
|
const uint16_t cmdMSP = cmd->cmd;
|
|
// initialize reply by default
|
|
reply->cmd = cmd->cmd;
|
|
|
|
if (MSP2_IS_SENSOR_MESSAGE(cmdMSP)) {
|
|
ret = mspProcessSensorCommand(cmdMSP, src);
|
|
} else if (mspFcProcessOutCommand(cmdMSP, dst, mspPostProcessFn)) {
|
|
ret = MSP_RESULT_ACK;
|
|
} else if (cmdMSP == MSP_SET_PASSTHROUGH) {
|
|
mspFcSetPassthroughCommand(dst, src, mspPostProcessFn);
|
|
ret = MSP_RESULT_ACK;
|
|
} else {
|
|
if (!mspFCProcessInOutCommand(cmdMSP, dst, src, &ret)) {
|
|
ret = mspFcProcessInCommand(cmdMSP, src);
|
|
}
|
|
}
|
|
|
|
// Process DONT_REPLY flag
|
|
if (cmd->flags & MSP_FLAG_DONT_REPLY) {
|
|
ret = MSP_RESULT_NO_REPLY;
|
|
}
|
|
|
|
reply->result = ret;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Return a pointer to the process command function
|
|
*/
|
|
void mspFcInit(void)
|
|
{
|
|
initActiveBoxIds();
|
|
}
|