Merge pull request #11500 from klutvott123/crsf-baud-negotiation-improvements-2
[betaflight.git] / src / main / telemetry / msp_shared.c
blob05fada4cb24172e8d6a8c9c8e9253df66b564dca
1 /*
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)
8 * any later version.
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/>.
21 #include <stdbool.h>
22 #include <stdint.h>
23 #include <string.h>
25 #include "platform.h"
27 #if defined(USE_MSP_OVER_TELEMETRY)
29 #include "build/build_config.h"
31 #include "common/utils.h"
32 #include "common/crc.h"
33 #include "common/streambuf.h"
35 #include "msp/msp.h"
36 #include "msp/msp_protocol.h"
37 #include "msp/msp_serial.h"
39 #include "telemetry/crsf.h"
40 #include "telemetry/msp_shared.h"
41 #include "telemetry/smartport.h"
45 ---------------------------------------------------------------
46 How MSP frames are sent over CRSF:
47 CRSF frame types: 0x7A for MSP requests, 0x7B for responses.
48 CRSF extended header frames are used. i.e., Destination and origin addresses added after CRSF type.
49 CRSF frame structure:
50 <sync/address><length><type><destination><origin><status><MSP_body><CRC>
51 Status byte consists of three parts:
52 bits 0-3 represent the sequence number of the MSP frame,
53 bit 4 checks if current MSP chunk is the beginning of a new frame (1 if true),
54 bits 5-6 represent the version number of MSP protocol (MSPv1 or MSPv2)
55 bit 7 represents Error (1 if there was an error)
56 MSP_body is unmodified MSP frame without header ($ + M|X + <|>|!) and CRC.
57 MSP might be MSPv1 or MSPv2 or MSPv1_Jumbo.
59 MSP_body might be sent in chunks.
60 First (or only) chunk must always set start bit (#4) of status byte.
61 Each next chunk must have increased sequence number in status byte.
62 Size of chunk is recovered from size of CRSF frame.
63 Although last / only CRSF frame might have size bigger than needed for MSP-body.
64 Extra bytes must be ignored. So, the real size of MSP-body must be parsed from the MSP-body itself.
65 CRSF frames might be any size until maximum of 64 bytes for a CRSF frame.
66 So, maximum chunk size is 57 bytes. Although, MSP-body might be sent in shorter chunks.
67 Although, first chunk must consist full size any type of the MSP frame.
69 MSP-CRC is not sent over CRSF due to ther is already CRC of CRSF frame.
70 So, it must be recalculated of needed for MSP-receiver.
72 MSP frame must be returned to the origin address of the request
74 ---------------------------------------------------------------
76 #define TELEMETRY_MSP_VERSION 2
77 #define TELEMETRY_MSP_RES_ERROR (-10)
79 #define TELEMETRY_REQUEST_SKIPS_AFTER_EEPROMWRITE 5
81 enum { // constants for status of msp-over-telemetry frame
82 MSP_STATUS_SEQUENCE_MASK = 0x0f, // 0b00001111, // sequence number mask
83 MSP_STATUS_START_MASK = 0x10, // 0b00010000, // bit of starting frame (if 1, the frame is a first/single chunk of msp-frame)
84 MSP_STATUS_VERSION_MASK = 0x60, // 0b01100000, // MSP version mask
85 MSP_STATUS_ERROR_MASK = 0x80, // 0b10000000, // Error bit (1 if error)
86 MSP_STATUS_VERSION_SHIFT = 5, // MSP version shift
89 enum { // error codes (they are not sent anywhere)
90 TELEMETRY_MSP_VER_MISMATCH,
91 TELEMETRY_MSP_CRC_ERROR,
92 TELEMETRY_MSP_ERROR,
93 TELEMETRY_MSP_REQUEST_IS_TOO_BIG,
96 enum { // minimum length for a frame.
97 MIN_LENGTH_CHUNK = 2, // status + at_least_one_byte
98 MIN_LENGTH_REQUEST_V1 = 3, // status + length + ID
99 MIN_LENGTH_REQUEST_JUMBO = 5, // status + length=FF + ID + length_lo + length_hi
100 MIN_LENGTH_REQUEST_V2 = 6, // status + flag + ID_lo + ID_hi + size_lo + size_hi
103 enum { // byte position(index) in msp-over-telemetry request payload
104 // MSPv1
105 MSP_INDEX_STATUS = 0, // status byte
106 MSP_INDEX_SIZE_V1 = MSP_INDEX_STATUS + 1, // MSPv1 payload size
107 MSP_INDEX_ID_V1 = MSP_INDEX_SIZE_V1 + 1, // MSPv1 ID/command/function byte
108 MSP_INDEX_PAYLOAD_V1 = MSP_INDEX_ID_V1 + 1, // MSPv1 Payload start / CRC for zero payload
110 // MSPv1_Jumbo
111 MSP_INDEX_SIZE_JUMBO_LO = MSP_INDEX_PAYLOAD_V1, // MSPv1_Jumbo Lo byte of payload size
112 MSP_INDEX_SIZE_JUMBO_HI = MSP_INDEX_SIZE_JUMBO_LO + 1, // MSPv1_Jumbo Hi byte of payload size
113 MSP_INDEX_PAYLOAD_JUMBO = MSP_INDEX_SIZE_JUMBO_HI + 1, // MSPv1_Jumbo first byte of payload itself
115 // MSPv2
116 MSP_INDEX_FLAG_V2 = MSP_INDEX_SIZE_V1, // MSPv2 flags byte
117 MSP_INDEX_ID_LO = MSP_INDEX_ID_V1, // MSPv2 Lo byte of ID/command/function
118 MSP_INDEX_ID_HI = MSP_INDEX_ID_LO + 1, // MSPv2 Hi byte of ID/command/function
119 MSP_INDEX_SIZE_V2_LO = MSP_INDEX_ID_HI + 1, // MSPv2 Lo byte of payload size
120 MSP_INDEX_SIZE_V2_HI = MSP_INDEX_SIZE_V2_LO + 1, // MSPv2 Hi byte of payload size
121 MSP_INDEX_PAYLOAD_V2 = MSP_INDEX_SIZE_V2_HI + 1, // MSPv2 first byte of payload itself
124 STATIC_UNIT_TESTED uint8_t requestBuffer[MSP_TLM_INBUF_SIZE];
125 STATIC_UNIT_TESTED uint8_t responseBuffer[MSP_TLM_OUTBUF_SIZE];
126 STATIC_UNIT_TESTED mspPacket_t requestPacket;
127 STATIC_UNIT_TESTED mspPacket_t responsePacket;
128 static uint8_t lastRequestVersion; // MSP version of last request. Temporary solution. It's better to keep it in requestPacket.
130 static mspDescriptor_t mspSharedDescriptor;
132 void initSharedMsp(void)
134 responsePacket.buf.ptr = responseBuffer;
135 responsePacket.buf.end = ARRAYEND(responseBuffer);
137 mspSharedDescriptor = mspDescriptorAlloc();
140 static void processMspPacket(void)
142 responsePacket.cmd = 0;
143 responsePacket.result = 0;
144 responsePacket.buf.ptr = responseBuffer;
145 responsePacket.buf.end = ARRAYEND(responseBuffer);
147 mspPostProcessFnPtr mspPostProcessFn = NULL;
148 if (mspFcProcessCommand(mspSharedDescriptor, &requestPacket, &responsePacket, &mspPostProcessFn) == MSP_RESULT_ERROR) {
149 sbufWriteU8(&responsePacket.buf, TELEMETRY_MSP_ERROR);
151 if (mspPostProcessFn) {
152 mspPostProcessFn(NULL);
155 sbufSwitchToReader(&responsePacket.buf, responseBuffer);
158 void sendMspErrorResponse(uint8_t error, int16_t cmd)
160 responsePacket.cmd = cmd;
161 responsePacket.result = 0;
162 responsePacket.buf.ptr = responseBuffer;
164 sbufWriteU8(&responsePacket.buf, error);
165 responsePacket.result = TELEMETRY_MSP_RES_ERROR;
166 sbufSwitchToReader(&responsePacket.buf, responseBuffer);
169 // despite its name, the function actually handles telemetry frame payload with MSP in it
170 // it reads the MSP into requestPacket stucture and handles it after receiving all the chunks.
171 bool handleMspFrame(uint8_t *const payload, uint8_t const payloadLength, uint8_t *const skipsBeforeResponse)
173 if (payloadLength < MIN_LENGTH_CHUNK) {
174 return false; // prevent analyzing garbage data
177 static uint8_t mspStarted = 0;
178 static uint8_t lastSeq = 0;
180 sbuf_t sbufInput;
182 const uint8_t status = payload[MSP_INDEX_STATUS];
183 const uint8_t seqNumber = status & MSP_STATUS_SEQUENCE_MASK;
184 lastRequestVersion = (status & MSP_STATUS_VERSION_MASK) >> MSP_STATUS_VERSION_SHIFT;
186 if (lastRequestVersion > TELEMETRY_MSP_VERSION) {
187 sendMspErrorResponse(TELEMETRY_MSP_VER_MISMATCH, 0);
188 return true;
191 if (status & MSP_STATUS_START_MASK) { // first packet in sequence
192 uint16_t mspPayloadSize;
193 if (lastRequestVersion == 1) { // MSPv1
194 if (payloadLength < MIN_LENGTH_REQUEST_V1) {
195 return false; // prevent analyzing garbage data
198 mspPayloadSize = payload[MSP_INDEX_SIZE_V1];
199 requestPacket.cmd = payload[MSP_INDEX_ID_V1];
200 if (mspPayloadSize == 0xff) { // jumbo frame
201 if (payloadLength < MIN_LENGTH_REQUEST_JUMBO) {
202 return false; // prevent analyzing garbage data
204 mspPayloadSize = *(uint16_t*)&payload[MSP_INDEX_SIZE_JUMBO_LO];
205 sbufInit(&sbufInput, payload + MSP_INDEX_PAYLOAD_JUMBO, payload + payloadLength);
206 } else {
207 sbufInit(&sbufInput, payload + MSP_INDEX_PAYLOAD_V1, payload + payloadLength);
209 } else { // MSPv2
210 if (payloadLength < MIN_LENGTH_REQUEST_V2) {
211 return false; // prevent analyzing garbage data
213 requestPacket.flags = payload[MSP_INDEX_FLAG_V2];
214 requestPacket.cmd = *(uint16_t*)&payload[MSP_INDEX_ID_LO];
215 mspPayloadSize = *(uint16_t*)&payload[MSP_INDEX_SIZE_V2_LO];
216 sbufInit(&sbufInput, payload + MSP_INDEX_PAYLOAD_V2, payload + payloadLength);
218 if (mspPayloadSize <= sizeof(requestBuffer)) { // prevent buffer overrun
219 requestPacket.result = 0;
220 requestPacket.buf.ptr = requestBuffer;
221 requestPacket.buf.end = requestBuffer + mspPayloadSize;
222 mspStarted = 1;
223 } else { // this MSP packet is too big to fit in the buffer.
224 sendMspErrorResponse(TELEMETRY_MSP_REQUEST_IS_TOO_BIG, requestPacket.cmd);
225 return true;
227 } else { // second onward chunk
228 if (!mspStarted) { // no start packet yet, throw this one away
229 return false;
230 } else {
231 if (((lastSeq + 1) & MSP_STATUS_SEQUENCE_MASK) != seqNumber) {
232 // packet loss detected!
233 mspStarted = 0;
234 return false;
237 sbufInit(&sbufInput, payload + 1, payload + payloadLength);
240 lastSeq = seqNumber;
242 const int payloadExpecting = sbufBytesRemaining(&requestPacket.buf);
243 const int payloadIncoming = sbufBytesRemaining(&sbufInput);
245 if (payloadExpecting > payloadIncoming) {
246 sbufWriteData(&requestPacket.buf, sbufInput.ptr, payloadIncoming);
247 sbufAdvance(&sbufInput, payloadIncoming);
248 return false;
249 } else { // this is the last/only chunk
250 if (payloadExpecting) {
251 sbufWriteData(&requestPacket.buf, sbufInput.ptr, payloadExpecting);
252 sbufAdvance(&sbufInput, payloadExpecting);
256 // Skip a few telemetry requests if command is MSP_EEPROM_WRITE
257 if (requestPacket.cmd == MSP_EEPROM_WRITE && skipsBeforeResponse) {
258 *skipsBeforeResponse = TELEMETRY_REQUEST_SKIPS_AFTER_EEPROMWRITE;
261 mspStarted = 0;
262 sbufSwitchToReader(&requestPacket.buf, requestBuffer);
263 processMspPacket();
264 return true;
267 bool sendMspReply(const uint8_t payloadSizeMax, mspResponseFnPtr responseFn)
269 static uint8_t seq = 0;
271 uint8_t payloadArray[payloadSizeMax];
272 sbuf_t payloadBufStruct;
273 sbuf_t *payloadBuf = sbufInit(&payloadBufStruct, payloadArray, payloadArray + payloadSizeMax);
275 // detect first reply packet
276 if (responsePacket.buf.ptr == responseBuffer) {
277 // this is the first frame of the response packet. Add proper header and size.
278 // header
279 uint8_t status = MSP_STATUS_START_MASK | (seq++ & MSP_STATUS_SEQUENCE_MASK) | (lastRequestVersion << MSP_STATUS_VERSION_SHIFT);
280 if (responsePacket.result < 0) {
281 status |= MSP_STATUS_ERROR_MASK;
283 sbufWriteU8(payloadBuf, status);
285 const int size = sbufBytesRemaining(&responsePacket.buf); // size might be bigger than 0xff
286 if (lastRequestVersion == 1) { // MSPv1
287 if (size >= 0xff) {
288 // Sending Jumbo-frame
289 sbufWriteU8(payloadBuf, 0xff);
290 sbufWriteU8(payloadBuf, responsePacket.cmd);
291 sbufWriteU16(payloadBuf, (uint16_t)size);
292 } else {
293 sbufWriteU8(payloadBuf, size);
294 sbufWriteU8(payloadBuf, responsePacket.cmd);
296 } else { // MSPv2
297 sbufWriteU8 (payloadBuf, responsePacket.flags); // MSPv2 flags
298 sbufWriteU16(payloadBuf, responsePacket.cmd); // command is 16 bit in MSPv2
299 sbufWriteU16(payloadBuf, (uint16_t)size); // size is 16 bit in MSPv2
301 } else {
302 sbufWriteU8(payloadBuf, (seq++ & MSP_STATUS_SEQUENCE_MASK) | (lastRequestVersion << MSP_STATUS_VERSION_SHIFT)); // header without 'start' flag
305 const int inputRemainder = sbufBytesRemaining(&responsePacket.buf);// size might be bigger than 0xff
306 const int chunkRemainder = sbufBytesRemaining(payloadBuf); // free space remainder for current chunk
308 if (inputRemainder >= chunkRemainder) {
309 // partial send
310 sbufWriteData(payloadBuf, responsePacket.buf.ptr, chunkRemainder);
311 sbufAdvance(&responsePacket.buf, chunkRemainder);
312 responseFn(payloadArray, payloadSizeMax);
313 return true;
315 // last/only chunk
316 sbufWriteData(payloadBuf, responsePacket.buf.ptr, inputRemainder);
317 sbufAdvance(&responsePacket.buf, inputRemainder);
318 sbufSwitchToReader(&responsePacket.buf, responseBuffer);// for CRC calculation
320 responseFn(payloadArray, payloadBuf->ptr - payloadArray);
321 return false;
324 #endif