updates
[inav.git] / src / main / io / frsky_osd.c
bloba69df4e104c4cba6ecf885c85f6b70c17f0c26d9
1 #include <stddef.h>
2 #include <stdint.h>
3 #include <string.h>
5 #include "platform.h"
7 #if defined(USE_OSD) && defined(USE_FRSKYOSD)
9 #include "common/crc.h"
10 #include "common/log.h"
11 #include "common/maths.h"
12 #include "common/time.h"
13 #include "common/utils.h"
14 #include "common/uvarint.h"
16 #include "drivers/time.h"
18 #include "io/frsky_osd.h"
19 #include "io/serial.h"
21 #define FRSKY_OSD_DEFAULT_BAUDRATE_INDEX BAUD_115200
22 #define FRSKY_OSD_SUPPORTED_API_VERSION 2
24 #define FRSKY_OSD_PREAMBLE_BYTE_0 '$'
25 #define FRSKY_OSD_PREAMBLE_BYTE_1 'A'
27 #define FRSKY_OSD_GRID_BUFFER_CHAR_BITS 9
28 #define FRSKY_OSD_GRID_BUFFER_CHAR_MASK ((1 << FRSKY_OSD_GRID_BUFFER_CHAR_BITS) - 1)
29 #define FRSKY_OSD_GRID_BUFFER_ENCODE(chr, attr) ((chr & FRSKY_OSD_GRID_BUFFER_CHAR_MASK) | (attr << FRSKY_OSD_GRID_BUFFER_CHAR_BITS))
31 #define FRSKY_OSD_CHAR_ATTRIBUTE_COLOR_INVERSE (1 << 0)
32 #define FRSKY_OSD_CHAR_ATTRIBUTE_SOLID_BACKGROUND (1 << 1)
34 #define FRSKY_OSD_CHAR_DATA_BYTES 54
35 #define FRSKY_OSD_CHAR_METADATA_BYTES 10
36 #define FRSKY_OSD_CHAR_TOTAL_BYTES (FRSKY_OSD_CHAR_DATA_BYTES + FRSKY_OSD_CHAR_METADATA_BYTES)
38 #define FRSKY_OSD_SEND_BUFFER_SIZE 192
39 #define FRSKY_OSD_RECV_BUFFER_SIZE 128
41 #define FRSKY_OSD_CMD_RESPONSE_ERROR 0
43 #define FRSKY_OSD_INFO_INTERVAL_MS 100
44 #define FRSKY_OSD_INFO_READY_INTERVAL_MS 5000
46 #define FRSKY_OSD_TRACE(fmt, ...)
47 #define FRSKY_OSD_DEBUG(fmt, ...) LOG_DEBUG(OSD, "FrSky OSD: " fmt, ##__VA_ARGS__)
48 #define FRSKY_OSD_ERROR(fmt, ...) LOG_ERROR(OSD, "FrSky OSD: " fmt, ##__VA_ARGS__)
49 #define FRSKY_OSD_ASSERT(x)
51 typedef enum
53 OSD_CMD_RESPONSE_ERROR = 0,
55 OSD_CMD_INFO = 1,
56 OSD_CMD_READ_FONT = 2,
57 OSD_CMD_WRITE_FONT = 3,
58 OSD_CMD_GET_CAMERA = 4,
59 OSD_CMD_SET_CAMERA = 5,
60 OSD_CMD_GET_ACTIVE_CAMERA = 6,
61 OSD_CMD_GET_OSD_ENABLED = 7,
62 OSD_CMD_SET_OSD_ENABLED = 8,
64 OSD_CMD_TRANSACTION_BEGIN = 16,
65 OSD_CMD_TRANSACTION_COMMIT = 17,
66 OSD_CMD_TRANSACTION_BEGIN_PROFILED = 18,
67 OSD_CMD_TRANSACTION_BEGIN_RESET_DRAWING = 19,
69 OSD_CMD_DRAWING_SET_STROKE_COLOR = 22,
70 OSD_CMD_DRAWING_SET_FILL_COLOR = 23,
71 OSD_CMD_DRAWING_SET_STROKE_AND_FILL_COLOR = 24,
72 OSD_CMD_DRAWING_SET_COLOR_INVERSION = 25,
73 OSD_CMD_DRAWING_SET_PIXEL = 26,
74 OSD_CMD_DRAWING_SET_PIXEL_TO_STROKE_COLOR = 27,
75 OSD_CMD_DRAWING_SET_PIXEL_TO_FILL_COLOR = 28,
76 OSD_CMD_DRAWING_SET_STROKE_WIDTH = 29,
77 OSD_CMD_DRAWING_SET_LINE_OUTLINE_TYPE = 30,
78 OSD_CMD_DRAWING_SET_LINE_OUTLINE_COLOR = 31,
80 OSD_CMD_DRAWING_CLIP_TO_RECT = 40,
81 OSD_CMD_DRAWING_CLEAR_SCREEN = 41,
82 OSD_CMD_DRAWING_CLEAR_RECT = 42,
83 OSD_CMD_DRAWING_RESET = 43,
84 OSD_CMD_DRAWING_DRAW_BITMAP = 44,
85 OSD_CMD_DRAWING_DRAW_BITMAP_MASK = 45,
86 OSD_CMD_DRAWING_DRAW_CHAR = 46,
87 OSD_CMD_DRAWING_DRAW_CHAR_MASK = 47,
88 OSD_CMD_DRAWING_DRAW_STRING = 48,
89 OSD_CMD_DRAWING_DRAW_STRING_MASK = 49,
90 OSD_CMD_DRAWING_MOVE_TO_POINT = 50,
91 OSD_CMD_DRAWING_STROKE_LINE_TO_POINT = 51,
92 OSD_CMD_DRAWING_STROKE_TRIANGLE = 52,
93 OSD_CMD_DRAWING_FILL_TRIANGLE = 53,
94 OSD_CMD_DRAWING_FILL_STROKE_TRIANGLE = 54,
95 OSD_CMD_DRAWING_STROKE_RECT = 55,
96 OSD_CMD_DRAWING_FILL_RECT = 56,
97 OSD_CMD_DRAWING_FILL_STROKE_RECT = 57,
98 OSD_CMD_DRAWING_STROKE_ELLIPSE_IN_RECT = 58,
99 OSD_CMD_DRAWING_FILL_ELLIPSE_IN_RECT = 59,
100 OSD_CMD_DRAWING_FILL_STROKE_ELLIPSE_IN_RECT = 60,
102 OSD_CMD_CTM_RESET = 80,
103 OSD_CMD_CTM_SET = 81,
104 OSD_CMD_CTM_TRANSLATE = 82,
105 OSD_CMD_CTM_SCALE = 83,
106 OSD_CMD_CTM_ROTATE = 84,
107 OSD_CMD_CTM_ROTATE_ABOUT = 85,
108 OSD_CMD_CTM_SHEAR = 86,
109 OSD_CMD_CTM_SHEAR_ABOUT = 87,
110 OSD_CMD_CTM_MULTIPLY = 88,
112 OSD_CMD_CONTEXT_PUSH = 100,
113 OSD_CMD_CONTEXT_POP = 101,
115 // MAX7456 emulation commands
116 OSD_CMD_DRAW_GRID_CHR = 110,
117 OSD_CMD_DRAW_GRID_STR = 111,
118 OSD_CMD_DRAW_GRID_CHR_2 = 112, // API2
119 OSD_CMD_DRAW_GRID_STR_2 = 113, // API2
121 OSD_CMD_WIDGET_SET_CONFIG = 115, // API2
122 OSD_CMD_WIDGET_DRAW = 116, // API2
123 OSD_CMD_WIDGET_ERASE = 117, // API2
125 OSD_CMD_SET_DATA_RATE = 122,
126 } osdCommand_e;
128 typedef enum {
129 RECV_STATE_NONE,
130 RECV_STATE_SYNC,
131 RECV_STATE_LENGTH,
132 RECV_STATE_DATA,
133 RECV_STATE_CHECKSUM,
134 RECV_STATE_DONE,
135 } frskyOSDRecvState_e;
137 typedef struct frskyOSDInfoResponse_s {
138 uint8_t magic[3];
139 uint8_t versionMajor;
140 uint8_t versionMinor;
141 uint8_t versionPatch;
142 uint8_t gridRows;
143 uint8_t gridColumns;
144 uint16_t pixelWidth;
145 uint16_t pixelHeight;
146 uint8_t tvStandard;
147 uint8_t hasDetectedCamera;
148 uint16_t maxFrameSize;
149 uint8_t contextStackSize;
150 } __attribute__((packed)) frskyOSDInfoResponse_t;
152 typedef struct frskyOSDFontCharacter_s {
153 uint16_t addr;
154 struct {
155 uint8_t bitmap[FRSKY_OSD_CHAR_DATA_BYTES]; // 12x18 2bpp
156 uint8_t metadata[FRSKY_OSD_CHAR_METADATA_BYTES];
157 } data;
158 } __attribute__((packed)) frskyOSDCharacter_t;
160 typedef struct frskyOSDDrawGridCharCmd_s {
161 uint8_t gx;
162 uint8_t gy;
163 uint16_t chr;
164 uint8_t opts;
165 } __attribute__((packed)) frskyOSDDrawGridCharCmd_t;
167 typedef struct frskyOSDDrawGridStrHeaderCmd_s {
168 uint8_t gx;
169 uint8_t gy;
170 uint8_t opts;
171 // uvarint with size and blob follow
172 // string IS null-terminated
173 } __attribute__((packed)) frskyOSDDrawGridStrHeaderCmd_t;
175 typedef struct frskyOSDDrawGridStrV2HeaderCmd_s {
176 unsigned gx : 5; // +5 = 5
177 unsigned gy : 4; // +4 = 9
178 unsigned opts : 3; // +3 = 12
179 unsigned size : 4; // +4 = 16 = 2 bytes
180 // if size == 0, uvarint with size follows
181 // blob with the given size follows
182 // string IS NOT null terminated
183 } __attribute__((packed)) frskyOSDDrawGridStrV2HeaderCmd_t;
185 typedef struct frskyOSDTriangle_s {
186 frskyOSDPoint_t p1;
187 frskyOSDPoint_t p2;
188 frskyOSDPoint_t p3;
189 } __attribute__((packed)) frskyOSDTriangle_t;
191 typedef struct frskyOSDSetPixel_s {
192 frskyOSDPoint_t p;
193 uint8_t color;
194 } __attribute__((packed)) frskyOSDSetPixel_t;
196 typedef struct frskyOSDDrawCharacterCmd_s {
197 frskyOSDPoint_t p;
198 uint16_t chr;
199 uint8_t opts;
200 } __attribute__((packed)) frskyOSDDrawCharacterCmd_t;
202 typedef struct frskyOSDDrawCharacterMaskCmd_s {
203 frskyOSDDrawCharacterCmd_t dc;
204 uint8_t maskColor;
205 } __attribute__((packed)) frskyOSDDrawCharacterMaskCmd_t;
207 typedef struct frskyOSDDrawStrCommandHeaderCmd_s {
208 frskyOSDPoint_t p;
209 uint8_t opts;
210 // uvarint with size and blob follow
211 } __attribute__((packed)) frskyOSDDrawStrCommandHeaderCmd_t;
213 typedef struct frskyOSDDrawStrMaskCommandHeaderCmd_s {
214 frskyOSDPoint_t p;
215 uint8_t opts;
216 uint8_t maskColor;
217 // uvarint with size and blob follow
218 } __attribute__((packed)) frskyOSDDrawStrMaskCommandHeaderCmd_t;
220 typedef struct frskyOSDDrawGridChrV2Cmd_s
222 unsigned gx : 5; // +5 = 5
223 unsigned gy : 4; // +4 = 9
224 unsigned chr : 9; // +9 = 18
225 unsigned opts : 3; // +3 = 21, from osd_bitmap_opt_t
226 unsigned as_mask : 1; // +1 = 22
227 unsigned color : 2; // +2 = 24 = 3 bytes, only used when drawn as as_mask = 1
228 } __attribute__((packed)) frskyOSDDrawGridChrV2Cmd_t;
230 typedef struct frskyOSDError_s {
231 uint8_t command;
232 int8_t code;
233 } frskyOSDError_t;
236 typedef struct frskyOSDState_s {
237 struct {
238 uint8_t data[FRSKY_OSD_SEND_BUFFER_SIZE];
239 uint8_t pos;
240 } sendBuffer;
241 struct {
242 uint8_t state;
243 uint8_t crc;
244 uint16_t expected;
245 uint8_t expectedShift;
246 uint8_t data[FRSKY_OSD_RECV_BUFFER_SIZE];
247 uint8_t pos;
248 } recvBuffer;
249 struct {
250 uint8_t major;
251 uint8_t minor;
252 timeMs_t nextRequest;
253 struct {
254 uint8_t rows;
255 uint8_t columns;
256 } grid;
257 struct {
258 uint16_t width;
259 uint16_t height;
260 } viewport;
261 } info;
262 struct {
263 uint16_t addr;
264 osdCharacter_t *chr;
265 } recvOsdCharacter;
266 serialPort_t *port;
267 baudRate_e baudrate;
268 bool keepBaudrate;
269 bool initialized;
270 frskyOSDError_t error;
271 timeMs_t nextInfoRequest;
272 } frskyOSDState_t;
274 static frskyOSDState_t state;
276 static bool frskyOSDDispatchResponse(void);
278 static uint8_t frskyOSDChecksum(uint8_t crc, uint8_t c)
280 return crc8_dvb_s2(crc, c);
283 static bool frskyOSDSpeaksV2(void)
285 return state.info.major >= 2 || (state.info.major == 1 && state.info.minor >= 99);
288 static void frskyOSDResetReceiveBuffer(void)
290 state.recvBuffer.state = RECV_STATE_NONE;
291 state.recvBuffer.crc = 0;
292 state.recvBuffer.expected = 0;
293 state.recvBuffer.expectedShift = 0;
294 state.recvBuffer.pos = 0;
297 static void frskyOSDResetSendBuffer(void)
299 state.sendBuffer.pos = 0;
302 static void frskyOSDProcessCommandU8(uint8_t *crc, uint8_t c)
304 while (serialTxBytesFree(state.port) == 0) {
306 serialWrite(state.port, c);
307 if (crc) {
308 *crc = crc8_dvb_s2(*crc, c);
312 static void frskyOSDSendCommand(uint8_t cmd, const void *payload, size_t size)
314 int required = size + 1;
315 FRSKY_OSD_ASSERT(required <= sizeof(state.sendBuffer.data));
316 int rem = sizeof(state.sendBuffer.data) - state.sendBuffer.pos;
317 if (rem < required) {
318 frskyOSDFlushSendBuffer();
320 state.sendBuffer.data[state.sendBuffer.pos++] = cmd;
321 const uint8_t *ptr = payload;
322 for (size_t ii = 0; ii < size; ii++, ptr++) {
323 state.sendBuffer.data[state.sendBuffer.pos++] = *ptr;
327 static void frskyOSDStateReset(serialPort_t *port, baudRate_e baudrate)
329 frskyOSDResetReceiveBuffer();
330 frskyOSDResetSendBuffer();
331 state.info.grid.rows = 0;
332 state.info.grid.columns = 0;
333 state.info.viewport.width = 0;
334 state.info.viewport.height = 0;
336 state.port = port;
337 state.baudrate = baudrate;
338 state.keepBaudrate = false;
339 state.initialized = false;
342 static void frskyOSDUpdateReceiveBuffer(void)
344 while (serialRxBytesWaiting(state.port) > 0) {
345 uint8_t c = serialRead(state.port);
346 switch ((frskyOSDRecvState_e)state.recvBuffer.state) {
347 case RECV_STATE_NONE:
348 if (c != FRSKY_OSD_PREAMBLE_BYTE_0) {
349 break;
351 state.recvBuffer.state = RECV_STATE_SYNC;
352 break;
353 case RECV_STATE_SYNC:
354 if (c != FRSKY_OSD_PREAMBLE_BYTE_1) {
355 frskyOSDResetReceiveBuffer();
356 break;
358 state.recvBuffer.state = RECV_STATE_LENGTH;
359 break;
360 case RECV_STATE_LENGTH:
361 state.recvBuffer.crc = frskyOSDChecksum(state.recvBuffer.crc, c);
362 state.recvBuffer.expected |= (c & 0x7F) << state.recvBuffer.expectedShift;
363 state.recvBuffer.expectedShift += 7;
364 if (c < 0x80) {
365 // Full uvarint decoded. Check against buffer size.
366 if (state.recvBuffer.expected > sizeof(state.recvBuffer.data)) {
367 FRSKY_OSD_ERROR("Can't handle payload of size %u with a buffer of size %u",
368 state.recvBuffer.expected, (unsigned int)sizeof(state.recvBuffer.data));
369 frskyOSDResetReceiveBuffer();
370 break;
372 FRSKY_OSD_TRACE("Payload of size %u", state.recvBuffer.expected);
373 state.recvBuffer.state = state.recvBuffer.expected > 0 ? RECV_STATE_DATA : RECV_STATE_CHECKSUM;
375 break;
376 case RECV_STATE_DATA:
377 state.recvBuffer.data[state.recvBuffer.pos++] = c;
378 state.recvBuffer.crc = frskyOSDChecksum(state.recvBuffer.crc, c);
379 if (state.recvBuffer.pos == state.recvBuffer.expected) {
380 state.recvBuffer.state = RECV_STATE_CHECKSUM;
382 break;
383 case RECV_STATE_CHECKSUM:
384 if (c != state.recvBuffer.crc) {
385 FRSKY_OSD_DEBUG("Checksum error %u != %u. Discarding %u bytes",
386 c, state.recvBuffer.crc, state.recvBuffer.pos);
387 frskyOSDResetReceiveBuffer();
388 break;
390 state.recvBuffer.state = RECV_STATE_DONE;
391 break;
392 case RECV_STATE_DONE:
393 FRSKY_OSD_DEBUG("Received unexpected byte %u after data", c);
394 break;
399 static bool frskyOSDIsResponseAvailable(void)
401 return state.recvBuffer.state == RECV_STATE_DONE;
404 static void frskyOSDClearReceiveBuffer(void)
406 frskyOSDUpdateReceiveBuffer();
408 if (frskyOSDIsResponseAvailable()) {
409 frskyOSDDispatchResponse();
410 } else if (state.recvBuffer.pos > 0) {
411 FRSKY_OSD_DEBUG("Discarding receive buffer with %u bytes", state.recvBuffer.pos);
412 frskyOSDResetReceiveBuffer();
416 static void frskyOSDSendAsyncCommand(uint8_t cmd, const void *data, size_t size)
418 FRSKY_OSD_TRACE("Send async cmd %u", cmd);
419 frskyOSDSendCommand(cmd, data, size);
422 static bool frskyOSDSendSyncCommand(uint8_t cmd, const void *data, size_t size, timeMs_t timeout)
424 FRSKY_OSD_TRACE("Send sync cmd %u", cmd);
425 frskyOSDClearReceiveBuffer();
426 frskyOSDSendCommand(cmd, data, size);
427 frskyOSDFlushSendBuffer();
428 timeMs_t end = millis() + timeout;
429 while (millis() < end) {
430 frskyOSDUpdateReceiveBuffer();
431 if (frskyOSDIsResponseAvailable() && frskyOSDDispatchResponse()) {
432 FRSKY_OSD_TRACE("Got sync response");
433 return true;
436 FRSKY_OSD_DEBUG("Sync response failed");
437 return false;
440 static bool frskyOSDHandleCommand(osdCommand_e cmd, const void *payload, size_t size)
442 const uint8_t *ptr = payload;
444 state.error.command = 0;
445 state.error.code = 0;
447 switch (cmd) {
448 case OSD_CMD_RESPONSE_ERROR:
450 if (size >= 2) {
451 state.error.command = *ptr;
452 state.error.code = *(ptr + 1);
453 FRSKY_OSD_ERROR("Received an error %02x in response to command %u", *(ptr + 1), *ptr);
454 return true;
456 break;
458 case OSD_CMD_INFO:
460 if (size < sizeof(frskyOSDInfoResponse_t)) {
461 break;
463 const frskyOSDInfoResponse_t *resp = payload;
464 if (resp->magic[0] != 'A' || resp->magic[1] != 'G' || resp->magic[2] != 'H') {
465 FRSKY_OSD_ERROR("invalid magic number %x %x %x, expecting AGH",
466 resp->magic[0], resp->magic[1], resp->magic[2]);
467 return false;
469 FRSKY_OSD_TRACE("received OSD_CMD_INFO at %u", (unsigned)baudRates[state.baudrate]);
470 if (!state.keepBaudrate) {
471 const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_FRSKY_OSD);
472 if (portConfig && portConfig->peripheral_baudrateIndex > FRSKY_OSD_DEFAULT_BAUDRATE_INDEX &&
473 portConfig->peripheral_baudrateIndex != state.baudrate) {
475 // Try switching baudrates
476 uint32_t portBaudrate = baudRates[portConfig->peripheral_baudrateIndex];
477 FRSKY_OSD_TRACE("requesting baudrate switch from %u to %u",
478 (unsigned)baudRates[state.baudrate], (unsigned)portBaudrate);
479 frskyOSDSendAsyncCommand(OSD_CMD_SET_DATA_RATE, &portBaudrate, sizeof(portBaudrate));
480 frskyOSDFlushSendBuffer();
481 return true;
484 state.info.major = resp->versionMajor;
485 state.info.minor = resp->versionMinor;
486 state.info.grid.rows = resp->gridRows;
487 state.info.grid.columns = resp->gridColumns;
488 state.info.viewport.width = resp->pixelWidth;
489 state.info.viewport.height = resp->pixelHeight;
490 if (!state.initialized) {
491 FRSKY_OSD_DEBUG("initialized. Version %u.%u.%u, pixels=%ux%u, grid=%ux%u",
492 resp->versionMajor, resp->versionMinor, resp->versionPatch,
493 resp->pixelWidth, resp->pixelHeight, resp->gridColumns, resp->gridRows);
494 state.initialized = true;
495 frskyOSDClearScreen();
496 frskyOSDResetDrawingState();
498 return true;
500 case OSD_CMD_READ_FONT:
502 if (!state.recvOsdCharacter.chr) {
503 FRSKY_OSD_DEBUG("Got unexpected font character");
504 break;
506 if (size < sizeof(uint16_t) + FRSKY_OSD_CHAR_TOTAL_BYTES) {
507 FRSKY_OSD_TRACE("Received buffer too small for a character: %u bytes", size);
508 break;
510 const frskyOSDCharacter_t *chr = payload;
511 state.recvOsdCharacter.addr = chr->addr;
512 FRSKY_OSD_TRACE("Received character %u", chr->addr);
513 // Skip character address
514 memcpy(state.recvOsdCharacter.chr->data, &chr->data, MIN(sizeof(state.recvOsdCharacter.chr->data), (size_t)FRSKY_OSD_CHAR_TOTAL_BYTES));
515 return true;
517 case OSD_CMD_WRITE_FONT:
519 // We only wait for the confirmation, we're not interested in the data
520 return true;
522 case OSD_CMD_WIDGET_SET_CONFIG:
524 return true;
526 case OSD_CMD_SET_DATA_RATE:
528 if (size < sizeof(uint32_t)) {
529 break;
531 const uint32_t *newBaudrate = payload;
532 if (*newBaudrate && *newBaudrate != baudRates[state.baudrate]) {
533 FRSKY_OSD_TRACE("changed baudrate from %u to %u", (unsigned)baudRates[state.baudrate],
534 (unsigned)*newBaudrate);
535 serialSetBaudRate(state.port, *newBaudrate);
536 // OSD might have returned a different baudrate from our
537 // predefined ones. Be ready to handle that
538 state.baudrate = 0;
539 for (baudRate_e ii = 0; ii <= BAUD_MAX; ii++) {
540 if (baudRates[ii] == *newBaudrate) {
541 state.baudrate = ii;
542 break;
545 } else {
546 FRSKY_OSD_TRACE("baudrate refused, returned %u", (unsigned)*newBaudrate);
548 // Make sure we request OSD_CMD_INFO again as soon
549 // as possible and don't try to change the baudrate
550 // anymore.
551 state.nextInfoRequest = 0;
552 state.keepBaudrate = true;
553 return true;
555 default:
556 break;
558 return false;
561 static bool frskyOSDDispatchResponse(void)
563 const uint8_t *payload = state.recvBuffer.data;
564 int remaining = (int)state.recvBuffer.pos;
565 bool ok = false;
566 if (remaining > 0) {
567 // OSD sends commands one by one, so we don't need to handle
568 // a frame with multiple ones.
569 uint8_t cmd = *payload;
570 payload++;
571 remaining--;
572 if (frskyOSDHandleCommand(cmd, payload, remaining)) {
573 ok = true;
574 } else {
575 FRSKY_OSD_DEBUG("Discarding buffer due to unhandled command %u (%d bytes remaining)", cmd, remaining);
578 frskyOSDResetReceiveBuffer();
579 return ok;
582 static bool frskyOSDShouldRequestInfo(void)
584 return !frskyOSDIsReady() || millis() > state.nextInfoRequest;
587 static void frskyOSDRequestInfo(void)
589 timeMs_t now = millis();
590 if (state.info.nextRequest < now) {
591 timeMs_t interval;
592 if (frskyOSDIsReady()) {
593 // We already contacted the OSD, so we're just
594 // polling it to see if the video changed.
595 interval = FRSKY_OSD_INFO_READY_INTERVAL_MS;
596 } else {
597 // We haven't yet heard from the OSD. If this is not
598 // the first request, switch to the next baudrate.
599 if (state.info.nextRequest > 0 && !state.keepBaudrate) {
600 if (state.baudrate == BAUD_MAX) {
601 state.baudrate = FRSKY_OSD_DEFAULT_BAUDRATE_INDEX;
602 } else {
603 state.baudrate++;
605 serialSetBaudRate(state.port, baudRates[state.baudrate]);
607 interval = FRSKY_OSD_INFO_INTERVAL_MS;
609 state.info.nextRequest = now + interval;
611 uint8_t version = FRSKY_OSD_SUPPORTED_API_VERSION;
612 FRSKY_OSD_TRACE("request OSD_CMD_INFO at %u", (unsigned)baudRates[state.baudrate]);
613 frskyOSDSendAsyncCommand(OSD_CMD_INFO, &version, sizeof(version));
614 frskyOSDFlushSendBuffer();
618 static bool frskyOSDOpenPort(baudRate_e baudrate)
620 const serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_FRSKY_OSD);
621 if (portConfig) {
622 FRSKY_OSD_TRACE("configured, trying to connect...");
623 portOptions_t portOptions = SERIAL_STOPBITS_1 | SERIAL_PARITY_NO;
624 serialPort_t *port = openSerialPort(portConfig->identifier,
625 FUNCTION_FRSKY_OSD, NULL, NULL, baudRates[baudrate],
626 MODE_RXTX, portOptions);
628 if (port) {
629 frskyOSDStateReset(port, baudrate);
630 frskyOSDRequestInfo();
631 return true;
634 return false;
637 static uint8_t frskyOSDEncodeAttr(textAttributes_t attr)
639 uint8_t frskyOSDAttr = 0;
640 if (TEXT_ATTRIBUTES_HAVE_INVERTED(attr)) {
641 frskyOSDAttr |= FRSKY_OSD_CHAR_ATTRIBUTE_COLOR_INVERSE;
643 if (TEXT_ATTRIBUTES_HAVE_SOLID_BG(attr)) {
644 frskyOSDAttr |= FRSKY_OSD_CHAR_ATTRIBUTE_SOLID_BACKGROUND;
646 return frskyOSDAttr;
649 bool frskyOSDInit(videoSystem_e videoSystem)
651 UNUSED(videoSystem);
653 return frskyOSDOpenPort(FRSKY_OSD_DEFAULT_BAUDRATE_INDEX);
656 bool frskyOSDIsReady(void)
658 return state.info.minor > 0 || state.info.major > 0;
661 void frskyOSDUpdate(void)
663 if (!state.port) {
664 return;
666 frskyOSDUpdateReceiveBuffer();
668 if (frskyOSDIsResponseAvailable()) {
669 frskyOSDDispatchResponse();
672 if (frskyOSDShouldRequestInfo()) {
673 frskyOSDRequestInfo();
677 void frskyOSDBeginTransaction(frskyOSDTransactionOptions_e opts)
679 if (opts & FRSKY_OSD_TRANSACTION_OPT_PROFILED) {
680 frskyOSDPoint_t p = { .x = 0, .y = 10};
681 frskyOSDSendAsyncCommand(OSD_CMD_TRANSACTION_BEGIN_PROFILED, &p, sizeof(p));
682 if (opts & FRSKY_OSD_TRANSACTION_OPT_RESET_DRAWING) {
683 frskyOSDResetDrawingState();
685 } else if (opts & FRSKY_OSD_TRANSACTION_OPT_RESET_DRAWING) {
686 frskyOSDSendAsyncCommand(OSD_CMD_TRANSACTION_BEGIN_RESET_DRAWING, NULL, 0);
687 } else {
688 frskyOSDSendAsyncCommand(OSD_CMD_TRANSACTION_BEGIN, NULL, 0);
692 void frskyOSDCommitTransaction(void)
694 // Check wether the only command in the queue is a transaction begin.
695 // In that, case, discard the send buffer since it will make generate
696 // an empty transaction.
697 if (state.sendBuffer.pos == 1) {
698 if (state.sendBuffer.data[0] == OSD_CMD_TRANSACTION_BEGIN ||
699 state.sendBuffer.data[0] == OSD_CMD_TRANSACTION_BEGIN_RESET_DRAWING) {
701 state.sendBuffer.pos = 0;
702 return;
705 frskyOSDSendAsyncCommand(OSD_CMD_TRANSACTION_COMMIT, NULL, 0);
706 frskyOSDFlushSendBuffer();
709 void frskyOSDFlushSendBuffer(void)
711 if (state.sendBuffer.pos > 0) {
712 frskyOSDProcessCommandU8(NULL, FRSKY_OSD_PREAMBLE_BYTE_0);
713 frskyOSDProcessCommandU8(NULL, FRSKY_OSD_PREAMBLE_BYTE_1);
715 uint8_t crc = 0;
716 uint8_t buffer[4];
717 int lengthSize = uvarintEncode(state.sendBuffer.pos, buffer, sizeof(buffer));
718 for (int ii = 0; ii < lengthSize; ii++) {
719 frskyOSDProcessCommandU8(&crc, buffer[ii]);
721 for (unsigned ii = 0; ii < state.sendBuffer.pos; ii++) {
722 frskyOSDProcessCommandU8(&crc, state.sendBuffer.data[ii]);
724 frskyOSDProcessCommandU8(NULL, crc);
725 state.sendBuffer.pos = 0;
729 bool frskyOSDReadFontCharacter(unsigned char_address, osdCharacter_t *chr)
731 if (!frskyOSDIsReady()) {
732 return false;
735 uint16_t addr = char_address;
737 state.recvOsdCharacter.addr = UINT16_MAX;
738 state.recvOsdCharacter.chr = chr;
740 // 500ms should be more than enough to receive ~70 bytes @ 115200 bps
741 bool ok = frskyOSDSendSyncCommand(OSD_CMD_READ_FONT, &addr, sizeof(addr), 500);
743 state.recvOsdCharacter.chr = NULL;
745 if (ok && state.recvOsdCharacter.addr == addr) {
746 return true;
748 return false;
751 bool frskyOSDWriteFontCharacter(unsigned char_address, const osdCharacter_t *chr)
753 if (!frskyOSDIsReady()) {
754 return false;
757 frskyOSDCharacter_t c;
758 STATIC_ASSERT(sizeof(*chr) == sizeof(c.data), invalid_character_size);
760 memcpy(&c.data, chr, sizeof(c.data));
761 c.addr = char_address;
762 FRSKY_OSD_TRACE("Writing font character %u", char_address);
763 frskyOSDSendSyncCommand(OSD_CMD_WRITE_FONT, &c, sizeof(c), 1000);
764 return true;
767 unsigned frskyOSDGetGridRows(void)
769 return state.info.grid.rows;
772 unsigned frskyOSDGetGridCols(void)
774 return state.info.grid.columns;
777 unsigned frskyOSDGetPixelWidth(void)
779 return state.info.viewport.width;
782 unsigned frskyOSDGetPixelHeight(void)
784 return state.info.viewport.height;
787 static void frskyOSDSendAsyncBlobCommand(uint8_t cmd, const void *header, size_t headerSize, const void *blob, size_t blobSize, bool explicitBlobSize)
789 uint8_t payload[128];
791 memcpy(payload, header, headerSize);
793 int uvarintSize;
794 if (explicitBlobSize) {
795 uvarintSize = uvarintEncode(blobSize, &payload[headerSize], sizeof(payload) - headerSize);
796 } else {
797 uvarintSize = 0;
799 memcpy(&payload[headerSize + uvarintSize], blob, blobSize);
800 frskyOSDSendAsyncCommand(cmd, payload, headerSize + uvarintSize + blobSize);
803 static void frskyOSDSendAsyncBlobWithExplicitSizeCommand(uint8_t cmd, const void *header, size_t headerSize, const void *blob, size_t blobSize)
805 frskyOSDSendAsyncBlobCommand(cmd, header, headerSize, blob, blobSize, true);
808 static void frskyOSDSendAsyncBlobWithoutExplicitSizeCommand(uint8_t cmd, const void *header, size_t headerSize, const void *blob, size_t blobSize)
810 frskyOSDSendAsyncBlobCommand(cmd, header, headerSize, blob, blobSize, false);
813 static void frskyOSDSendCharInGrid_V1(unsigned x, unsigned y, uint16_t chr, textAttributes_t attr)
815 uint8_t payload[] = {
818 chr & 0xFF,
819 chr >> 8,
820 frskyOSDEncodeAttr(attr),
822 frskyOSDSendAsyncCommand(OSD_CMD_DRAW_GRID_CHR, payload, sizeof(payload));
825 static void frskyOSDSendCharInGrid_V2(unsigned x, unsigned y, uint16_t chr, textAttributes_t attr)
827 frskyOSDDrawGridChrV2Cmd_t payload = {
828 .gx = x,
829 .gy = y,
830 .chr = chr,
831 .opts = frskyOSDEncodeAttr(attr),
833 frskyOSDSendAsyncCommand(OSD_CMD_DRAW_GRID_CHR_2, &payload, sizeof(payload));
836 static void frskyOSDSendCharInGrid(unsigned x, unsigned y, uint16_t chr, textAttributes_t attr)
838 if (frskyOSDSpeaksV2()) {
839 frskyOSDSendCharInGrid_V2(x, y, chr, attr);
840 } else {
841 frskyOSDSendCharInGrid_V1(x, y, chr, attr);
845 static void frskyOSDSendCharSendStringInGrid_V1(unsigned x, unsigned y, const char *buff, textAttributes_t attr)
847 frskyOSDDrawGridStrHeaderCmd_t cmd = {
848 .gx = x,
849 .gy = y,
850 .opts = frskyOSDEncodeAttr(attr),
852 frskyOSDSendAsyncBlobWithExplicitSizeCommand(OSD_CMD_DRAW_GRID_STR, &cmd, sizeof(cmd), buff, strlen(buff) + 1);
855 static void frskyOSDSendCharSendStringInGrid_V2(unsigned x, unsigned y, const char *buff, textAttributes_t attr)
857 frskyOSDDrawGridStrV2HeaderCmd_t cmd = {
858 .gx = x,
859 .gy = y,
860 .opts = frskyOSDEncodeAttr(attr),
862 size_t len = strlen(buff);
863 if (len <= 15) {
864 cmd.size = len;
865 frskyOSDSendAsyncBlobWithoutExplicitSizeCommand(OSD_CMD_DRAW_GRID_STR_2, &cmd, sizeof(cmd), buff, len);
866 } else {
867 frskyOSDSendAsyncBlobWithExplicitSizeCommand(OSD_CMD_DRAW_GRID_STR_2, &cmd, sizeof(cmd), buff, len);
871 static void frskyOSDSendCharSendStringInGrid(unsigned x, unsigned y, const char *buff, textAttributes_t attr)
873 if (frskyOSDSpeaksV2()) {
874 frskyOSDSendCharSendStringInGrid_V2(x, y, buff, attr);
875 } else {
876 frskyOSDSendCharSendStringInGrid_V1(x, y, buff, attr);
880 void frskyOSDDrawStringInGrid(unsigned x, unsigned y, const char *buff, textAttributes_t attr)
882 unsigned charsUpdated = 0;
883 const char *updatedCharAt = NULL;
884 uint16_t *entry = osdCharacterGridBufferGetEntryPtr(x, y);
885 const char *p;
886 unsigned xx;
887 for (p = buff, xx = x; *p && xx < state.info.grid.columns; p++, entry++, xx++) {
888 unsigned val = FRSKY_OSD_GRID_BUFFER_ENCODE(*p, attr);
889 if (*entry != val) {
890 if (++charsUpdated == 1) {
891 // First character that needs to be updated, save it
892 // in case we can issue a single update.
893 updatedCharAt = p;
895 *entry = val;
899 if (charsUpdated == 0) {
900 return;
903 if (charsUpdated == 1) {
904 frskyOSDSendCharInGrid(x + (updatedCharAt - buff), y, *updatedCharAt, attr);
905 return;
908 frskyOSDSendCharSendStringInGrid(x, y, buff, attr);
911 void frskyOSDDrawCharInGrid(unsigned x, unsigned y, uint16_t chr, textAttributes_t attr)
913 uint16_t *entry = osdCharacterGridBufferGetEntryPtr(x, y);
914 unsigned val = FRSKY_OSD_GRID_BUFFER_ENCODE(chr, attr);
916 if (*entry == val) {
917 return;
920 frskyOSDSendCharInGrid(x, y, chr, attr);
922 *entry = val;
925 bool frskyOSDReadCharInGrid(unsigned x, unsigned y, uint16_t *c, textAttributes_t *attr)
927 uint16_t val = *osdCharacterGridBufferGetEntryPtr(x, y);
928 // We use the lower 10 bits for characters
929 *c = val & FRSKY_OSD_GRID_BUFFER_CHAR_MASK;
930 *attr = val >> FRSKY_OSD_GRID_BUFFER_CHAR_BITS;
931 return true;
934 void frskyOSDClearScreen(void)
936 osdCharacterGridBufferClear();
937 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_CLEAR_SCREEN, NULL, 0);
940 void frskyOSDSetStrokeColor(frskyOSDColor_e color)
942 uint8_t c = color;
943 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_SET_STROKE_COLOR, &c, sizeof(c));
946 void frskyOSDSetFillColor(frskyOSDColor_e color)
948 uint8_t c = color;
949 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_SET_FILL_COLOR, &c, sizeof(c));
952 void frskyOSDSetStrokeAndFillColor(frskyOSDColor_e color)
954 uint8_t c = color;
955 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_SET_STROKE_AND_FILL_COLOR, &c, sizeof(c));
958 void frskyOSDSetColorInversion(bool inverted)
960 uint8_t c = inverted ? 1 : 0;
961 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_SET_COLOR_INVERSION, &c, sizeof(c));
964 void frskyOSDSetPixel(int x, int y, frskyOSDColor_e color)
966 frskyOSDSetPixel_t sp = {.p = {.x = x, .y = y}, .color = color};
967 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_SET_PIXEL, &sp, sizeof(sp));
970 void frskyOSDSetPixelToStrokeColor(int x, int y)
972 frskyOSDPoint_t p = { .x = x, .y = y};
973 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_SET_PIXEL_TO_STROKE_COLOR, &p, sizeof(p));
976 void frskyOSDSetPixelToFillColor(int x, int y)
978 frskyOSDPoint_t p = { .x = x, .y = y};
979 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_SET_PIXEL_TO_FILL_COLOR, &p, sizeof(p));
982 void frskyOSDSetStrokeWidth(unsigned width)
984 uint8_t w = width;
985 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_SET_STROKE_WIDTH, &w, sizeof(w));
988 void frskyOSDSetLineOutlineType(frskyOSDLineOutlineType_e outlineType)
990 uint8_t type = outlineType;
991 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_SET_LINE_OUTLINE_TYPE, &type, sizeof(type));
994 void frskyOSDSetLineOutlineColor(frskyOSDColor_e outlineColor)
996 uint8_t color = outlineColor;
997 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_SET_LINE_OUTLINE_COLOR, &color, sizeof(color));
1000 void frskyOSDClipToRect(int x, int y, int w, int h)
1002 frskyOSDRect_t r = { .origin = { .x = x, .y = y}, .size = {.w = w, .h = h}};
1003 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_CLIP_TO_RECT, &r, sizeof(r));
1006 void frskyOSDClearRect(int x, int y, int w, int h)
1008 frskyOSDRect_t r = { .origin = { .x = x, .y = y}, .size = {.w = w, .h = h}};
1009 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_CLEAR_RECT, &r, sizeof(r));
1012 void frskyOSDResetDrawingState(void)
1014 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_RESET, NULL, 0);
1017 void frskyOSDDrawCharacter(int x, int y, uint16_t chr, uint8_t opts)
1019 frskyOSDDrawCharacterCmd_t dc = { .p = {.x = x, .y = y}, .chr = chr, .opts = opts};
1020 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_DRAW_CHAR, &dc, sizeof(dc));
1023 void frskyOSDDrawCharacterMask(int x, int y, uint16_t chr, frskyOSDColor_e color, uint8_t opts)
1025 frskyOSDDrawCharacterMaskCmd_t dc = { .dc = { .p = {.x = x, .y = y}, .chr = chr, .opts = opts}, .maskColor = color};
1026 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_DRAW_CHAR_MASK, &dc, sizeof(dc));
1029 void frskyOSDDrawString(int x, int y, const char *s, uint8_t opts)
1031 frskyOSDDrawStrCommandHeaderCmd_t cmd;
1032 cmd.p.x = x;
1033 cmd.p.y = y;
1034 cmd.opts = opts;
1036 frskyOSDSendAsyncBlobWithExplicitSizeCommand(OSD_CMD_DRAWING_DRAW_STRING, &cmd, sizeof(cmd), s, strlen(s) + 1);
1039 void frskyOSDDrawStringMask(int x, int y, const char *s, frskyOSDColor_e color, uint8_t opts)
1041 frskyOSDDrawStrMaskCommandHeaderCmd_t cmd;
1042 cmd.p.x = x;
1043 cmd.p.y = y;
1044 cmd.opts = opts;
1045 cmd.maskColor = color;
1047 frskyOSDSendAsyncBlobWithExplicitSizeCommand(OSD_CMD_DRAWING_DRAW_STRING_MASK, &cmd, sizeof(cmd), s, strlen(s) + 1);
1050 void frskyOSDMoveToPoint(int x, int y)
1052 frskyOSDPoint_t p = { .x = x, .y = y};
1053 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_MOVE_TO_POINT, &p, sizeof(p));
1056 void frskyOSDStrokeLineToPoint(int x, int y)
1058 frskyOSDPoint_t p = { .x = x, .y = y};
1059 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_STROKE_LINE_TO_POINT, &p, sizeof(p));
1062 void frskyOSDStrokeTriangle(int x1, int y1, int x2, int y2, int x3, int y3)
1064 frskyOSDTriangle_t t = {.p1 = {.x = x1, .y = y1}, .p2 = {.x = x2, .y = y2}, .p3 = { .x = x3, .y = y3}};
1065 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_STROKE_TRIANGLE, &t, sizeof(t));
1068 void frskyOSDFillTriangle(int x1, int y1, int x2, int y2, int x3, int y3)
1070 frskyOSDTriangle_t t = {.p1 = {.x = x1, .y = y1}, .p2 = {.x = x2, .y = y2}, .p3 = { .x = x3, .y = y3}};
1071 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_FILL_TRIANGLE, &t, sizeof(t));
1074 void frskyOSDFillStrokeTriangle(int x1, int y1, int x2, int y2, int x3, int y3)
1076 frskyOSDTriangle_t t = {.p1 = {.x = x1, .y = y1}, .p2 = {.x = x2, .y = y2}, .p3 = { .x = x3, .y = y3}};
1077 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_FILL_STROKE_TRIANGLE, &t, sizeof(t));
1080 void frskyOSDStrokeRect(int x, int y, int w, int h)
1082 frskyOSDRect_t r = { .origin = { .x = x, .y = y}, .size = {.w = w, .h = h}};
1083 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_STROKE_RECT, &r, sizeof(r));
1086 void frskyOSDFillRect(int x, int y, int w, int h)
1088 frskyOSDRect_t r = { .origin = { .x = x, .y = y}, .size = {.w = w, .h = h}};
1089 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_FILL_RECT, &r, sizeof(r));
1092 void frskyOSDFillStrokeRect(int x, int y, int w, int h)
1094 frskyOSDRect_t r = { .origin = { .x = x, .y = y}, .size = {.w = w, .h = h}};
1095 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_FILL_STROKE_RECT, &r, sizeof(r));
1098 void frskyOSDStrokeEllipseInRect(int x, int y, int w, int h)
1100 frskyOSDRect_t r = { .origin = { .x = x, .y = y}, .size = {.w = w, .h = h}};
1101 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_STROKE_ELLIPSE_IN_RECT, &r, sizeof(r));
1104 void frskyOSDFillEllipseInRect(int x, int y, int w, int h)
1106 frskyOSDRect_t r = { .origin = { .x = x, .y = y}, .size = {.w = w, .h = h}};
1107 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_FILL_ELLIPSE_IN_RECT, &r, sizeof(r));
1110 void frskyOSDFillStrokeEllipseInRect(int x, int y, int w, int h)
1112 frskyOSDRect_t r = { .origin = { .x = x, .y = y}, .size = {.w = w, .h = h}};
1113 frskyOSDSendAsyncCommand(OSD_CMD_DRAWING_FILL_STROKE_ELLIPSE_IN_RECT, &r, sizeof(r));
1116 void frskyOSDCtmReset(void)
1118 frskyOSDSendAsyncCommand(OSD_CMD_CTM_RESET, NULL, 0);
1121 void frskyOSDCtmSet(float m11, float m12, float m21, float m22, float m31, float m32)
1123 float values[] = {
1124 m11, m12,
1125 m21, m22,
1126 m31, m32,
1128 frskyOSDSendAsyncCommand(OSD_CMD_CTM_SET, values, sizeof(values));
1131 void frskyOSDCtmTranslate(float tx, float ty)
1133 float values[] = {
1137 frskyOSDSendAsyncCommand(OSD_CMD_CTM_TRANSLATE, values, sizeof(values));
1140 void frskyOSDCtmScale(float sx, float sy)
1142 float values[] = {
1146 frskyOSDSendAsyncCommand(OSD_CMD_CTM_SCALE, values, sizeof(values));
1149 void frskyOSDCtmRotate(float r)
1151 frskyOSDSendAsyncCommand(OSD_CMD_CTM_ROTATE, &r, sizeof(r));
1154 void frskyOSDContextPush(void)
1156 frskyOSDSendAsyncCommand(OSD_CMD_CONTEXT_PUSH, NULL, 0);
1159 void frskyOSDContextPop(void)
1161 frskyOSDSendAsyncCommand(OSD_CMD_CONTEXT_POP, NULL, 0);
1164 bool frskyOSDSupportsWidgets(void)
1166 return frskyOSDSpeaksV2();
1169 bool frskyOSDSetWidgetConfig(frskyOSDWidgetID_e widget, const void *config, size_t configSize)
1171 if (!frskyOSDSupportsWidgets()) {
1172 return false;
1175 uint8_t buffer[configSize + 1];
1176 buffer[0] = widget;
1177 memcpy(buffer + 1, config, configSize);
1178 bool ok = frskyOSDSendSyncCommand(OSD_CMD_WIDGET_SET_CONFIG, buffer, sizeof(buffer), 500);
1179 return ok && state.error.code == 0;
1182 bool frskyOSDDrawWidget(frskyOSDWidgetID_e widget, const void *data, size_t dataSize)
1184 if (!frskyOSDSupportsWidgets()) {
1185 return false;
1188 uint8_t buffer[dataSize + 1];
1189 buffer[0] = widget;
1190 memcpy(buffer + 1, data, dataSize);
1191 frskyOSDSendAsyncCommand(OSD_CMD_WIDGET_DRAW, buffer, sizeof(buffer));
1192 return true;
1195 uint32_t frskyOSDQuantize(float val, float min, float max, int bits)
1197 if (val < min) {
1198 val = max - (min - val);
1199 } else if (val > max) {
1200 val = min + (val - max);
1202 return (val * (1 << bits)) / max;
1205 #endif