2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
31 #include "blackbox/blackbox.h"
32 #include "blackbox/blackbox_io.h"
34 #include "build/build_config.h"
35 #include "build/debug.h"
36 #include "build/version.h"
40 #include "common/axis.h"
41 #include "common/bitarray.h"
42 #include "common/color.h"
43 #include "common/huffman.h"
44 #include "common/maths.h"
45 #include "common/streambuf.h"
46 #include "common/utils.h"
48 #include "config/config.h"
49 #include "config/config_eeprom.h"
50 #include "config/feature.h"
51 #include "config/simplified_tuning.h"
53 #include "drivers/accgyro/accgyro.h"
54 #include "drivers/bus_i2c.h"
55 #include "drivers/bus_spi.h"
56 #include "drivers/camera_control.h"
57 #include "drivers/compass/compass.h"
58 #include "drivers/display.h"
59 #include "drivers/dshot.h"
60 #include "drivers/dshot_command.h"
61 #include "drivers/flash.h"
62 #include "drivers/io.h"
63 #include "drivers/motor.h"
64 #include "drivers/osd.h"
65 #include "drivers/pwm_output.h"
66 #include "drivers/sdcard.h"
67 #include "drivers/serial.h"
68 #include "drivers/serial_escserial.h"
69 #include "drivers/system.h"
70 #include "drivers/transponder_ir.h"
71 #include "drivers/usb_msc.h"
72 #include "drivers/vtx_common.h"
73 #include "drivers/vtx_table.h"
75 #include "fc/board_info.h"
76 #include "fc/controlrate_profile.h"
79 #include "fc/rc_adjustments.h"
80 #include "fc/rc_controls.h"
81 #include "fc/rc_modes.h"
82 #include "fc/runtime_config.h"
84 #include "flight/failsafe.h"
85 #include "flight/gps_rescue.h"
86 #include "flight/imu.h"
87 #include "flight/mixer.h"
88 #include "flight/pid.h"
89 #include "flight/pid_init.h"
90 #include "flight/position.h"
91 #include "flight/rpm_filter.h"
92 #include "flight/servos.h"
94 #include "io/asyncfatfs/asyncfatfs.h"
95 #include "io/beeper.h"
96 #include "io/flashfs.h"
97 #include "io/gimbal.h"
99 #include "io/ledstrip.h"
100 #include "io/serial.h"
101 #include "io/serial_4way.h"
102 #include "io/servos.h"
103 #include "io/transponder_ir.h"
104 #include "io/usb_msc.h"
105 #include "io/vtx_control.h"
108 #include "msp/msp_box.h"
109 #include "msp/msp_protocol.h"
110 #include "msp/msp_protocol_v2_betaflight.h"
111 #include "msp/msp_protocol_v2_common.h"
112 #include "msp/msp_serial.h"
115 #include "osd/osd_elements.h"
116 #include "osd/osd_warnings.h"
118 #include "pg/beeper.h"
119 #include "pg/board.h"
120 #include "pg/dyn_notch.h"
121 #include "pg/gyrodev.h"
122 #include "pg/motor.h"
124 #include "pg/rx_spi.h"
127 #include "pg/vtx_table.h"
130 #include "rx/rx_bind.h"
133 #include "scheduler/scheduler.h"
135 #include "sensors/acceleration.h"
136 #include "sensors/barometer.h"
137 #include "sensors/battery.h"
138 #include "sensors/boardalignment.h"
139 #include "sensors/compass.h"
140 #include "sensors/esc_sensor.h"
141 #include "sensors/gyro.h"
142 #include "sensors/gyro_init.h"
143 #include "sensors/rangefinder.h"
145 #include "telemetry/telemetry.h"
147 #ifdef USE_HARDWARE_REVISION_DETECTION
148 #include "hardware_revision.h"
154 static const char * const flightControllerIdentifier
= FC_FIRMWARE_IDENTIFIER
; // 4 UPPER CASE alpha numeric characters that identify the flight controller.
157 MSP_REBOOT_FIRMWARE
= 0,
158 MSP_REBOOT_BOOTLOADER_ROM
,
161 MSP_REBOOT_BOOTLOADER_FLASH
,
165 static uint8_t rebootMode
;
168 MSP_SDCARD_STATE_NOT_PRESENT
= 0,
169 MSP_SDCARD_STATE_FATAL
= 1,
170 MSP_SDCARD_STATE_CARD_INIT
= 2,
171 MSP_SDCARD_STATE_FS_INIT
= 3,
172 MSP_SDCARD_STATE_READY
= 4
176 MSP_SDCARD_FLAG_SUPPORTED
= 1
180 MSP_FLASHFS_FLAG_READY
= 1,
181 MSP_FLASHFS_FLAG_SUPPORTED
= 2
185 MSP_PASSTHROUGH_ESC_SIMONK
= PROTOCOL_SIMONK
,
186 MSP_PASSTHROUGH_ESC_BLHELI
= PROTOCOL_BLHELI
,
187 MSP_PASSTHROUGH_ESC_KISS
= PROTOCOL_KISS
,
188 MSP_PASSTHROUGH_ESC_KISSALL
= PROTOCOL_KISSALL
,
189 MSP_PASSTHROUGH_ESC_CASTLE
= PROTOCOL_CASTLE
,
191 MSP_PASSTHROUGH_SERIAL_ID
= 0xFD,
192 MSP_PASSTHROUGH_SERIAL_FUNCTION_ID
= 0xFE,
194 MSP_PASSTHROUGH_ESC_4WAY
= 0xFF,
195 } mspPassthroughType_e
;
197 #define RATEPROFILE_MASK (1 << 7)
199 #define RTC_NOT_SUPPORTED 0xff
202 DEFAULTS_TYPE_BASE
= 0,
203 DEFAULTS_TYPE_CUSTOM
,
207 static bool vtxTableNeedsInit
= false;
210 static int mspDescriptor
= 0;
212 mspDescriptor_t
mspDescriptorAlloc(void)
214 return (mspDescriptor_t
)mspDescriptor
++;
217 static uint32_t mspArmingDisableFlags
= 0;
219 static void mspArmingDisableByDescriptor(mspDescriptor_t desc
)
221 mspArmingDisableFlags
|= (1 << desc
);
224 static void mspArmingEnableByDescriptor(mspDescriptor_t desc
)
226 mspArmingDisableFlags
&= ~(1 << desc
);
229 static bool mspIsMspArmingEnabled(void)
231 return mspArmingDisableFlags
== 0;
234 #define MSP_PASSTHROUGH_ESC_4WAY 0xff
236 static uint8_t mspPassthroughMode
;
237 static uint8_t mspPassthroughArgument
;
240 static void mspEscPassthroughFn(serialPort_t
*serialPort
)
242 escEnablePassthrough(serialPort
, &motorConfig()->dev
, mspPassthroughArgument
, mspPassthroughMode
);
246 static serialPort_t
*mspFindPassthroughSerialPort(void)
248 serialPortUsage_t
*portUsage
= NULL
;
250 switch (mspPassthroughMode
) {
251 case MSP_PASSTHROUGH_SERIAL_ID
:
253 portUsage
= findSerialPortUsageByIdentifier(mspPassthroughArgument
);
256 case MSP_PASSTHROUGH_SERIAL_FUNCTION_ID
:
258 const serialPortConfig_t
*portConfig
= findSerialPortConfig(1 << mspPassthroughArgument
);
260 portUsage
= findSerialPortUsageByIdentifier(portConfig
->identifier
);
265 return portUsage
? portUsage
->serialPort
: NULL
;
268 static void mspSerialPassthroughFn(serialPort_t
*serialPort
)
270 serialPort_t
*passthroughPort
= mspFindPassthroughSerialPort();
271 if (passthroughPort
&& serialPort
) {
272 serialPassthrough(passthroughPort
, serialPort
, NULL
, NULL
);
276 static void mspFcSetPassthroughCommand(sbuf_t
*dst
, sbuf_t
*src
, mspPostProcessFnPtr
*mspPostProcessFn
)
278 const unsigned int dataSize
= sbufBytesRemaining(src
);
281 mspPassthroughMode
= MSP_PASSTHROUGH_ESC_4WAY
;
283 mspPassthroughMode
= sbufReadU8(src
);
284 mspPassthroughArgument
= sbufReadU8(src
);
287 switch (mspPassthroughMode
) {
288 case MSP_PASSTHROUGH_SERIAL_ID
:
289 case MSP_PASSTHROUGH_SERIAL_FUNCTION_ID
:
290 if (mspFindPassthroughSerialPort()) {
291 if (mspPostProcessFn
) {
292 *mspPostProcessFn
= mspSerialPassthroughFn
;
299 #ifdef USE_SERIAL_4WAY_BLHELI_INTERFACE
300 case MSP_PASSTHROUGH_ESC_4WAY
:
301 // get channel number
302 // switch all motor lines HI
303 // reply with the count of ESC found
304 sbufWriteU8(dst
, esc4wayInit());
306 if (mspPostProcessFn
) {
307 *mspPostProcessFn
= esc4wayProcess
;
312 case MSP_PASSTHROUGH_ESC_SIMONK
:
313 case MSP_PASSTHROUGH_ESC_BLHELI
:
314 case MSP_PASSTHROUGH_ESC_KISS
:
315 case MSP_PASSTHROUGH_ESC_KISSALL
:
316 case MSP_PASSTHROUGH_ESC_CASTLE
:
317 if (mspPassthroughArgument
< getMotorCount() || (mspPassthroughMode
== MSP_PASSTHROUGH_ESC_KISS
&& mspPassthroughArgument
== ALL_MOTORS
)) {
320 if (mspPostProcessFn
) {
321 *mspPostProcessFn
= mspEscPassthroughFn
;
327 #endif // USE_ESCSERIAL
328 #endif //USE_SERIAL_4WAY_BLHELI_INTERFACE
334 // TODO: Remove the pragma once this is called from unconditional code
335 #pragma GCC diagnostic ignored "-Wunused-function"
336 static void configRebootUpdateCheckU8(uint8_t *parm
, uint8_t value
)
338 if (*parm
!= value
) {
343 #pragma GCC diagnostic pop
345 static void mspRebootFn(serialPort_t
*serialPort
)
351 switch (rebootMode
) {
352 case MSP_REBOOT_FIRMWARE
:
356 case MSP_REBOOT_BOOTLOADER_ROM
:
357 systemResetToBootloader(BOOTLOADER_REQUEST_ROM
);
360 #if defined(USE_USB_MSC)
362 case MSP_REBOOT_MSC_UTC
: {
364 const int16_t timezoneOffsetMinutes
= (rebootMode
== MSP_REBOOT_MSC
) ? timeConfig()->tz_offsetMinutes
: 0;
365 systemResetToMsc(timezoneOffsetMinutes
);
372 #if defined(USE_FLASH_BOOT_LOADER)
373 case MSP_REBOOT_BOOTLOADER_FLASH
:
374 systemResetToBootloader(BOOTLOADER_REQUEST_FLASH
);
383 // control should never return here.
387 static void serializeSDCardSummaryReply(sbuf_t
*dst
)
391 uint8_t lastError
= 0;
392 uint32_t freeSpace
= 0;
393 uint32_t totalSpace
= 0;
395 #if defined(USE_SDCARD)
396 if (sdcardConfig()->mode
!= SDCARD_MODE_NONE
) {
397 flags
= MSP_SDCARD_FLAG_SUPPORTED
;
399 // Merge the card and filesystem states together
400 if (!sdcard_isInserted()) {
401 state
= MSP_SDCARD_STATE_NOT_PRESENT
;
402 } else if (!sdcard_isFunctional()) {
403 state
= MSP_SDCARD_STATE_FATAL
;
405 switch (afatfs_getFilesystemState()) {
406 case AFATFS_FILESYSTEM_STATE_READY
:
407 state
= MSP_SDCARD_STATE_READY
;
410 case AFATFS_FILESYSTEM_STATE_INITIALIZATION
:
411 if (sdcard_isInitialized()) {
412 state
= MSP_SDCARD_STATE_FS_INIT
;
414 state
= MSP_SDCARD_STATE_CARD_INIT
;
418 case AFATFS_FILESYSTEM_STATE_FATAL
:
419 case AFATFS_FILESYSTEM_STATE_UNKNOWN
:
421 state
= MSP_SDCARD_STATE_FATAL
;
426 lastError
= afatfs_getLastError();
427 // Write free space and total space in kilobytes
428 if (state
== MSP_SDCARD_STATE_READY
) {
429 freeSpace
= afatfs_getContiguousFreeSpace() / 1024;
430 totalSpace
= sdcard_getMetadata()->numBlocks
/ 2;
435 sbufWriteU8(dst
, flags
);
436 sbufWriteU8(dst
, state
);
437 sbufWriteU8(dst
, lastError
);
438 sbufWriteU32(dst
, freeSpace
);
439 sbufWriteU32(dst
, totalSpace
);
442 static void serializeDataflashSummaryReply(sbuf_t
*dst
)
445 if (flashfsIsSupported()) {
446 uint8_t flags
= MSP_FLASHFS_FLAG_SUPPORTED
;
447 flags
|= (flashfsIsReady() ? MSP_FLASHFS_FLAG_READY
: 0);
449 const flashPartition_t
*flashPartition
= flashPartitionFindByType(FLASH_PARTITION_TYPE_FLASHFS
);
451 sbufWriteU8(dst
, flags
);
452 sbufWriteU32(dst
, FLASH_PARTITION_SECTOR_COUNT(flashPartition
));
453 sbufWriteU32(dst
, flashfsGetSize());
454 sbufWriteU32(dst
, flashfsGetOffset()); // Effectively the current number of bytes stored on the volume
458 // FlashFS is not configured or valid device is not detected
461 sbufWriteU32(dst
, 0);
462 sbufWriteU32(dst
, 0);
463 sbufWriteU32(dst
, 0);
468 enum compressionType_e
{
473 static void serializeDataflashReadReply(sbuf_t
*dst
, uint32_t address
, const uint16_t size
, bool useLegacyFormat
, bool allowCompression
)
475 STATIC_ASSERT(MSP_PORT_DATAFLASH_INFO_SIZE
>= 16, MSP_PORT_DATAFLASH_INFO_SIZE_invalid
);
477 uint16_t readLen
= size
;
478 const int bytesRemainingInBuf
= sbufBytesRemaining(dst
) - MSP_PORT_DATAFLASH_INFO_SIZE
;
479 if (readLen
> bytesRemainingInBuf
) {
480 readLen
= bytesRemainingInBuf
;
482 // size will be lower than that requested if we reach end of volume
483 const uint32_t flashfsSize
= flashfsGetSize();
484 if (readLen
> flashfsSize
- address
) {
485 // truncate the request
486 readLen
= flashfsSize
- address
;
488 sbufWriteU32(dst
, address
);
490 // legacy format does not support compression
492 const uint8_t compressionMethod
= (!allowCompression
|| useLegacyFormat
) ? NO_COMPRESSION
: HUFFMAN
;
494 const uint8_t compressionMethod
= NO_COMPRESSION
;
495 UNUSED(allowCompression
);
498 if (compressionMethod
== NO_COMPRESSION
) {
500 uint16_t *readLenPtr
= (uint16_t *)sbufPtr(dst
);
501 if (!useLegacyFormat
) {
502 // new format supports variable read lengths
503 sbufWriteU16(dst
, readLen
);
504 sbufWriteU8(dst
, 0); // placeholder for compression format
507 const int bytesRead
= flashfsReadAbs(address
, sbufPtr(dst
), readLen
);
509 if (!useLegacyFormat
) {
510 // update the 'read length' with the actual amount read from flash.
511 *readLenPtr
= bytesRead
;
514 sbufAdvance(dst
, bytesRead
);
516 if (useLegacyFormat
) {
517 // pad the buffer with zeros
518 for (int i
= bytesRead
; i
< size
; i
++) {
524 // compress in 256-byte chunks
525 const uint16_t READ_BUFFER_SIZE
= 256;
526 // This may be DMAable, so make it cache aligned
527 __attribute__ ((aligned(32))) uint8_t readBuffer
[READ_BUFFER_SIZE
];
529 huffmanState_t state
= {
531 .outByte
= sbufPtr(dst
) + sizeof(uint16_t) + sizeof(uint8_t) + HUFFMAN_INFO_SIZE
,
532 .outBufLen
= readLen
,
537 uint16_t bytesReadTotal
= 0;
538 // read until output buffer overflows or flash is exhausted
539 while (state
.bytesWritten
< state
.outBufLen
&& address
+ bytesReadTotal
< flashfsSize
) {
540 const int bytesRead
= flashfsReadAbs(address
+ bytesReadTotal
, readBuffer
,
541 MIN(sizeof(readBuffer
), flashfsSize
- address
- bytesReadTotal
));
543 const int status
= huffmanEncodeBufStreaming(&state
, readBuffer
, bytesRead
, huffmanTable
);
549 bytesReadTotal
+= bytesRead
;
552 if (state
.outBit
!= 0x80) {
553 ++state
.bytesWritten
;
557 sbufWriteU16(dst
, HUFFMAN_INFO_SIZE
+ state
.bytesWritten
);
558 sbufWriteU8(dst
, compressionMethod
);
560 sbufWriteU16(dst
, bytesReadTotal
);
561 sbufAdvance(dst
, state
.bytesWritten
);
565 #endif // USE_FLASHFS
568 * Returns true if the command was processd, false otherwise.
569 * May set mspPostProcessFunc to a function to be called once the command has been processed
571 static bool mspCommonProcessOutCommand(int16_t cmdMSP
, sbuf_t
*dst
, mspPostProcessFnPtr
*mspPostProcessFn
)
573 UNUSED(mspPostProcessFn
);
576 case MSP_API_VERSION
:
577 sbufWriteU8(dst
, MSP_PROTOCOL_VERSION
);
578 sbufWriteU8(dst
, API_VERSION_MAJOR
);
579 sbufWriteU8(dst
, API_VERSION_MINOR
);
583 sbufWriteData(dst
, flightControllerIdentifier
, FLIGHT_CONTROLLER_IDENTIFIER_LENGTH
);
587 sbufWriteU8(dst
, FC_VERSION_MAJOR
);
588 sbufWriteU8(dst
, FC_VERSION_MINOR
);
589 sbufWriteU8(dst
, FC_VERSION_PATCH_LEVEL
);
594 sbufWriteData(dst
, systemConfig()->boardIdentifier
, BOARD_IDENTIFIER_LENGTH
);
595 #ifdef USE_HARDWARE_REVISION_DETECTION
596 sbufWriteU16(dst
, hardwareRevision
);
598 sbufWriteU16(dst
, 0); // No other build targets currently have hardware revision detection.
600 #if defined(USE_MAX7456)
601 sbufWriteU8(dst
, 2); // 2 == FC with MAX7456
603 sbufWriteU8(dst
, 0); // 0 == FC
606 // Target capabilities (uint8)
607 #define TARGET_HAS_VCP 0
608 #define TARGET_HAS_SOFTSERIAL 1
609 #define TARGET_IS_UNIFIED 2
610 #define TARGET_HAS_FLASH_BOOTLOADER 3
611 #define TARGET_SUPPORTS_CUSTOM_DEFAULTS 4
612 #define TARGET_HAS_CUSTOM_DEFAULTS 5
613 #define TARGET_SUPPORTS_RX_BIND 6
615 uint8_t targetCapabilities
= 0;
617 targetCapabilities
|= BIT(TARGET_HAS_VCP
);
619 #if defined(USE_SOFTSERIAL1) || defined(USE_SOFTSERIAL2)
620 targetCapabilities
|= BIT(TARGET_HAS_SOFTSERIAL
);
622 #if defined(USE_UNIFIED_TARGET)
623 targetCapabilities
|= BIT(TARGET_IS_UNIFIED
);
625 #if defined(USE_FLASH_BOOT_LOADER)
626 targetCapabilities
|= BIT(TARGET_HAS_FLASH_BOOTLOADER
);
628 #if defined(USE_CUSTOM_DEFAULTS)
629 targetCapabilities
|= BIT(TARGET_SUPPORTS_CUSTOM_DEFAULTS
);
630 if (hasCustomDefaults()) {
631 targetCapabilities
|= BIT(TARGET_HAS_CUSTOM_DEFAULTS
);
634 #if defined(USE_RX_BIND)
635 if (getRxBindSupported()) {
636 targetCapabilities
|= BIT(TARGET_SUPPORTS_RX_BIND
);
640 sbufWriteU8(dst
, targetCapabilities
);
642 // Target name with explicit length
643 sbufWriteU8(dst
, strlen(targetName
));
644 sbufWriteData(dst
, targetName
, strlen(targetName
));
646 #if defined(USE_BOARD_INFO)
647 // Board name with explicit length
648 char *value
= getBoardName();
649 sbufWriteU8(dst
, strlen(value
));
650 sbufWriteString(dst
, value
);
652 // Manufacturer id with explicit length
653 value
= getManufacturerId();
654 sbufWriteU8(dst
, strlen(value
));
655 sbufWriteString(dst
, value
);
661 #if defined(USE_SIGNATURE)
663 sbufWriteData(dst
, getSignature(), SIGNATURE_LENGTH
);
665 uint8_t emptySignature
[SIGNATURE_LENGTH
];
666 memset(emptySignature
, 0, sizeof(emptySignature
));
667 sbufWriteData(dst
, &emptySignature
, sizeof(emptySignature
));
670 sbufWriteU8(dst
, getMcuTypeId());
672 // Added in API version 1.42
673 sbufWriteU8(dst
, systemConfig()->configurationState
);
675 // Added in API version 1.43
676 sbufWriteU16(dst
, gyro
.sampleRateHz
); // informational so the configurator can display the correct gyro/pid frequencies in the drop-down
678 // Configuration warnings / problems (uint32_t)
679 #define PROBLEM_ACC_NEEDS_CALIBRATION 0
680 #define PROBLEM_MOTOR_PROTOCOL_DISABLED 1
682 uint32_t configurationProblems
= 0;
685 if (!accHasBeenCalibrated()) {
686 configurationProblems
|= BIT(PROBLEM_ACC_NEEDS_CALIBRATION
);
690 if (!checkMotorProtocolEnabled(&motorConfig()->dev
, NULL
)) {
691 configurationProblems
|= BIT(PROBLEM_MOTOR_PROTOCOL_DISABLED
);
694 sbufWriteU32(dst
, configurationProblems
);
696 // Added in MSP API 1.44
698 sbufWriteU8(dst
, spiGetRegisteredDeviceCount());
703 sbufWriteU8(dst
, i2cGetRegisteredDeviceCount());
712 sbufWriteData(dst
, buildDate
, BUILD_DATE_LENGTH
);
713 sbufWriteData(dst
, buildTime
, BUILD_TIME_LENGTH
);
714 sbufWriteData(dst
, shortGitRevision
, GIT_SHORT_REVISION_LENGTH
);
718 sbufWriteU8(dst
, (uint8_t)constrain(getLegacyBatteryVoltage(), 0, 255));
719 sbufWriteU16(dst
, (uint16_t)constrain(getMAhDrawn(), 0, 0xFFFF)); // milliamp hours drawn from battery
720 sbufWriteU16(dst
, getRssi());
721 sbufWriteU16(dst
, (int16_t)constrain(getAmperage(), -0x8000, 0x7FFF)); // send current in 0.01 A steps, range is -320A to 320A
722 sbufWriteU16(dst
, getBatteryVoltage());
726 for (int i
= 0; i
< DEBUG16_VALUE_COUNT
; i
++) {
727 sbufWriteU16(dst
, debug
[i
]); // 4 variables are here for general monitoring purpose
732 sbufWriteU32(dst
, U_ID_0
);
733 sbufWriteU32(dst
, U_ID_1
);
734 sbufWriteU32(dst
, U_ID_2
);
737 case MSP_FEATURE_CONFIG
:
738 sbufWriteU32(dst
, featureConfig()->enabledFeatures
);
742 case MSP_BEEPER_CONFIG
:
743 sbufWriteU32(dst
, beeperConfig()->beeper_off_flags
);
744 sbufWriteU8(dst
, beeperConfig()->dshotBeaconTone
);
745 sbufWriteU32(dst
, beeperConfig()->dshotBeaconOffFlags
);
749 case MSP_BATTERY_STATE
: {
750 // battery characteristics
751 sbufWriteU8(dst
, (uint8_t)constrain(getBatteryCellCount(), 0, 255)); // 0 indicates battery not detected.
752 sbufWriteU16(dst
, batteryConfig()->batteryCapacity
); // in mAh
755 sbufWriteU8(dst
, (uint8_t)constrain(getLegacyBatteryVoltage(), 0, 255)); // in 0.1V steps
756 sbufWriteU16(dst
, (uint16_t)constrain(getMAhDrawn(), 0, 0xFFFF)); // milliamp hours drawn from battery
757 sbufWriteU16(dst
, (int16_t)constrain(getAmperage(), -0x8000, 0x7FFF)); // send current in 0.01 A steps, range is -320A to 320A
760 sbufWriteU8(dst
, (uint8_t)getBatteryState());
762 sbufWriteU16(dst
, getBatteryVoltage()); // in 0.01V steps
766 case MSP_VOLTAGE_METERS
: {
767 // write out id and voltage meter values, once for each meter we support
768 uint8_t count
= supportedVoltageMeterCount
;
769 #ifdef USE_ESC_SENSOR
770 count
-= VOLTAGE_METER_ID_ESC_COUNT
- getMotorCount();
773 for (int i
= 0; i
< count
; i
++) {
775 voltageMeter_t meter
;
776 uint8_t id
= (uint8_t)voltageMeterIds
[i
];
777 voltageMeterRead(id
, &meter
);
779 sbufWriteU8(dst
, id
);
780 sbufWriteU8(dst
, (uint8_t)constrain((meter
.displayFiltered
+ 5) / 10, 0, 255));
785 case MSP_CURRENT_METERS
: {
786 // write out id and current meter values, once for each meter we support
787 uint8_t count
= supportedCurrentMeterCount
;
788 #ifdef USE_ESC_SENSOR
789 count
-= VOLTAGE_METER_ID_ESC_COUNT
- getMotorCount();
791 for (int i
= 0; i
< count
; i
++) {
793 currentMeter_t meter
;
794 uint8_t id
= (uint8_t)currentMeterIds
[i
];
795 currentMeterRead(id
, &meter
);
797 sbufWriteU8(dst
, id
);
798 sbufWriteU16(dst
, (uint16_t)constrain(meter
.mAhDrawn
, 0, 0xFFFF)); // milliamp hours drawn from battery
799 sbufWriteU16(dst
, (uint16_t)constrain(meter
.amperage
* 10, 0, 0xFFFF)); // send amperage in 0.001 A steps (mA). Negative range is truncated to zero
804 case MSP_VOLTAGE_METER_CONFIG
:
806 // by using a sensor type and a sub-frame length it's possible to configure any type of voltage meter,
807 // e.g. an i2c/spi/can sensor or any sensor not built directly into the FC such as ESC/RX/SPort/SBus that has
808 // different configuration requirements.
809 STATIC_ASSERT(VOLTAGE_SENSOR_ADC_VBAT
== 0, VOLTAGE_SENSOR_ADC_VBAT_incorrect
); // VOLTAGE_SENSOR_ADC_VBAT should be the first index
810 sbufWriteU8(dst
, MAX_VOLTAGE_SENSOR_ADC
); // voltage meters in payload
811 for (int i
= VOLTAGE_SENSOR_ADC_VBAT
; i
< MAX_VOLTAGE_SENSOR_ADC
; i
++) {
812 const uint8_t adcSensorSubframeLength
= 1 + 1 + 1 + 1 + 1; // length of id, type, vbatscale, vbatresdivval, vbatresdivmultipler, in bytes
813 sbufWriteU8(dst
, adcSensorSubframeLength
); // ADC sensor sub-frame length
815 sbufWriteU8(dst
, voltageMeterADCtoIDMap
[i
]); // id of the sensor
816 sbufWriteU8(dst
, VOLTAGE_SENSOR_TYPE_ADC_RESISTOR_DIVIDER
); // indicate the type of sensor that the next part of the payload is for
818 sbufWriteU8(dst
, voltageSensorADCConfig(i
)->vbatscale
);
819 sbufWriteU8(dst
, voltageSensorADCConfig(i
)->vbatresdivval
);
820 sbufWriteU8(dst
, voltageSensorADCConfig(i
)->vbatresdivmultiplier
);
822 // if we had any other voltage sensors, this is where we would output any needed configuration
826 case MSP_CURRENT_METER_CONFIG
: {
827 // the ADC and VIRTUAL sensors have the same configuration requirements, however this API reflects
828 // that this situation may change and allows us to support configuration of any current sensor with
829 // specialist configuration requirements.
831 int currentMeterCount
= 1;
833 #ifdef USE_VIRTUAL_CURRENT_METER
836 sbufWriteU8(dst
, currentMeterCount
);
838 const uint8_t adcSensorSubframeLength
= 1 + 1 + 2 + 2; // length of id, type, scale, offset, in bytes
839 sbufWriteU8(dst
, adcSensorSubframeLength
);
840 sbufWriteU8(dst
, CURRENT_METER_ID_BATTERY_1
); // the id of the meter
841 sbufWriteU8(dst
, CURRENT_SENSOR_ADC
); // indicate the type of sensor that the next part of the payload is for
842 sbufWriteU16(dst
, currentSensorADCConfig()->scale
);
843 sbufWriteU16(dst
, currentSensorADCConfig()->offset
);
845 #ifdef USE_VIRTUAL_CURRENT_METER
846 const int8_t virtualSensorSubframeLength
= 1 + 1 + 2 + 2; // length of id, type, scale, offset, in bytes
847 sbufWriteU8(dst
, virtualSensorSubframeLength
);
848 sbufWriteU8(dst
, CURRENT_METER_ID_VIRTUAL_1
); // the id of the meter
849 sbufWriteU8(dst
, CURRENT_SENSOR_VIRTUAL
); // indicate the type of sensor that the next part of the payload is for
850 sbufWriteU16(dst
, currentSensorVirtualConfig()->scale
);
851 sbufWriteU16(dst
, currentSensorVirtualConfig()->offset
);
854 // if we had any other current sensors, this is where we would output any needed configuration
858 case MSP_BATTERY_CONFIG
:
859 sbufWriteU8(dst
, (batteryConfig()->vbatmincellvoltage
+ 5) / 10);
860 sbufWriteU8(dst
, (batteryConfig()->vbatmaxcellvoltage
+ 5) / 10);
861 sbufWriteU8(dst
, (batteryConfig()->vbatwarningcellvoltage
+ 5) / 10);
862 sbufWriteU16(dst
, batteryConfig()->batteryCapacity
);
863 sbufWriteU8(dst
, batteryConfig()->voltageMeterSource
);
864 sbufWriteU8(dst
, batteryConfig()->currentMeterSource
);
865 sbufWriteU16(dst
, batteryConfig()->vbatmincellvoltage
);
866 sbufWriteU16(dst
, batteryConfig()->vbatmaxcellvoltage
);
867 sbufWriteU16(dst
, batteryConfig()->vbatwarningcellvoltage
);
870 case MSP_TRANSPONDER_CONFIG
: {
871 #ifdef USE_TRANSPONDER
872 // Backward compatibility to BFC 3.1.1 is lost for this message type
873 sbufWriteU8(dst
, TRANSPONDER_PROVIDER_COUNT
);
874 for (unsigned int i
= 0; i
< TRANSPONDER_PROVIDER_COUNT
; i
++) {
875 sbufWriteU8(dst
, transponderRequirements
[i
].provider
);
876 sbufWriteU8(dst
, transponderRequirements
[i
].dataLength
);
879 uint8_t provider
= transponderConfig()->provider
;
880 sbufWriteU8(dst
, provider
);
883 uint8_t requirementIndex
= provider
- 1;
884 uint8_t providerDataLength
= transponderRequirements
[requirementIndex
].dataLength
;
886 for (unsigned int i
= 0; i
< providerDataLength
; i
++) {
887 sbufWriteU8(dst
, transponderConfig()->data
[i
]);
891 sbufWriteU8(dst
, 0); // no providers
896 case MSP_OSD_CONFIG
: {
897 #define OSD_FLAGS_OSD_FEATURE (1 << 0)
898 //#define OSD_FLAGS_OSD_SLAVE (1 << 1)
899 #define OSD_FLAGS_RESERVED_1 (1 << 2)
900 #define OSD_FLAGS_OSD_HARDWARE_FRSKYOSD (1 << 3)
901 #define OSD_FLAGS_OSD_HARDWARE_MAX_7456 (1 << 4)
902 #define OSD_FLAGS_OSD_DEVICE_DETECTED (1 << 5)
904 uint8_t osdFlags
= 0;
906 osdFlags
|= OSD_FLAGS_OSD_FEATURE
;
908 osdDisplayPortDevice_e deviceType
;
909 displayPort_t
*osdDisplayPort
= osdGetDisplayPort(&deviceType
);
910 bool displayIsReady
= osdDisplayPort
&& displayCheckReady(osdDisplayPort
, true);
911 switch (deviceType
) {
912 case OSD_DISPLAYPORT_DEVICE_MAX7456
:
913 osdFlags
|= OSD_FLAGS_OSD_HARDWARE_MAX_7456
;
914 if (displayIsReady
) {
915 osdFlags
|= OSD_FLAGS_OSD_DEVICE_DETECTED
;
919 case OSD_DISPLAYPORT_DEVICE_FRSKYOSD
:
920 osdFlags
|= OSD_FLAGS_OSD_HARDWARE_FRSKYOSD
;
921 if (displayIsReady
) {
922 osdFlags
|= OSD_FLAGS_OSD_DEVICE_DETECTED
;
930 sbufWriteU8(dst
, osdFlags
);
933 // send video system (AUTO/PAL/NTSC)
934 sbufWriteU8(dst
, vcdProfile()->video_system
);
940 // OSD specific, not applicable to OSD slaves.
943 sbufWriteU8(dst
, osdConfig()->units
);
946 sbufWriteU8(dst
, osdConfig()->rssi_alarm
);
947 sbufWriteU16(dst
, osdConfig()->cap_alarm
);
949 // Reuse old timer alarm (U16) as OSD_ITEM_COUNT
951 sbufWriteU8(dst
, OSD_ITEM_COUNT
);
953 sbufWriteU16(dst
, osdConfig()->alt_alarm
);
955 // Element position and visibility
956 for (int i
= 0; i
< OSD_ITEM_COUNT
; i
++) {
957 sbufWriteU16(dst
, osdElementConfig()->item_pos
[i
]);
960 // Post flight statistics
961 sbufWriteU8(dst
, OSD_STAT_COUNT
);
962 for (int i
= 0; i
< OSD_STAT_COUNT
; i
++ ) {
963 sbufWriteU8(dst
, osdStatGetState(i
));
967 sbufWriteU8(dst
, OSD_TIMER_COUNT
);
968 for (int i
= 0; i
< OSD_TIMER_COUNT
; i
++) {
969 sbufWriteU16(dst
, osdConfig()->timers
[i
]);
973 // Send low word first for backwards compatibility (API < 1.41)
974 sbufWriteU16(dst
, (uint16_t)(osdConfig()->enabledWarnings
& 0xFFFF));
976 // Send the warnings count and 32bit enabled warnings flags.
977 // Add currently active OSD profile (0 indicates OSD profiles not available).
978 // Add OSD stick overlay mode (0 indicates OSD stick overlay not available).
979 sbufWriteU8(dst
, OSD_WARNING_COUNT
);
980 sbufWriteU32(dst
, osdConfig()->enabledWarnings
);
982 #ifdef USE_OSD_PROFILES
983 sbufWriteU8(dst
, OSD_PROFILE_COUNT
); // available profiles
984 sbufWriteU8(dst
, osdConfig()->osdProfileIndex
); // selected profile
986 // If the feature is not available there is only 1 profile and it's always selected
989 #endif // USE_OSD_PROFILES
991 #ifdef USE_OSD_STICK_OVERLAY
992 sbufWriteU8(dst
, osdConfig()->overlay_radio_mode
);
995 #endif // USE_OSD_STICK_OVERLAY
998 // Add the camera frame element width/height
999 sbufWriteU8(dst
, osdConfig()->camera_frame_width
);
1000 sbufWriteU8(dst
, osdConfig()->camera_frame_height
);
1012 static bool mspProcessOutCommand(int16_t cmdMSP
, sbuf_t
*dst
)
1014 bool unsupportedCommand
= false;
1020 boxBitmask_t flightModeFlags
;
1021 const int flagBits
= packFlightModeFlags(&flightModeFlags
);
1023 sbufWriteU16(dst
, getTaskDeltaTimeUs(TASK_PID
));
1025 sbufWriteU16(dst
, i2cGetErrorCounter());
1027 sbufWriteU16(dst
, 0);
1029 sbufWriteU16(dst
, sensors(SENSOR_ACC
) | sensors(SENSOR_BARO
) << 1 | sensors(SENSOR_MAG
) << 2 | sensors(SENSOR_GPS
) << 3 | sensors(SENSOR_RANGEFINDER
) << 4 | sensors(SENSOR_GYRO
) << 5);
1030 sbufWriteData(dst
, &flightModeFlags
, 4); // unconditional part of flags, first 32 bits
1031 sbufWriteU8(dst
, getCurrentPidProfileIndex());
1032 sbufWriteU16(dst
, constrain(getAverageSystemLoadPercent(), 0, LOAD_PERCENTAGE_ONE
));
1033 if (cmdMSP
== MSP_STATUS_EX
) {
1034 sbufWriteU8(dst
, PID_PROFILE_COUNT
);
1035 sbufWriteU8(dst
, getCurrentControlRateProfileIndex());
1036 } else { // MSP_STATUS
1037 sbufWriteU16(dst
, 0); // gyro cycle time
1040 // write flightModeFlags header. Lowest 4 bits contain number of bytes that follow
1041 // header is emited even when all bits fit into 32 bits to allow future extension
1042 int byteCount
= (flagBits
- 32 + 7) / 8; // 32 already stored, round up
1043 byteCount
= constrain(byteCount
, 0, 15); // limit to 16 bytes (128 bits)
1044 sbufWriteU8(dst
, byteCount
);
1045 sbufWriteData(dst
, ((uint8_t*)&flightModeFlags
) + 4, byteCount
);
1047 // Write arming disable flags
1048 // 1 byte, flag count
1049 sbufWriteU8(dst
, ARMING_DISABLE_FLAGS_COUNT
);
1051 const uint32_t armingDisableFlags
= getArmingDisableFlags();
1052 sbufWriteU32(dst
, armingDisableFlags
);
1054 // config state flags - bits to indicate the state of the configuration, reboot required, etc.
1055 // other flags can be added as needed
1056 sbufWriteU8(dst
, (getRebootRequired() << 0));
1062 #if defined(USE_ACC)
1063 // Hack scale due to choice of units for sensor data in multiwii
1066 if (acc
.dev
.acc_1G
> 512 * 4) {
1068 } else if (acc
.dev
.acc_1G
> 512 * 2) {
1070 } else if (acc
.dev
.acc_1G
>= 512) {
1077 for (int i
= 0; i
< 3; i
++) {
1078 #if defined(USE_ACC)
1079 sbufWriteU16(dst
, lrintf(acc
.accADC
[i
] / scale
));
1081 sbufWriteU16(dst
, 0);
1084 for (int i
= 0; i
< 3; i
++) {
1085 sbufWriteU16(dst
, gyroRateDps(i
));
1087 for (int i
= 0; i
< 3; i
++) {
1088 #if defined(USE_MAG)
1089 sbufWriteU16(dst
, lrintf(mag
.magADC
[i
]));
1091 sbufWriteU16(dst
, 0);
1099 const int nameLen
= strlen(pilotConfig()->name
);
1100 for (int i
= 0; i
< nameLen
; i
++) {
1101 sbufWriteU8(dst
, pilotConfig()->name
[i
]);
1108 sbufWriteData(dst
, &servo
, MAX_SUPPORTED_SERVOS
* 2);
1110 case MSP_SERVO_CONFIGURATIONS
:
1111 for (int i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
1112 sbufWriteU16(dst
, servoParams(i
)->min
);
1113 sbufWriteU16(dst
, servoParams(i
)->max
);
1114 sbufWriteU16(dst
, servoParams(i
)->middle
);
1115 sbufWriteU8(dst
, servoParams(i
)->rate
);
1116 sbufWriteU8(dst
, servoParams(i
)->forwardFromChannel
);
1117 sbufWriteU32(dst
, servoParams(i
)->reversedSources
);
1121 case MSP_SERVO_MIX_RULES
:
1122 for (int i
= 0; i
< MAX_SERVO_RULES
; i
++) {
1123 sbufWriteU8(dst
, customServoMixers(i
)->targetChannel
);
1124 sbufWriteU8(dst
, customServoMixers(i
)->inputSource
);
1125 sbufWriteU8(dst
, customServoMixers(i
)->rate
);
1126 sbufWriteU8(dst
, customServoMixers(i
)->speed
);
1127 sbufWriteU8(dst
, customServoMixers(i
)->min
);
1128 sbufWriteU8(dst
, customServoMixers(i
)->max
);
1129 sbufWriteU8(dst
, customServoMixers(i
)->box
);
1135 for (unsigned i
= 0; i
< 8; i
++) {
1137 if (!motorIsEnabled() || i
>= MAX_SUPPORTED_MOTORS
|| !motorIsMotorEnabled(i
)) {
1138 sbufWriteU16(dst
, 0);
1142 sbufWriteU16(dst
, motorConvertToExternal(motor
[i
]));
1144 sbufWriteU16(dst
, 0);
1150 // Added in API version 1.42
1151 case MSP_MOTOR_TELEMETRY
:
1152 sbufWriteU8(dst
, getMotorCount());
1153 for (unsigned i
= 0; i
< getMotorCount(); i
++) {
1155 uint16_t invalidPct
= 0;
1156 uint8_t escTemperature
= 0; // degrees celcius
1157 uint16_t escVoltage
= 0; // 0.01V per unit
1158 uint16_t escCurrent
= 0; // 0.01A per unit
1159 uint16_t escConsumption
= 0; // mAh
1161 bool rpmDataAvailable
= false;
1163 #ifdef USE_DSHOT_TELEMETRY
1164 if (motorConfig()->dev
.useDshotTelemetry
) {
1165 rpm
= (int)getDshotTelemetry(i
) * 100 * 2 / motorConfig()->motorPoleCount
;
1166 rpmDataAvailable
= true;
1167 invalidPct
= 10000; // 100.00%
1168 #ifdef USE_DSHOT_TELEMETRY_STATS
1169 if (isDshotMotorTelemetryActive(i
)) {
1170 invalidPct
= getDshotTelemetryMotorInvalidPercent(i
);
1176 #ifdef USE_ESC_SENSOR
1177 if (featureIsEnabled(FEATURE_ESC_SENSOR
)) {
1178 escSensorData_t
*escData
= getEscSensorData(i
);
1179 if (!rpmDataAvailable
) { // We want DSHOT telemetry RPM data (if available) to have precedence
1180 rpm
= calcEscRpm(escData
->rpm
);
1181 rpmDataAvailable
= true;
1183 escTemperature
= escData
->temperature
;
1184 escVoltage
= escData
->voltage
;
1185 escCurrent
= escData
->current
;
1186 escConsumption
= escData
->consumption
;
1190 sbufWriteU32(dst
, (rpmDataAvailable
? rpm
: 0));
1191 sbufWriteU16(dst
, invalidPct
);
1192 sbufWriteU8(dst
, escTemperature
);
1193 sbufWriteU16(dst
, escVoltage
);
1194 sbufWriteU16(dst
, escCurrent
);
1195 sbufWriteU16(dst
, escConsumption
);
1199 case MSP2_MOTOR_OUTPUT_REORDERING
:
1201 sbufWriteU8(dst
, MAX_SUPPORTED_MOTORS
);
1203 for (unsigned i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
1204 sbufWriteU8(dst
, motorConfig()->dev
.motorOutputReordering
[i
]);
1209 #ifdef USE_VTX_COMMON
1210 case MSP2_GET_VTX_DEVICE_STATUS
:
1212 const vtxDevice_t
*vtxDevice
= vtxCommonDevice();
1213 vtxCommonSerializeDeviceStatus(vtxDevice
, dst
);
1219 case MSP2_GET_OSD_WARNINGS
:
1222 uint8_t displayAttr
;
1223 char warningsBuffer
[OSD_FORMAT_MESSAGE_BUFFER_SIZE
];
1225 renderOsdWarning(warningsBuffer
, &isBlinking
, &displayAttr
);
1226 const uint8_t warningsLen
= strlen(warningsBuffer
);
1229 displayAttr
|= DISPLAYPORT_ATTR_BLINK
;
1231 sbufWriteU8(dst
, displayAttr
); // see displayPortAttr_e
1232 sbufWriteU8(dst
, warningsLen
); // length byte followed by the actual characters
1233 for (unsigned i
= 0; i
< warningsLen
; i
++) {
1234 sbufWriteU8(dst
, warningsBuffer
[i
]);
1241 for (int i
= 0; i
< rxRuntimeState
.channelCount
; i
++) {
1242 sbufWriteU16(dst
, rcData
[i
]);
1247 sbufWriteU16(dst
, attitude
.values
.roll
);
1248 sbufWriteU16(dst
, attitude
.values
.pitch
);
1249 sbufWriteU16(dst
, DECIDEGREES_TO_DEGREES(attitude
.values
.yaw
));
1253 sbufWriteU32(dst
, getEstimatedAltitudeCm());
1255 sbufWriteU16(dst
, getEstimatedVario());
1257 sbufWriteU16(dst
, 0);
1261 case MSP_SONAR_ALTITUDE
:
1262 #if defined(USE_RANGEFINDER)
1263 sbufWriteU32(dst
, rangefinderGetLatestAltitude());
1265 sbufWriteU32(dst
, 0);
1269 case MSP_BOARD_ALIGNMENT_CONFIG
:
1270 sbufWriteU16(dst
, boardAlignment()->rollDegrees
);
1271 sbufWriteU16(dst
, boardAlignment()->pitchDegrees
);
1272 sbufWriteU16(dst
, boardAlignment()->yawDegrees
);
1275 case MSP_ARMING_CONFIG
:
1276 sbufWriteU8(dst
, armingConfig()->auto_disarm_delay
);
1277 sbufWriteU8(dst
, 0);
1278 sbufWriteU8(dst
, imuConfig()->small_angle
);
1282 sbufWriteU8(dst
, currentControlRateProfile
->rcRates
[FD_ROLL
]);
1283 sbufWriteU8(dst
, currentControlRateProfile
->rcExpo
[FD_ROLL
]);
1284 for (int i
= 0 ; i
< 3; i
++) {
1285 sbufWriteU8(dst
, currentControlRateProfile
->rates
[i
]); // R,P,Y see flight_dynamics_index_t
1287 sbufWriteU8(dst
, currentControlRateProfile
->tpa_rate
);
1288 sbufWriteU8(dst
, currentControlRateProfile
->thrMid8
);
1289 sbufWriteU8(dst
, currentControlRateProfile
->thrExpo8
);
1290 sbufWriteU16(dst
, currentControlRateProfile
->tpa_breakpoint
);
1291 sbufWriteU8(dst
, currentControlRateProfile
->rcExpo
[FD_YAW
]);
1292 sbufWriteU8(dst
, currentControlRateProfile
->rcRates
[FD_YAW
]);
1293 sbufWriteU8(dst
, currentControlRateProfile
->rcRates
[FD_PITCH
]);
1294 sbufWriteU8(dst
, currentControlRateProfile
->rcExpo
[FD_PITCH
]);
1297 sbufWriteU8(dst
, currentControlRateProfile
->throttle_limit_type
);
1298 sbufWriteU8(dst
, currentControlRateProfile
->throttle_limit_percent
);
1301 sbufWriteU16(dst
, currentControlRateProfile
->rate_limit
[FD_ROLL
]);
1302 sbufWriteU16(dst
, currentControlRateProfile
->rate_limit
[FD_PITCH
]);
1303 sbufWriteU16(dst
, currentControlRateProfile
->rate_limit
[FD_YAW
]);
1306 sbufWriteU8(dst
, currentControlRateProfile
->rates_type
);
1311 for (int i
= 0; i
< PID_ITEM_COUNT
; i
++) {
1312 sbufWriteU8(dst
, currentPidProfile
->pid
[i
].P
);
1313 sbufWriteU8(dst
, currentPidProfile
->pid
[i
].I
);
1314 sbufWriteU8(dst
, currentPidProfile
->pid
[i
].D
);
1319 for (const char *c
= pidNames
; *c
; c
++) {
1320 sbufWriteU8(dst
, *c
);
1324 case MSP_PID_CONTROLLER
:
1325 sbufWriteU8(dst
, PID_CONTROLLER_BETAFLIGHT
);
1328 case MSP_MODE_RANGES
:
1329 for (int i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
1330 const modeActivationCondition_t
*mac
= modeActivationConditions(i
);
1331 const box_t
*box
= findBoxByBoxId(mac
->modeId
);
1332 sbufWriteU8(dst
, box
->permanentId
);
1333 sbufWriteU8(dst
, mac
->auxChannelIndex
);
1334 sbufWriteU8(dst
, mac
->range
.startStep
);
1335 sbufWriteU8(dst
, mac
->range
.endStep
);
1339 case MSP_MODE_RANGES_EXTRA
:
1340 sbufWriteU8(dst
, MAX_MODE_ACTIVATION_CONDITION_COUNT
); // prepend number of EXTRAs array elements
1342 for (int i
= 0; i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
; i
++) {
1343 const modeActivationCondition_t
*mac
= modeActivationConditions(i
);
1344 const box_t
*box
= findBoxByBoxId(mac
->modeId
);
1345 const box_t
*linkedBox
= findBoxByBoxId(mac
->linkedTo
);
1346 sbufWriteU8(dst
, box
->permanentId
); // each element is aligned with MODE_RANGES by the permanentId
1347 sbufWriteU8(dst
, mac
->modeLogic
);
1348 sbufWriteU8(dst
, linkedBox
->permanentId
);
1352 case MSP_ADJUSTMENT_RANGES
:
1353 for (int i
= 0; i
< MAX_ADJUSTMENT_RANGE_COUNT
; i
++) {
1354 const adjustmentRange_t
*adjRange
= adjustmentRanges(i
);
1355 sbufWriteU8(dst
, 0); // was adjRange->adjustmentIndex
1356 sbufWriteU8(dst
, adjRange
->auxChannelIndex
);
1357 sbufWriteU8(dst
, adjRange
->range
.startStep
);
1358 sbufWriteU8(dst
, adjRange
->range
.endStep
);
1359 sbufWriteU8(dst
, adjRange
->adjustmentConfig
);
1360 sbufWriteU8(dst
, adjRange
->auxSwitchChannelIndex
);
1364 case MSP_MOTOR_CONFIG
:
1365 sbufWriteU16(dst
, motorConfig()->minthrottle
);
1366 sbufWriteU16(dst
, motorConfig()->maxthrottle
);
1367 sbufWriteU16(dst
, motorConfig()->mincommand
);
1370 sbufWriteU8(dst
, getMotorCount());
1371 sbufWriteU8(dst
, motorConfig()->motorPoleCount
);
1372 #ifdef USE_DSHOT_TELEMETRY
1373 sbufWriteU8(dst
, motorConfig()->dev
.useDshotTelemetry
);
1375 sbufWriteU8(dst
, 0);
1378 #ifdef USE_ESC_SENSOR
1379 sbufWriteU8(dst
, featureIsEnabled(FEATURE_ESC_SENSOR
)); // ESC sensor available
1381 sbufWriteU8(dst
, 0);
1385 #if defined(USE_ESC_SENSOR)
1386 // Deprecated in favor of MSP_MOTOR_TELEMETY as of API version 1.42
1387 case MSP_ESC_SENSOR_DATA
:
1388 if (featureIsEnabled(FEATURE_ESC_SENSOR
)) {
1389 sbufWriteU8(dst
, getMotorCount());
1390 for (int i
= 0; i
< getMotorCount(); i
++) {
1391 const escSensorData_t
*escData
= getEscSensorData(i
);
1392 sbufWriteU8(dst
, escData
->temperature
);
1393 sbufWriteU16(dst
, escData
->rpm
);
1396 unsupportedCommand
= true;
1403 case MSP_GPS_CONFIG
:
1404 sbufWriteU8(dst
, gpsConfig()->provider
);
1405 sbufWriteU8(dst
, gpsConfig()->sbasMode
);
1406 sbufWriteU8(dst
, gpsConfig()->autoConfig
);
1407 sbufWriteU8(dst
, gpsConfig()->autoBaud
);
1408 // Added in API version 1.43
1409 sbufWriteU8(dst
, gpsConfig()->gps_set_home_point_once
);
1410 sbufWriteU8(dst
, gpsConfig()->gps_ublox_use_galileo
);
1414 sbufWriteU8(dst
, STATE(GPS_FIX
));
1415 sbufWriteU8(dst
, gpsSol
.numSat
);
1416 sbufWriteU32(dst
, gpsSol
.llh
.lat
);
1417 sbufWriteU32(dst
, gpsSol
.llh
.lon
);
1418 sbufWriteU16(dst
, (uint16_t)constrain(gpsSol
.llh
.altCm
/ 100, 0, UINT16_MAX
)); // alt changed from 1m to 0.01m per lsb since MSP API 1.39 by RTH. To maintain backwards compatibility compensate to 1m per lsb in MSP again.
1419 sbufWriteU16(dst
, gpsSol
.groundSpeed
);
1420 sbufWriteU16(dst
, gpsSol
.groundCourse
);
1421 // Added in API version 1.44
1422 sbufWriteU16(dst
, gpsSol
.hdop
);
1426 sbufWriteU16(dst
, GPS_distanceToHome
);
1427 sbufWriteU16(dst
, GPS_directionToHome
);
1428 sbufWriteU8(dst
, GPS_update
& 1);
1432 sbufWriteU8(dst
, GPS_numCh
);
1433 for (int i
= 0; i
< GPS_numCh
; i
++) {
1434 sbufWriteU8(dst
, GPS_svinfo_chn
[i
]);
1435 sbufWriteU8(dst
, GPS_svinfo_svid
[i
]);
1436 sbufWriteU8(dst
, GPS_svinfo_quality
[i
]);
1437 sbufWriteU8(dst
, GPS_svinfo_cno
[i
]);
1441 #ifdef USE_GPS_RESCUE
1442 case MSP_GPS_RESCUE
:
1443 sbufWriteU16(dst
, gpsRescueConfig()->angle
);
1444 sbufWriteU16(dst
, gpsRescueConfig()->initialAltitudeM
);
1445 sbufWriteU16(dst
, gpsRescueConfig()->descentDistanceM
);
1446 sbufWriteU16(dst
, gpsRescueConfig()->rescueGroundspeed
);
1447 sbufWriteU16(dst
, gpsRescueConfig()->throttleMin
);
1448 sbufWriteU16(dst
, gpsRescueConfig()->throttleMax
);
1449 sbufWriteU16(dst
, gpsRescueConfig()->throttleHover
);
1450 sbufWriteU8(dst
, gpsRescueConfig()->sanityChecks
);
1451 sbufWriteU8(dst
, gpsRescueConfig()->minSats
);
1452 // Added in API version 1.43
1453 sbufWriteU16(dst
, gpsRescueConfig()->ascendRate
);
1454 sbufWriteU16(dst
, gpsRescueConfig()->descendRate
);
1455 sbufWriteU8(dst
, gpsRescueConfig()->allowArmingWithoutFix
);
1456 sbufWriteU8(dst
, gpsRescueConfig()->altitudeMode
);
1457 // Added in API version 1.44
1458 sbufWriteU16(dst
, gpsRescueConfig()->minRescueDth
);
1461 case MSP_GPS_RESCUE_PIDS
:
1462 sbufWriteU16(dst
, gpsRescueConfig()->throttleP
);
1463 sbufWriteU16(dst
, gpsRescueConfig()->throttleI
);
1464 sbufWriteU16(dst
, gpsRescueConfig()->throttleD
);
1465 sbufWriteU16(dst
, gpsRescueConfig()->velP
);
1466 sbufWriteU16(dst
, gpsRescueConfig()->velI
);
1467 sbufWriteU16(dst
, gpsRescueConfig()->velD
);
1468 sbufWriteU16(dst
, gpsRescueConfig()->yawP
);
1473 #if defined(USE_ACC)
1475 sbufWriteU16(dst
, accelerometerConfig()->accelerometerTrims
.values
.pitch
);
1476 sbufWriteU16(dst
, accelerometerConfig()->accelerometerTrims
.values
.roll
);
1480 case MSP_MIXER_CONFIG
:
1481 sbufWriteU8(dst
, mixerConfig()->mixerMode
);
1482 sbufWriteU8(dst
, mixerConfig()->yaw_motors_reversed
);
1486 sbufWriteU8(dst
, rxConfig()->serialrx_provider
);
1487 sbufWriteU16(dst
, rxConfig()->maxcheck
);
1488 sbufWriteU16(dst
, rxConfig()->midrc
);
1489 sbufWriteU16(dst
, rxConfig()->mincheck
);
1490 sbufWriteU8(dst
, rxConfig()->spektrum_sat_bind
);
1491 sbufWriteU16(dst
, rxConfig()->rx_min_usec
);
1492 sbufWriteU16(dst
, rxConfig()->rx_max_usec
);
1493 sbufWriteU8(dst
, 0); // not required in API 1.44, was rxConfig()->rcInterpolation
1494 sbufWriteU8(dst
, 0); // not required in API 1.44, was rxConfig()->rcInterpolationInterval
1495 sbufWriteU16(dst
, rxConfig()->airModeActivateThreshold
* 10 + 1000);
1497 sbufWriteU8(dst
, rxSpiConfig()->rx_spi_protocol
);
1498 sbufWriteU32(dst
, rxSpiConfig()->rx_spi_id
);
1499 sbufWriteU8(dst
, rxSpiConfig()->rx_spi_rf_channel_count
);
1501 sbufWriteU8(dst
, 0);
1502 sbufWriteU32(dst
, 0);
1503 sbufWriteU8(dst
, 0);
1505 sbufWriteU8(dst
, rxConfig()->fpvCamAngleDegrees
);
1506 sbufWriteU8(dst
, 0); // not required in API 1.44, was rxConfig()->rcSmoothingChannels
1507 #if defined(USE_RC_SMOOTHING_FILTER)
1508 sbufWriteU8(dst
, 0); // not required in API 1.44, was rxConfig()->rc_smoothing_type
1509 sbufWriteU8(dst
, rxConfig()->rc_smoothing_setpoint_cutoff
);
1510 sbufWriteU8(dst
, rxConfig()->rc_smoothing_feedforward_cutoff
);
1511 sbufWriteU8(dst
, 0); // not required in API 1.44, was rxConfig()->rc_smoothing_input_type
1512 sbufWriteU8(dst
, 0); // not required in API 1.44, was rxConfig()->rc_smoothing_derivative_type
1514 sbufWriteU8(dst
, 0);
1515 sbufWriteU8(dst
, 0);
1516 sbufWriteU8(dst
, 0);
1517 sbufWriteU8(dst
, 0);
1518 sbufWriteU8(dst
, 0);
1520 #if defined(USE_USB_CDC_HID)
1521 sbufWriteU8(dst
, usbDevConfig()->type
);
1523 sbufWriteU8(dst
, 0);
1525 // Added in MSP API 1.42
1526 #if defined(USE_RC_SMOOTHING_FILTER)
1527 sbufWriteU8(dst
, rxConfig()->rc_smoothing_auto_factor_rpy
);
1529 sbufWriteU8(dst
, 0);
1531 // Added in MSP API 1.44
1532 #if defined(USE_RC_SMOOTHING_FILTER)
1533 sbufWriteU8(dst
, rxConfig()->rc_smoothing_mode
);
1535 sbufWriteU8(dst
, 0);
1538 case MSP_FAILSAFE_CONFIG
:
1539 sbufWriteU8(dst
, failsafeConfig()->failsafe_delay
);
1540 sbufWriteU8(dst
, failsafeConfig()->failsafe_off_delay
);
1541 sbufWriteU16(dst
, failsafeConfig()->failsafe_throttle
);
1542 sbufWriteU8(dst
, failsafeConfig()->failsafe_switch_mode
);
1543 sbufWriteU16(dst
, failsafeConfig()->failsafe_throttle_low_delay
);
1544 sbufWriteU8(dst
, failsafeConfig()->failsafe_procedure
);
1547 case MSP_RXFAIL_CONFIG
:
1548 for (int i
= 0; i
< rxRuntimeState
.channelCount
; i
++) {
1549 sbufWriteU8(dst
, rxFailsafeChannelConfigs(i
)->mode
);
1550 sbufWriteU16(dst
, RXFAIL_STEP_TO_CHANNEL_VALUE(rxFailsafeChannelConfigs(i
)->step
));
1554 case MSP_RSSI_CONFIG
:
1555 sbufWriteU8(dst
, rxConfig()->rssi_channel
);
1559 sbufWriteData(dst
, rxConfig()->rcmap
, RX_MAPPABLE_CHANNEL_COUNT
);
1562 case MSP_CF_SERIAL_CONFIG
:
1563 for (int i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
1564 if (!serialIsPortAvailable(serialConfig()->portConfigs
[i
].identifier
)) {
1567 sbufWriteU8(dst
, serialConfig()->portConfigs
[i
].identifier
);
1568 sbufWriteU16(dst
, serialConfig()->portConfigs
[i
].functionMask
);
1569 sbufWriteU8(dst
, serialConfig()->portConfigs
[i
].msp_baudrateIndex
);
1570 sbufWriteU8(dst
, serialConfig()->portConfigs
[i
].gps_baudrateIndex
);
1571 sbufWriteU8(dst
, serialConfig()->portConfigs
[i
].telemetry_baudrateIndex
);
1572 sbufWriteU8(dst
, serialConfig()->portConfigs
[i
].blackbox_baudrateIndex
);
1575 case MSP2_COMMON_SERIAL_CONFIG
: {
1577 for (int i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
1578 if (serialIsPortAvailable(serialConfig()->portConfigs
[i
].identifier
)) {
1582 sbufWriteU8(dst
, count
);
1583 for (int i
= 0; i
< SERIAL_PORT_COUNT
; i
++) {
1584 if (!serialIsPortAvailable(serialConfig()->portConfigs
[i
].identifier
)) {
1587 sbufWriteU8(dst
, serialConfig()->portConfigs
[i
].identifier
);
1588 sbufWriteU32(dst
, serialConfig()->portConfigs
[i
].functionMask
);
1589 sbufWriteU8(dst
, serialConfig()->portConfigs
[i
].msp_baudrateIndex
);
1590 sbufWriteU8(dst
, serialConfig()->portConfigs
[i
].gps_baudrateIndex
);
1591 sbufWriteU8(dst
, serialConfig()->portConfigs
[i
].telemetry_baudrateIndex
);
1592 sbufWriteU8(dst
, serialConfig()->portConfigs
[i
].blackbox_baudrateIndex
);
1597 #ifdef USE_LED_STRIP_STATUS_MODE
1598 case MSP_LED_COLORS
:
1599 for (int i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
1600 const hsvColor_t
*color
= &ledStripStatusModeConfig()->colors
[i
];
1601 sbufWriteU16(dst
, color
->h
);
1602 sbufWriteU8(dst
, color
->s
);
1603 sbufWriteU8(dst
, color
->v
);
1608 #ifdef USE_LED_STRIP
1609 case MSP_LED_STRIP_CONFIG
:
1610 for (int i
= 0; i
< LED_MAX_STRIP_LENGTH
; i
++) {
1611 #ifdef USE_LED_STRIP_STATUS_MODE
1612 const ledConfig_t
*ledConfig
= &ledStripStatusModeConfig()->ledConfigs
[i
];
1613 sbufWriteU32(dst
, *ledConfig
);
1615 sbufWriteU32(dst
, 0);
1619 // API 1.41 - add indicator for advanced profile support and the current profile selection
1620 // 0 = basic ledstrip available
1621 // 1 = advanced ledstrip available
1622 #ifdef USE_LED_STRIP_STATUS_MODE
1623 sbufWriteU8(dst
, 1); // advanced ledstrip available
1625 sbufWriteU8(dst
, 0); // only simple ledstrip available
1627 sbufWriteU8(dst
, ledStripConfig()->ledstrip_profile
);
1631 #ifdef USE_LED_STRIP_STATUS_MODE
1632 case MSP_LED_STRIP_MODECOLOR
:
1633 for (int i
= 0; i
< LED_MODE_COUNT
; i
++) {
1634 for (int j
= 0; j
< LED_DIRECTION_COUNT
; j
++) {
1635 sbufWriteU8(dst
, i
);
1636 sbufWriteU8(dst
, j
);
1637 sbufWriteU8(dst
, ledStripStatusModeConfig()->modeColors
[i
].color
[j
]);
1641 for (int j
= 0; j
< LED_SPECIAL_COLOR_COUNT
; j
++) {
1642 sbufWriteU8(dst
, LED_MODE_COUNT
);
1643 sbufWriteU8(dst
, j
);
1644 sbufWriteU8(dst
, ledStripStatusModeConfig()->specialColors
.color
[j
]);
1647 sbufWriteU8(dst
, LED_AUX_CHANNEL
);
1648 sbufWriteU8(dst
, 0);
1649 sbufWriteU8(dst
, ledStripStatusModeConfig()->ledstrip_aux_channel
);
1653 case MSP_DATAFLASH_SUMMARY
:
1654 serializeDataflashSummaryReply(dst
);
1657 case MSP_BLACKBOX_CONFIG
:
1659 sbufWriteU8(dst
, 1); //Blackbox supported
1660 sbufWriteU8(dst
, blackboxConfig()->device
);
1661 sbufWriteU8(dst
, 1); // Rate numerator, not used anymore
1662 sbufWriteU8(dst
, blackboxGetRateDenom());
1663 sbufWriteU16(dst
, blackboxGetPRatio());
1664 sbufWriteU8(dst
, blackboxConfig()->sample_rate
);
1666 sbufWriteU8(dst
, 0); // Blackbox not supported
1667 sbufWriteU8(dst
, 0);
1668 sbufWriteU8(dst
, 0);
1669 sbufWriteU8(dst
, 0);
1670 sbufWriteU16(dst
, 0);
1671 sbufWriteU8(dst
, 0);
1675 case MSP_SDCARD_SUMMARY
:
1676 serializeSDCardSummaryReply(dst
);
1679 case MSP_MOTOR_3D_CONFIG
:
1680 sbufWriteU16(dst
, flight3DConfig()->deadband3d_low
);
1681 sbufWriteU16(dst
, flight3DConfig()->deadband3d_high
);
1682 sbufWriteU16(dst
, flight3DConfig()->neutral3d
);
1685 case MSP_RC_DEADBAND
:
1686 sbufWriteU8(dst
, rcControlsConfig()->deadband
);
1687 sbufWriteU8(dst
, rcControlsConfig()->yaw_deadband
);
1688 sbufWriteU8(dst
, rcControlsConfig()->alt_hold_deadband
);
1689 sbufWriteU16(dst
, flight3DConfig()->deadband3d_throttle
);
1693 case MSP_SENSOR_ALIGNMENT
: {
1694 uint8_t gyroAlignment
;
1695 #ifdef USE_MULTI_GYRO
1696 switch (gyroConfig()->gyro_to_use
) {
1697 case GYRO_CONFIG_USE_GYRO_2
:
1698 gyroAlignment
= gyroDeviceConfig(1)->alignment
;
1700 case GYRO_CONFIG_USE_GYRO_BOTH
:
1701 // for dual-gyro in "BOTH" mode we only read/write gyro 0
1703 gyroAlignment
= gyroDeviceConfig(0)->alignment
;
1707 gyroAlignment
= gyroDeviceConfig(0)->alignment
;
1709 sbufWriteU8(dst
, gyroAlignment
);
1710 sbufWriteU8(dst
, gyroAlignment
); // Starting with 4.0 gyro and acc alignment are the same
1711 #if defined(USE_MAG)
1712 sbufWriteU8(dst
, compassConfig()->mag_alignment
);
1714 sbufWriteU8(dst
, 0);
1717 // API 1.41 - Add multi-gyro indicator, selected gyro, and support for separate gyro 1 & 2 alignment
1718 sbufWriteU8(dst
, getGyroDetectionFlags());
1719 #ifdef USE_MULTI_GYRO
1720 sbufWriteU8(dst
, gyroConfig()->gyro_to_use
);
1721 sbufWriteU8(dst
, gyroDeviceConfig(0)->alignment
);
1722 sbufWriteU8(dst
, gyroDeviceConfig(1)->alignment
);
1724 sbufWriteU8(dst
, GYRO_CONFIG_USE_GYRO_1
);
1725 sbufWriteU8(dst
, gyroDeviceConfig(0)->alignment
);
1726 sbufWriteU8(dst
, ALIGN_DEFAULT
);
1731 case MSP_ADVANCED_CONFIG
:
1732 sbufWriteU8(dst
, 1); // was gyroConfig()->gyro_sync_denom - removed in API 1.43
1733 sbufWriteU8(dst
, pidConfig()->pid_process_denom
);
1734 sbufWriteU8(dst
, motorConfig()->dev
.useUnsyncedPwm
);
1735 sbufWriteU8(dst
, motorConfig()->dev
.motorPwmProtocol
);
1736 sbufWriteU16(dst
, motorConfig()->dev
.motorPwmRate
);
1737 sbufWriteU16(dst
, motorConfig()->digitalIdleOffsetValue
);
1738 sbufWriteU8(dst
, 0); // DEPRECATED: gyro_use_32kHz
1739 sbufWriteU8(dst
, motorConfig()->dev
.motorPwmInversion
);
1740 sbufWriteU8(dst
, gyroConfig()->gyro_to_use
);
1741 sbufWriteU8(dst
, gyroConfig()->gyro_high_fsr
);
1742 sbufWriteU8(dst
, gyroConfig()->gyroMovementCalibrationThreshold
);
1743 sbufWriteU16(dst
, gyroConfig()->gyroCalibrationDuration
);
1744 sbufWriteU16(dst
, gyroConfig()->gyro_offset_yaw
);
1745 sbufWriteU8(dst
, gyroConfig()->checkOverflow
);
1746 //Added in MSP API 1.42
1747 sbufWriteU8(dst
, systemConfig()->debug_mode
);
1748 sbufWriteU8(dst
, DEBUG_COUNT
);
1751 case MSP_FILTER_CONFIG
:
1752 sbufWriteU8(dst
, gyroConfig()->gyro_lpf1_static_hz
);
1753 sbufWriteU16(dst
, currentPidProfile
->dterm_lpf1_static_hz
);
1754 sbufWriteU16(dst
, currentPidProfile
->yaw_lowpass_hz
);
1755 sbufWriteU16(dst
, gyroConfig()->gyro_soft_notch_hz_1
);
1756 sbufWriteU16(dst
, gyroConfig()->gyro_soft_notch_cutoff_1
);
1757 sbufWriteU16(dst
, currentPidProfile
->dterm_notch_hz
);
1758 sbufWriteU16(dst
, currentPidProfile
->dterm_notch_cutoff
);
1759 sbufWriteU16(dst
, gyroConfig()->gyro_soft_notch_hz_2
);
1760 sbufWriteU16(dst
, gyroConfig()->gyro_soft_notch_cutoff_2
);
1761 sbufWriteU8(dst
, currentPidProfile
->dterm_lpf1_type
);
1762 sbufWriteU8(dst
, gyroConfig()->gyro_hardware_lpf
);
1763 sbufWriteU8(dst
, 0); // DEPRECATED: gyro_32khz_hardware_lpf
1764 sbufWriteU16(dst
, gyroConfig()->gyro_lpf1_static_hz
);
1765 sbufWriteU16(dst
, gyroConfig()->gyro_lpf2_static_hz
);
1766 sbufWriteU8(dst
, gyroConfig()->gyro_lpf1_type
);
1767 sbufWriteU8(dst
, gyroConfig()->gyro_lpf2_type
);
1768 sbufWriteU16(dst
, currentPidProfile
->dterm_lpf2_static_hz
);
1769 // Added in MSP API 1.41
1770 sbufWriteU8(dst
, currentPidProfile
->dterm_lpf2_type
);
1771 #if defined(USE_DYN_LPF)
1772 sbufWriteU16(dst
, gyroConfig()->gyro_lpf1_dyn_min_hz
);
1773 sbufWriteU16(dst
, gyroConfig()->gyro_lpf1_dyn_max_hz
);
1774 sbufWriteU16(dst
, currentPidProfile
->dterm_lpf1_dyn_min_hz
);
1775 sbufWriteU16(dst
, currentPidProfile
->dterm_lpf1_dyn_max_hz
);
1777 sbufWriteU16(dst
, 0);
1778 sbufWriteU16(dst
, 0);
1779 sbufWriteU16(dst
, 0);
1780 sbufWriteU16(dst
, 0);
1782 // Added in MSP API 1.42
1783 #if defined(USE_DYN_NOTCH_FILTER)
1784 sbufWriteU8(dst
, 0); // DEPRECATED 1.43: dyn_notch_range
1785 sbufWriteU8(dst
, 0); // DEPRECATED 1.44: dyn_notch_width_percent
1786 sbufWriteU16(dst
, dynNotchConfig()->dyn_notch_q
);
1787 sbufWriteU16(dst
, dynNotchConfig()->dyn_notch_min_hz
);
1789 sbufWriteU8(dst
, 0);
1790 sbufWriteU8(dst
, 0);
1791 sbufWriteU16(dst
, 0);
1792 sbufWriteU16(dst
, 0);
1794 #if defined(USE_RPM_FILTER)
1795 sbufWriteU8(dst
, rpmFilterConfig()->rpm_filter_harmonics
);
1796 sbufWriteU8(dst
, rpmFilterConfig()->rpm_filter_min_hz
);
1798 sbufWriteU8(dst
, 0);
1799 sbufWriteU8(dst
, 0);
1801 #if defined(USE_DYN_NOTCH_FILTER)
1802 // Added in MSP API 1.43
1803 sbufWriteU16(dst
, dynNotchConfig()->dyn_notch_max_hz
);
1805 sbufWriteU16(dst
, 0);
1807 #if defined(USE_DYN_LPF)
1808 // Added in MSP API 1.44
1809 sbufWriteU8(dst
, currentPidProfile
->dterm_lpf1_dyn_expo
);
1811 sbufWriteU8(dst
, 0);
1813 #if defined(USE_DYN_NOTCH_FILTER)
1814 sbufWriteU8(dst
, dynNotchConfig()->dyn_notch_count
);
1816 sbufWriteU8(dst
, 0);
1820 case MSP_PID_ADVANCED
:
1821 sbufWriteU16(dst
, 0);
1822 sbufWriteU16(dst
, 0);
1823 sbufWriteU16(dst
, 0); // was pidProfile.yaw_p_limit
1824 sbufWriteU8(dst
, 0); // reserved
1825 sbufWriteU8(dst
, 0); // was vbatPidCompensation
1826 #if defined(USE_FEEDFORWARD)
1827 sbufWriteU8(dst
, currentPidProfile
->feedforward_transition
);
1829 sbufWriteU8(dst
, 0);
1831 sbufWriteU8(dst
, 0); // was low byte of currentPidProfile->dtermSetpointWeight
1832 sbufWriteU8(dst
, 0); // reserved
1833 sbufWriteU8(dst
, 0); // reserved
1834 sbufWriteU8(dst
, 0); // reserved
1835 sbufWriteU16(dst
, currentPidProfile
->rateAccelLimit
);
1836 sbufWriteU16(dst
, currentPidProfile
->yawRateAccelLimit
);
1837 sbufWriteU8(dst
, currentPidProfile
->levelAngleLimit
);
1838 sbufWriteU8(dst
, 0); // was pidProfile.levelSensitivity
1839 sbufWriteU16(dst
, currentPidProfile
->itermThrottleThreshold
);
1840 sbufWriteU16(dst
, currentPidProfile
->itermAcceleratorGain
);
1841 sbufWriteU16(dst
, 0); // was currentPidProfile->dtermSetpointWeight
1842 sbufWriteU8(dst
, currentPidProfile
->iterm_rotation
);
1843 sbufWriteU8(dst
, 0); // was currentPidProfile->smart_feedforward
1844 #if defined(USE_ITERM_RELAX)
1845 sbufWriteU8(dst
, currentPidProfile
->iterm_relax
);
1846 sbufWriteU8(dst
, currentPidProfile
->iterm_relax_type
);
1848 sbufWriteU8(dst
, 0);
1849 sbufWriteU8(dst
, 0);
1851 #if defined(USE_ABSOLUTE_CONTROL)
1852 sbufWriteU8(dst
, currentPidProfile
->abs_control_gain
);
1854 sbufWriteU8(dst
, 0);
1856 #if defined(USE_THROTTLE_BOOST)
1857 sbufWriteU8(dst
, currentPidProfile
->throttle_boost
);
1859 sbufWriteU8(dst
, 0);
1861 #if defined(USE_ACRO_TRAINER)
1862 sbufWriteU8(dst
, currentPidProfile
->acro_trainer_angle_limit
);
1864 sbufWriteU8(dst
, 0);
1866 sbufWriteU16(dst
, currentPidProfile
->pid
[PID_ROLL
].F
);
1867 sbufWriteU16(dst
, currentPidProfile
->pid
[PID_PITCH
].F
);
1868 sbufWriteU16(dst
, currentPidProfile
->pid
[PID_YAW
].F
);
1870 sbufWriteU8(dst
, currentPidProfile
->antiGravityMode
);
1871 #if defined(USE_D_MIN)
1872 sbufWriteU8(dst
, currentPidProfile
->d_min
[PID_ROLL
]);
1873 sbufWriteU8(dst
, currentPidProfile
->d_min
[PID_PITCH
]);
1874 sbufWriteU8(dst
, currentPidProfile
->d_min
[PID_YAW
]);
1875 sbufWriteU8(dst
, currentPidProfile
->d_min_gain
);
1876 sbufWriteU8(dst
, currentPidProfile
->d_min_advance
);
1878 sbufWriteU8(dst
, 0);
1879 sbufWriteU8(dst
, 0);
1880 sbufWriteU8(dst
, 0);
1881 sbufWriteU8(dst
, 0);
1882 sbufWriteU8(dst
, 0);
1884 #if defined(USE_INTEGRATED_YAW_CONTROL)
1885 sbufWriteU8(dst
, currentPidProfile
->use_integrated_yaw
);
1886 sbufWriteU8(dst
, currentPidProfile
->integrated_yaw_relax
);
1888 sbufWriteU8(dst
, 0);
1889 sbufWriteU8(dst
, 0);
1891 #if defined(USE_ITERM_RELAX)
1892 // Added in MSP API 1.42
1893 sbufWriteU8(dst
, currentPidProfile
->iterm_relax_cutoff
);
1895 sbufWriteU8(dst
, 0);
1897 // Added in MSP API 1.43
1898 sbufWriteU8(dst
, currentPidProfile
->motor_output_limit
);
1899 sbufWriteU8(dst
, currentPidProfile
->auto_profile_cell_count
);
1900 #if defined(USE_DYN_IDLE)
1901 sbufWriteU8(dst
, currentPidProfile
->dyn_idle_min_rpm
);
1903 sbufWriteU8(dst
, 0);
1905 // Added in MSP API 1.44
1906 #if defined(USE_FEEDFORWARD)
1907 sbufWriteU8(dst
, currentPidProfile
->feedforward_averaging
);
1908 sbufWriteU8(dst
, currentPidProfile
->feedforward_smooth_factor
);
1909 sbufWriteU8(dst
, currentPidProfile
->feedforward_boost
);
1910 sbufWriteU8(dst
, currentPidProfile
->feedforward_max_rate_limit
);
1911 sbufWriteU8(dst
, currentPidProfile
->feedforward_jitter_factor
);
1913 sbufWriteU8(dst
, 0);
1914 sbufWriteU8(dst
, 0);
1915 sbufWriteU8(dst
, 0);
1916 sbufWriteU8(dst
, 0);
1917 sbufWriteU8(dst
, 0);
1919 #if defined(USE_BATTERY_VOLTAGE_SAG_COMPENSATION)
1920 sbufWriteU8(dst
, currentPidProfile
->vbat_sag_compensation
);
1922 sbufWriteU8(dst
, 0);
1924 #if defined(USE_THRUST_LINEARIZATION)
1925 sbufWriteU8(dst
, currentPidProfile
->thrustLinearization
);
1927 sbufWriteU8(dst
, 0);
1930 case MSP_SENSOR_CONFIG
:
1931 #if defined(USE_ACC)
1932 sbufWriteU8(dst
, accelerometerConfig()->acc_hardware
);
1934 sbufWriteU8(dst
, 0);
1937 sbufWriteU8(dst
, barometerConfig()->baro_hardware
);
1939 sbufWriteU8(dst
, BARO_NONE
);
1942 sbufWriteU8(dst
, compassConfig()->mag_hardware
);
1944 sbufWriteU8(dst
, MAG_NONE
);
1948 #if defined(USE_VTX_COMMON)
1949 case MSP_VTX_CONFIG
:
1951 const vtxDevice_t
*vtxDevice
= vtxCommonDevice();
1952 unsigned vtxStatus
= 0;
1953 vtxDevType_e vtxType
= VTXDEV_UNKNOWN
;
1954 uint8_t deviceIsReady
= 0;
1956 vtxCommonGetStatus(vtxDevice
, &vtxStatus
);
1957 vtxType
= vtxCommonGetDeviceType(vtxDevice
);
1958 deviceIsReady
= vtxCommonDeviceIsReady(vtxDevice
) ? 1 : 0;
1960 sbufWriteU8(dst
, vtxType
);
1961 sbufWriteU8(dst
, vtxSettingsConfig()->band
);
1962 sbufWriteU8(dst
, vtxSettingsConfig()->channel
);
1963 sbufWriteU8(dst
, vtxSettingsConfig()->power
);
1964 sbufWriteU8(dst
, (vtxStatus
& VTX_STATUS_PIT_MODE
) ? 1 : 0);
1965 sbufWriteU16(dst
, vtxSettingsConfig()->freq
);
1966 sbufWriteU8(dst
, deviceIsReady
);
1967 sbufWriteU8(dst
, vtxSettingsConfig()->lowPowerDisarm
);
1970 sbufWriteU16(dst
, vtxSettingsConfig()->pitModeFreq
);
1971 #ifdef USE_VTX_TABLE
1972 sbufWriteU8(dst
, 1); // vtxtable is available
1973 sbufWriteU8(dst
, vtxTableConfig()->bands
);
1974 sbufWriteU8(dst
, vtxTableConfig()->channels
);
1975 sbufWriteU8(dst
, vtxTableConfig()->powerLevels
);
1977 sbufWriteU8(dst
, 0);
1978 sbufWriteU8(dst
, 0);
1979 sbufWriteU8(dst
, 0);
1980 sbufWriteU8(dst
, 0);
1988 sbufWriteU8(dst
, rssiSource
);
1989 uint8_t rtcDateTimeIsSet
= 0;
1992 if (rtcGetDateTime(&dt
)) {
1993 rtcDateTimeIsSet
= 1;
1996 rtcDateTimeIsSet
= RTC_NOT_SUPPORTED
;
1998 sbufWriteU8(dst
, rtcDateTimeIsSet
);
2005 if (rtcGetDateTime(&dt
)) {
2006 sbufWriteU16(dst
, dt
.year
);
2007 sbufWriteU8(dst
, dt
.month
);
2008 sbufWriteU8(dst
, dt
.day
);
2009 sbufWriteU8(dst
, dt
.hours
);
2010 sbufWriteU8(dst
, dt
.minutes
);
2011 sbufWriteU8(dst
, dt
.seconds
);
2012 sbufWriteU16(dst
, dt
.millis
);
2019 unsupportedCommand
= true;
2021 return !unsupportedCommand
;
2025 #ifdef USE_SIMPLIFIED_TUNING
2026 // Reads simplified PID tuning values from MSP buffer
2027 static void readSimplifiedPids(pidProfile_t
* pidProfile
, sbuf_t
*src
)
2029 pidProfile
->simplified_pids_mode
= sbufReadU8(src
);
2030 pidProfile
->simplified_master_multiplier
= sbufReadU8(src
);
2031 pidProfile
->simplified_roll_pitch_ratio
= sbufReadU8(src
);
2032 pidProfile
->simplified_i_gain
= sbufReadU8(src
);
2033 pidProfile
->simplified_d_gain
= sbufReadU8(src
);
2034 pidProfile
->simplified_pi_gain
= sbufReadU8(src
);
2036 pidProfile
->simplified_dmin_ratio
= sbufReadU8(src
);
2040 pidProfile
->simplified_feedforward_gain
= sbufReadU8(src
);
2041 pidProfile
->simplified_pitch_pi_gain
= sbufReadU8(src
);
2042 sbufReadU32(src
); // reserved for future use
2043 sbufReadU32(src
); // reserved for future use
2046 // Writes simplified PID tuning values to MSP buffer
2047 static void writeSimplifiedPids(const pidProfile_t
*pidProfile
, sbuf_t
*dst
)
2049 sbufWriteU8(dst
, pidProfile
->simplified_pids_mode
);
2050 sbufWriteU8(dst
, pidProfile
->simplified_master_multiplier
);
2051 sbufWriteU8(dst
, pidProfile
->simplified_roll_pitch_ratio
);
2052 sbufWriteU8(dst
, pidProfile
->simplified_i_gain
);
2053 sbufWriteU8(dst
, pidProfile
->simplified_d_gain
);
2054 sbufWriteU8(dst
, pidProfile
->simplified_pi_gain
);
2056 sbufWriteU8(dst
, pidProfile
->simplified_dmin_ratio
);
2058 sbufWriteU8(dst
, 0);
2060 sbufWriteU8(dst
, pidProfile
->simplified_feedforward_gain
);
2061 sbufWriteU8(dst
, pidProfile
->simplified_pitch_pi_gain
);
2062 sbufWriteU32(dst
, 0); // reserved for future use
2063 sbufWriteU32(dst
, 0); // reserved for future use
2066 // Reads simplified Dterm Filter values from MSP buffer
2067 static void readSimplifiedDtermFilters(pidProfile_t
* pidProfile
, sbuf_t
*src
)
2069 pidProfile
->simplified_dterm_filter
= sbufReadU8(src
);
2070 pidProfile
->simplified_dterm_filter_multiplier
= sbufReadU8(src
);
2071 pidProfile
->dterm_lpf1_static_hz
= sbufReadU16(src
);
2072 pidProfile
->dterm_lpf2_static_hz
= sbufReadU16(src
);
2073 #if defined(USE_DYN_LPF)
2074 pidProfile
->dterm_lpf1_dyn_min_hz
= sbufReadU16(src
);
2075 pidProfile
->dterm_lpf1_dyn_max_hz
= sbufReadU16(src
);
2080 sbufReadU32(src
); // reserved for future use
2081 sbufReadU32(src
); // reserved for future use
2084 // Writes simplified Dterm Filter values into MSP buffer
2085 static void writeSimplifiedDtermFilters(const pidProfile_t
* pidProfile
, sbuf_t
*dst
)
2087 sbufWriteU8(dst
, pidProfile
->simplified_dterm_filter
);
2088 sbufWriteU8(dst
, pidProfile
->simplified_dterm_filter_multiplier
);
2089 sbufWriteU16(dst
, pidProfile
->dterm_lpf1_static_hz
);
2090 sbufWriteU16(dst
, pidProfile
->dterm_lpf2_static_hz
);
2091 #if defined(USE_DYN_LPF)
2092 sbufWriteU16(dst
, pidProfile
->dterm_lpf1_dyn_min_hz
);
2093 sbufWriteU16(dst
, pidProfile
->dterm_lpf1_dyn_max_hz
);
2095 sbufWriteU16(dst
, 0);
2096 sbufWriteU16(dst
, 0);
2098 sbufWriteU32(dst
, 0); // reserved for future use
2099 sbufWriteU32(dst
, 0); // reserved for future use
2102 // Writes simplified Gyro Filter values from MSP buffer
2103 static void readSimplifiedGyroFilters(gyroConfig_t
*gyroConfig
, sbuf_t
*src
)
2105 gyroConfig
->simplified_gyro_filter
= sbufReadU8(src
);
2106 gyroConfig
->simplified_gyro_filter_multiplier
= sbufReadU8(src
);
2107 gyroConfig
->gyro_lpf1_static_hz
= sbufReadU16(src
);
2108 gyroConfig
->gyro_lpf2_static_hz
= sbufReadU16(src
);
2109 #if defined(USE_DYN_LPF)
2110 gyroConfig
->gyro_lpf1_dyn_min_hz
= sbufReadU16(src
);
2111 gyroConfig
->gyro_lpf1_dyn_max_hz
= sbufReadU16(src
);
2116 sbufReadU32(src
); // reserved for future use
2117 sbufReadU32(src
); // reserved for future use
2120 // Writes simplified Gyro Filter values into MSP buffer
2121 static void writeSimplifiedGyroFilters(const gyroConfig_t
*gyroConfig
, sbuf_t
*dst
)
2123 sbufWriteU8(dst
, gyroConfig
->simplified_gyro_filter
);
2124 sbufWriteU8(dst
, gyroConfig
->simplified_gyro_filter_multiplier
);
2125 sbufWriteU16(dst
, gyroConfig
->gyro_lpf1_static_hz
);
2126 sbufWriteU16(dst
, gyroConfig
->gyro_lpf2_static_hz
);
2127 #if defined(USE_DYN_LPF)
2128 sbufWriteU16(dst
, gyroConfig
->gyro_lpf1_dyn_min_hz
);
2129 sbufWriteU16(dst
, gyroConfig
->gyro_lpf1_dyn_max_hz
);
2131 sbufWriteU16(dst
, 0);
2132 sbufWriteU16(dst
, 0);
2134 sbufWriteU32(dst
, 0); // reserved for future use
2135 sbufWriteU32(dst
, 0); // reserved for future use
2138 // writes results of simplified PID tuning values to MSP buffer
2139 static void writePidfs(pidProfile_t
* pidProfile
, sbuf_t
*dst
)
2141 for (int i
= 0; i
< XYZ_AXIS_COUNT
; i
++) {
2142 sbufWriteU8(dst
, pidProfile
->pid
[i
].P
);
2143 sbufWriteU8(dst
, pidProfile
->pid
[i
].I
);
2144 sbufWriteU8(dst
, pidProfile
->pid
[i
].D
);
2145 sbufWriteU8(dst
, pidProfile
->d_min
[i
]);
2146 sbufWriteU16(dst
, pidProfile
->pid
[i
].F
);
2149 #endif // USE_SIMPLIFIED_TUNING
2151 static mspResult_e
mspFcProcessOutCommandWithArg(mspDescriptor_t srcDesc
, int16_t cmdMSP
, sbuf_t
*src
, sbuf_t
*dst
, mspPostProcessFnPtr
*mspPostProcessFn
)
2157 const int page
= sbufBytesRemaining(src
) ? sbufReadU8(src
) : 0;
2158 serializeBoxReply(dst
, page
, &serializeBoxNameFn
);
2163 const int page
= sbufBytesRemaining(src
) ? sbufReadU8(src
) : 0;
2164 serializeBoxReply(dst
, page
, &serializeBoxPermanentIdFn
);
2168 if (sbufBytesRemaining(src
)) {
2169 rebootMode
= sbufReadU8(src
);
2171 if (rebootMode
>= MSP_REBOOT_COUNT
2172 #if !defined(USE_USB_MSC)
2173 || rebootMode
== MSP_REBOOT_MSC
|| rebootMode
== MSP_REBOOT_MSC_UTC
2176 return MSP_RESULT_ERROR
;
2179 rebootMode
= MSP_REBOOT_FIRMWARE
;
2182 sbufWriteU8(dst
, rebootMode
);
2184 #if defined(USE_USB_MSC)
2185 if (rebootMode
== MSP_REBOOT_MSC
) {
2186 if (mscCheckFilesystemReady()) {
2187 sbufWriteU8(dst
, 1);
2189 sbufWriteU8(dst
, 0);
2191 return MSP_RESULT_ACK
;
2196 if (mspPostProcessFn
) {
2197 *mspPostProcessFn
= mspRebootFn
;
2201 case MSP_MULTIPLE_MSP
:
2203 uint8_t maxMSPs
= 0;
2204 if (sbufBytesRemaining(src
) == 0) {
2205 return MSP_RESULT_ERROR
;
2207 int bytesRemaining
= sbufBytesRemaining(dst
) - 1; // need to keep one byte for checksum
2208 mspPacket_t packetIn
, packetOut
;
2209 sbufInit(&packetIn
.buf
, src
->end
, src
->end
);
2210 uint8_t* resetInputPtr
= src
->ptr
;
2211 while (sbufBytesRemaining(src
) && bytesRemaining
> 0) {
2212 uint8_t newMSP
= sbufReadU8(src
);
2213 sbufInit(&packetOut
.buf
, dst
->ptr
, dst
->end
);
2214 packetIn
.cmd
= newMSP
;
2215 mspFcProcessCommand(srcDesc
, &packetIn
, &packetOut
, NULL
);
2216 uint8_t mspSize
= sbufPtr(&packetOut
.buf
) - dst
->ptr
;
2217 mspSize
++; // need to add length information for each MSP
2218 bytesRemaining
-= mspSize
;
2219 if (bytesRemaining
>= 0) {
2223 src
->ptr
= resetInputPtr
;
2224 sbufInit(&packetOut
.buf
, dst
->ptr
, dst
->end
);
2225 for (int i
= 0; i
< maxMSPs
; i
++) {
2226 uint8_t* sizePtr
= sbufPtr(&packetOut
.buf
);
2227 sbufWriteU8(&packetOut
.buf
, 0); // dummy
2228 packetIn
.cmd
= sbufReadU8(src
);
2229 mspFcProcessCommand(srcDesc
, &packetIn
, &packetOut
, NULL
);
2230 (*sizePtr
) = sbufPtr(&packetOut
.buf
) - (sizePtr
+ 1);
2232 dst
->ptr
= packetOut
.buf
.ptr
;
2236 #ifdef USE_VTX_TABLE
2237 case MSP_VTXTABLE_BAND
:
2239 const uint8_t band
= sbufBytesRemaining(src
) ? sbufReadU8(src
) : 0;
2240 if (band
> 0 && band
<= VTX_TABLE_MAX_BANDS
) {
2241 sbufWriteU8(dst
, band
); // band number (same as request)
2242 sbufWriteU8(dst
, VTX_TABLE_BAND_NAME_LENGTH
); // band name length
2243 for (int i
= 0; i
< VTX_TABLE_BAND_NAME_LENGTH
; i
++) { // band name bytes
2244 sbufWriteU8(dst
, vtxTableConfig()->bandNames
[band
- 1][i
]);
2246 sbufWriteU8(dst
, vtxTableConfig()->bandLetters
[band
- 1]); // band letter
2247 sbufWriteU8(dst
, vtxTableConfig()->isFactoryBand
[band
- 1]); // CUSTOM = 0; FACTORY = 1
2248 sbufWriteU8(dst
, vtxTableConfig()->channels
); // number of channel frequencies to follow
2249 for (int i
= 0; i
< vtxTableConfig()->channels
; i
++) { // the frequency for each channel
2250 sbufWriteU16(dst
, vtxTableConfig()->frequency
[band
- 1][i
]);
2253 return MSP_RESULT_ERROR
;
2258 case MSP_VTXTABLE_POWERLEVEL
:
2260 const uint8_t powerLevel
= sbufBytesRemaining(src
) ? sbufReadU8(src
) : 0;
2261 if (powerLevel
> 0 && powerLevel
<= VTX_TABLE_MAX_POWER_LEVELS
) {
2262 sbufWriteU8(dst
, powerLevel
); // powerLevel number (same as request)
2263 sbufWriteU16(dst
, vtxTableConfig()->powerValues
[powerLevel
- 1]);
2264 sbufWriteU8(dst
, VTX_TABLE_POWER_LABEL_LENGTH
); // powerLevel label length
2265 for (int i
= 0; i
< VTX_TABLE_POWER_LABEL_LENGTH
; i
++) { // powerlevel label bytes
2266 sbufWriteU8(dst
, vtxTableConfig()->powerLabels
[powerLevel
- 1][i
]);
2269 return MSP_RESULT_ERROR
;
2273 #endif // USE_VTX_TABLE
2275 #ifdef USE_SIMPLIFIED_TUNING
2276 // Added in MSP API 1.44
2277 case MSP_SIMPLIFIED_TUNING
:
2279 writeSimplifiedPids(currentPidProfile
, dst
);
2280 writeSimplifiedDtermFilters(currentPidProfile
, dst
);
2281 writeSimplifiedGyroFilters(gyroConfig(), dst
);
2285 case MSP_CALCULATE_SIMPLIFIED_PID
:
2287 pidProfile_t tempPidProfile
= *currentPidProfile
;
2288 readSimplifiedPids(&tempPidProfile
, src
);
2289 applySimplifiedTuningPids(&tempPidProfile
);
2290 writePidfs(&tempPidProfile
, dst
);
2294 case MSP_CALCULATE_SIMPLIFIED_DTERM
:
2296 pidProfile_t tempPidProfile
= *currentPidProfile
;
2297 readSimplifiedDtermFilters(&tempPidProfile
, src
);
2298 applySimplifiedTuningDtermFilters(&tempPidProfile
);
2299 writeSimplifiedDtermFilters(&tempPidProfile
, dst
);
2303 case MSP_CALCULATE_SIMPLIFIED_GYRO
:
2305 gyroConfig_t tempGyroConfig
= *gyroConfig();
2306 readSimplifiedGyroFilters(&tempGyroConfig
, src
);
2307 applySimplifiedTuningGyroFilters(&tempGyroConfig
);
2308 writeSimplifiedGyroFilters(&tempGyroConfig
, dst
);
2312 case MSP_VALIDATE_SIMPLIFIED_TUNING
:
2314 pidProfile_t tempPidProfile
= *currentPidProfile
;
2315 applySimplifiedTuningPids(&tempPidProfile
);
2318 for (int i
= 0; i
< XYZ_AXIS_COUNT
; i
++) {
2320 tempPidProfile
.pid
[i
].P
== currentPidProfile
->pid
[i
].P
&&
2321 tempPidProfile
.pid
[i
].I
== currentPidProfile
->pid
[i
].I
&&
2322 tempPidProfile
.pid
[i
].D
== currentPidProfile
->pid
[i
].D
&&
2323 tempPidProfile
.d_min
[i
] == currentPidProfile
->d_min
[i
] &&
2324 tempPidProfile
.pid
[i
].F
== currentPidProfile
->pid
[i
].F
;
2327 sbufWriteU8(dst
, result
);
2329 gyroConfig_t tempGyroConfig
= *gyroConfig();
2330 applySimplifiedTuningGyroFilters(&tempGyroConfig
);
2332 tempGyroConfig
.gyro_lpf1_static_hz
== gyroConfig()->gyro_lpf1_static_hz
&&
2333 tempGyroConfig
.gyro_lpf2_static_hz
== gyroConfig()->gyro_lpf2_static_hz
;
2335 #if defined(USE_DYN_LPF)
2337 tempGyroConfig
.gyro_lpf1_dyn_min_hz
== gyroConfig()->gyro_lpf1_dyn_min_hz
&&
2338 tempGyroConfig
.gyro_lpf1_dyn_max_hz
== gyroConfig()->gyro_lpf1_dyn_max_hz
;
2341 sbufWriteU8(dst
, result
);
2343 applySimplifiedTuningDtermFilters(&tempPidProfile
);
2345 tempPidProfile
.dterm_lpf1_static_hz
== currentPidProfile
->dterm_lpf1_static_hz
&&
2346 tempPidProfile
.dterm_lpf2_static_hz
== currentPidProfile
->dterm_lpf2_static_hz
;
2348 #if defined(USE_DYN_LPF)
2350 tempPidProfile
.dterm_lpf1_dyn_min_hz
== currentPidProfile
->dterm_lpf1_dyn_min_hz
&&
2351 tempPidProfile
.dterm_lpf1_dyn_max_hz
== currentPidProfile
->dterm_lpf1_dyn_max_hz
;
2354 sbufWriteU8(dst
, result
);
2359 case MSP_RESET_CONF
:
2361 #if defined(USE_CUSTOM_DEFAULTS)
2362 defaultsType_e defaultsType
= DEFAULTS_TYPE_CUSTOM
;
2364 if (sbufBytesRemaining(src
) >= 1) {
2365 // Added in MSP API 1.42
2366 #if defined(USE_CUSTOM_DEFAULTS)
2367 defaultsType
= sbufReadU8(src
);
2373 bool success
= false;
2374 if (!ARMING_FLAG(ARMED
)) {
2375 #if defined(USE_CUSTOM_DEFAULTS)
2376 success
= resetEEPROM(defaultsType
== DEFAULTS_TYPE_CUSTOM
);
2378 success
= resetEEPROM(false);
2381 if (success
&& mspPostProcessFn
) {
2382 rebootMode
= MSP_REBOOT_FIRMWARE
;
2383 *mspPostProcessFn
= mspRebootFn
;
2387 // Added in API version 1.42
2388 sbufWriteU8(dst
, success
);
2393 return MSP_RESULT_CMD_UNKNOWN
;
2395 return MSP_RESULT_ACK
;
2399 static void mspFcDataFlashReadCommand(sbuf_t
*dst
, sbuf_t
*src
)
2401 const unsigned int dataSize
= sbufBytesRemaining(src
);
2402 const uint32_t readAddress
= sbufReadU32(src
);
2403 uint16_t readLength
;
2404 bool allowCompression
= false;
2405 bool useLegacyFormat
;
2406 if (dataSize
>= sizeof(uint32_t) + sizeof(uint16_t)) {
2407 readLength
= sbufReadU16(src
);
2408 if (sbufBytesRemaining(src
)) {
2409 allowCompression
= sbufReadU8(src
);
2411 useLegacyFormat
= false;
2414 useLegacyFormat
= true;
2417 serializeDataflashReadReply(dst
, readAddress
, readLength
, useLegacyFormat
, allowCompression
);
2421 static mspResult_e
mspProcessInCommand(mspDescriptor_t srcDesc
, int16_t cmdMSP
, sbuf_t
*src
)
2425 const unsigned int dataSize
= sbufBytesRemaining(src
);
2427 case MSP_SELECT_SETTING
:
2428 value
= sbufReadU8(src
);
2429 if ((value
& RATEPROFILE_MASK
) == 0) {
2430 if (!ARMING_FLAG(ARMED
)) {
2431 if (value
>= PID_PROFILE_COUNT
) {
2434 changePidProfile(value
);
2437 value
= value
& ~RATEPROFILE_MASK
;
2439 if (value
>= CONTROL_RATE_PROFILE_COUNT
) {
2442 changeControlRateProfile(value
);
2446 case MSP_COPY_PROFILE
:
2447 value
= sbufReadU8(src
); // 0 = pid profile, 1 = control rate profile
2448 uint8_t dstProfileIndex
= sbufReadU8(src
);
2449 uint8_t srcProfileIndex
= sbufReadU8(src
);
2451 pidCopyProfile(dstProfileIndex
, srcProfileIndex
);
2453 else if (value
== 1) {
2454 copyControlRateProfile(dstProfileIndex
, srcProfileIndex
);
2458 #if defined(USE_GPS) || defined(USE_MAG)
2459 case MSP_SET_HEADING
:
2460 magHold
= sbufReadU16(src
);
2464 case MSP_SET_RAW_RC
:
2467 uint8_t channelCount
= dataSize
/ sizeof(uint16_t);
2468 if (channelCount
> MAX_SUPPORTED_RC_CHANNEL_COUNT
) {
2469 return MSP_RESULT_ERROR
;
2471 uint16_t frame
[MAX_SUPPORTED_RC_CHANNEL_COUNT
];
2472 for (int i
= 0; i
< channelCount
; i
++) {
2473 frame
[i
] = sbufReadU16(src
);
2475 rxMspFrameReceive(frame
, channelCount
);
2480 #if defined(USE_ACC)
2481 case MSP_SET_ACC_TRIM
:
2482 accelerometerConfigMutable()->accelerometerTrims
.values
.pitch
= sbufReadU16(src
);
2483 accelerometerConfigMutable()->accelerometerTrims
.values
.roll
= sbufReadU16(src
);
2487 case MSP_SET_ARMING_CONFIG
:
2488 armingConfigMutable()->auto_disarm_delay
= sbufReadU8(src
);
2489 sbufReadU8(src
); // reserved
2490 if (sbufBytesRemaining(src
)) {
2491 imuConfigMutable()->small_angle
= sbufReadU8(src
);
2495 case MSP_SET_PID_CONTROLLER
:
2499 for (int i
= 0; i
< PID_ITEM_COUNT
; i
++) {
2500 currentPidProfile
->pid
[i
].P
= sbufReadU8(src
);
2501 currentPidProfile
->pid
[i
].I
= sbufReadU8(src
);
2502 currentPidProfile
->pid
[i
].D
= sbufReadU8(src
);
2504 pidInitConfig(currentPidProfile
);
2507 case MSP_SET_MODE_RANGE
:
2508 i
= sbufReadU8(src
);
2509 if (i
< MAX_MODE_ACTIVATION_CONDITION_COUNT
) {
2510 modeActivationCondition_t
*mac
= modeActivationConditionsMutable(i
);
2511 i
= sbufReadU8(src
);
2512 const box_t
*box
= findBoxByPermanentId(i
);
2514 mac
->modeId
= box
->boxId
;
2515 mac
->auxChannelIndex
= sbufReadU8(src
);
2516 mac
->range
.startStep
= sbufReadU8(src
);
2517 mac
->range
.endStep
= sbufReadU8(src
);
2518 if (sbufBytesRemaining(src
) != 0) {
2519 mac
->modeLogic
= sbufReadU8(src
);
2521 i
= sbufReadU8(src
);
2522 mac
->linkedTo
= findBoxByPermanentId(i
)->boxId
;
2526 return MSP_RESULT_ERROR
;
2529 return MSP_RESULT_ERROR
;
2533 case MSP_SET_ADJUSTMENT_RANGE
:
2534 i
= sbufReadU8(src
);
2535 if (i
< MAX_ADJUSTMENT_RANGE_COUNT
) {
2536 adjustmentRange_t
*adjRange
= adjustmentRangesMutable(i
);
2537 sbufReadU8(src
); // was adjRange->adjustmentIndex
2538 adjRange
->auxChannelIndex
= sbufReadU8(src
);
2539 adjRange
->range
.startStep
= sbufReadU8(src
);
2540 adjRange
->range
.endStep
= sbufReadU8(src
);
2541 adjRange
->adjustmentConfig
= sbufReadU8(src
);
2542 adjRange
->auxSwitchChannelIndex
= sbufReadU8(src
);
2544 activeAdjustmentRangeReset();
2546 return MSP_RESULT_ERROR
;
2550 case MSP_SET_RC_TUNING
:
2551 if (sbufBytesRemaining(src
) >= 10) {
2552 value
= sbufReadU8(src
);
2553 if (currentControlRateProfile
->rcRates
[FD_PITCH
] == currentControlRateProfile
->rcRates
[FD_ROLL
]) {
2554 currentControlRateProfile
->rcRates
[FD_PITCH
] = value
;
2556 currentControlRateProfile
->rcRates
[FD_ROLL
] = value
;
2558 value
= sbufReadU8(src
);
2559 if (currentControlRateProfile
->rcExpo
[FD_PITCH
] == currentControlRateProfile
->rcExpo
[FD_ROLL
]) {
2560 currentControlRateProfile
->rcExpo
[FD_PITCH
] = value
;
2562 currentControlRateProfile
->rcExpo
[FD_ROLL
] = value
;
2564 for (int i
= 0; i
< 3; i
++) {
2565 currentControlRateProfile
->rates
[i
] = sbufReadU8(src
);
2568 value
= sbufReadU8(src
);
2569 currentControlRateProfile
->tpa_rate
= MIN(value
, CONTROL_RATE_CONFIG_TPA_MAX
);
2570 currentControlRateProfile
->thrMid8
= sbufReadU8(src
);
2571 currentControlRateProfile
->thrExpo8
= sbufReadU8(src
);
2572 currentControlRateProfile
->tpa_breakpoint
= sbufReadU16(src
);
2574 if (sbufBytesRemaining(src
) >= 1) {
2575 currentControlRateProfile
->rcExpo
[FD_YAW
] = sbufReadU8(src
);
2578 if (sbufBytesRemaining(src
) >= 1) {
2579 currentControlRateProfile
->rcRates
[FD_YAW
] = sbufReadU8(src
);
2582 if (sbufBytesRemaining(src
) >= 1) {
2583 currentControlRateProfile
->rcRates
[FD_PITCH
] = sbufReadU8(src
);
2586 if (sbufBytesRemaining(src
) >= 1) {
2587 currentControlRateProfile
->rcExpo
[FD_PITCH
] = sbufReadU8(src
);
2591 if (sbufBytesRemaining(src
) >= 2) {
2592 currentControlRateProfile
->throttle_limit_type
= sbufReadU8(src
);
2593 currentControlRateProfile
->throttle_limit_percent
= sbufReadU8(src
);
2597 if (sbufBytesRemaining(src
) >= 6) {
2598 currentControlRateProfile
->rate_limit
[FD_ROLL
] = sbufReadU16(src
);
2599 currentControlRateProfile
->rate_limit
[FD_PITCH
] = sbufReadU16(src
);
2600 currentControlRateProfile
->rate_limit
[FD_YAW
] = sbufReadU16(src
);
2604 if (sbufBytesRemaining(src
) >= 1) {
2605 currentControlRateProfile
->rates_type
= sbufReadU8(src
);
2610 return MSP_RESULT_ERROR
;
2614 case MSP_SET_MOTOR_CONFIG
:
2615 motorConfigMutable()->minthrottle
= sbufReadU16(src
);
2616 motorConfigMutable()->maxthrottle
= sbufReadU16(src
);
2617 motorConfigMutable()->mincommand
= sbufReadU16(src
);
2620 if (sbufBytesRemaining(src
) >= 2) {
2621 motorConfigMutable()->motorPoleCount
= sbufReadU8(src
);
2622 #if defined(USE_DSHOT_TELEMETRY)
2623 motorConfigMutable()->dev
.useDshotTelemetry
= sbufReadU8(src
);
2631 case MSP_SET_GPS_CONFIG
:
2632 gpsConfigMutable()->provider
= sbufReadU8(src
);
2633 gpsConfigMutable()->sbasMode
= sbufReadU8(src
);
2634 gpsConfigMutable()->autoConfig
= sbufReadU8(src
);
2635 gpsConfigMutable()->autoBaud
= sbufReadU8(src
);
2636 if (sbufBytesRemaining(src
) >= 2) {
2637 // Added in API version 1.43
2638 gpsConfigMutable()->gps_set_home_point_once
= sbufReadU8(src
);
2639 gpsConfigMutable()->gps_ublox_use_galileo
= sbufReadU8(src
);
2643 #ifdef USE_GPS_RESCUE
2644 case MSP_SET_GPS_RESCUE
:
2645 gpsRescueConfigMutable()->angle
= sbufReadU16(src
);
2646 gpsRescueConfigMutable()->initialAltitudeM
= sbufReadU16(src
);
2647 gpsRescueConfigMutable()->descentDistanceM
= sbufReadU16(src
);
2648 gpsRescueConfigMutable()->rescueGroundspeed
= sbufReadU16(src
);
2649 gpsRescueConfigMutable()->throttleMin
= sbufReadU16(src
);
2650 gpsRescueConfigMutable()->throttleMax
= sbufReadU16(src
);
2651 gpsRescueConfigMutable()->throttleHover
= sbufReadU16(src
);
2652 gpsRescueConfigMutable()->sanityChecks
= sbufReadU8(src
);
2653 gpsRescueConfigMutable()->minSats
= sbufReadU8(src
);
2654 if (sbufBytesRemaining(src
) >= 6) {
2655 // Added in API version 1.43
2656 gpsRescueConfigMutable()->ascendRate
= sbufReadU16(src
);
2657 gpsRescueConfigMutable()->descendRate
= sbufReadU16(src
);
2658 gpsRescueConfigMutable()->allowArmingWithoutFix
= sbufReadU8(src
);
2659 gpsRescueConfigMutable()->altitudeMode
= sbufReadU8(src
);
2661 if (sbufBytesRemaining(src
) >= 2) {
2662 // Added in API version 1.44
2663 gpsRescueConfigMutable()->minRescueDth
= sbufReadU16(src
);
2667 case MSP_SET_GPS_RESCUE_PIDS
:
2668 gpsRescueConfigMutable()->throttleP
= sbufReadU16(src
);
2669 gpsRescueConfigMutable()->throttleI
= sbufReadU16(src
);
2670 gpsRescueConfigMutable()->throttleD
= sbufReadU16(src
);
2671 gpsRescueConfigMutable()->velP
= sbufReadU16(src
);
2672 gpsRescueConfigMutable()->velI
= sbufReadU16(src
);
2673 gpsRescueConfigMutable()->velD
= sbufReadU16(src
);
2674 gpsRescueConfigMutable()->yawP
= sbufReadU16(src
);
2680 for (int i
= 0; i
< getMotorCount(); i
++) {
2681 motor_disarmed
[i
] = motorConvertFromExternal(sbufReadU16(src
));
2685 case MSP_SET_SERVO_CONFIGURATION
:
2687 if (dataSize
!= 1 + 12) {
2688 return MSP_RESULT_ERROR
;
2690 i
= sbufReadU8(src
);
2691 if (i
>= MAX_SUPPORTED_SERVOS
) {
2692 return MSP_RESULT_ERROR
;
2694 servoParamsMutable(i
)->min
= sbufReadU16(src
);
2695 servoParamsMutable(i
)->max
= sbufReadU16(src
);
2696 servoParamsMutable(i
)->middle
= sbufReadU16(src
);
2697 servoParamsMutable(i
)->rate
= sbufReadU8(src
);
2698 servoParamsMutable(i
)->forwardFromChannel
= sbufReadU8(src
);
2699 servoParamsMutable(i
)->reversedSources
= sbufReadU32(src
);
2704 case MSP_SET_SERVO_MIX_RULE
:
2706 i
= sbufReadU8(src
);
2707 if (i
>= MAX_SERVO_RULES
) {
2708 return MSP_RESULT_ERROR
;
2710 customServoMixersMutable(i
)->targetChannel
= sbufReadU8(src
);
2711 customServoMixersMutable(i
)->inputSource
= sbufReadU8(src
);
2712 customServoMixersMutable(i
)->rate
= sbufReadU8(src
);
2713 customServoMixersMutable(i
)->speed
= sbufReadU8(src
);
2714 customServoMixersMutable(i
)->min
= sbufReadU8(src
);
2715 customServoMixersMutable(i
)->max
= sbufReadU8(src
);
2716 customServoMixersMutable(i
)->box
= sbufReadU8(src
);
2717 loadCustomServoMixer();
2722 case MSP_SET_MOTOR_3D_CONFIG
:
2723 flight3DConfigMutable()->deadband3d_low
= sbufReadU16(src
);
2724 flight3DConfigMutable()->deadband3d_high
= sbufReadU16(src
);
2725 flight3DConfigMutable()->neutral3d
= sbufReadU16(src
);
2728 case MSP_SET_RC_DEADBAND
:
2729 rcControlsConfigMutable()->deadband
= sbufReadU8(src
);
2730 rcControlsConfigMutable()->yaw_deadband
= sbufReadU8(src
);
2731 rcControlsConfigMutable()->alt_hold_deadband
= sbufReadU8(src
);
2732 flight3DConfigMutable()->deadband3d_throttle
= sbufReadU16(src
);
2735 case MSP_SET_RESET_CURR_PID
:
2736 resetPidProfile(currentPidProfile
);
2739 case MSP_SET_SENSOR_ALIGNMENT
: {
2740 // maintain backwards compatibility for API < 1.41
2741 const uint8_t gyroAlignment
= sbufReadU8(src
);
2742 sbufReadU8(src
); // discard deprecated acc_align
2743 #if defined(USE_MAG)
2744 compassConfigMutable()->mag_alignment
= sbufReadU8(src
);
2749 if (sbufBytesRemaining(src
) >= 3) {
2750 // API >= 1.41 - support the gyro_to_use and alignment for gyros 1 & 2
2751 #ifdef USE_MULTI_GYRO
2752 gyroConfigMutable()->gyro_to_use
= sbufReadU8(src
);
2753 gyroDeviceConfigMutable(0)->alignment
= sbufReadU8(src
);
2754 gyroDeviceConfigMutable(1)->alignment
= sbufReadU8(src
);
2756 sbufReadU8(src
); // unused gyro_to_use
2757 gyroDeviceConfigMutable(0)->alignment
= sbufReadU8(src
);
2758 sbufReadU8(src
); // unused gyro_2_sensor_align
2761 // maintain backwards compatibility for API < 1.41
2762 #ifdef USE_MULTI_GYRO
2763 switch (gyroConfig()->gyro_to_use
) {
2764 case GYRO_CONFIG_USE_GYRO_2
:
2765 gyroDeviceConfigMutable(1)->alignment
= gyroAlignment
;
2767 case GYRO_CONFIG_USE_GYRO_BOTH
:
2768 // For dual-gyro in "BOTH" mode we'll only update gyro 0
2770 gyroDeviceConfigMutable(0)->alignment
= gyroAlignment
;
2774 gyroDeviceConfigMutable(0)->alignment
= gyroAlignment
;
2781 case MSP_SET_ADVANCED_CONFIG
:
2782 sbufReadU8(src
); // was gyroConfigMutable()->gyro_sync_denom - removed in API 1.43
2783 pidConfigMutable()->pid_process_denom
= sbufReadU8(src
);
2784 motorConfigMutable()->dev
.useUnsyncedPwm
= sbufReadU8(src
);
2785 motorConfigMutable()->dev
.motorPwmProtocol
= sbufReadU8(src
);
2786 motorConfigMutable()->dev
.motorPwmRate
= sbufReadU16(src
);
2787 if (sbufBytesRemaining(src
) >= 2) {
2788 motorConfigMutable()->digitalIdleOffsetValue
= sbufReadU16(src
);
2790 if (sbufBytesRemaining(src
)) {
2791 sbufReadU8(src
); // DEPRECATED: gyro_use_32khz
2793 if (sbufBytesRemaining(src
)) {
2794 motorConfigMutable()->dev
.motorPwmInversion
= sbufReadU8(src
);
2796 if (sbufBytesRemaining(src
) >= 8) {
2797 gyroConfigMutable()->gyro_to_use
= sbufReadU8(src
);
2798 gyroConfigMutable()->gyro_high_fsr
= sbufReadU8(src
);
2799 gyroConfigMutable()->gyroMovementCalibrationThreshold
= sbufReadU8(src
);
2800 gyroConfigMutable()->gyroCalibrationDuration
= sbufReadU16(src
);
2801 gyroConfigMutable()->gyro_offset_yaw
= sbufReadU16(src
);
2802 gyroConfigMutable()->checkOverflow
= sbufReadU8(src
);
2804 if (sbufBytesRemaining(src
) >= 1) {
2805 //Added in MSP API 1.42
2806 systemConfigMutable()->debug_mode
= sbufReadU8(src
);
2809 validateAndFixGyroConfig();
2812 case MSP_SET_FILTER_CONFIG
:
2813 gyroConfigMutable()->gyro_lpf1_static_hz
= sbufReadU8(src
);
2814 currentPidProfile
->dterm_lpf1_static_hz
= sbufReadU16(src
);
2815 currentPidProfile
->yaw_lowpass_hz
= sbufReadU16(src
);
2816 if (sbufBytesRemaining(src
) >= 8) {
2817 gyroConfigMutable()->gyro_soft_notch_hz_1
= sbufReadU16(src
);
2818 gyroConfigMutable()->gyro_soft_notch_cutoff_1
= sbufReadU16(src
);
2819 currentPidProfile
->dterm_notch_hz
= sbufReadU16(src
);
2820 currentPidProfile
->dterm_notch_cutoff
= sbufReadU16(src
);
2822 if (sbufBytesRemaining(src
) >= 4) {
2823 gyroConfigMutable()->gyro_soft_notch_hz_2
= sbufReadU16(src
);
2824 gyroConfigMutable()->gyro_soft_notch_cutoff_2
= sbufReadU16(src
);
2826 if (sbufBytesRemaining(src
) >= 1) {
2827 currentPidProfile
->dterm_lpf1_type
= sbufReadU8(src
);
2829 if (sbufBytesRemaining(src
) >= 10) {
2830 gyroConfigMutable()->gyro_hardware_lpf
= sbufReadU8(src
);
2831 sbufReadU8(src
); // DEPRECATED: gyro_32khz_hardware_lpf
2832 gyroConfigMutable()->gyro_lpf1_static_hz
= sbufReadU16(src
);
2833 gyroConfigMutable()->gyro_lpf2_static_hz
= sbufReadU16(src
);
2834 gyroConfigMutable()->gyro_lpf1_type
= sbufReadU8(src
);
2835 gyroConfigMutable()->gyro_lpf2_type
= sbufReadU8(src
);
2836 currentPidProfile
->dterm_lpf2_static_hz
= sbufReadU16(src
);
2838 if (sbufBytesRemaining(src
) >= 9) {
2839 // Added in MSP API 1.41
2840 currentPidProfile
->dterm_lpf2_type
= sbufReadU8(src
);
2841 #if defined(USE_DYN_LPF)
2842 gyroConfigMutable()->gyro_lpf1_dyn_min_hz
= sbufReadU16(src
);
2843 gyroConfigMutable()->gyro_lpf1_dyn_max_hz
= sbufReadU16(src
);
2844 currentPidProfile
->dterm_lpf1_dyn_min_hz
= sbufReadU16(src
);
2845 currentPidProfile
->dterm_lpf1_dyn_max_hz
= sbufReadU16(src
);
2853 if (sbufBytesRemaining(src
) >= 8) {
2854 // Added in MSP API 1.42
2855 #if defined(USE_DYN_NOTCH_FILTER)
2856 sbufReadU8(src
); // DEPRECATED 1.43: dyn_notch_range
2857 sbufReadU8(src
); // DEPRECATED 1.44: dyn_notch_width_percent
2858 dynNotchConfigMutable()->dyn_notch_q
= sbufReadU16(src
);
2859 dynNotchConfigMutable()->dyn_notch_min_hz
= sbufReadU16(src
);
2866 #if defined(USE_RPM_FILTER)
2867 rpmFilterConfigMutable()->rpm_filter_harmonics
= sbufReadU8(src
);
2868 rpmFilterConfigMutable()->rpm_filter_min_hz
= sbufReadU8(src
);
2874 if (sbufBytesRemaining(src
) >= 2) {
2875 #if defined(USE_DYN_NOTCH_FILTER)
2876 // Added in MSP API 1.43
2877 dynNotchConfigMutable()->dyn_notch_max_hz
= sbufReadU16(src
);
2882 if (sbufBytesRemaining(src
) >= 2) {
2883 // Added in MSP API 1.44
2884 #if defined(USE_DYN_LPF)
2885 currentPidProfile
->dterm_lpf1_dyn_expo
= sbufReadU8(src
);
2889 #if defined(USE_DYN_NOTCH_FILTER)
2890 dynNotchConfigMutable()->dyn_notch_count
= sbufReadU8(src
);
2896 // reinitialize the gyro filters with the new values
2897 validateAndFixGyroConfig();
2899 // reinitialize the PID filters with the new values
2900 pidInitFilters(currentPidProfile
);
2903 case MSP_SET_PID_ADVANCED
:
2906 sbufReadU16(src
); // was pidProfile.yaw_p_limit
2907 sbufReadU8(src
); // reserved
2908 sbufReadU8(src
); // was vbatPidCompensation
2909 #if defined(USE_FEEDFORWARD)
2910 currentPidProfile
->feedforward_transition
= sbufReadU8(src
);
2914 sbufReadU8(src
); // was low byte of currentPidProfile->dtermSetpointWeight
2915 sbufReadU8(src
); // reserved
2916 sbufReadU8(src
); // reserved
2917 sbufReadU8(src
); // reserved
2918 currentPidProfile
->rateAccelLimit
= sbufReadU16(src
);
2919 currentPidProfile
->yawRateAccelLimit
= sbufReadU16(src
);
2920 if (sbufBytesRemaining(src
) >= 2) {
2921 currentPidProfile
->levelAngleLimit
= sbufReadU8(src
);
2922 sbufReadU8(src
); // was pidProfile.levelSensitivity
2924 if (sbufBytesRemaining(src
) >= 4) {
2925 currentPidProfile
->itermThrottleThreshold
= sbufReadU16(src
);
2926 currentPidProfile
->itermAcceleratorGain
= sbufReadU16(src
);
2928 if (sbufBytesRemaining(src
) >= 2) {
2929 sbufReadU16(src
); // was currentPidProfile->dtermSetpointWeight
2931 if (sbufBytesRemaining(src
) >= 14) {
2932 // Added in MSP API 1.40
2933 currentPidProfile
->iterm_rotation
= sbufReadU8(src
);
2934 sbufReadU8(src
); // was currentPidProfile->smart_feedforward
2935 #if defined(USE_ITERM_RELAX)
2936 currentPidProfile
->iterm_relax
= sbufReadU8(src
);
2937 currentPidProfile
->iterm_relax_type
= sbufReadU8(src
);
2942 #if defined(USE_ABSOLUTE_CONTROL)
2943 currentPidProfile
->abs_control_gain
= sbufReadU8(src
);
2947 #if defined(USE_THROTTLE_BOOST)
2948 currentPidProfile
->throttle_boost
= sbufReadU8(src
);
2952 #if defined(USE_ACRO_TRAINER)
2953 currentPidProfile
->acro_trainer_angle_limit
= sbufReadU8(src
);
2957 // PID controller feedforward terms
2958 currentPidProfile
->pid
[PID_ROLL
].F
= sbufReadU16(src
);
2959 currentPidProfile
->pid
[PID_PITCH
].F
= sbufReadU16(src
);
2960 currentPidProfile
->pid
[PID_YAW
].F
= sbufReadU16(src
);
2962 currentPidProfile
->antiGravityMode
= sbufReadU8(src
);
2964 if (sbufBytesRemaining(src
) >= 7) {
2965 // Added in MSP API 1.41
2966 #if defined(USE_D_MIN)
2967 currentPidProfile
->d_min
[PID_ROLL
] = sbufReadU8(src
);
2968 currentPidProfile
->d_min
[PID_PITCH
] = sbufReadU8(src
);
2969 currentPidProfile
->d_min
[PID_YAW
] = sbufReadU8(src
);
2970 currentPidProfile
->d_min_gain
= sbufReadU8(src
);
2971 currentPidProfile
->d_min_advance
= sbufReadU8(src
);
2979 #if defined(USE_INTEGRATED_YAW_CONTROL)
2980 currentPidProfile
->use_integrated_yaw
= sbufReadU8(src
);
2981 currentPidProfile
->integrated_yaw_relax
= sbufReadU8(src
);
2987 if(sbufBytesRemaining(src
) >= 1) {
2988 // Added in MSP API 1.42
2989 #if defined(USE_ITERM_RELAX)
2990 currentPidProfile
->iterm_relax_cutoff
= sbufReadU8(src
);
2995 if (sbufBytesRemaining(src
) >= 3) {
2996 // Added in MSP API 1.43
2997 currentPidProfile
->motor_output_limit
= sbufReadU8(src
);
2998 currentPidProfile
->auto_profile_cell_count
= sbufReadU8(src
);
2999 #if defined(USE_DYN_IDLE)
3000 currentPidProfile
->dyn_idle_min_rpm
= sbufReadU8(src
);
3005 if (sbufBytesRemaining(src
) >= 7) {
3006 // Added in MSP API 1.44
3007 #if defined(USE_FEEDFORWARD)
3008 currentPidProfile
->feedforward_averaging
= sbufReadU8(src
);
3009 currentPidProfile
->feedforward_smooth_factor
= sbufReadU8(src
);
3010 currentPidProfile
->feedforward_boost
= sbufReadU8(src
);
3011 currentPidProfile
->feedforward_max_rate_limit
= sbufReadU8(src
);
3012 currentPidProfile
->feedforward_jitter_factor
= sbufReadU8(src
);
3021 #if defined(USE_BATTERY_VOLTAGE_SAG_COMPENSATION)
3022 currentPidProfile
->vbat_sag_compensation
= sbufReadU8(src
);
3026 #if defined(USE_THRUST_LINEARIZATION)
3027 currentPidProfile
->thrustLinearization
= sbufReadU8(src
);
3032 pidInitConfig(currentPidProfile
);
3037 case MSP_SET_SENSOR_CONFIG
:
3038 #if defined(USE_ACC)
3039 accelerometerConfigMutable()->acc_hardware
= sbufReadU8(src
);
3043 #if defined(USE_BARO)
3044 barometerConfigMutable()->baro_hardware
= sbufReadU8(src
);
3048 #if defined(USE_MAG)
3049 compassConfigMutable()->mag_hardware
= sbufReadU8(src
);
3056 case MSP_ACC_CALIBRATION
:
3057 if (!ARMING_FLAG(ARMED
))
3058 accStartCalibration();
3062 #if defined(USE_MAG)
3063 case MSP_MAG_CALIBRATION
:
3064 if (!ARMING_FLAG(ARMED
)) {
3065 compassStartCalibration();
3070 case MSP_EEPROM_WRITE
:
3071 if (ARMING_FLAG(ARMED
)) {
3072 return MSP_RESULT_ERROR
;
3075 // This is going to take some time and won't be done where real-time performance is needed so
3076 // ignore how long it takes to avoid confusing the scheduler
3077 schedulerIgnoreTaskStateTime();
3082 #ifdef USE_VTX_TABLE
3083 if (vtxTableNeedsInit
) {
3084 vtxTableNeedsInit
= false;
3085 vtxTableInit(); // Reinitialize and refresh the in-memory copies
3092 case MSP_SET_BLACKBOX_CONFIG
:
3093 // Don't allow config to be updated while Blackbox is logging
3094 if (blackboxMayEditConfig()) {
3095 blackboxConfigMutable()->device
= sbufReadU8(src
);
3096 const int rateNum
= sbufReadU8(src
); // was rate_num
3097 const int rateDenom
= sbufReadU8(src
); // was rate_denom
3098 uint16_t pRatio
= 0;
3099 if (sbufBytesRemaining(src
) >= 2) {
3100 // p_ratio specified, so use it directly
3101 pRatio
= sbufReadU16(src
);
3103 // p_ratio not specified in MSP, so calculate it from old rateNum and rateDenom
3104 pRatio
= blackboxCalculatePDenom(rateNum
, rateDenom
);
3107 if (sbufBytesRemaining(src
) >= 1) {
3108 // sample_rate specified, so use it directly
3109 blackboxConfigMutable()->sample_rate
= sbufReadU8(src
);
3111 // sample_rate not specified in MSP, so calculate it from old p_ratio
3112 blackboxConfigMutable()->sample_rate
= blackboxCalculateSampleRate(pRatio
);
3118 #ifdef USE_VTX_COMMON
3119 case MSP_SET_VTX_CONFIG
:
3121 vtxDevice_t
*vtxDevice
= vtxCommonDevice();
3122 vtxDevType_e vtxType
= VTXDEV_UNKNOWN
;
3124 vtxType
= vtxCommonGetDeviceType(vtxDevice
);
3126 uint16_t newFrequency
= sbufReadU16(src
);
3127 if (newFrequency
<= VTXCOMMON_MSP_BANDCHAN_CHKVAL
) { // Value is band and channel
3128 const uint8_t newBand
= (newFrequency
/ 8) + 1;
3129 const uint8_t newChannel
= (newFrequency
% 8) + 1;
3130 vtxSettingsConfigMutable()->band
= newBand
;
3131 vtxSettingsConfigMutable()->channel
= newChannel
;
3132 vtxSettingsConfigMutable()->freq
= vtxCommonLookupFrequency(vtxDevice
, newBand
, newChannel
);
3133 } else if (newFrequency
<= VTX_SETTINGS_MAX_FREQUENCY_MHZ
) { // Value is frequency in MHz
3134 vtxSettingsConfigMutable()->band
= 0;
3135 vtxSettingsConfigMutable()->freq
= newFrequency
;
3138 if (sbufBytesRemaining(src
) >= 2) {
3139 vtxSettingsConfigMutable()->power
= sbufReadU8(src
);
3140 const uint8_t newPitmode
= sbufReadU8(src
);
3141 if (vtxType
!= VTXDEV_UNKNOWN
) {
3142 // Delegate pitmode to vtx directly
3143 unsigned vtxCurrentStatus
;
3144 vtxCommonGetStatus(vtxDevice
, &vtxCurrentStatus
);
3145 if ((bool)(vtxCurrentStatus
& VTX_STATUS_PIT_MODE
) != (bool)newPitmode
) {
3146 vtxCommonSetPitMode(vtxDevice
, newPitmode
);
3151 if (sbufBytesRemaining(src
)) {
3152 vtxSettingsConfigMutable()->lowPowerDisarm
= sbufReadU8(src
);
3155 // API version 1.42 - this parameter kept separate since clients may already be supplying
3156 if (sbufBytesRemaining(src
) >= 2) {
3157 vtxSettingsConfigMutable()->pitModeFreq
= sbufReadU16(src
);
3160 // API version 1.42 - extensions for non-encoded versions of the band, channel or frequency
3161 if (sbufBytesRemaining(src
) >= 4) {
3162 // Added standalone values for band, channel and frequency to move
3163 // away from the flawed encoded combined method originally implemented.
3164 uint8_t newBand
= sbufReadU8(src
);
3165 const uint8_t newChannel
= sbufReadU8(src
);
3166 uint16_t newFreq
= sbufReadU16(src
);
3168 newFreq
= vtxCommonLookupFrequency(vtxDevice
, newBand
, newChannel
);
3170 vtxSettingsConfigMutable()->band
= newBand
;
3171 vtxSettingsConfigMutable()->channel
= newChannel
;
3172 vtxSettingsConfigMutable()->freq
= newFreq
;
3175 // API version 1.42 - extensions for vtxtable support
3176 if (sbufBytesRemaining(src
) >= 4) {
3177 #ifdef USE_VTX_TABLE
3178 const uint8_t newBandCount
= sbufReadU8(src
);
3179 const uint8_t newChannelCount
= sbufReadU8(src
);
3180 const uint8_t newPowerCount
= sbufReadU8(src
);
3182 if ((newBandCount
> VTX_TABLE_MAX_BANDS
) ||
3183 (newChannelCount
> VTX_TABLE_MAX_CHANNELS
) ||
3184 (newPowerCount
> VTX_TABLE_MAX_POWER_LEVELS
)) {
3185 return MSP_RESULT_ERROR
;
3187 vtxTableConfigMutable()->bands
= newBandCount
;
3188 vtxTableConfigMutable()->channels
= newChannelCount
;
3189 vtxTableConfigMutable()->powerLevels
= newPowerCount
;
3191 // boolean to determine whether the vtxtable should be cleared in
3192 // expectation that the detailed band/channel and power level messages
3193 // will follow to repopulate the tables
3194 if (sbufReadU8(src
)) {
3195 for (int i
= 0; i
< VTX_TABLE_MAX_BANDS
; i
++) {
3196 vtxTableConfigClearBand(vtxTableConfigMutable(), i
);
3197 vtxTableConfigClearChannels(vtxTableConfigMutable(), i
, 0);
3199 vtxTableConfigClearPowerLabels(vtxTableConfigMutable(), 0);
3200 vtxTableConfigClearPowerValues(vtxTableConfigMutable(), 0);
3213 #ifdef USE_VTX_TABLE
3214 case MSP_SET_VTXTABLE_BAND
:
3216 char bandName
[VTX_TABLE_BAND_NAME_LENGTH
+ 1];
3217 memset(bandName
, 0, VTX_TABLE_BAND_NAME_LENGTH
+ 1);
3218 uint16_t frequencies
[VTX_TABLE_MAX_CHANNELS
];
3219 const uint8_t band
= sbufReadU8(src
);
3220 const uint8_t bandNameLength
= sbufReadU8(src
);
3221 for (int i
= 0; i
< bandNameLength
; i
++) {
3222 const char nameChar
= sbufReadU8(src
);
3223 if (i
< VTX_TABLE_BAND_NAME_LENGTH
) {
3224 bandName
[i
] = toupper(nameChar
);
3227 const char bandLetter
= toupper(sbufReadU8(src
));
3228 const bool isFactoryBand
= (bool)sbufReadU8(src
);
3229 const uint8_t channelCount
= sbufReadU8(src
);
3230 for (int i
= 0; i
< channelCount
; i
++) {
3231 const uint16_t frequency
= sbufReadU16(src
);
3232 if (i
< vtxTableConfig()->channels
) {
3233 frequencies
[i
] = frequency
;
3237 if (band
> 0 && band
<= vtxTableConfig()->bands
) {
3238 vtxTableStrncpyWithPad(vtxTableConfigMutable()->bandNames
[band
- 1], bandName
, VTX_TABLE_BAND_NAME_LENGTH
);
3239 vtxTableConfigMutable()->bandLetters
[band
- 1] = bandLetter
;
3240 vtxTableConfigMutable()->isFactoryBand
[band
- 1] = isFactoryBand
;
3241 for (int i
= 0; i
< vtxTableConfig()->channels
; i
++) {
3242 vtxTableConfigMutable()->frequency
[band
- 1][i
] = frequencies
[i
];
3244 // If this is the currently selected band then reset the frequency
3245 if (band
== vtxSettingsConfig()->band
) {
3246 uint16_t newFreq
= 0;
3247 if (vtxSettingsConfig()->channel
> 0 && vtxSettingsConfig()->channel
<= vtxTableConfig()->channels
) {
3248 newFreq
= frequencies
[vtxSettingsConfig()->channel
- 1];
3250 vtxSettingsConfigMutable()->freq
= newFreq
;
3252 vtxTableNeedsInit
= true; // reinintialize vtxtable after eeprom write
3254 return MSP_RESULT_ERROR
;
3259 case MSP_SET_VTXTABLE_POWERLEVEL
:
3261 char powerLevelLabel
[VTX_TABLE_POWER_LABEL_LENGTH
+ 1];
3262 memset(powerLevelLabel
, 0, VTX_TABLE_POWER_LABEL_LENGTH
+ 1);
3263 const uint8_t powerLevel
= sbufReadU8(src
);
3264 const uint16_t powerValue
= sbufReadU16(src
);
3265 const uint8_t powerLevelLabelLength
= sbufReadU8(src
);
3266 for (int i
= 0; i
< powerLevelLabelLength
; i
++) {
3267 const char labelChar
= sbufReadU8(src
);
3268 if (i
< VTX_TABLE_POWER_LABEL_LENGTH
) {
3269 powerLevelLabel
[i
] = toupper(labelChar
);
3273 if (powerLevel
> 0 && powerLevel
<= vtxTableConfig()->powerLevels
) {
3274 vtxTableConfigMutable()->powerValues
[powerLevel
- 1] = powerValue
;
3275 vtxTableStrncpyWithPad(vtxTableConfigMutable()->powerLabels
[powerLevel
- 1], powerLevelLabel
, VTX_TABLE_POWER_LABEL_LENGTH
);
3276 vtxTableNeedsInit
= true; // reinintialize vtxtable after eeprom write
3278 return MSP_RESULT_ERROR
;
3284 case MSP2_SET_MOTOR_OUTPUT_REORDERING
:
3286 const uint8_t arraySize
= sbufReadU8(src
);
3288 for (unsigned i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
3291 if (i
< arraySize
) {
3292 value
= sbufReadU8(src
);
3295 motorConfigMutable()->dev
.motorOutputReordering
[i
] = value
;
3301 case MSP2_SEND_DSHOT_COMMAND
:
3303 const bool armed
= ARMING_FLAG(ARMED
);
3306 const uint8_t commandType
= sbufReadU8(src
);
3307 const uint8_t motorIndex
= sbufReadU8(src
);
3308 const uint8_t commandCount
= sbufReadU8(src
);
3310 if (DSHOT_CMD_TYPE_BLOCKING
== commandType
) {
3314 for (uint8_t i
= 0; i
< commandCount
; i
++) {
3315 const uint8_t commandIndex
= sbufReadU8(src
);
3316 dshotCommandWrite(motorIndex
, getMotorCount(), commandIndex
, commandType
);
3319 if (DSHOT_CMD_TYPE_BLOCKING
== commandType
) {
3327 #ifdef USE_SIMPLIFIED_TUNING
3328 // Added in MSP API 1.44
3329 case MSP_SET_SIMPLIFIED_TUNING
:
3331 readSimplifiedPids(currentPidProfile
, src
);
3332 readSimplifiedDtermFilters(currentPidProfile
, src
);
3333 readSimplifiedGyroFilters(gyroConfigMutable(), src
);
3334 applySimplifiedTuning(currentPidProfile
, gyroConfigMutable());
3339 #ifdef USE_CAMERA_CONTROL
3340 case MSP_CAMERA_CONTROL
:
3342 if (ARMING_FLAG(ARMED
)) {
3343 return MSP_RESULT_ERROR
;
3346 const uint8_t key
= sbufReadU8(src
);
3347 cameraControlKeyPress(key
, 0);
3352 case MSP_SET_ARMING_DISABLED
:
3354 const uint8_t command
= sbufReadU8(src
);
3355 uint8_t disableRunawayTakeoff
= 0;
3356 #ifndef USE_RUNAWAY_TAKEOFF
3357 UNUSED(disableRunawayTakeoff
);
3359 if (sbufBytesRemaining(src
)) {
3360 disableRunawayTakeoff
= sbufReadU8(src
);
3363 mspArmingDisableByDescriptor(srcDesc
);
3364 setArmingDisabled(ARMING_DISABLED_MSP
);
3365 if (ARMING_FLAG(ARMED
)) {
3366 disarm(DISARM_REASON_ARMING_DISABLED
);
3368 #ifdef USE_RUNAWAY_TAKEOFF
3369 runawayTakeoffTemporaryDisable(false);
3372 mspArmingEnableByDescriptor(srcDesc
);
3373 if (mspIsMspArmingEnabled()) {
3374 unsetArmingDisabled(ARMING_DISABLED_MSP
);
3375 #ifdef USE_RUNAWAY_TAKEOFF
3376 runawayTakeoffTemporaryDisable(disableRunawayTakeoff
);
3384 case MSP_DATAFLASH_ERASE
:
3391 case MSP_SET_RAW_GPS
:
3392 gpsSetFixState(sbufReadU8(src
));
3393 gpsSol
.numSat
= sbufReadU8(src
);
3394 gpsSol
.llh
.lat
= sbufReadU32(src
);
3395 gpsSol
.llh
.lon
= sbufReadU32(src
);
3396 gpsSol
.llh
.altCm
= sbufReadU16(src
) * 100; // alt changed from 1m to 0.01m per lsb since MSP API 1.39 by RTH. Received MSP altitudes in 1m per lsb have to upscaled.
3397 gpsSol
.groundSpeed
= sbufReadU16(src
);
3398 GPS_update
|= GPS_MSP_UPDATE
; // MSP data signalisation to GPS functions
3401 case MSP_SET_FEATURE_CONFIG
:
3402 featureConfigReplace(sbufReadU32(src
));
3406 case MSP_SET_BEEPER_CONFIG
:
3407 beeperConfigMutable()->beeper_off_flags
= sbufReadU32(src
);
3408 if (sbufBytesRemaining(src
) >= 1) {
3409 beeperConfigMutable()->dshotBeaconTone
= sbufReadU8(src
);
3411 if (sbufBytesRemaining(src
) >= 4) {
3412 beeperConfigMutable()->dshotBeaconOffFlags
= sbufReadU32(src
);
3417 case MSP_SET_BOARD_ALIGNMENT_CONFIG
:
3418 boardAlignmentMutable()->rollDegrees
= sbufReadU16(src
);
3419 boardAlignmentMutable()->pitchDegrees
= sbufReadU16(src
);
3420 boardAlignmentMutable()->yawDegrees
= sbufReadU16(src
);
3423 case MSP_SET_MIXER_CONFIG
:
3424 #ifndef USE_QUAD_MIXER_ONLY
3425 mixerConfigMutable()->mixerMode
= sbufReadU8(src
);
3429 if (sbufBytesRemaining(src
) >= 1) {
3430 mixerConfigMutable()->yaw_motors_reversed
= sbufReadU8(src
);
3434 case MSP_SET_RX_CONFIG
:
3435 rxConfigMutable()->serialrx_provider
= sbufReadU8(src
);
3436 rxConfigMutable()->maxcheck
= sbufReadU16(src
);
3437 rxConfigMutable()->midrc
= sbufReadU16(src
);
3438 rxConfigMutable()->mincheck
= sbufReadU16(src
);
3439 rxConfigMutable()->spektrum_sat_bind
= sbufReadU8(src
);
3440 if (sbufBytesRemaining(src
) >= 4) {
3441 rxConfigMutable()->rx_min_usec
= sbufReadU16(src
);
3442 rxConfigMutable()->rx_max_usec
= sbufReadU16(src
);
3444 if (sbufBytesRemaining(src
) >= 4) {
3445 sbufReadU8(src
); // not required in API 1.44, was rxConfigMutable()->rcInterpolation
3446 sbufReadU8(src
); // not required in API 1.44, was rxConfigMutable()->rcInterpolationInterval
3447 rxConfigMutable()->airModeActivateThreshold
= (sbufReadU16(src
) - 1000) / 10;
3449 if (sbufBytesRemaining(src
) >= 6) {
3451 rxSpiConfigMutable()->rx_spi_protocol
= sbufReadU8(src
);
3452 rxSpiConfigMutable()->rx_spi_id
= sbufReadU32(src
);
3453 rxSpiConfigMutable()->rx_spi_rf_channel_count
= sbufReadU8(src
);
3460 if (sbufBytesRemaining(src
) >= 1) {
3461 rxConfigMutable()->fpvCamAngleDegrees
= sbufReadU8(src
);
3463 if (sbufBytesRemaining(src
) >= 6) {
3464 // Added in MSP API 1.40
3465 sbufReadU8(src
); // not required in API 1.44, was rxConfigMutable()->rcSmoothingChannels
3466 #if defined(USE_RC_SMOOTHING_FILTER)
3467 sbufReadU8(src
); // not required in API 1.44, was rc_smoothing_type
3468 configRebootUpdateCheckU8(&rxConfigMutable()->rc_smoothing_setpoint_cutoff
, sbufReadU8(src
));
3469 configRebootUpdateCheckU8(&rxConfigMutable()->rc_smoothing_feedforward_cutoff
, sbufReadU8(src
));
3470 sbufReadU8(src
); // not required in API 1.44, was rc_smoothing_input_type
3471 sbufReadU8(src
); // not required in API 1.44, was rc_smoothing_derivative_type
3480 if (sbufBytesRemaining(src
) >= 1) {
3481 // Added in MSP API 1.40
3482 // Kept separate from the section above to work around missing Configurator support in version < 10.4.2
3483 #if defined(USE_USB_CDC_HID)
3484 usbDevConfigMutable()->type
= sbufReadU8(src
);
3489 if (sbufBytesRemaining(src
) >= 1) {
3490 // Added in MSP API 1.42
3491 #if defined(USE_RC_SMOOTHING_FILTER)
3492 // Added extra validation/range constraint for rc_smoothing_auto_factor as a workaround for a bug in
3493 // the 10.6 configurator where it was possible to submit an invalid out-of-range value. We might be
3494 // able to remove the constraint at some point in the future once the affected versions are deprecated
3495 // enough that the risk is low.
3496 configRebootUpdateCheckU8(&rxConfigMutable()->rc_smoothing_auto_factor_rpy
, constrain(sbufReadU8(src
), RC_SMOOTHING_AUTO_FACTOR_MIN
, RC_SMOOTHING_AUTO_FACTOR_MAX
));
3501 if (sbufBytesRemaining(src
) >= 1) {
3502 // Added in MSP API 1.44
3503 #if defined(USE_RC_SMOOTHING_FILTER)
3504 configRebootUpdateCheckU8(&rxConfigMutable()->rc_smoothing_mode
, sbufReadU8(src
));
3510 case MSP_SET_FAILSAFE_CONFIG
:
3511 failsafeConfigMutable()->failsafe_delay
= sbufReadU8(src
);
3512 failsafeConfigMutable()->failsafe_off_delay
= sbufReadU8(src
);
3513 failsafeConfigMutable()->failsafe_throttle
= sbufReadU16(src
);
3514 failsafeConfigMutable()->failsafe_switch_mode
= sbufReadU8(src
);
3515 failsafeConfigMutable()->failsafe_throttle_low_delay
= sbufReadU16(src
);
3516 failsafeConfigMutable()->failsafe_procedure
= sbufReadU8(src
);
3519 case MSP_SET_RXFAIL_CONFIG
:
3520 i
= sbufReadU8(src
);
3521 if (i
< MAX_SUPPORTED_RC_CHANNEL_COUNT
) {
3522 rxFailsafeChannelConfigsMutable(i
)->mode
= sbufReadU8(src
);
3523 rxFailsafeChannelConfigsMutable(i
)->step
= CHANNEL_VALUE_TO_RXFAIL_STEP(sbufReadU16(src
));
3525 return MSP_RESULT_ERROR
;
3529 case MSP_SET_RSSI_CONFIG
:
3530 rxConfigMutable()->rssi_channel
= sbufReadU8(src
);
3533 case MSP_SET_RX_MAP
:
3534 for (int i
= 0; i
< RX_MAPPABLE_CHANNEL_COUNT
; i
++) {
3535 rxConfigMutable()->rcmap
[i
] = sbufReadU8(src
);
3539 case MSP_SET_CF_SERIAL_CONFIG
:
3541 uint8_t portConfigSize
= sizeof(uint8_t) + sizeof(uint16_t) + (sizeof(uint8_t) * 4);
3543 if (dataSize
% portConfigSize
!= 0) {
3544 return MSP_RESULT_ERROR
;
3547 uint8_t remainingPortsInPacket
= dataSize
/ portConfigSize
;
3549 while (remainingPortsInPacket
--) {
3550 uint8_t identifier
= sbufReadU8(src
);
3552 serialPortConfig_t
*portConfig
= serialFindPortConfigurationMutable(identifier
);
3555 return MSP_RESULT_ERROR
;
3558 portConfig
->identifier
= identifier
;
3559 portConfig
->functionMask
= sbufReadU16(src
);
3560 portConfig
->msp_baudrateIndex
= sbufReadU8(src
);
3561 portConfig
->gps_baudrateIndex
= sbufReadU8(src
);
3562 portConfig
->telemetry_baudrateIndex
= sbufReadU8(src
);
3563 portConfig
->blackbox_baudrateIndex
= sbufReadU8(src
);
3567 case MSP2_COMMON_SET_SERIAL_CONFIG
: {
3569 return MSP_RESULT_ERROR
;
3571 unsigned count
= sbufReadU8(src
);
3572 unsigned portConfigSize
= (dataSize
- 1) / count
;
3573 unsigned expectedPortSize
= sizeof(uint8_t) + sizeof(uint32_t) + (sizeof(uint8_t) * 4);
3574 if (portConfigSize
< expectedPortSize
) {
3575 return MSP_RESULT_ERROR
;
3577 for (unsigned ii
= 0; ii
< count
; ii
++) {
3578 unsigned start
= sbufBytesRemaining(src
);
3579 uint8_t identifier
= sbufReadU8(src
);
3580 serialPortConfig_t
*portConfig
= serialFindPortConfigurationMutable(identifier
);
3583 return MSP_RESULT_ERROR
;
3586 portConfig
->identifier
= identifier
;
3587 portConfig
->functionMask
= sbufReadU32(src
);
3588 portConfig
->msp_baudrateIndex
= sbufReadU8(src
);
3589 portConfig
->gps_baudrateIndex
= sbufReadU8(src
);
3590 portConfig
->telemetry_baudrateIndex
= sbufReadU8(src
);
3591 portConfig
->blackbox_baudrateIndex
= sbufReadU8(src
);
3592 // Skip unknown bytes
3593 while (start
- sbufBytesRemaining(src
) < portConfigSize
&& sbufBytesRemaining(src
)) {
3600 #ifdef USE_LED_STRIP_STATUS_MODE
3601 case MSP_SET_LED_COLORS
:
3602 for (int i
= 0; i
< LED_CONFIGURABLE_COLOR_COUNT
; i
++) {
3603 hsvColor_t
*color
= &ledStripStatusModeConfigMutable()->colors
[i
];
3604 color
->h
= sbufReadU16(src
);
3605 color
->s
= sbufReadU8(src
);
3606 color
->v
= sbufReadU8(src
);
3611 #ifdef USE_LED_STRIP
3612 case MSP_SET_LED_STRIP_CONFIG
:
3614 i
= sbufReadU8(src
);
3615 if (i
>= LED_MAX_STRIP_LENGTH
|| dataSize
!= (1 + 4)) {
3616 return MSP_RESULT_ERROR
;
3618 #ifdef USE_LED_STRIP_STATUS_MODE
3619 ledConfig_t
*ledConfig
= &ledStripStatusModeConfigMutable()->ledConfigs
[i
];
3620 *ledConfig
= sbufReadU32(src
);
3621 reevaluateLedConfig();
3625 // API 1.41 - selected ledstrip_profile
3626 if (sbufBytesRemaining(src
) >= 1) {
3627 ledStripConfigMutable()->ledstrip_profile
= sbufReadU8(src
);
3633 #ifdef USE_LED_STRIP_STATUS_MODE
3634 case MSP_SET_LED_STRIP_MODECOLOR
:
3636 ledModeIndex_e modeIdx
= sbufReadU8(src
);
3637 int funIdx
= sbufReadU8(src
);
3638 int color
= sbufReadU8(src
);
3640 if (!setModeColor(modeIdx
, funIdx
, color
)) {
3641 return MSP_RESULT_ERROR
;
3648 memset(pilotConfigMutable()->name
, 0, ARRAYLEN(pilotConfig()->name
));
3649 for (unsigned int i
= 0; i
< MIN(MAX_NAME_LENGTH
, dataSize
); i
++) {
3650 pilotConfigMutable()->name
[i
] = sbufReadU8(src
);
3653 osdAnalyzeActiveElements();
3660 // Use seconds and milliseconds to make senders
3661 // easier to implement. Generating a 64 bit value
3662 // might not be trivial in some platforms.
3663 int32_t secs
= (int32_t)sbufReadU32(src
);
3664 uint16_t millis
= sbufReadU16(src
);
3665 rtcTime_t t
= rtcTimeMake(secs
, millis
);
3672 case MSP_SET_TX_INFO
:
3673 setRssiMsp(sbufReadU8(src
));
3677 #if defined(USE_BOARD_INFO)
3678 case MSP_SET_BOARD_INFO
:
3679 if (!boardInformationIsSet()) {
3680 uint8_t length
= sbufReadU8(src
);
3681 char boardName
[MAX_BOARD_NAME_LENGTH
+ 1];
3682 sbufReadData(src
, boardName
, MIN(length
, MAX_BOARD_NAME_LENGTH
));
3683 if (length
> MAX_BOARD_NAME_LENGTH
) {
3684 sbufAdvance(src
, length
- MAX_BOARD_NAME_LENGTH
);
3685 length
= MAX_BOARD_NAME_LENGTH
;
3687 boardName
[length
] = '\0';
3688 length
= sbufReadU8(src
);
3689 char manufacturerId
[MAX_MANUFACTURER_ID_LENGTH
+ 1];
3690 sbufReadData(src
, manufacturerId
, MIN(length
, MAX_MANUFACTURER_ID_LENGTH
));
3691 if (length
> MAX_MANUFACTURER_ID_LENGTH
) {
3692 sbufAdvance(src
, length
- MAX_MANUFACTURER_ID_LENGTH
);
3693 length
= MAX_MANUFACTURER_ID_LENGTH
;
3695 manufacturerId
[length
] = '\0';
3697 setBoardName(boardName
);
3698 setManufacturerId(manufacturerId
);
3699 persistBoardInformation();
3701 return MSP_RESULT_ERROR
;
3705 #if defined(USE_SIGNATURE)
3706 case MSP_SET_SIGNATURE
:
3707 if (!signatureIsSet()) {
3708 uint8_t signature
[SIGNATURE_LENGTH
];
3709 sbufReadData(src
, signature
, SIGNATURE_LENGTH
);
3710 setSignature(signature
);
3713 return MSP_RESULT_ERROR
;
3718 #endif // USE_BOARD_INFO
3719 #if defined(USE_RX_BIND)
3720 case MSP2_BETAFLIGHT_BIND
:
3721 if (!startRxBind()) {
3722 return MSP_RESULT_ERROR
;
3728 // we do not know how to handle the (valid) message, indicate error MSP $M!
3729 return MSP_RESULT_ERROR
;
3731 return MSP_RESULT_ACK
;
3734 static mspResult_e
mspCommonProcessInCommand(mspDescriptor_t srcDesc
, int16_t cmdMSP
, sbuf_t
*src
, mspPostProcessFnPtr
*mspPostProcessFn
)
3736 UNUSED(mspPostProcessFn
);
3737 const unsigned int dataSize
= sbufBytesRemaining(src
);
3738 UNUSED(dataSize
); // maybe unused due to compiler options
3741 #ifdef USE_TRANSPONDER
3742 case MSP_SET_TRANSPONDER_CONFIG
: {
3743 // Backward compatibility to BFC 3.1.1 is lost for this message type
3745 uint8_t provider
= sbufReadU8(src
);
3746 uint8_t bytesRemaining
= dataSize
- 1;
3748 if (provider
> TRANSPONDER_PROVIDER_COUNT
) {
3749 return MSP_RESULT_ERROR
;
3752 const uint8_t requirementIndex
= provider
- 1;
3753 const uint8_t transponderDataSize
= transponderRequirements
[requirementIndex
].dataLength
;
3755 transponderConfigMutable()->provider
= provider
;
3757 if (provider
== TRANSPONDER_NONE
) {
3761 if (bytesRemaining
!= transponderDataSize
) {
3762 return MSP_RESULT_ERROR
;
3765 if (provider
!= transponderConfig()->provider
) {
3766 transponderStopRepeating();
3769 memset(transponderConfigMutable()->data
, 0, sizeof(transponderConfig()->data
));
3771 for (unsigned int i
= 0; i
< transponderDataSize
; i
++) {
3772 transponderConfigMutable()->data
[i
] = sbufReadU8(src
);
3774 transponderUpdateData();
3779 case MSP_SET_VOLTAGE_METER_CONFIG
: {
3780 int8_t id
= sbufReadU8(src
);
3783 // find and configure an ADC voltage sensor
3785 int8_t voltageSensorADCIndex
;
3786 for (voltageSensorADCIndex
= 0; voltageSensorADCIndex
< MAX_VOLTAGE_SENSOR_ADC
; voltageSensorADCIndex
++) {
3787 if (id
== voltageMeterADCtoIDMap
[voltageSensorADCIndex
]) {
3792 if (voltageSensorADCIndex
< MAX_VOLTAGE_SENSOR_ADC
) {
3793 voltageSensorADCConfigMutable(voltageSensorADCIndex
)->vbatscale
= sbufReadU8(src
);
3794 voltageSensorADCConfigMutable(voltageSensorADCIndex
)->vbatresdivval
= sbufReadU8(src
);
3795 voltageSensorADCConfigMutable(voltageSensorADCIndex
)->vbatresdivmultiplier
= sbufReadU8(src
);
3797 // if we had any other types of voltage sensor to configure, this is where we'd do it.
3805 case MSP_SET_CURRENT_METER_CONFIG
: {
3806 int id
= sbufReadU8(src
);
3809 case CURRENT_METER_ID_BATTERY_1
:
3810 currentSensorADCConfigMutable()->scale
= sbufReadU16(src
);
3811 currentSensorADCConfigMutable()->offset
= sbufReadU16(src
);
3813 #ifdef USE_VIRTUAL_CURRENT_METER
3814 case CURRENT_METER_ID_VIRTUAL_1
:
3815 currentSensorVirtualConfigMutable()->scale
= sbufReadU16(src
);
3816 currentSensorVirtualConfigMutable()->offset
= sbufReadU16(src
);
3827 case MSP_SET_BATTERY_CONFIG
:
3828 batteryConfigMutable()->vbatmincellvoltage
= sbufReadU8(src
) * 10; // vbatlevel_warn1 in MWC2.3 GUI
3829 batteryConfigMutable()->vbatmaxcellvoltage
= sbufReadU8(src
) * 10; // vbatlevel_warn2 in MWC2.3 GUI
3830 batteryConfigMutable()->vbatwarningcellvoltage
= sbufReadU8(src
) * 10; // vbatlevel when buzzer starts to alert
3831 batteryConfigMutable()->batteryCapacity
= sbufReadU16(src
);
3832 batteryConfigMutable()->voltageMeterSource
= sbufReadU8(src
);
3833 batteryConfigMutable()->currentMeterSource
= sbufReadU8(src
);
3834 if (sbufBytesRemaining(src
) >= 6) {
3835 batteryConfigMutable()->vbatmincellvoltage
= sbufReadU16(src
);
3836 batteryConfigMutable()->vbatmaxcellvoltage
= sbufReadU16(src
);
3837 batteryConfigMutable()->vbatwarningcellvoltage
= sbufReadU16(src
);
3841 #if defined(USE_OSD)
3842 case MSP_SET_OSD_CONFIG
:
3844 const uint8_t addr
= sbufReadU8(src
);
3846 if ((int8_t)addr
== -1) {
3847 /* Set general OSD settings */
3849 vcdProfileMutable()->video_system
= sbufReadU8(src
);
3851 sbufReadU8(src
); // Skip video system
3853 #if defined(USE_OSD)
3854 osdConfigMutable()->units
= sbufReadU8(src
);
3857 osdConfigMutable()->rssi_alarm
= sbufReadU8(src
);
3858 osdConfigMutable()->cap_alarm
= sbufReadU16(src
);
3859 sbufReadU16(src
); // Skip unused (previously fly timer)
3860 osdConfigMutable()->alt_alarm
= sbufReadU16(src
);
3862 if (sbufBytesRemaining(src
) >= 2) {
3863 /* Enabled warnings */
3864 // API < 1.41 supports only the low 16 bits
3865 osdConfigMutable()->enabledWarnings
= sbufReadU16(src
);
3868 if (sbufBytesRemaining(src
) >= 4) {
3869 // 32bit version of enabled warnings (API >= 1.41)
3870 osdConfigMutable()->enabledWarnings
= sbufReadU32(src
);
3873 if (sbufBytesRemaining(src
) >= 1) {
3875 // selected OSD profile
3876 #ifdef USE_OSD_PROFILES
3877 changeOsdProfileIndex(sbufReadU8(src
));
3880 #endif // USE_OSD_PROFILES
3883 if (sbufBytesRemaining(src
) >= 1) {
3885 // OSD stick overlay mode
3887 #ifdef USE_OSD_STICK_OVERLAY
3888 osdConfigMutable()->overlay_radio_mode
= sbufReadU8(src
);
3891 #endif // USE_OSD_STICK_OVERLAY
3895 if (sbufBytesRemaining(src
) >= 2) {
3897 // OSD camera frame element width/height
3898 osdConfigMutable()->camera_frame_width
= sbufReadU8(src
);
3899 osdConfigMutable()->camera_frame_height
= sbufReadU8(src
);
3902 } else if ((int8_t)addr
== -2) {
3903 #if defined(USE_OSD)
3905 uint8_t index
= sbufReadU8(src
);
3906 if (index
> OSD_TIMER_COUNT
) {
3907 return MSP_RESULT_ERROR
;
3909 osdConfigMutable()->timers
[index
] = sbufReadU16(src
);
3911 return MSP_RESULT_ERROR
;
3913 #if defined(USE_OSD)
3914 const uint16_t value
= sbufReadU16(src
);
3916 /* Get screen index, 0 is post flight statistics, 1 and above are in flight OSD screens */
3917 const uint8_t screen
= (sbufBytesRemaining(src
) >= 1) ? sbufReadU8(src
) : 1;
3919 if (screen
== 0 && addr
< OSD_STAT_COUNT
) {
3920 /* Set statistic item enable */
3921 osdStatSetState(addr
, (value
!= 0));
3922 } else if (addr
< OSD_ITEM_COUNT
) {
3923 /* Set element positions */
3924 osdElementConfigMutable()->item_pos
[addr
] = value
;
3925 osdAnalyzeActiveElements();
3927 return MSP_RESULT_ERROR
;
3930 return MSP_RESULT_ERROR
;
3936 case MSP_OSD_CHAR_WRITE
:
3939 size_t osdCharacterBytes
;
3941 if (dataSize
>= OSD_CHAR_VISIBLE_BYTES
+ 2) {
3942 if (dataSize
>= OSD_CHAR_BYTES
+ 2) {
3943 // 16 bit address, full char with metadata
3944 addr
= sbufReadU16(src
);
3945 osdCharacterBytes
= OSD_CHAR_BYTES
;
3946 } else if (dataSize
>= OSD_CHAR_BYTES
+ 1) {
3947 // 8 bit address, full char with metadata
3948 addr
= sbufReadU8(src
);
3949 osdCharacterBytes
= OSD_CHAR_BYTES
;
3951 // 16 bit character address, only visible char bytes
3952 addr
= sbufReadU16(src
);
3953 osdCharacterBytes
= OSD_CHAR_VISIBLE_BYTES
;
3956 // 8 bit character address, only visible char bytes
3957 addr
= sbufReadU8(src
);
3958 osdCharacterBytes
= OSD_CHAR_VISIBLE_BYTES
;
3960 for (unsigned ii
= 0; ii
< MIN(osdCharacterBytes
, sizeof(chr
.data
)); ii
++) {
3961 chr
.data
[ii
] = sbufReadU8(src
);
3963 displayPort_t
*osdDisplayPort
= osdGetDisplayPort(NULL
);
3964 if (!osdDisplayPort
) {
3965 return MSP_RESULT_ERROR
;
3968 if (!displayWriteFontCharacter(osdDisplayPort
, addr
, &chr
)) {
3969 return MSP_RESULT_ERROR
;
3976 return mspProcessInCommand(srcDesc
, cmdMSP
, src
);
3978 return MSP_RESULT_ACK
;
3982 * Returns MSP_RESULT_ACK, MSP_RESULT_ERROR or MSP_RESULT_NO_REPLY
3984 mspResult_e
mspFcProcessCommand(mspDescriptor_t srcDesc
, mspPacket_t
*cmd
, mspPacket_t
*reply
, mspPostProcessFnPtr
*mspPostProcessFn
)
3986 int ret
= MSP_RESULT_ACK
;
3987 sbuf_t
*dst
= &reply
->buf
;
3988 sbuf_t
*src
= &cmd
->buf
;
3989 const int16_t cmdMSP
= cmd
->cmd
;
3990 // initialize reply by default
3991 reply
->cmd
= cmd
->cmd
;
3993 if (mspCommonProcessOutCommand(cmdMSP
, dst
, mspPostProcessFn
)) {
3994 ret
= MSP_RESULT_ACK
;
3995 } else if (mspProcessOutCommand(cmdMSP
, dst
)) {
3996 ret
= MSP_RESULT_ACK
;
3997 } else if ((ret
= mspFcProcessOutCommandWithArg(srcDesc
, cmdMSP
, src
, dst
, mspPostProcessFn
)) != MSP_RESULT_CMD_UNKNOWN
) {
3999 } else if (cmdMSP
== MSP_SET_PASSTHROUGH
) {
4000 mspFcSetPassthroughCommand(dst
, src
, mspPostProcessFn
);
4001 ret
= MSP_RESULT_ACK
;
4003 } else if (cmdMSP
== MSP_DATAFLASH_READ
) {
4004 mspFcDataFlashReadCommand(dst
, src
);
4005 ret
= MSP_RESULT_ACK
;
4008 ret
= mspCommonProcessInCommand(srcDesc
, cmdMSP
, src
, mspPostProcessFn
);
4010 reply
->result
= ret
;
4014 void mspFcProcessReply(mspPacket_t
*reply
)
4016 sbuf_t
*src
= &reply
->buf
;
4017 UNUSED(src
); // potentially unused depending on compile options.
4019 switch (reply
->cmd
) {
4022 uint8_t batteryVoltage
= sbufReadU8(src
);
4023 uint16_t mAhDrawn
= sbufReadU16(src
);
4024 uint16_t rssi
= sbufReadU16(src
);
4025 uint16_t amperage
= sbufReadU16(src
);
4028 UNUSED(batteryVoltage
);
4032 #ifdef USE_MSP_CURRENT_METER
4033 currentMeterMSPSet(amperage
, mAhDrawn
);