7 #if defined(USE_FRSKYOSD)
9 #include "common/crc.h"
10 #include "common/maths.h"
11 #include "common/time.h"
12 #include "common/utils.h"
13 #include "common/uvarint.h"
15 #include "drivers/time.h"
17 #include "io/frsky_osd.h"
18 #include "io/serial.h"
20 #define FRSKY_OSD_BAUDRATE 115200
21 #define FRSKY_OSD_SUPPORTED_API_VERSION 1
23 #define FRSKY_OSD_PREAMBLE_BYTE_0 '$'
24 #define FRSKY_OSD_PREAMBLE_BYTE_1 'A'
26 #define FRSKY_OSD_GRID_BUFFER_CHAR_BITS 9
27 #define FRSKY_OSD_GRID_BUFFER_CHAR_MASK ((1 << FRSKY_OSD_GRID_BUFFER_CHAR_BITS) - 1)
28 #define FRSKY_OSD_GRID_BUFFER_ENCODE(chr, attr) ((chr & FRSKY_OSD_GRID_BUFFER_CHAR_MASK) | (attr << FRSKY_OSD_GRID_BUFFER_CHAR_BITS))
30 #define FRSKY_OSD_CHAR_ATTRIBUTE_COLOR_INVERSE (1 << 0)
31 #define FRSKY_OSD_CHAR_ATTRIBUTE_SOLID_BACKGROUND (1 << 1)
33 #define FRSKY_OSD_CHAR_DATA_BYTES 54
34 #define FRSKY_OSD_CHAR_METADATA_BYTES 10
35 #define FRSKY_OSD_CHAR_TOTAL_BYTES (FRSKY_OSD_CHAR_DATA_BYTES + FRSKY_OSD_CHAR_METADATA_BYTES)
37 #define FRSKY_OSD_SEND_BUFFER_SIZE 192
38 #define FRSKY_OSD_RECV_BUFFER_SIZE 128
40 #define FRSKY_OSD_CMD_RESPONSE_ERROR 0
42 #define FRSKY_OSD_INFO_INTERVAL_MS 1000
44 #define FRSKY_OSD_TRACE(...)
45 #define FRSKY_OSD_DEBUG(...)
46 #define FRSKY_OSD_ERROR(...)
47 #define FRSKY_OSD_ASSERT(x)
51 OSD_CMD_RESPONSE_ERROR
= 0,
54 OSD_CMD_READ_FONT
= 2,
55 OSD_CMD_WRITE_FONT
= 3,
56 OSD_CMD_GET_CAMERA
= 4,
57 OSD_CMD_SET_CAMERA
= 5,
58 OSD_CMD_GET_ACTIVE_CAMERA
= 6,
59 OSD_CMD_GET_OSD_ENABLED
= 7,
60 OSD_CMD_SET_OSD_ENABLED
= 8,
62 OSD_CMD_TRANSACTION_BEGIN
= 16,
63 OSD_CMD_TRANSACTION_COMMIT
= 17,
64 OSD_CMD_TRANSACTION_BEGIN_PROFILED
= 18,
65 OSD_CMD_TRANSACTION_BEGIN_RESET_DRAWING
= 19,
67 OSD_CMD_DRAWING_SET_STROKE_COLOR
= 22,
68 OSD_CMD_DRAWING_SET_FILL_COLOR
= 23,
69 OSD_CMD_DRAWING_SET_STROKE_AND_FILL_COLOR
= 24,
70 OSD_CMD_DRAWING_SET_COLOR_INVERSION
= 25,
71 OSD_CMD_DRAWING_SET_PIXEL
= 26,
72 OSD_CMD_DRAWING_SET_PIXEL_TO_STROKE_COLOR
= 27,
73 OSD_CMD_DRAWING_SET_PIXEL_TO_FILL_COLOR
= 28,
74 OSD_CMD_DRAWING_SET_STROKE_WIDTH
= 29,
75 OSD_CMD_DRAWING_SET_LINE_OUTLINE_TYPE
= 30,
76 OSD_CMD_DRAWING_SET_LINE_OUTLINE_COLOR
= 31,
78 OSD_CMD_DRAWING_CLIP_TO_RECT
= 40,
79 OSD_CMD_DRAWING_CLEAR_SCREEN
= 41,
80 OSD_CMD_DRAWING_CLEAR_RECT
= 42,
81 OSD_CMD_DRAWING_RESET
= 43,
82 OSD_CMD_DRAWING_DRAW_BITMAP
= 44,
83 OSD_CMD_DRAWING_DRAW_BITMAP_MASK
= 45,
84 OSD_CMD_DRAWING_DRAW_CHAR
= 46,
85 OSD_CMD_DRAWING_DRAW_CHAR_MASK
= 47,
86 OSD_CMD_DRAWING_DRAW_STRING
= 48,
87 OSD_CMD_DRAWING_DRAW_STRING_MASK
= 49,
88 OSD_CMD_DRAWING_MOVE_TO_POINT
= 50,
89 OSD_CMD_DRAWING_STROKE_LINE_TO_POINT
= 51,
90 OSD_CMD_DRAWING_STROKE_TRIANGLE
= 52,
91 OSD_CMD_DRAWING_FILL_TRIANGLE
= 53,
92 OSD_CMD_DRAWING_FILL_STROKE_TRIANGLE
= 54,
93 OSD_CMD_DRAWING_STROKE_RECT
= 55,
94 OSD_CMD_DRAWING_FILL_RECT
= 56,
95 OSD_CMD_DRAWING_FILL_STROKE_RECT
= 57,
96 OSD_CMD_DRAWING_STROKE_ELLIPSE_IN_RECT
= 58,
97 OSD_CMD_DRAWING_FILL_ELLIPSE_IN_RECT
= 59,
98 OSD_CMD_DRAWING_FILL_STROKE_ELLIPSE_IN_RECT
= 60,
100 OSD_CMD_CTM_RESET
= 80,
101 OSD_CMD_CTM_SET
= 81,
102 OSD_CMD_CTM_TRANSLATE
= 82,
103 OSD_CMD_CTM_SCALE
= 83,
104 OSD_CMD_CTM_ROTATE
= 84,
105 OSD_CMD_CTM_ROTATE_ABOUT
= 85,
106 OSD_CMD_CTM_SHEAR
= 86,
107 OSD_CMD_CTM_SHEAR_ABOUT
= 87,
108 OSD_CMD_CTM_MULTIPLY
= 88,
110 OSD_CMD_CONTEXT_PUSH
= 100,
111 OSD_CMD_CONTEXT_POP
= 101,
113 // MAX7456 emulation commands
114 OSD_CMD_DRAW_GRID_CHR
= 110,
115 OSD_CMD_DRAW_GRID_STR
= 111,
125 } frskyOsdRecvState_e
;
127 typedef struct frskyOsdInfoResponse_s
{
129 uint8_t versionMajor
;
130 uint8_t versionMinor
;
131 uint8_t versionPatch
;
135 uint16_t pixelHeight
;
137 uint8_t hasDetectedCamera
;
138 uint16_t maxFrameSize
;
139 uint8_t contextStackSize
;
140 } __attribute__((packed
)) frskyOsdInfoResponse_t
;
142 typedef struct frskyOsdFontCharacter_s
{
145 uint8_t bitmap
[FRSKY_OSD_CHAR_DATA_BYTES
]; // 12x18 2bpp
146 uint8_t metadata
[FRSKY_OSD_CHAR_METADATA_BYTES
];
148 } __attribute__((packed
)) frskyOsdCharacter_t
;
150 typedef struct frskyOsdDrawGridCharCmd_s
{
155 } __attribute__((packed
)) frskyOsdDrawGridCharCmd_t
;
157 typedef struct frskyOsdDrawGridStrHeaderCmd_s
{
161 // uvarint with size and blob folow
162 } __attribute__((packed
)) frskyOsdDrawGridStrHeaderCmd_t
;
164 typedef struct frskyOsdPoint_s
{
167 } __attribute__((packed
)) frskyOsdPoint_t
;
169 typedef struct frskyOsdSize_s
{
172 } __attribute__((packed
)) frskyOsdSize_t
;
174 typedef struct frskyOsdRect_s
{
175 frskyOsdPoint_t origin
;
177 } __attribute__((packed
)) frskyOsdRect_t
;
179 typedef struct frskyOsdTriangle_s
{
183 } __attribute__((packed
)) frskyOsdTriangle_t
;
185 typedef struct frskyOsdSetPixel_s
{
188 } __attribute__((packed
)) frskyOsdSetPixel_t
;
190 typedef struct frskyOsdDrawCharacterCmd_s
{
194 } __attribute__((packed
)) frskyOsdDrawCharacterCmd_t
;
196 typedef struct frskyOsdDrawCharacterMaskCmd_s
{
197 frskyOsdDrawCharacterCmd_t dc
;
199 } __attribute__((packed
)) frskyOsdDrawCharacterMaskCmd_t
;
201 typedef struct frskyOsdDrawStrCommandHeaderCmd_s
{
204 // uvarint with size and blob follow
205 } __attribute__((packed
)) frskyOsdDrawStrCommandHeaderCmd_t
;
207 typedef struct frskyOsdDrawStrMaskCommandHeaderCmd_s
{
211 // uvarint with size and blob follow
212 } __attribute__((packed
)) frskyOsdDrawStrMaskCommandHeaderCmd_t
;
215 typedef struct frskyOsdState_s
{
217 uint8_t data
[FRSKY_OSD_SEND_BUFFER_SIZE
];
224 uint8_t expectedShift
;
225 uint8_t data
[FRSKY_OSD_RECV_BUFFER_SIZE
];
231 timeMs_t nextRequest
;
247 timeMs_t nextInfoRequest
;
250 static frskyOsdState_t state
;
252 static uint8_t frskyOsdChecksum(uint8_t crc
, uint8_t c
)
254 return crc8_dvb_s2(crc
, c
);
257 static void frskyOsdResetReceiveBuffer(void)
259 state
.recvBuffer
.state
= RECV_STATE_NONE
;
260 state
.recvBuffer
.crc
= 0;
261 state
.recvBuffer
.expected
= 0;
262 state
.recvBuffer
.expectedShift
= 0;
263 state
.recvBuffer
.pos
= 0;
266 static void frskyOsdResetSendBuffer(void)
268 state
.sendBuffer
.pos
= 0;
271 static void frskyOsdProcessCommandU8(uint8_t *crc
, uint8_t c
)
273 while (serialTxBytesFree(state
.port
) == 0) {
275 serialWrite(state
.port
, c
);
277 *crc
= crc8_dvb_s2(*crc
, c
);
281 static void frskyOsdSendCommand(uint8_t cmd
, const void *payload
, size_t size
)
283 int required
= size
+ 1;
284 FRSKY_OSD_ASSERT(required
<= sizeof(state
.sendBuffer
.data
));
285 int rem
= sizeof(state
.sendBuffer
.data
) - state
.sendBuffer
.pos
;
286 if (rem
< required
) {
287 frskyOsdFlushSendBuffer();
289 state
.sendBuffer
.data
[state
.sendBuffer
.pos
++] = cmd
;
290 const uint8_t *ptr
= payload
;
291 for (size_t ii
= 0; ii
< size
; ii
++, ptr
++) {
292 state
.sendBuffer
.data
[state
.sendBuffer
.pos
++] = *ptr
;
296 static void frskyOsdStateReset(serialPort_t
*port
)
298 frskyOsdResetReceiveBuffer();
299 frskyOsdResetSendBuffer();
300 state
.info
.grid
.rows
= 0;
301 state
.info
.grid
.columns
= 0;
302 state
.info
.viewport
.width
= 0;
303 state
.info
.viewport
.height
= 0;
306 state
.initialized
= false;
309 static void frskyOsdUpdateReceiveBuffer(void)
311 while (serialRxBytesWaiting(state
.port
) > 0) {
312 uint8_t c
= serialRead(state
.port
);
313 switch ((frskyOsdRecvState_e
)state
.recvBuffer
.state
) {
314 case RECV_STATE_NONE
:
315 if (c
!= FRSKY_OSD_PREAMBLE_BYTE_0
) {
318 state
.recvBuffer
.state
= RECV_STATE_SYNC
;
320 case RECV_STATE_SYNC
:
321 if (c
!= FRSKY_OSD_PREAMBLE_BYTE_1
) {
322 frskyOsdResetReceiveBuffer();
325 state
.recvBuffer
.state
= RECV_STATE_LENGTH
;
327 case RECV_STATE_LENGTH
:
328 state
.recvBuffer
.crc
= frskyOsdChecksum(state
.recvBuffer
.crc
, c
);
329 state
.recvBuffer
.expected
|= (c
& 0x7F) << state
.recvBuffer
.expectedShift
;
330 state
.recvBuffer
.expectedShift
+= 7;
332 // Full uvarint decoded. Check against buffer size.
333 if (state
.recvBuffer
.expected
> sizeof(state
.recvBuffer
.data
)) {
334 FRSKY_OSD_ERROR("Can't handle payload of size %u with a buffer of size %u",
335 state
.recvBuffer
.expected
, sizeof(state
.recvBuffer
.data
));
336 frskyOsdResetReceiveBuffer();
339 FRSKY_OSD_TRACE("Payload of size %u", state
.recvBuffer
.expected
);
340 state
.recvBuffer
.state
= state
.recvBuffer
.expected
> 0 ? RECV_STATE_DATA
: RECV_STATE_CHECKSUM
;
343 case RECV_STATE_DATA
:
344 state
.recvBuffer
.data
[state
.recvBuffer
.pos
++] = c
;
345 state
.recvBuffer
.crc
= frskyOsdChecksum(state
.recvBuffer
.crc
, c
);
346 if (state
.recvBuffer
.pos
== state
.recvBuffer
.expected
) {
347 state
.recvBuffer
.state
= RECV_STATE_CHECKSUM
;
350 case RECV_STATE_CHECKSUM
:
351 if (c
!= state
.recvBuffer
.crc
) {
352 FRSKY_OSD_DEBUG("Checksum error %u != %u. Discarding %u bytes",
353 c
, state
.recvBuffer
.crc
, state
.recvBuffer
.pos
);
354 frskyOsdResetReceiveBuffer();
357 state
.recvBuffer
.state
= RECV_STATE_DONE
;
359 case RECV_STATE_DONE
:
360 FRSKY_OSD_DEBUG("Received unexpected byte %u after data", c
);
366 static bool frskyOsdIsResponseAvailable(void)
368 return state
.recvBuffer
.state
== RECV_STATE_DONE
;
371 static bool frskyOsdHandleCommand(osdCommand_e cmd
, const void *payload
, size_t size
)
374 case OSD_CMD_RESPONSE_ERROR
:
377 FRSKY_OSD_ERROR("Received an error %02x in response to command %u", *(ptr
+ 1), *ptr
);
384 if (size
< sizeof(frskyOsdInfoResponse_t
)) {
387 const frskyOsdInfoResponse_t
*resp
= payload
;
388 if (resp
->magic
[0] != 'A' || resp
->magic
[1] != 'G' || resp
->magic
[2] != 'H') {
389 FRSKY_OSD_ERROR("Invalid magic number %x %x %x, expecting AGH",
390 resp
->magic
[0], resp
->magic
[1], resp
->magic
[2]);
393 state
.info
.major
= resp
->versionMajor
;
394 state
.info
.minor
= resp
->versionMinor
;
395 state
.info
.grid
.rows
= resp
->gridRows
;
396 state
.info
.grid
.columns
= resp
->gridColumns
;
397 state
.info
.viewport
.width
= resp
->pixelWidth
;
398 state
.info
.viewport
.height
= resp
->pixelHeight
;
399 if (!state
.initialized
) {
400 FRSKY_OSD_DEBUG("FrSky OSD initialized. Version %u.%u.%u, pixels=%ux%u, grid=%ux%u",
401 resp
->versionMajor
, resp
->versionMinor
, resp
->versionPatch
,
402 resp
->pixelWidth
, resp
->pixelHeight
, resp
->gridColumns
, resp
->gridRows
);
403 state
.initialized
= true;
404 frskyOsdClearScreen();
405 frskyOsdResetDrawingState();
409 case OSD_CMD_READ_FONT
:
411 if (!state
.recvOsdCharacter
.chr
) {
412 FRSKY_OSD_DEBUG("Got unexpected font character");
415 if (size
< sizeof(uint16_t) + FRSKY_OSD_CHAR_TOTAL_BYTES
) {
416 FRSKY_OSD_TRACE("Received buffer too small for a character: %u bytes", size
);
419 const frskyOsdCharacter_t
*chr
= payload
;
420 state
.recvOsdCharacter
.addr
= chr
->addr
;
421 FRSKY_OSD_TRACE("Received character %u", chr
->addr
);
422 // Skip character address
423 memcpy(state
.recvOsdCharacter
.chr
->data
, &chr
->data
, MIN(sizeof(state
.recvOsdCharacter
.chr
->data
), (size_t)FRSKY_OSD_CHAR_TOTAL_BYTES
));
426 case OSD_CMD_WRITE_FONT
:
428 // We only wait for the confirmation, we're not interested in the data
437 static bool frskyOsdDispatchResponse(void)
439 const uint8_t *payload
= state
.recvBuffer
.data
;
440 int remaining
= (int)state
.recvBuffer
.pos
;
443 // OSD sends commands one by one, so we don't need to handle
444 // a frame with multiple ones.
445 uint8_t cmd
= *payload
;
448 if (frskyOsdHandleCommand(cmd
, payload
, remaining
)) {
451 FRSKY_OSD_DEBUG("Discarding buffer due to unhandled command %u (%d bytes remaining)", cmd
, remaining
);
454 frskyOsdResetReceiveBuffer();
458 static void frskyOsdClearReceiveBuffer(void)
460 frskyOsdUpdateReceiveBuffer();
462 if (frskyOsdIsResponseAvailable()) {
463 frskyOsdDispatchResponse();
464 } else if (state
.recvBuffer
.pos
> 0) {
465 FRSKY_OSD_DEBUG("Discarding receive buffer with %u bytes", state
.recvBuffer
.pos
);
466 frskyOsdResetReceiveBuffer();
470 static void frskyOsdSendAsyncCommand(uint8_t cmd
, const void *data
, size_t size
)
472 FRSKY_OSD_TRACE("Send async cmd %u", cmd
);
473 frskyOsdSendCommand(cmd
, data
, size
);
476 static bool frskyOsdSendSyncCommand(uint8_t cmd
, const void *data
, size_t size
, timeMs_t timeout
)
478 FRSKY_OSD_TRACE("Send sync cmd %u", cmd
);
479 frskyOsdClearReceiveBuffer();
480 frskyOsdSendCommand(cmd
, data
, size
);
481 frskyOsdFlushSendBuffer();
482 timeMs_t end
= millis() + timeout
;
483 while (millis() < end
) {
484 frskyOsdUpdateReceiveBuffer();
485 if (frskyOsdIsResponseAvailable() && frskyOsdDispatchResponse()) {
486 FRSKY_OSD_DEBUG("Got sync response");
490 FRSKY_OSD_DEBUG("Sync response failed");
494 static bool frskyOsdShouldRequestInfo(void)
496 return !frskyOsdIsReady() || millis() > state
.nextInfoRequest
;
499 static void frskyOsdRequestInfo(void)
501 timeMs_t now
= millis();
502 if (state
.info
.nextRequest
< now
) {
503 uint8_t version
= FRSKY_OSD_SUPPORTED_API_VERSION
;
504 frskyOsdSendAsyncCommand(OSD_CMD_INFO
, &version
, sizeof(version
));
505 frskyOsdFlushSendBuffer();
506 state
.info
.nextRequest
= now
+ FRSKY_OSD_INFO_INTERVAL_MS
;
510 bool frskyOsdInit(videoSystem_e videoSystem
)
513 FRSKY_OSD_TRACE("frskyOsdInit()");
514 // TODO: Use videoSystem to set the signal standard when
515 // no input is detected.
516 const serialPortConfig_t
*portConfig
= findSerialPortConfig(FUNCTION_FRSKY_OSD
);
518 FRSKY_OSD_TRACE("FrSky OSD configured, trying to connect...");
519 portOptions_e portOptions
= 0;
520 serialPort_t
*port
= openSerialPort(portConfig
->identifier
,
521 FUNCTION_FRSKY_OSD
, NULL
, NULL
, FRSKY_OSD_BAUDRATE
,
522 MODE_RXTX
, portOptions
);
525 frskyOsdStateReset(port
);
526 frskyOsdRequestInfo();
533 bool frskyOsdIsReady(void)
535 return state
.info
.minor
> 0 || state
.info
.major
> 0;
538 void frskyOsdUpdate(void)
543 frskyOsdUpdateReceiveBuffer();
545 if (frskyOsdIsResponseAvailable()) {
546 frskyOsdDispatchResponse();
549 if (frskyOsdShouldRequestInfo()) {
550 frskyOsdRequestInfo();
554 void frskyOsdBeginTransaction(frskyOsdTransactionOptions_e opts
)
556 if (opts
& FRSKY_OSD_TRANSACTION_OPT_PROFILED
) {
557 frskyOsdPoint_t p
= { .x
= 0, .y
= 10};
558 frskyOsdSendAsyncCommand(OSD_CMD_TRANSACTION_BEGIN_PROFILED
, &p
, sizeof(p
));
559 if (opts
& FRSKY_OSD_TRANSACTION_OPT_RESET_DRAWING
) {
560 frskyOsdResetDrawingState();
562 } else if (opts
& FRSKY_OSD_TRANSACTION_OPT_RESET_DRAWING
) {
563 frskyOsdSendAsyncCommand(OSD_CMD_TRANSACTION_BEGIN_RESET_DRAWING
, NULL
, 0);
565 frskyOsdSendAsyncCommand(OSD_CMD_TRANSACTION_BEGIN
, NULL
, 0);
569 void frskyOsdCommitTransaction(void)
571 // Check wether the only command in the queue is a transaction begin.
572 // In that, case, discard the send buffer since it will make generate
573 // an empty transaction.
574 if (state
.sendBuffer
.pos
== 1) {
575 if (state
.sendBuffer
.data
[0] == OSD_CMD_TRANSACTION_BEGIN
||
576 state
.sendBuffer
.data
[0] == OSD_CMD_TRANSACTION_BEGIN_RESET_DRAWING
) {
578 state
.sendBuffer
.pos
= 0;
582 frskyOsdSendAsyncCommand(OSD_CMD_TRANSACTION_COMMIT
, NULL
, 0);
583 frskyOsdFlushSendBuffer();
586 void frskyOsdFlushSendBuffer(void)
588 if (state
.sendBuffer
.pos
> 0) {
589 frskyOsdProcessCommandU8(NULL
, FRSKY_OSD_PREAMBLE_BYTE_0
);
590 frskyOsdProcessCommandU8(NULL
, FRSKY_OSD_PREAMBLE_BYTE_1
);
594 int lengthSize
= uvarintEncode(state
.sendBuffer
.pos
, buffer
, sizeof(buffer
));
595 for (int ii
= 0; ii
< lengthSize
; ii
++) {
596 frskyOsdProcessCommandU8(&crc
, buffer
[ii
]);
598 for (unsigned ii
= 0; ii
< state
.sendBuffer
.pos
; ii
++) {
599 frskyOsdProcessCommandU8(&crc
, state
.sendBuffer
.data
[ii
]);
601 frskyOsdProcessCommandU8(NULL
, crc
);
602 state
.sendBuffer
.pos
= 0;
606 bool frskyOsdReadFontCharacter(unsigned char_address
, osdCharacter_t
*chr
)
608 uint16_t addr
= char_address
;
610 state
.recvOsdCharacter
.addr
= UINT16_MAX
;
611 state
.recvOsdCharacter
.chr
= chr
;
613 // 500ms should be more than enough to receive ~70 bytes @ 115200 bps
614 bool ok
= frskyOsdSendSyncCommand(OSD_CMD_READ_FONT
, &addr
, sizeof(addr
), 500);
616 state
.recvOsdCharacter
.chr
= NULL
;
618 if (ok
&& state
.recvOsdCharacter
.addr
== addr
) {
624 bool frskyOsdWriteFontCharacter(unsigned char_address
, const osdCharacter_t
*chr
)
626 frskyOsdCharacter_t c
;
627 STATIC_ASSERT(sizeof(*chr
) == sizeof(c
.data
), invalid_character_size
);
629 memcpy(&c
.data
, chr
, sizeof(c
.data
));
630 c
.addr
= char_address
;
631 FRSKY_OSD_TRACE("Writing font character %u", char_address
);
632 frskyOsdSendSyncCommand(OSD_CMD_WRITE_FONT
, &c
, sizeof(c
), 1000);
636 unsigned frskyOsdGetGridRows(void)
638 return state
.info
.grid
.rows
;
641 unsigned frskyOsdGetGridCols(void)
643 return state
.info
.grid
.columns
;
646 unsigned frskyOsdGetPixelWidth(void)
648 return state
.info
.viewport
.width
;
651 unsigned frskyOsdGetPixelHeight(void)
653 return state
.info
.viewport
.height
;
656 static void frskyOsdSendCharInGrid(unsigned x
, unsigned y
, uint16_t chr
)
658 uint8_t payload
[] = {
665 frskyOsdSendAsyncCommand(OSD_CMD_DRAW_GRID_CHR
, payload
, sizeof(payload
));
668 static void frskyOsdSendAsyncBlobCommand(uint8_t cmd
, const void *header
, size_t headerSize
, const void *blob
, size_t blobSize
)
670 uint8_t payload
[128];
672 memcpy(payload
, header
, headerSize
);
674 int uvarintSize
= uvarintEncode(blobSize
, &payload
[headerSize
], sizeof(payload
) - headerSize
);
675 memcpy(&payload
[headerSize
+ uvarintSize
], blob
, blobSize
);
676 frskyOsdSendAsyncCommand(cmd
, payload
, headerSize
+ uvarintSize
+ blobSize
);
679 void frskyOsdDrawStringInGrid(unsigned x
, unsigned y
, const char *buff
)
681 frskyOsdDrawGridStrHeaderCmd_t cmd
;
686 frskyOsdSendAsyncBlobCommand(OSD_CMD_DRAW_GRID_STR
, &cmd
, sizeof(cmd
), buff
, strlen(buff
) + 1);
689 void frskyOsdDrawCharInGrid(unsigned x
, unsigned y
, uint16_t chr
)
691 frskyOsdSendCharInGrid(x
, y
, chr
);
694 void frskyOsdClearScreen(void)
696 if (!frskyOsdIsReady()) {
699 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_CLEAR_SCREEN
, NULL
, 0);
702 void frskyOsdSetStrokeColor(frskyOsdColor_e color
)
705 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_SET_STROKE_COLOR
, &c
, sizeof(c
));
708 void frskyOsdSetFillColor(frskyOsdColor_e color
)
711 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_SET_FILL_COLOR
, &c
, sizeof(c
));
714 void frskyOsdSetStrokeAndFillColor(frskyOsdColor_e color
)
717 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_SET_STROKE_AND_FILL_COLOR
, &c
, sizeof(c
));
720 void frskyOsdSetColorInversion(bool inverted
)
722 uint8_t c
= inverted
? 1 : 0;
723 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_SET_COLOR_INVERSION
, &c
, sizeof(c
));
726 void frskyOsdSetPixel(int x
, int y
, frskyOsdColor_e color
)
728 frskyOsdSetPixel_t sp
= {.p
= {.x
= x
, .y
= y
}, .color
= color
};
729 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_SET_PIXEL
, &sp
, sizeof(sp
));
732 void frskyOsdSetPixelToStrokeColor(int x
, int y
)
734 frskyOsdPoint_t p
= { .x
= x
, .y
= y
};
735 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_SET_PIXEL_TO_STROKE_COLOR
, &p
, sizeof(p
));
738 void frskyOsdSetPixelToFillColor(int x
, int y
)
740 frskyOsdPoint_t p
= { .x
= x
, .y
= y
};
741 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_SET_PIXEL_TO_FILL_COLOR
, &p
, sizeof(p
));
744 void frskyOsdSetStrokeWidth(unsigned width
)
747 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_SET_STROKE_WIDTH
, &w
, sizeof(w
));
750 void frskyOsdSetLineOutlineType(frskyOsdLineOutlineType_e outlineType
)
752 uint8_t type
= outlineType
;
753 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_SET_LINE_OUTLINE_TYPE
, &type
, sizeof(type
));
756 void frskyOsdSetLineOutlineColor(frskyOsdColor_e outlineColor
)
758 uint8_t color
= outlineColor
;
759 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_SET_LINE_OUTLINE_COLOR
, &color
, sizeof(color
));
762 void frskyOsdClipToRect(int x
, int y
, int w
, int h
)
764 frskyOsdRect_t r
= { .origin
= { .x
= x
, .y
= y
}, .size
= {.w
= w
, .h
= h
}};
765 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_CLIP_TO_RECT
, &r
, sizeof(r
));
768 void frskyOsdClearRect(int x
, int y
, int w
, int h
)
770 frskyOsdRect_t r
= { .origin
= { .x
= x
, .y
= y
}, .size
= {.w
= w
, .h
= h
}};
771 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_CLEAR_RECT
, &r
, sizeof(r
));
774 void frskyOsdResetDrawingState(void)
776 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_RESET
, NULL
, 0);
779 void frskyOsdDrawCharacter(int x
, int y
, uint16_t chr
, uint8_t opts
)
781 frskyOsdDrawCharacterCmd_t dc
= { .p
= {.x
= x
, .y
= y
}, .chr
= chr
, .opts
= opts
};
782 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_DRAW_CHAR
, &dc
, sizeof(dc
));
785 void frskyOsdDrawCharacterMask(int x
, int y
, uint16_t chr
, frskyOsdColor_e color
, uint8_t opts
)
787 frskyOsdDrawCharacterMaskCmd_t dc
= { .dc
= { .p
= {.x
= x
, .y
= y
}, .chr
= chr
, .opts
= opts
}, .maskColor
= color
};
788 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_DRAW_CHAR_MASK
, &dc
, sizeof(dc
));
791 void frskyOsdDrawString(int x
, int y
, const char *s
, uint8_t opts
)
793 frskyOsdDrawStrCommandHeaderCmd_t cmd
;
798 frskyOsdSendAsyncBlobCommand(OSD_CMD_DRAWING_DRAW_STRING
, &cmd
, sizeof(cmd
), s
, strlen(s
) + 1);
801 void frskyOsdDrawStringMask(int x
, int y
, const char *s
, frskyOsdColor_e color
, uint8_t opts
)
803 frskyOsdDrawStrMaskCommandHeaderCmd_t cmd
;
807 cmd
.maskColor
= color
;
809 frskyOsdSendAsyncBlobCommand(OSD_CMD_DRAWING_DRAW_STRING_MASK
, &cmd
, sizeof(cmd
), s
, strlen(s
) + 1);
812 void frskyOsdMoveToPoint(int x
, int y
)
814 frskyOsdPoint_t p
= { .x
= x
, .y
= y
};
815 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_MOVE_TO_POINT
, &p
, sizeof(p
));
818 void frskyOsdStrokeLineToPoint(int x
, int y
)
820 frskyOsdPoint_t p
= { .x
= x
, .y
= y
};
821 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_STROKE_LINE_TO_POINT
, &p
, sizeof(p
));
824 void frskyOsdStrokeTriangle(int x1
, int y1
, int x2
, int y2
, int x3
, int y3
)
826 frskyOsdTriangle_t t
= {.p1
= {.x
= x1
, .y
= y1
}, .p2
= {.x
= x2
, .y
= y2
}, .p3
= { .x
= x3
, .y
= y3
}};
827 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_STROKE_TRIANGLE
, &t
, sizeof(t
));
830 void frskyOsdFillTriangle(int x1
, int y1
, int x2
, int y2
, int x3
, int y3
)
832 frskyOsdTriangle_t t
= {.p1
= {.x
= x1
, .y
= y1
}, .p2
= {.x
= x2
, .y
= y2
}, .p3
= { .x
= x3
, .y
= y3
}};
833 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_FILL_TRIANGLE
, &t
, sizeof(t
));
836 void frskyOsdFillStrokeTriangle(int x1
, int y1
, int x2
, int y2
, int x3
, int y3
)
838 frskyOsdTriangle_t t
= {.p1
= {.x
= x1
, .y
= y1
}, .p2
= {.x
= x2
, .y
= y2
}, .p3
= { .x
= x3
, .y
= y3
}};
839 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_FILL_STROKE_TRIANGLE
, &t
, sizeof(t
));
842 void frskyOsdStrokeRect(int x
, int y
, int w
, int h
)
844 frskyOsdRect_t r
= { .origin
= { .x
= x
, .y
= y
}, .size
= {.w
= w
, .h
= h
}};
845 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_STROKE_RECT
, &r
, sizeof(r
));
848 void frskyOsdFillRect(int x
, int y
, int w
, int h
)
850 frskyOsdRect_t r
= { .origin
= { .x
= x
, .y
= y
}, .size
= {.w
= w
, .h
= h
}};
851 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_FILL_RECT
, &r
, sizeof(r
));
854 void frskyOsdFillStrokeRect(int x
, int y
, int w
, int h
)
856 frskyOsdRect_t r
= { .origin
= { .x
= x
, .y
= y
}, .size
= {.w
= w
, .h
= h
}};
857 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_FILL_STROKE_RECT
, &r
, sizeof(r
));
860 void frskyOsdStrokeEllipseInRect(int x
, int y
, int w
, int h
)
862 frskyOsdRect_t r
= { .origin
= { .x
= x
, .y
= y
}, .size
= {.w
= w
, .h
= h
}};
863 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_STROKE_ELLIPSE_IN_RECT
, &r
, sizeof(r
));
866 void frskyOsdFillEllipseInRect(int x
, int y
, int w
, int h
)
868 frskyOsdRect_t r
= { .origin
= { .x
= x
, .y
= y
}, .size
= {.w
= w
, .h
= h
}};
869 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_FILL_ELLIPSE_IN_RECT
, &r
, sizeof(r
));
872 void frskyOsdFillStrokeEllipseInRect(int x
, int y
, int w
, int h
)
874 frskyOsdRect_t r
= { .origin
= { .x
= x
, .y
= y
}, .size
= {.w
= w
, .h
= h
}};
875 frskyOsdSendAsyncCommand(OSD_CMD_DRAWING_FILL_STROKE_ELLIPSE_IN_RECT
, &r
, sizeof(r
));
878 void frskyOsdCtmReset(void)
880 frskyOsdSendAsyncCommand(OSD_CMD_CTM_RESET
, NULL
, 0);
883 void frskyOsdCtmSet(float m11
, float m12
, float m21
, float m22
, float m31
, float m32
)
890 frskyOsdSendAsyncCommand(OSD_CMD_CTM_SET
, values
, sizeof(values
));
893 void frskyOsdCtmTranslate(float tx
, float ty
)
899 frskyOsdSendAsyncCommand(OSD_CMD_CTM_TRANSLATE
, values
, sizeof(values
));
902 void frskyOsdCtmScale(float sx
, float sy
)
908 frskyOsdSendAsyncCommand(OSD_CMD_CTM_SCALE
, values
, sizeof(values
));
911 void frskyOsdCtmRotate(float r
)
913 frskyOsdSendAsyncCommand(OSD_CMD_CTM_ROTATE
, &r
, sizeof(r
));
916 void frskyOsdContextPush(void)
918 frskyOsdSendAsyncCommand(OSD_CMD_CONTEXT_PUSH
, NULL
, 0);
921 void frskyOsdContextPop(void)
923 frskyOsdSendAsyncCommand(OSD_CMD_CONTEXT_POP
, NULL
, 0);