2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
24 #include "common/crc.h"
25 #include "common/maths.h"
26 #include "common/streambuf.h"
27 #include "common/utils.h"
29 #include "config/parameter_group.h"
30 #include "config/parameter_group_ids.h"
32 #include "drivers/time.h"
34 #include "io/serial.h"
40 #define RCDEVICE_INIT_DEVICE_ATTEMPTS 6
41 #define RCDEVICE_INIT_DEVICE_ATTEMPT_INTERVAL 1000
43 typedef struct runcamDeviceExpectedResponseLength_s
{
45 uint8_t reponseLength
;
46 } runcamDeviceExpectedResponseLength_t
;
48 static runcamDeviceExpectedResponseLength_t expectedResponsesLength
[] = {
49 { RCDEVICE_PROTOCOL_COMMAND_GET_DEVICE_INFO
, 5},
50 { RCDEVICE_PROTOCOL_COMMAND_5KEY_SIMULATION_PRESS
, 2},
51 { RCDEVICE_PROTOCOL_COMMAND_5KEY_SIMULATION_RELEASE
, 2},
52 { RCDEVICE_PROTOCOL_COMMAND_5KEY_CONNECTION
, 3},
55 rcdeviceWaitingResponseQueue watingResponseQueue
;
56 static uint8_t recvBuf
[RCDEVICE_PROTOCOL_MAX_PACKET_SIZE
]; // all the response contexts using same recv buffer
58 static uint8_t runcamDeviceGetRespLen(uint8_t command
)
60 for (unsigned int i
= 0; i
< ARRAYLEN(expectedResponsesLength
); i
++) {
61 if (expectedResponsesLength
[i
].command
== command
) {
62 return expectedResponsesLength
[i
].reponseLength
;
69 static bool rcdeviceRespCtxQueuePush(rcdeviceWaitingResponseQueue
*queue
, rcdeviceResponseParsingContext_t
*respCtx
)
71 if (queue
== NULL
|| (queue
->itemCount
+ 1) > MAX_WAITING_RESPONSES
) {
75 queue
->buffer
[queue
->tailPos
] = *respCtx
;
77 int newTailPos
= queue
->tailPos
+ 1;
78 if (newTailPos
>= MAX_WAITING_RESPONSES
) {
81 queue
->itemCount
+= 1;
82 queue
->tailPos
= newTailPos
;
87 static rcdeviceResponseParsingContext_t
* rcdeviceRespCtxQueuePeekFront(rcdeviceWaitingResponseQueue
*queue
)
89 if (queue
== NULL
|| queue
->itemCount
== 0) {
93 rcdeviceResponseParsingContext_t
*ctx
= &queue
->buffer
[queue
->headPos
];
97 STATIC_UNIT_TESTED rcdeviceResponseParsingContext_t
* rcdeviceRespCtxQueueShift(rcdeviceWaitingResponseQueue
*queue
)
99 if (queue
== NULL
|| queue
->itemCount
== 0) {
103 rcdeviceResponseParsingContext_t
*ctx
= &queue
->buffer
[queue
->headPos
];
104 int newHeadPos
= queue
->headPos
+ 1;
105 if (newHeadPos
>= MAX_WAITING_RESPONSES
) {
108 queue
->itemCount
-= 1;
109 queue
->headPos
= newHeadPos
;
114 // every time send packet to device, and want to get something from device,
115 // it'd better call the method to clear the rx buffer before the packet send,
116 // else may be the useless data in rx buffer will cause the response decoding
118 static void runcamDeviceFlushRxBuffer(runcamDevice_t
*device
)
120 while (serialRxBytesWaiting(device
->serialPort
) > 0) {
121 serialRead(device
->serialPort
);
125 // a common way to send packet to device
126 static void runcamDeviceSendPacket(runcamDevice_t
*device
, uint8_t command
, uint8_t *paramData
, int paramDataLen
)
128 // is this device open?
129 if (!device
->serialPort
) {
135 buf
.ptr
= device
->buffer
;
136 buf
.end
= ARRAYEND(device
->buffer
);
138 sbufWriteU8(&buf
, RCDEVICE_PROTOCOL_HEADER
);
139 sbufWriteU8(&buf
, command
);
141 if (paramData
&& paramDataLen
<= RCDEVICE_PROTOCOL_MAX_DATA_SIZE
) {
142 sbufWriteData(&buf
, paramData
, paramDataLen
);
145 // add crc over (all) data
146 crc8_dvb_s2_sbuf_append(&buf
, device
->buffer
);
149 sbufSwitchToReader(&buf
, device
->buffer
);
151 // send data if possible
152 serialWriteBuf(device
->serialPort
, device
->buffer
, sbufBytesRemaining(&buf
));
155 // a common way to send a packet to device, and get response from the device.
156 static void runcamDeviceSendRequestAndWaitingResp(runcamDevice_t
*device
, uint8_t commandID
, uint8_t *paramData
, uint8_t paramDataLen
, timeMs_t tiemout
, int maxRetryTimes
, void *userInfo
, rcdeviceResponseCallback parseFunc
)
158 // runcamDeviceFlushRxBuffer(device);
160 rcdeviceResponseParsingContext_t responseCtx
;
161 memset(&responseCtx
, 0, sizeof(rcdeviceResponseParsingContext_t
));
162 responseCtx
.recvBuf
= recvBuf
;
163 responseCtx
.command
= commandID
;
164 responseCtx
.maxRetryTimes
= maxRetryTimes
;
165 responseCtx
.expectedRespLen
= runcamDeviceGetRespLen(commandID
);
166 responseCtx
.timeout
= tiemout
;
167 responseCtx
.timeoutTimestamp
= millis() + tiemout
;
168 responseCtx
.parserFunc
= parseFunc
;
169 responseCtx
.device
= device
;
170 responseCtx
.protocolVersion
= RCDEVICE_PROTOCOL_VERSION_1_0
;
171 if (paramData
!= NULL
&& paramDataLen
<= RCDEVICE_PROTOCOL_MAX_DATA_SIZE
) {
172 memcpy(responseCtx
.paramData
, paramData
, paramDataLen
);
173 responseCtx
.paramDataLen
= paramDataLen
;
175 responseCtx
.userInfo
= userInfo
;
176 rcdeviceRespCtxQueuePush(&watingResponseQueue
, &responseCtx
);
179 runcamDeviceSendPacket(device
, commandID
, paramData
, paramDataLen
);
182 static void runcamDeviceParseV1DeviceInfo(rcdeviceResponseParsingContext_t
*ctx
)
184 if (ctx
->result
!= RCDEVICE_RESP_SUCCESS
) {
188 runcamDevice_t
*device
= ctx
->device
;
189 device
->info
.protocolVersion
= RCDEVICE_PROTOCOL_RCSPLIT_VERSION
;
190 device
->info
.features
= RCDEVICE_PROTOCOL_FEATURE_SIMULATE_POWER_BUTTON
| RCDEVICE_PROTOCOL_FEATURE_SIMULATE_WIFI_BUTTON
| RCDEVICE_PROTOCOL_FEATURE_CHANGE_MODE
;
191 device
->isReady
= true;
194 static uint8_t crc8HighFirst(uint8_t *ptr
, uint8_t len
)
199 for (unsigned i
= 8; i
> 0; --i
) {
201 crc
= (crc
<< 1) ^ 0x31;
209 // for the rcsplits that firmware <= 1.1.0
210 static void runcamSplitSendCommand(runcamDevice_t
*device
, uint8_t argument
)
212 if (!device
->serialPort
) {
216 uint8_t uart_buffer
[5] = {0};
219 uart_buffer
[0] = RCSPLIT_PACKET_HEADER
;
220 uart_buffer
[1] = RCSPLIT_PACKET_CMD_CTRL
;
221 uart_buffer
[2] = argument
;
222 uart_buffer
[3] = RCSPLIT_PACKET_TAIL
;
223 crc
= crc8HighFirst(uart_buffer
, 4);
225 // build up a full request [header]+[command]+[argument]+[crc]+[tail]
226 uart_buffer
[3] = crc
;
227 uart_buffer
[4] = RCSPLIT_PACKET_TAIL
;
230 serialWriteBuf(device
->serialPort
, uart_buffer
, 5);
233 static void runcamDeviceSendV1Initialize(runcamDevice_t
*device
)
235 runcamDeviceFlushRxBuffer(device
);
237 rcdeviceResponseParsingContext_t responseCtx
;
238 memset(&responseCtx
, 0, sizeof(rcdeviceResponseParsingContext_t
));
239 responseCtx
.recvBuf
= recvBuf
;
240 responseCtx
.command
= 0xFF;
241 responseCtx
.maxRetryTimes
= RCDEVICE_INIT_DEVICE_ATTEMPTS
;
242 responseCtx
.expectedRespLen
= 5;
243 responseCtx
.timeout
= RCDEVICE_INIT_DEVICE_ATTEMPT_INTERVAL
;
244 responseCtx
.timeoutTimestamp
= millis() + RCDEVICE_INIT_DEVICE_ATTEMPT_INTERVAL
;
245 responseCtx
.parserFunc
= runcamDeviceParseV1DeviceInfo
;
246 responseCtx
.device
= device
;
247 responseCtx
.protocolVersion
= RCDEVICE_PROTOCOL_RCSPLIT_VERSION
;
248 rcdeviceRespCtxQueuePush(&watingResponseQueue
, &responseCtx
);
250 runcamSplitSendCommand(device
, 0xFF);
253 static void runcamDeviceParseV2DeviceInfo(rcdeviceResponseParsingContext_t
*ctx
)
255 if (ctx
->result
!= RCDEVICE_RESP_SUCCESS
) {
256 runcamDeviceSendV1Initialize(ctx
->device
);
260 runcamDevice_t
*device
= ctx
->device
;
261 device
->info
.protocolVersion
= ctx
->recvBuf
[1];
263 uint8_t featureLowBits
= ctx
->recvBuf
[2];
264 uint8_t featureHighBits
= ctx
->recvBuf
[3];
265 device
->info
.features
= (featureHighBits
<< 8) | featureLowBits
;
266 device
->isReady
= true;
269 // get the device info(firmware version, protocol version and features, see the
270 // definition of runcamDeviceInfo_t to know more)
271 static void runcamDeviceGetDeviceInfo(runcamDevice_t
*device
)
273 runcamDeviceSendRequestAndWaitingResp(device
, RCDEVICE_PROTOCOL_COMMAND_GET_DEVICE_INFO
, NULL
, 0, RCDEVICE_INIT_DEVICE_ATTEMPT_INTERVAL
, RCDEVICE_INIT_DEVICE_ATTEMPTS
, NULL
, runcamDeviceParseV2DeviceInfo
);
276 // init the runcam device, it'll search the UART port with FUNCTION_RCDEVICE id
277 // this function will delay 3 seconds to wait the device prepared(special for runcam split)
278 void runcamDeviceInit(runcamDevice_t
*device
)
280 device
->isReady
= false;
281 serialPortFunction_e portID
= FUNCTION_RCDEVICE
;
282 serialPortConfig_t
*portConfig
= findSerialPortConfig(portID
);
283 if (portConfig
!= NULL
) {
284 device
->serialPort
= openSerialPort(portConfig
->identifier
, portID
, NULL
, NULL
, baudRates
[BAUD_115200
], MODE_RXTX
, SERIAL_NOT_INVERTED
);
285 // device->info.protocolVersion = rcdeviceConfig()->protocolVersion;
286 device
->info
.protocolVersion
= RCDEVICE_PROTOCOL_VERSION_1_0
;
287 if (device
->serialPort
!= NULL
) {
288 runcamDeviceGetDeviceInfo(device
);
293 bool runcamDeviceSimulateCameraButton(runcamDevice_t
*device
, uint8_t operation
)
295 if (device
->info
.protocolVersion
== RCDEVICE_PROTOCOL_VERSION_1_0
) {
296 runcamDeviceSendPacket(device
, RCDEVICE_PROTOCOL_COMMAND_CAMERA_CONTROL
, &operation
, sizeof(operation
));
297 } else if (device
->info
.protocolVersion
== RCDEVICE_PROTOCOL_RCSPLIT_VERSION
) {
298 runcamSplitSendCommand(device
, operation
+ 1);
306 // every time start to control the OSD menu of camera, must call this method to
308 void runcamDeviceOpen5KeyOSDCableConnection(runcamDevice_t
*device
, rcdeviceResponseCallback callback
)
310 uint8_t operation
= RCDEVICE_PROTOCOL_5KEY_CONNECTION_OPEN
;
311 runcamDeviceSendRequestAndWaitingResp(device
, RCDEVICE_PROTOCOL_COMMAND_5KEY_CONNECTION
, &operation
, sizeof(uint8_t), 400, 2, NULL
, callback
);
314 // when the control was stop, must call this method to the camera to disconnect
316 void runcamDeviceClose5KeyOSDCableConnection(runcamDevice_t
*device
, rcdeviceResponseCallback callback
)
318 uint8_t operation
= RCDEVICE_PROTOCOL_5KEY_CONNECTION_CLOSE
;
319 runcamDeviceSendRequestAndWaitingResp(device
, RCDEVICE_PROTOCOL_COMMAND_5KEY_CONNECTION
, &operation
, sizeof(uint8_t), 400, 2, NULL
, callback
);
322 // simulate button press event of 5 key osd cable with special button
323 void runcamDeviceSimulate5KeyOSDCableButtonPress(runcamDevice_t
*device
, uint8_t operation
, rcdeviceResponseCallback callback
)
325 if (operation
== RCDEVICE_PROTOCOL_5KEY_SIMULATION_NONE
) {
329 runcamDeviceSendRequestAndWaitingResp(device
, RCDEVICE_PROTOCOL_COMMAND_5KEY_SIMULATION_PRESS
, &operation
, sizeof(uint8_t), 400, 2, NULL
, callback
);
332 // simulate button release event of 5 key osd cable
333 void runcamDeviceSimulate5KeyOSDCableButtonRelease(runcamDevice_t
*device
, rcdeviceResponseCallback callback
)
335 runcamDeviceSendRequestAndWaitingResp(device
, RCDEVICE_PROTOCOL_COMMAND_5KEY_SIMULATION_RELEASE
, NULL
, 0, 400, 2, NULL
, callback
);
338 static rcdeviceResponseParsingContext_t
* getWaitingResponse(timeMs_t currentTimeMs
)
340 rcdeviceResponseParsingContext_t
*respCtx
= rcdeviceRespCtxQueuePeekFront(&watingResponseQueue
);
341 while (respCtx
!= NULL
&& respCtx
->timeoutTimestamp
!= 0 && currentTimeMs
> respCtx
->timeoutTimestamp
) {
342 if (respCtx
->maxRetryTimes
> 0) {
343 if (respCtx
->protocolVersion
== RCDEVICE_PROTOCOL_VERSION_1_0
) {
344 runcamDeviceSendPacket(respCtx
->device
, respCtx
->command
, respCtx
->paramData
, respCtx
->paramDataLen
);
345 } else if (respCtx
->protocolVersion
== RCDEVICE_PROTOCOL_RCSPLIT_VERSION
) {
346 runcamSplitSendCommand(respCtx
->device
, respCtx
->command
);
349 respCtx
->recvRespLen
= 0;
350 respCtx
->timeoutTimestamp
= currentTimeMs
+ respCtx
->timeout
;
351 respCtx
->maxRetryTimes
-= 1;
355 respCtx
->result
= RCDEVICE_RESP_TIMEOUT
;
356 if (respCtx
->parserFunc
!= NULL
) {
357 respCtx
->parserFunc(respCtx
);
360 // dequeue and get next waiting response context
361 rcdeviceRespCtxQueueShift(&watingResponseQueue
);
362 respCtx
= rcdeviceRespCtxQueuePeekFront(&watingResponseQueue
);
369 void rcdeviceReceive(timeUs_t currentTimeUs
)
371 UNUSED(currentTimeUs
);
373 rcdeviceResponseParsingContext_t
*respCtx
= NULL
;
374 while ((respCtx
= getWaitingResponse(millis())) != NULL
) {
375 if (!serialRxBytesWaiting(respCtx
->device
->serialPort
)) {
379 const uint8_t c
= serialRead(respCtx
->device
->serialPort
);
380 if (respCtx
->recvRespLen
== 0) {
381 // Only start receiving packet when we found a header
382 if ((respCtx
->protocolVersion
== RCDEVICE_PROTOCOL_VERSION_1_0
&& c
!= RCDEVICE_PROTOCOL_HEADER
) || (respCtx
->protocolVersion
== RCDEVICE_PROTOCOL_RCSPLIT_VERSION
&& c
!= RCSPLIT_PACKET_HEADER
)) {
387 respCtx
->recvBuf
[respCtx
->recvRespLen
] = c
;
388 respCtx
->recvRespLen
+= 1;
390 // if data received done, trigger callback to parse response data, and update rcdevice state
391 if (respCtx
->recvRespLen
== respCtx
->expectedRespLen
) {
392 if (respCtx
->protocolVersion
== RCDEVICE_PROTOCOL_VERSION_1_0
) {
394 for (int i
= 0; i
< respCtx
->recvRespLen
; i
++) {
395 crc
= crc8_dvb_s2(crc
, respCtx
->recvBuf
[i
]);
398 respCtx
->result
= (crc
== 0) ? RCDEVICE_RESP_SUCCESS
: RCDEVICE_RESP_INCORRECT_CRC
;
399 } else if (respCtx
->protocolVersion
== RCDEVICE_PROTOCOL_RCSPLIT_VERSION
) {
400 if (respCtx
->recvBuf
[0] == RCSPLIT_PACKET_HEADER
&& respCtx
->recvBuf
[1] == RCSPLIT_PACKET_CMD_CTRL
&& respCtx
->recvBuf
[2] == 0xFF && respCtx
->recvBuf
[4] == RCSPLIT_PACKET_TAIL
) {
401 uint8_t crcFromPacket
= respCtx
->recvBuf
[3];
402 respCtx
->recvBuf
[3] = respCtx
->recvBuf
[4]; // move packet tail field to crc field, and calc crc with first 4 bytes
403 uint8_t crc
= crc8HighFirst(respCtx
->recvBuf
, 4);
404 respCtx
->result
= crc
== crcFromPacket
? RCDEVICE_RESP_SUCCESS
: RCDEVICE_RESP_INCORRECT_CRC
;
406 respCtx
->result
= RCDEVICE_RESP_INCORRECT_CRC
;
410 if (respCtx
->parserFunc
!= NULL
) {
411 respCtx
->parserFunc(respCtx
);
414 if (respCtx
->result
== RCDEVICE_RESP_SUCCESS
) {
415 rcdeviceRespCtxQueueShift(&watingResponseQueue
);