dRonin  adbada4
dRonin firmware
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
onscreendisplay.c
Go to the documentation of this file.
1 
17 /*
18  * This program is free software; you can redistribute it and/or modify
19  * it under the terms of the GNU General Public License as published by
20  * the Free Software Foundation; either version 3 of the License, or
21  * (at your option) any later version.
22  *
23  * This program is distributed in the hope that it will be useful, but
24  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
25  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
26  * for more details.
27  *
28  * You should have received a copy of the GNU General Public License along
29  * with this program; if not, see <http://www.gnu.org/licenses/>
30  *
31  * Additional note on redistribution: The copyright and license notices above
32  * must be maintained in each individual source file that is a derivative work
33  * of this source file; otherwise redistribution is prohibited.
34  */
35 
36 // ****************
37 
38 // #define DEBUG_TIMING
39 
40 // static assert
41 #define ASSERT_CONCAT_(a, b) a ## b
42 #define ASSERT_CONCAT(a, b) ASSERT_CONCAT_(a, b)
43 /* These can't be used after statements in c89. */
44 #ifdef __COUNTER__
45  #define STATIC_ASSERT(e,m) \
46  { enum { ASSERT_CONCAT(static_assert_, __COUNTER__) = 1/(!!(e)) }; }
47 #else
48 /* This can't be used twice on the same line so ensure if using in headers
49  * that the headers are not included twice (by wrapping in #ifndef...#endif)
50  * Note it doesn't cause an issue when used on same line of separate modules
51  * compiled with gcc -combine -fwhole-program. */
52  #define STATIC_ASSERT(e,m) \
53  { enum { ASSERT_CONCAT(assert_line_, __LINE__) = 1/(!!(e)) }; }
54 #endif
55 
56 #include <openpilot.h>
57 #include "stm32f4xx_flash.h"
58 #include <pios_board_info.h>
59 #include "pios_thread.h"
60 #include "pios_semaphore.h"
61 #include "misc_math.h"
62 #include "pios_modules.h"
63 #include "pios_sensors.h"
64 
65 #include "onscreendisplay.h"
66 #include "onscreendisplaysettings.h"
67 #include "onscreendisplaypagesettings.h"
68 #include "onscreendisplaypagesettings2.h"
69 #include "onscreendisplaypagesettings3.h"
70 #include "onscreendisplaypagesettings4.h"
71 
72 #include "modulesettings.h"
73 #include "pios_video.h"
74 
75 #include "physical_constants.h"
76 
77 #include "accels.h"
78 #include "airspeedactual.h"
79 #include "attitudeactual.h"
80 #include "baroaltitude.h"
81 #include "flightstatus.h"
82 #include "flightbatterystate.h"
83 #include "flightbatterysettings.h"
84 #include "flightstats.h"
85 #include "gpsposition.h"
86 #include "positionactual.h"
87 #include "gpstime.h"
88 #include "gpssatellites.h"
89 #include "gpsvelocity.h"
90 #include "homelocation.h"
91 #include "magnetometer.h"
92 #include "manualcontrolcommand.h"
93 #include "modulesettings.h"
94 #include "stabilizationdesired.h"
95 #include "stabilizationsettings.h"
96 #include "stateestimation.h"
97 #include "systemalarms.h"
98 #include "systemstats.h"
99 #include "tabletinfo.h"
100 #include "taskinfo.h"
101 #include "velocityactual.h"
102 #include "vtxinfo.h"
103 #include "waypoint.h"
104 #include "waypointactive.h"
105 
106 #include "osd_utils.h"
107 #include "osd_menu.h"
108 #include "fonts.h"
109 #include "WMMInternal.h"
110 #include "mgrs.h"
111 
112 extern uint8_t PIOS_Board_Revision(void);
113 
114 extern uint8_t *draw_buffer_level;
115 extern uint8_t *draw_buffer_mask;
116 
117 // ****************
118 // Private functions
119 static void onScreenDisplayTask(void *parameters);
120 
121 // ****************
122 // Private constants
123 #define STACK_SIZE_BYTES 2048
124 #define TASK_PRIORITY PIOS_THREAD_PRIO_LOW
125 #define BLINK_INTERVAL_FRAMES 12
126 #define BOOT_DISPLAY_TIME_MS (10*1000)
127 #define STATS_DELAY_MS 1500
128 
129 const char METRIC_DIST_UNIT_LONG[] = "km";
130 const char METRIC_DIST_UNIT_SHORT[] = "m";
131 const char METRIC_SPEED_UNIT[] = "km/h";
132 
133 const char IMPERIAL_DIST_UNIT_LONG[] = "M";
134 const char IMPERIAL_DIST_UNIT_SHORT[] = "ft";
135 const char IMPERIAL_SPEED_UNIT[] = "MPH";
136 
137 volatile bool video_active;
138 
139 const point_t HOME_ARROW[] = {
140  {
141  .x = 0,
142  .y = -10,
143  },
144  {
145  .x = 9,
146  .y = 1,
147  },
148  {
149  .x = 3,
150  .y = 1,
151  },
152  {
153  .x = 3,
154  .y = 8,
155  },
156  {
157  .x = -3,
158  .y = 8,
159  },
160  {
161  .x = -3,
162  .y = 1,
163  },
164  {
165  .x = -9,
166  .y = 1,
167  }
168 };
169 
170 // Unit conversion constants
171 #define MS_TO_KMH 3.6f
172 #define MS_TO_MPH 2.23694f
173 #define M_TO_FEET 3.28084f
174 
175 // ****************
176 // Private variables
177 uint16_t frame_counter = 0;
178 static bool module_enabled = false;
179 static bool has_battery = false;
180 static bool has_gps = false;
181 static bool has_nav = false;
182 static bool has_baro = false;
183 static bool has_mag = false;
184 static struct pios_thread *taskHandle;
192 const char digits[16] = "0123456789abcdef";
193 
195 static volatile bool osd_settings_updated = true;
196 static volatile bool osd_page_updated = true;
197 static OnScreenDisplaySettingsData osd_settings;
198 static bool blink;
199 
200 
201 #ifdef DEBUG_TIMING
202 static uint32_t in_ticks = 0;
203 static uint32_t out_ticks = 0;
204 static uint16_t in_time = 0;
205 static uint16_t out_time = 0;
206 #endif
207 
208 
209 void drawBattery(uint16_t x, uint16_t y, uint8_t battery, uint16_t size)
210 {
211  write_rectangle_outlined(x - 2, y + 2, 2, size / 3 - 4, 0, 1);
212  write_rectangle_outlined(x, y, size, size / 3, 0, 1);
213  uint8_t charge_width = battery * (size - 2) / 100;
214  write_filled_rectangle_lm(x + size - charge_width, y + 1, charge_width, size / 3, 1, 1);
215 }
216 
234 // #define VERTICAL_SCALE_BRUTE_FORCE_BLANK_OUT
235 #define VERTICAL_SCALE_FILLED_NUMBER
236 #define VSCALE_FONT FONT8X10
237 void hud_draw_vertical_scale(int v, int range, int halign, int x, int y, int height, int mintick_step, int majtick_step, int mintick_len,
238  int majtick_len,
239  int boundtick_len, __attribute__((unused)) int max_val, int flags)
240 {
241  char temp[15];
242  const struct FontEntry *font_info;
243  struct FontDimensions dim;
244  // Compute the position of the elements.
245  int majtick_start = 0, majtick_end = 0, mintick_start = 0, mintick_end = 0, boundtick_start = 0, boundtick_end = 0;
246 
247  majtick_start = x;
248  mintick_start = x;
249  boundtick_start = x;
250  if (halign == -1) {
251  majtick_end = x + majtick_len;
252  mintick_end = x + mintick_len;
253  boundtick_end = x + boundtick_len;
254  } else if (halign == +1) {
255  majtick_end = x - majtick_len;
256  mintick_end = x - mintick_len;
257  boundtick_end = x - boundtick_len;
258  }
259  // Retrieve width of large font (font #0); from this calculate the x spacing.
260  font_info = get_font_info(VSCALE_FONT);
261  if (font_info == NULL)
262  return;
263  int arrow_len = (font_info->height / 2) + 1;
264  int text_x_spacing = (font_info->width / 2);
265  int max_text_y = 0, text_length = 0;
266  int small_font_char_width = font_info->width + 1; // +1 for horizontal spacing = 1
267  // For -(range / 2) to +(range / 2), draw the scale.
268  int range_2 = range / 2; // , height_2 = height / 2;
269  int r = 0, rr = 0, rv = 0, ys = 0, style = 0; // calc_ys = 0,
270  // Iterate through each step.
271  for (r = -range_2; r <= +range_2; r++) {
272  style = 0;
273  rr = r + range_2 - v; // normalise range for modulo, subtract value to move ticker tape
274  rv = -rr + range_2; // for number display
275  if (flags & HUD_VSCALE_FLAG_NO_NEGATIVE) {
276  rr += majtick_step / 2;
277  }
278  if (rr % majtick_step == 0) {
279  style = 1; // major tick
280  } else if (rr % mintick_step == 0) {
281  style = 2; // minor tick
282  } else {
283  style = 0;
284  }
285  if (flags & HUD_VSCALE_FLAG_NO_NEGATIVE && rv < 0) {
286  continue;
287  }
288  if (style) {
289  // Calculate y position.
290  ys = ((long int)(r * height) / (long int)range) + y;
291  // Depending on style, draw a minor or a major tick.
292  if (style == 1) {
293  write_hline_outlined(majtick_start, majtick_end, ys, 2, 2, 0, 1);
294  memset(temp, ' ', 10);
295  sprintf(temp, "%d", rv);
296  text_length = (strlen(temp) + 1) * small_font_char_width; // add 1 for margin
297  if (text_length > max_text_y) {
298  max_text_y = text_length;
299  }
300  if (halign == -1) {
301  write_string(temp, majtick_end + text_x_spacing + 1, ys, 1, 0, TEXT_VA_MIDDLE, TEXT_HA_LEFT, 0, FONT_OUTLINED8X8);
302  } else {
303  write_string(temp, majtick_end - text_x_spacing + 1, ys, 1, 0, TEXT_VA_MIDDLE, TEXT_HA_RIGHT, 0, FONT_OUTLINED8X8);
304  }
305  } else if (style == 2) {
306  write_hline_outlined(mintick_start, mintick_end, ys, 2, 2, 0, 1);
307  }
308  }
309  }
310  // Generate the string for the value, as well as calculating its dimensions.
311  memset(temp, ' ', 10);
312  // my_itoa(v, temp);
313  sprintf(temp, "%02d", v);
314  // TODO: add auto-sizing.
315  calc_text_dimensions(temp, font_info, 1, 0, &dim);
316  int xx = 0, i = 0;
317  if (halign == -1) {
318  xx = majtick_end + text_x_spacing;
319  } else {
320  xx = majtick_end - text_x_spacing;
321  }
322  y++;
323  uint8_t width = dim.width + 4;
324  // Draw an arrow from the number to the point.
325  for (i = 0; i < arrow_len; i++) {
326  if (halign == -1) {
327  write_pixel_lm(xx - arrow_len + i, y - i - 1, 1, 1);
328  write_pixel_lm(xx - arrow_len + i, y + i - 1, 1, 1);
329 #ifdef VERTICAL_SCALE_FILLED_NUMBER
330  write_hline_lm(xx + width - 1, xx - arrow_len + i + 1, y - i - 1, 0, 1);
331  write_hline_lm(xx + width - 1, xx - arrow_len + i + 1, y + i - 1, 0, 1);
332 #else
333  write_hline_lm(xx + width - 1, xx - arrow_len + i + 1, y - i - 1, 0, 0);
334  write_hline_lm(xx + width - 1, xx - arrow_len + i + 1, y + i - 1, 0, 0);
335 #endif
336  } else {
337  write_pixel_lm(xx + arrow_len - i, y - i - 1, 1, 1);
338  write_pixel_lm(xx + arrow_len - i, y + i - 1, 1, 1);
339 #ifdef VERTICAL_SCALE_FILLED_NUMBER
340  write_hline_lm(xx - width - 1, xx + arrow_len - i - 1, y - i - 1, 0, 1);
341  write_hline_lm(xx - width - 1, xx + arrow_len - i - 1, y + i - 1, 0, 1);
342 #else
343  write_hline_lm(xx - width - 1, xx + arrow_len - i - 1, y - i - 1, 0, 0);
344  write_hline_lm(xx - width - 1, xx + arrow_len - i - 1, y + i - 1, 0, 0);
345 #endif
346  }
347  }
348  if (halign == -1) {
349  write_hline_lm(xx, xx + width -1, y - arrow_len, 1, 1);
350  write_hline_lm(xx, xx + width - 1, y + arrow_len - 2, 1, 1);
351  write_vline_lm(xx + width - 1, y - arrow_len, y + arrow_len - 2, 1, 1);
352  } else {
353  write_hline_lm(xx, xx - width - 1, y - arrow_len, 1, 1);
354  write_hline_lm(xx, xx - width - 1, y + arrow_len - 2, 1, 1);
355  write_vline_lm(xx - width - 1, y - arrow_len, y + arrow_len - 2, 1, 1);
356  }
357  // Draw the text.
358  if (halign == -1) {
359  write_string(temp, xx + width / 2, y - 1, 1, 0, TEXT_VA_MIDDLE, TEXT_HA_CENTER, 0, VSCALE_FONT);
360  } else {
361  write_string(temp, xx - width / 2, y - 1, 1, 0, TEXT_VA_MIDDLE, TEXT_HA_CENTER, 0, VSCALE_FONT);
362  }
363 #ifdef VERTICAL_SCALE_BRUTE_FORCE_BLANK_OUT
364  // This is a bad brute force method destuctive to other things that maybe drawn underneath like e.g. the artificial horizon:
365  // Then, add a slow cut off on the edges, so the text doesn't sharply
366  // disappear. We simply clear the areas above and below the ticker, and we
367  // use little markers on the edges.
368  if (halign == -1) {
369  write_filled_rectangle_lm(majtick_end + text_x_spacing, y + (height / 2) - (font_info->height / 2), max_text_y - boundtick_start,
370  font_info->height, 0, 0);
371  write_filled_rectangle_lm(majtick_end + text_x_spacing, y - (height / 2) - (font_info->height / 2), max_text_y - boundtick_start,
372  font_info->height, 0, 0);
373  } else {
374  write_filled_rectangle_lm(majtick_end - text_x_spacing - max_text_y, y + (height / 2) - (font_info->height / 2), max_text_y,
375  font_info->height, 0, 0);
376  write_filled_rectangle_lm(majtick_end - text_x_spacing - max_text_y, y - (height / 2) - (font_info->height / 2), max_text_y,
377  font_info->height, 0, 0);
378  }
379 #endif
380  y--;
381  write_hline_outlined(boundtick_start, boundtick_end, y + (height / 2), 2, 2, 0, 1);
382  write_hline_outlined(boundtick_start, boundtick_end, y - (height / 2), 2, 2, 0, 1);
383 }
384 
399 #define COMPASS_SMALL_NUMBER
400 // #define COMPASS_FILLED_NUMBER
401 void hud_draw_linear_compass(int v, int home_dir, int range, int width, int x, int y, int mintick_step, int majtick_step, int mintick_len,
402  int majtick_len, __attribute__((unused)) int flags)
403 {
404  v %= 360; // wrap, just in case.
405  const struct FontEntry *font_info;
406  int majtick_start = 0, majtick_end = 0, mintick_start = 0, mintick_end = 0, textoffset = 0;
407  char headingstr[4];
408  majtick_start = y;
409  majtick_end = y - majtick_len;
410  mintick_start = y;
411  mintick_end = y - mintick_len;
412  textoffset = 8;
413  int r, style, rr, xs; // rv,
414  int range_2 = range / 2;
415  bool home_drawn = false;
416  for (r = -range_2; r <= +range_2; r++) {
417  style = 0;
418  rr = (v + r + 360) % 360; // normalise range for modulo, add to move compass track
419  // rv = -rr + range_2; // for number display
420  if (rr % majtick_step == 0) {
421  style = 1; // major tick
422  } else if (rr % mintick_step == 0) {
423  style = 2; // minor tick
424  }
425  if (style) {
426  // Calculate x position.
427  xs = ((long int)(r * width) / (long int)range) + x;
428  // Draw it.
429  if (style == 1) {
430  write_vline_outlined(xs, majtick_start, majtick_end, 2, 2, 0, 1);
431  // Draw heading above this tick.
432  // If it's not one of north, south, east, west, draw the heading.
433  // Otherwise, draw one of the identifiers.
434  if (rr % 90 != 0) {
435  // We abbreviate heading to two digits. This has the side effect of being easy to compute.
436  headingstr[0] = '0' + (rr / 100);
437  headingstr[1] = '0' + ((rr / 10) % 10);
438  headingstr[2] = 0;
439  headingstr[3] = 0; // nul to terminate
440  } else {
441  switch (rr) {
442  case 0:
443  headingstr[0] = 'N';
444  break;
445  case 90:
446  headingstr[0] = 'E';
447  break;
448  case 180:
449  headingstr[0] = 'S';
450  break;
451  case 270:
452  headingstr[0] = 'W';
453  break;
454  }
455  headingstr[1] = 0;
456  headingstr[2] = 0;
457  headingstr[3] = 0;
458  }
459  // +1 fudge...!
460  write_string(headingstr, xs + 1, majtick_start + textoffset, 1, 0, TEXT_VA_MIDDLE, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
461 
462  } else if (style == 2) {
463  write_vline_outlined(xs, mintick_start, mintick_end, 2, 2, 0, 1);
464  }
465  }
466 
467  // Put home direction
468  if (rr == home_dir) {
469  xs = ((long int)(r * width) / (long int)range) + x;
470  write_filled_rectangle_lm(xs - 5, majtick_start + textoffset + 7, 10, 10, 0, 1);
471  write_string("H", xs + 1, majtick_start + textoffset + 12, 1, 0, TEXT_VA_MIDDLE, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
472  home_drawn = true;
473  }
474  }
475 
476  if (home_dir > 0 && !home_drawn) {
477  if (((v > home_dir) && (v - home_dir < 180)) || ((v < home_dir) && (home_dir -v > 180)))
478  r = x - ((long int)(range_2 * width) / (long int)range);
479  else
480  r = x + ((long int)(range_2 * width) / (long int)range);
481 
482  write_filled_rectangle_lm(r - 5, majtick_start + textoffset + 7, 10, 10, 0, 1);
483  write_string("H", r + 1, majtick_start + textoffset + 12, 1, 0, TEXT_VA_MIDDLE, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
484  }
485 
486  // Then, draw a rectangle with the present heading in it.
487  // We want to cover up any other markers on the bottom.
488  // First compute font size.
489  headingstr[0] = '0' + (v / 100);
490  headingstr[1] = '0' + ((v / 10) % 10);
491  headingstr[2] = '0' + (v % 10);
492  headingstr[3] = 0;
493  font_info = get_font_info(FONT12X18);
494  if (font_info == NULL)
495  return;
496 #ifdef COMPASS_SMALL_NUMBER
497  int rect_width = font_info->width * 3;
498 #ifdef COMPASS_FILLED_NUMBER
499  write_filled_rectangle_lm(x - (rect_width / 2), majtick_start - 7, rect_width, font_info->height, 0, 1);
500 #else
501  write_filled_rectangle_lm(x - (rect_width / 2), majtick_start - 7, rect_width, font_info->height, 0, 0);
502 #endif
503  write_rectangle_outlined(x - (rect_width / 2), majtick_start - 7, rect_width, font_info->height, 0, 1);
504  write_string(headingstr, x + 1, majtick_start + textoffset - 6, 0, 0, TEXT_VA_MIDDLE, TEXT_HA_CENTER, 1, FONT_OUTLINED8X14);
505 #else
506  int rect_width = (font_info->width + 1) * 3 + 2;
507 #ifdef COMPASS_FILLED_NUMBER
508  write_filled_rectangle_lm(x - (rect_width / 2), majtick_start + 2, rect_width, font_info->height + 2, 0, 1);
509 #else
510  write_filled_rectangle_lm(x - (rect_width / 2), majtick_start + 2, rect_width, font_info->height + 2, 0, 0);
511 #endif
512  write_rectangle_outlined(x - (rect_width / 2), majtick_start + 2, rect_width, font_info->height + 2, 0, 1);
513  write_string(headingstr, x + 1, majtick_start + textoffset, 0, 0, TEXT_VA_MIDDLE, TEXT_HA_CENTER, 1, FONT12X18);
514 #endif
515 }
516 
517 #define CENTER_BODY 3
518 #define CENTER_WING 7
519 #define CENTER_RUDDER 5
520 #define PITCH_STEP 10
521 void simple_artificial_horizon(float roll, float pitch, int16_t x, int16_t y,
522  int16_t width, int16_t height, int8_t max_pitch,
523  uint8_t n_pitch_steps, bool show_horizon,
524  OnScreenDisplayPageSettingsCenterMarkOptions center_mark)
525 {
526  float camera_tilt;
527 
528  StabilizationSettingsCameraTiltGet(&camera_tilt);
529 
530  width /= 2;
531  height /= 2;
532 
533  if (show_horizon) {
534  float sin_roll = sinf(roll * (float)(M_PI / 180));
535  float cos_roll = cosf(roll * (float)(M_PI / 180));
536 
537  pitch += cos_roll * camera_tilt;
538 
539  int pitch_step_offset = pitch / PITCH_STEP;
540 
541  /* how many degrees the "lines" are offset from their ideal pos
542  * since we need both, don't do fmodf.. */
543  float modulo_pitch = pitch - pitch_step_offset * 10.0f;
544 
545  // roll to pitch transformation
546  int16_t pp_x = x + width * ((sin_roll * modulo_pitch) / (float)max_pitch);
547  int16_t pp_y = y + height * ((cos_roll * modulo_pitch) / (float)max_pitch);
548 
549  int16_t d_x, d_x2; // delta x
550  int16_t d_y, d_y2; // delta y
551 
552  d_x = cos_roll * width / 2;
553  d_y = sin_roll * height / 2;
554 
555  d_x = 3 * d_x / 4;
556  d_y = 3 * d_y / 4;
557  d_x2 = 3 * d_x / 4;
558  d_y2 = 3 * d_y / 4;
559 
560  int16_t d_x_10 = width * sin_roll * PITCH_STEP / (float)max_pitch;
561  int16_t d_y_10 = height * cos_roll * PITCH_STEP / (float)max_pitch;
562 
563  int16_t d_x_2 = d_x_10 / 6;
564  int16_t d_y_2 = d_y_10 / 6;
565 
566  for (int i = (-max_pitch / 10)-1; i<(max_pitch/10)+1; i++) {
567  int angle = (pitch_step_offset + i);
568 
569  if (angle < -n_pitch_steps) continue;
570  if (angle > n_pitch_steps) continue;
571 
572  angle *= PITCH_STEP;
573 
574  /* Wraparound */
575  if (angle > 90) {
576  angle = 180 - angle;
577  } else if (angle < -90) {
578  angle = -180 - angle;
579  }
580 
581  int16_t pp_x2 = pp_x - i * d_x_10;
582  int16_t pp_y2 = pp_y - i * d_y_10;
583 
584  char tmp_str[5];
585 
586  sprintf(tmp_str, "%d", angle);
587 
588  if (angle < 0) {
589  write_line_outlined_dashed(pp_x2 - d_x2, pp_y2 + d_y2, pp_x2 + d_x2, pp_y2 - d_y2, 2, 2, 0, 1, 5);
590  write_line_outlined(pp_x2 - d_x2, pp_y2 + d_y2, pp_x2 - d_x2 - d_x_2, pp_y2 + d_y2 - d_y_2, 2, 2, 0, 1);
591  write_line_outlined(pp_x2 + d_x2, pp_y2 - d_y2, pp_x2 + d_x2 - d_x_2, pp_y2 - d_y2 - d_y_2, 2, 2, 0, 1);
592 
593  write_string(tmp_str, pp_x2 - d_x - 4, pp_y2 + d_y, 0, 0, TEXT_VA_MIDDLE, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
594  write_string(tmp_str, pp_x2 + d_x + 4, pp_y2 - d_y, 0, 0, TEXT_VA_MIDDLE, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
595  } else if (angle > 0) {
596  write_line_outlined(pp_x2 - d_x2, pp_y2 + d_y2, pp_x2 + d_x2, pp_y2 - d_y2, 2, 2, 0, 1);
597  write_line_outlined(pp_x2 - d_x2, pp_y2 + d_y2, pp_x2 - d_x2 + d_x_2, pp_y2 + d_y2 + d_y_2, 2, 2, 0, 1);
598  write_line_outlined(pp_x2 + d_x2, pp_y2 - d_y2, pp_x2 + d_x2 + d_x_2, pp_y2 - d_y2 + d_y_2, 2, 2, 0, 1);
599 
600  write_string(tmp_str, pp_x2 - d_x - 4, pp_y2 + d_y, 0, 0, TEXT_VA_MIDDLE, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
601  write_string(tmp_str, pp_x2 + d_x + 4, pp_y2 - d_y, 0, 0, TEXT_VA_MIDDLE, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
602  } else {
603  write_line_outlined(pp_x2 - d_x, pp_y2 + d_y, pp_x2 - d_x / 3, pp_y2 + d_y / 3, 2, 2, 0, 1);
604  write_line_outlined(pp_x2 + d_x / 3, pp_y2 - d_y / 3, pp_x2 + d_x, pp_y2 - d_y, 2, 2, 0, 1);
605  }
606  }
607  }
608 
609  // Center mark
610  if ((center_mark == ONSCREENDISPLAYPAGESETTINGS_CENTERMARK_MIDDLE) ||
611  (center_mark == ONSCREENDISPLAYPAGESETTINGS_CENTERMARK_CAMERAPITCH)) {
612  if (center_mark == ONSCREENDISPLAYPAGESETTINGS_CENTERMARK_CAMERAPITCH) {
613  /* Force the plane onto the screen... meh.
614  * Should not be necessary */
615  if (camera_tilt > max_pitch) {
616  camera_tilt = max_pitch;
617  }
618 
619  camera_tilt /= max_pitch;
620  camera_tilt *= height;
621  } else {
622  camera_tilt = 0;
623  }
624 
626  GRAPHICS_X_MIDDLE - CENTER_BODY, GRAPHICS_Y_MIDDLE + camera_tilt, 2, 0, 0, 1);
628  GRAPHICS_X_MIDDLE + 1 + CENTER_BODY + CENTER_WING, GRAPHICS_Y_MIDDLE + camera_tilt, 0, 2, 0, 1);
630  GRAPHICS_Y_MIDDLE - CENTER_BODY + camera_tilt, 2, 0, 0, 1);
631  }
632 
633 }
634 
635 void draw_flight_mode(int x, int y, int xs, int ys, int va, int ha, int flags, int font)
636 {
637  SharedDefsFlightModeOptions mode;
638  FlightStatusFlightModeGet(&mode);
639 
640  switch (mode)
641  {
642  case SHAREDDEFS_FLIGHTMODE_MANUAL:
643  write_string("MAN", x, y, xs, ys, va, ha, flags, font);
644  break;
645  case SHAREDDEFS_FLIGHTMODE_ACRO:
646  write_string("ACRO", x, y, xs, ys, va, ha, flags, font);
647  break;
648  case SHAREDDEFS_FLIGHTMODE_ACROPLUS:
649  write_string("ACROPLUS", x, y, xs, ys, va, ha, flags, font);
650  break;
651  case SHAREDDEFS_FLIGHTMODE_ACRODYNE:
652  write_string("ACRODYNE", x, y, xs, ys, va, ha, flags, font);
653  break;
654  case SHAREDDEFS_FLIGHTMODE_LEVELING:
655  write_string("LEVEL", x, y, xs, ys, va, ha, flags, font);
656  break;
657  case SHAREDDEFS_FLIGHTMODE_HORIZON:
658  write_string("HOR", x, y, xs, ys, va, ha, flags, font);
659  break;
660  case SHAREDDEFS_FLIGHTMODE_AXISLOCK:
661  write_string("ALCK", x, y, xs, ys, va, ha, flags, font);
662  break;
663  case SHAREDDEFS_FLIGHTMODE_VIRTUALBAR:
664  write_string("VBAR", x, y, xs, ys, va, ha, flags, font);
665  break;
666  case SHAREDDEFS_FLIGHTMODE_STABILIZED1:
667  write_string("ST1", x, y, xs, ys, va, ha, flags, font);
668  break;
669  case SHAREDDEFS_FLIGHTMODE_STABILIZED2:
670  write_string("ST2", x, y, xs, ys, va, ha, flags, font);
671  break;
672  case SHAREDDEFS_FLIGHTMODE_STABILIZED3:
673  write_string("ST3", x, y, xs, ys, va, ha, flags, font);
674  break;
675  case SHAREDDEFS_FLIGHTMODE_AUTOTUNE:
676  write_string("TUNE", x, y, xs, ys, va, ha, flags, font);
677  break;
678  case SHAREDDEFS_FLIGHTMODE_ALTITUDEHOLD:
679  write_string("AHLD", x, y, xs, ys, va, ha, flags, font);
680  break;
681  case SHAREDDEFS_FLIGHTMODE_POSITIONHOLD:
682  write_string("PHLD", x, y, xs, ys, va, ha, flags, font);
683  break;
684  case SHAREDDEFS_FLIGHTMODE_RETURNTOHOME:
685  write_string("RTH", x, y, xs, ys, va, ha, flags, font);
686  break;
687  case SHAREDDEFS_FLIGHTMODE_PATHPLANNER:
688  write_string("PLAN", x, y, xs, ys, va, ha, flags, font);
689  break;
690  case SHAREDDEFS_FLIGHTMODE_FAILSAFE:
691  write_string("FAILSAFE", x, y, xs, ys, va, ha, flags, font);
692  break;
693  case SHAREDDEFS_FLIGHTMODE_TABLETCONTROL:
694  (void) 0;
695  TabletInfoTabletModeDesiredOptions tab_mode;
696  TabletInfoTabletModeDesiredGet(&tab_mode);
697  switch (tab_mode) {
698  case TABLETINFO_TABLETMODEDESIRED_POSITIONHOLD:
699  write_string("TAB PH", x, y, xs, ys, va, ha, flags, font);
700  break;
701  case TABLETINFO_TABLETMODEDESIRED_RETURNTOHOME:
702  write_string("TAB RTH", x, y, xs, ys, va, ha, flags, font);
703  break;
704  case TABLETINFO_TABLETMODEDESIRED_RETURNTOTABLET:
705  write_string("TAB RTT", x, y, xs, ys, va, ha, flags, font);
706  break;
707  case TABLETINFO_TABLETMODEDESIRED_PATHPLANNER:
708  write_string("TAB Path", x, y, xs, ys, va, ha, flags, font);
709  break;
710  case TABLETINFO_TABLETMODEDESIRED_FOLLOWME:
711  write_string("TAB FollowMe", x, y, xs, ys, va, ha, flags, font);
712  break;
713  case TABLETINFO_TABLETMODEDESIRED_LAND:
714  write_string("TAB Land", x, y, xs, ys, va, ha, flags, font);
715  break;
716  case TABLETINFO_TABLETMODEDESIRED_CAMERAPOI:
717  write_string("TAB POI", x, y, xs, ys, va, ha, flags, font);
718  break;
719  }
720  break;
721  case SHAREDDEFS_FLIGHTMODE_LQG:
722  write_string("LQG-R", x, y, xs, ys, va, ha, flags, font);
723  break;
724  case SHAREDDEFS_FLIGHTMODE_LQGLEVELING:
725  write_string("LQG-L", x, y, xs, ys, va, ha, flags, font);
726  break;
727  case SHAREDDEFS_FLIGHTMODE_FLIPREVERSED:
728  write_string("FLIPOVER", x, y, xs, ys, va, ha, flags, font);
729  break;
730  }
731 }
732 
733 void draw_alarms(int x, int y, int xs, int ys, int va, int ha, int flags, int font)
734 {
735  char buf[100] = { 0 };
736  SystemAlarmsData alarm;
737  int pos = 0;
738 
739  SystemAlarmsGet(&alarm);
740 
741  // Boot alarm for a bit.
743  const char *boot_reason = AlarmBootReason(alarm.RebootCause);
744  strncpy((char*)buf, boot_reason, sizeof(buf));
745  buf[sizeof(buf) - 2] = '\0';
746  pos = strlen(buf);
747  buf[pos++] = ' ';
748  }
749 
750  uint8_t state;
751 
752  // With the above arrangement, can pass a length of 1 to this if
753  // there's an impossibly long boot reason.
754  // which then does the right thing and just fills it with a null.
755  int32_t len = AlarmString(&alarm, buf + pos, sizeof(buf) - pos,
756  blink, &state);
757 
758  if (len > 0) {
759  pos += len;
760  }
761 
762  if (pos > 0) {
763  buf[pos] = '\0';
764  write_string(buf, x, y, xs, ys, va, ha, flags, font);
765  }
766 }
767 
768 // map with home at center
769 void draw_map_home_center(int width_px, int height_px, int width_m, int height_m, bool show_wp, bool show_home, bool show_tablet)
770 {
771  char tmp_str[10] = { 0 };
772  WaypointData waypoint;
773  WaypointActiveData waypoint_active;
774  float scale_x, scale_y;
775  float p_north, p_east, p_north_draw, p_east_draw, yaw, rot, aspect, aspect_pos;
776  int x, y;
777  bool draw_uav;
778 
779  scale_x = (float)width_px / width_m;
780  scale_y = (float)height_px / height_m;
781 
782  // draw waypoints
783  if (show_wp && WaypointHandle() && WaypointActiveHandle()) {
784  int num_wp = UAVObjGetNumInstances(WaypointHandle());
785  WaypointActiveGet(&waypoint_active);
786  for (int i = 0; i < num_wp; i++) {
787  WaypointInstGet(i, &waypoint);
788  if ((num_wp == 1) && (waypoint.Position[WAYPOINT_POSITION_EAST] == 0.f) &&
789  (waypoint.Position[WAYPOINT_POSITION_NORTH] == 0.f)) {
790  // single waypoint at home
791  break;
792  }
793  if ((fabs(waypoint.Position[WAYPOINT_POSITION_EAST]) < width_m / 2) &&
794  (fabs(waypoint.Position[WAYPOINT_POSITION_NORTH]) < height_m / 2)) {
795  x = GRAPHICS_X_MIDDLE + scale_x * waypoint.Position[WAYPOINT_POSITION_EAST];
796  y = GRAPHICS_Y_MIDDLE - scale_y * waypoint.Position[WAYPOINT_POSITION_NORTH];
797  if (i == waypoint_active.Index) {
798  write_filled_rectangle_lm(x - 5, y - 5, i < 9 ? 10 : 18, 10, 0, 1);
799  }
800  sprintf(tmp_str, "%d", i + 1);
801  write_string(tmp_str, x, y - 4, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
802  }
803  }
804  }
805 
806  // draw home
807  if (show_home) {
809  }
810 
811  // Draw Tablet
812  if (show_tablet) {
813  TabletInfoData tabletInfo;
814  TabletInfoGet(&tabletInfo);
815  if (tabletInfo.Connected) {
816  float NED[3];
817  lla_to_ned(tabletInfo.Latitude, tabletInfo.Longitude, tabletInfo.Altitude, NED);
818 
819  if ((fabs(NED[1]) < width_m / 2) && (fabs(NED[0]) < height_m / 2)) {
820  x = GRAPHICS_X_MIDDLE + scale_x * NED[1];
821  y = GRAPHICS_Y_MIDDLE - scale_y * NED[0];
822  write_string("T", x, y - 4, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
823  }
824  }
825  }
826 
827  // draw UAV position and orientation
828  PositionActualNorthGet(&p_north);
829  PositionActualEastGet(&p_east);
830 
831  // decide wether the UAV is outside of the map range and where to draw it
832  if ((2.0f * (float)fabs(p_north) > height_m) || (2.0f * (float)fabs(p_east) > width_m)) {
833  if (blink) {
834  aspect = (float)width_m / (float)height_m;
835  aspect_pos = p_north / p_east;
836  if ((float)fabs(aspect_pos) < aspect) {
837  // left or right of map
838  p_east_draw = sign(p_east) * width_m / 2.f;
839  p_north_draw = p_east_draw * aspect_pos;
840  } else {
841  // above or below map
842  p_north_draw = sign(p_north) * height_m / 2.f;
843  p_east_draw = p_north_draw / aspect_pos;
844  }
845  p_north_draw = GRAPHICS_Y_MIDDLE - p_north_draw * scale_y;
846  p_east_draw = GRAPHICS_X_MIDDLE + p_east_draw * scale_x;
847  draw_uav = true;
848  } else {
849  draw_uav = false;
850  }
851  } else {
852  // inside map
853  p_north_draw = GRAPHICS_Y_MIDDLE - p_north * scale_y;
854  p_east_draw = GRAPHICS_X_MIDDLE + p_east * scale_x;
855  draw_uav = true;
856  }
857 
858  if (draw_uav) {
859  AttitudeActualYawGet(&yaw);
860  if (yaw < 0)
861  yaw += 360;
862 
863  rot = yaw - 210;
864  if (rot < 0)
865  rot += 360;
866  x = p_east_draw + 10.f * sinf(rot * (float)(M_PI / 180));
867  y = p_north_draw - 10.f * cosf(rot * (float)(M_PI / 180));
868  write_line_outlined(p_east_draw, p_north_draw, x, y, 2, 0, 0, 1);
869  rot = yaw - 150;
870  if (rot < 0)
871  rot += 360;
872  x = p_east_draw + 10 * sinf(rot * (float)(M_PI / 180));
873  y = p_north_draw - 10 * cosf(rot * (float)(M_PI / 180));
874  write_line_outlined(p_east_draw, p_north_draw, x, y, 2, 0, 0, 1);
875  }
876 }
877 
878 // map with uav at center
879 void draw_map_uav_center(int width_px, int height_px, int width_m, int height_m, bool show_wp, bool show_uav, bool show_tablet)
880 {
881  char tmp_str[10] = { 0 };
882  WaypointData waypoint;
883  WaypointActiveData waypoint_active;
884  float scale_x, scale_y;
885  float p_north, p_east, p_north_draw, p_east_draw, p_north_draw2, p_east_draw2, yaw, aspect, aspect_pos, sin_yaw, cos_yaw;
886  int x, y;
887  bool draw_this_wp = false;
888 
889  // scaling
890  scale_x = (float)width_px / (float)width_m;
891  scale_y = (float)height_px / (float)height_m;
892  aspect = (float)width_m / (float)height_m;
893 
894  // Get UAV position an yaw
895  AttitudeActualYawGet(&yaw);
896  PositionActualNorthGet(&p_north);
897  PositionActualEastGet(&p_east);
898  if (yaw < 0)
899  yaw += 360;
900  sin_yaw = sinf(yaw * (float)(M_PI / 180));
901  cos_yaw = cosf(yaw * (float)(M_PI / 180));
902 
903  // Draw waypoints
904  if (show_wp && WaypointHandle() && WaypointActiveHandle()) {
905  int num_wp = UAVObjGetNumInstances(WaypointHandle());
906  WaypointActiveGet(&waypoint_active);
907  for (int i = 0; i < num_wp; i++) {
908  WaypointInstGet(i, &waypoint);
909  if ((num_wp == 1) && (waypoint.Position[WAYPOINT_POSITION_EAST] == 0.f) &&
910  (waypoint.Position[WAYPOINT_POSITION_NORTH] == 0.f)) {
911  // single waypoint at home
912  break;
913  }
914  // translation
915  p_east_draw2 = waypoint.Position[WAYPOINT_POSITION_EAST] - p_east;
916  p_north_draw2 = waypoint.Position[WAYPOINT_POSITION_NORTH] - p_north;
917 
918  // rotation
919  p_east_draw = cos_yaw * p_east_draw2 - sin_yaw * p_north_draw2;
920  p_north_draw = sin_yaw * p_east_draw2 + cos_yaw * p_north_draw2;
921 
922  draw_this_wp = false;
923  if ((2.0f * (float)fabs(p_north_draw) > height_m) || (2.0f * (float)fabs(p_east_draw) > width_m)) {
924  if (blink) {
925  aspect_pos = p_north_draw / p_east_draw;
926  if ((float)fabs(aspect_pos) < aspect) {
927  // left or right of map
928  p_east_draw = sign(p_east_draw) * width_m / 2.f;
929  p_north_draw = p_east_draw * aspect_pos;
930  } else {
931  // above or below map
932  p_north_draw = sign(p_north_draw) * height_m / 2.f;
933  p_east_draw = p_north_draw / aspect_pos;
934  }
935  draw_this_wp = true;
936  }
937  } else {
938  // inside map
939  draw_this_wp = true;
940  }
941  if (draw_this_wp) {
942  x = GRAPHICS_X_MIDDLE + p_east_draw * scale_x;
943  y = GRAPHICS_Y_MIDDLE - p_north_draw * scale_y;
944  if (i == waypoint_active.Index) {
945  write_filled_rectangle_lm(x - 5, y - 5, i < 9 ? 10 : 18, 10, 0, 1);
946  }
947  sprintf(tmp_str, "%d", i + 1);
948  write_string(tmp_str, x, y - 4, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
949  }
950  }
951  }
952 
953  // Draw the home location
954  p_east_draw = cos_yaw * -1 * p_east + sin_yaw * p_north;
955  p_north_draw = sin_yaw * -1 * p_east - cos_yaw * p_north;
956 
957  if ((2.0f * (float)fabs(p_north_draw) > height_m) || (2.0f * (float)fabs(p_east_draw) > width_m)) {
958  if (blink) {
959  aspect_pos = p_north_draw / p_east_draw;
960  if ((float)fabs(aspect_pos) < aspect) {
961  // left or right of map
962  p_east_draw = sign(p_east_draw) * width_m / 2.f;
963  p_north_draw = p_east_draw * aspect_pos;
964  } else {
965  // above or below map
966  p_north_draw = sign(p_north_draw) * height_m / 2.f;
967  p_east_draw = p_north_draw / aspect_pos;
968  }
969  x = GRAPHICS_X_MIDDLE + p_east_draw * scale_x;
970  y = GRAPHICS_Y_MIDDLE - p_north_draw * scale_y;
971  write_string("H", x, y- 4, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
972  }
973  } else {
974  // inside map
975  x = GRAPHICS_X_MIDDLE + p_east_draw * scale_x;
976  y = GRAPHICS_Y_MIDDLE - p_north_draw * scale_y;
977  write_string("H", x, y- 4, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
978  }
979 
980  // Draw Tablet
981  if (show_tablet) {
982  TabletInfoData tabletInfo;
983  TabletInfoGet(&tabletInfo);
984  if (tabletInfo.Connected) {
985  float NED[3];
986  lla_to_ned(tabletInfo.Latitude, tabletInfo.Longitude, tabletInfo.Altitude, NED);
987 
988  // translation
989  p_east_draw2 = NED[1] - p_east;
990  p_north_draw2 = NED[0] - p_north;
991 
992  // rotation
993  p_east_draw = cos_yaw * p_east_draw2 - sin_yaw * p_north_draw2;
994  p_north_draw = sin_yaw * p_east_draw2 + cos_yaw * p_north_draw2;
995 
996  if ((2.0f * (float)fabs(p_north_draw) > height_m) || (2.0f * (float)fabs(p_east_draw) > width_m)) {
997  if (blink) {
998  aspect_pos = p_north_draw / p_east_draw;
999  if ((float)fabs(aspect_pos) < aspect) {
1000  // left or right of map
1001  p_east_draw = sign(p_east_draw) * width_m / 2.f;
1002  p_north_draw = p_east_draw * aspect_pos;
1003  } else {
1004  // above or below map
1005  p_north_draw = sign(p_north_draw) * height_m / 2.f;
1006  p_east_draw = p_north_draw / aspect_pos;
1007  }
1008  x = GRAPHICS_X_MIDDLE + p_east_draw * scale_x;
1009  y = GRAPHICS_Y_MIDDLE - p_north_draw * scale_y;
1010  write_string("T", x, y- 4, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, 1);
1011  }
1012  } else {
1013  // inside map
1014  x = GRAPHICS_X_MIDDLE + p_east_draw * scale_x;
1015  y = GRAPHICS_Y_MIDDLE - p_north_draw * scale_y;
1016  write_string("T", x, y- 4, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, FONT_OUTLINED8X8);
1017  }
1018  }
1019  }
1020 
1021  // Draw UAV
1022  if (show_uav) {
1025  }
1026 }
1027 
1028 void introGraphics(int16_t x, int16_t y)
1029 {
1030 #ifdef OSD_USE_BRAINFPV_LOGO
1031  /* logo */
1033  draw_image(x + 10, y - image_dronin.height / 2, &image_dronin);
1034 #else
1036  &image_droninbig);
1037 #endif
1038 }
1039 
1040 void introText(int16_t x, int16_t y)
1041 {
1042  write_string("dRonin/" DRONIN_TARGET, x, y, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, FONT12X18);
1043 }
1044 
1045 void printFWVersion(int16_t x, int16_t y)
1046 {
1047  const struct pios_board_info * bdinfo = &pios_board_info_blob;
1048  char tmp[64] = { 0 };
1049  char this_char;
1050  uint8_t pos = 0;
1051  tmp[pos++] = 'V';
1052  tmp[pos++] = 'E';
1053  tmp[pos++] = 'R';
1054  tmp[pos++] = ':';
1055  tmp[pos++] = ' ';
1056 
1057  // Git tag
1058  for (int i = 0; i < 26; i++) {
1059  this_char = *(char*)(bdinfo->fw_base + bdinfo->fw_size + 14 + i);
1060  if (this_char != 0) {
1061  tmp[pos++] = this_char;
1062  } else
1063  break;
1064  }
1065 
1066  tmp[pos++] = ' ';
1067 
1068  // Git commit hash
1069  for (int i = 0; i < 4; i++) {
1070  this_char = *(char*)(bdinfo->fw_base + bdinfo->fw_size + 7 - i);
1071  tmp[pos++] = digits[(this_char & 0xF0) >> 4];
1072  tmp[pos++] = digits[(this_char & 0x0F)];
1073  }
1074 
1075  write_string(tmp, x, y, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, FONT8X10);
1076 }
1077 
1078 void showVideoType(int16_t x, int16_t y)
1079 {
1081  write_string("NTSC", x, y, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, FONT12X18);
1082  } else {
1083  write_string("PAL", x, y, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, FONT12X18);
1084  }
1085 }
1086 
1087 void render_user_page(OnScreenDisplayPageSettingsData * page)
1088 {
1089  char tmp_str[100] = { 0 };
1090  float tmp, tmp1;
1091  float home_dist = -1.f;
1092  int home_dir = -1;
1093  uint8_t tmp_uint8;
1094  int16_t tmp_int16;
1095  uint32_t tmp_uint32;
1096  int tmp_int1, tmp_int2;
1097 
1098  if (page == NULL)
1099  return;
1100 
1101  // Get home distance and direction (only makes sense if GPS is enabled
1102  if (has_nav && (page->HomeDistance || page->CompassHomeDir) && PositionActualHandle() ) {
1103  PositionActualNorthGet(&tmp);
1104  PositionActualEastGet(&tmp1);
1105 
1106  if (page->HomeDistance)
1107  home_dist = (float)sqrt(tmp * tmp + tmp1 * tmp1) * convert_distance;
1108 
1109  // XXX check HomeArrow
1110  if (page->CompassHomeDir)
1111  home_dir = (int)(atan2f(tmp1, tmp) * RAD2DEG) + 180;
1112  }
1113 
1114  // Draw Map
1115  if (has_nav && page->Map && PositionActualHandle() ) {
1116  if (page->MapCenterMode == ONSCREENDISPLAYPAGESETTINGS_MAPCENTERMODE_UAV) {
1117  draw_map_uav_center(page->MapWidthPixels, page->MapHeightPixels,
1118  page->MapWidthMeters, page->MapHeightMeters,
1119  page->MapShowWp, page->MapShowUavHome,
1120  page->MapShowTablet);
1121 
1122  } else {
1123  draw_map_home_center(page->MapWidthPixels, page->MapHeightPixels,
1124  page->MapWidthMeters, page->MapHeightMeters,
1125  page->MapShowWp, page->MapShowUavHome,
1126  page->MapShowTablet);
1127  }
1128  }
1129 
1130  // Alarms
1131  if (page->Alarm) {
1132  draw_alarms((int)page->AlarmPosX, (int)page->AlarmPosY, 0, 0, TEXT_VA_TOP, (int)page->AlarmAlign, 0,
1133  page->AlarmFont);
1134  }
1135 
1136  // Altitude Scale
1137  if (page->AltitudeScale) {
1138  bool valid_altitude = false;
1139  if (page->AltitudeScaleSource == ONSCREENDISPLAYPAGESETTINGS_ALTITUDESCALESOURCE_BARO) {
1140  if (has_baro){
1141  BaroAltitudeAltitudeGet(&tmp);
1142  tmp -= home_baro_altitude;
1143  valid_altitude = true;
1144  }
1145  } else if (PositionActualHandle()) {
1146  PositionActualDownGet(&tmp);
1147  tmp *= -1.0f;
1148  valid_altitude = true;
1149  }
1150  if (valid_altitude) {
1151  if (page->AltitudeScaleAlign == ONSCREENDISPLAYPAGESETTINGS_ALTITUDESCALEALIGN_LEFT)
1152  hud_draw_vertical_scale(tmp * convert_distance, 100, -1, page->AltitudeScalePos, GRAPHICS_Y_MIDDLE, 120, 10, 20, 5, 8,
1153  11, 10000, 0);
1154  else
1155  hud_draw_vertical_scale(tmp * convert_distance, 100, 1, page->AltitudeScalePos, GRAPHICS_Y_MIDDLE, 120, 10, 20, 5, 8,
1156  11, 10000, 0);
1157  }
1158  }
1159 
1160  // Altitude Numeric
1161  if (page->AltitudeNumeric) {
1162  bool valid_altitude = false;
1163  if (page->AltitudeNumericSource == ONSCREENDISPLAYPAGESETTINGS_ALTITUDENUMERICSOURCE_BARO) {
1164  if (has_baro) {
1165  BaroAltitudeAltitudeGet(&tmp);
1166  tmp -= home_baro_altitude;
1167  valid_altitude = true;
1168  }
1169  } else if (PositionActualHandle()) {
1170  PositionActualDownGet(&tmp);
1171  tmp *= -1.0f;
1172  valid_altitude = true;
1173  }
1174  if (valid_altitude) {
1175  sprintf(tmp_str, "%d", (int)(tmp * convert_distance));
1176  write_string(tmp_str, page->AltitudeNumericPosX, page->AltitudeNumericPosY, 0, 0, TEXT_VA_TOP, (int)page->AltitudeNumericAlign,
1177  0, page->AltitudeNumericFont);
1178  }
1179  }
1180 
1181  // Arming Status
1182  if (page->ArmStatus) {
1183  FlightStatusArmedGet(&tmp_uint8);
1184  if (tmp_uint8 == FLIGHTSTATUS_ARMED_ARMED)
1185  write_string("ARMED", page->ArmStatusPosX, page->ArmStatusPosY, 0, 0, TEXT_VA_TOP, (int)page->ArmStatusAlign, 0,
1186  page->ArmStatusFont);
1187  else if (tmp_uint8 == FLIGHTSTATUS_ARMED_ARMING)
1188  write_string("arm..", page->ArmStatusPosX, page->ArmStatusPosY, 0, 0, TEXT_VA_TOP, (int)page->ArmStatusAlign, 0,
1189  page->ArmStatusFont);
1190  }
1191 
1192  // Artificial Horizon (and centermark)
1193  if (page->ArtificialHorizon || page->CenterMark) {
1194  AttitudeActualRollGet(&tmp);
1195  AttitudeActualPitchGet(&tmp1);
1197  page->ArtificialHorizonMaxPitch, page->ArtificialHorizonPitchSteps, page->ArtificialHorizon, page->CenterMark);
1198  }
1199 
1200  // Battery
1201  if (has_battery && FlightBatteryStateHandle()) {
1202  if (page->BatteryVolt) {
1203  FlightBatteryStateVoltageGet(&tmp);
1204  sprintf(tmp_str, "%0.1fV", (double)tmp);
1205  write_string(tmp_str, page->BatteryVoltPosX, page->BatteryVoltPosY, 0, 0, TEXT_VA_TOP, (int)page->BatteryVoltAlign, 0,
1206  page->BatteryVoltFont);
1207  }
1208  if (page->BatteryCurrent) {
1209  FlightBatteryStateCurrentGet(&tmp);
1210  sprintf(tmp_str, "%0.1fA", (double)tmp);
1211  write_string(tmp_str, page->BatteryCurrentPosX, page->BatteryCurrentPosY, 0, 0, TEXT_VA_TOP,
1212  (int)page->BatteryCurrentAlign, 0, page->BatteryCurrentFont);
1213  }
1214  if (page->BatteryConsumed) {
1215  FlightBatteryStateConsumedEnergyGet(&tmp);
1216  sprintf(tmp_str, "%0.0fmAh", (double)tmp);
1217  write_string(tmp_str, page->BatteryConsumedPosX, page->BatteryConsumedPosY, 0, 0, TEXT_VA_TOP,
1218  (int)page->BatteryConsumedAlign, 0, page->BatteryConsumedFont);
1219  }
1220 
1221  if (page->BatteryChargeState) {
1222  FlightBatteryStateConsumedEnergyGet(&tmp);
1223  FlightBatterySettingsCapacityGet(&tmp_uint32);
1224  drawBattery(page->BatteryChargeStatePosX, page->BatteryChargeStatePosY, 100 - 100 * tmp / tmp_uint32, 24);
1225  }
1226  }
1227 
1228  // Climb rate
1229  if (page->ClimbRate && VelocityActualHandle() && has_baro) {
1230  VelocityActualDownGet(&tmp);
1231  sprintf(tmp_str, "%0.1f", (double)(-1.f * convert_distance * tmp));
1232  write_string(tmp_str, page->ClimbRatePosX, page->ClimbRatePosY, 0, 0, TEXT_VA_TOP, (int)page->ClimbRateAlign, 0,
1233  page->ClimbRateFont);
1234  }
1235 
1236  // Compass
1237  if (page->Compass) {
1238  bool do_compass = has_mag;
1239 
1240  if (!do_compass && has_nav) {
1241  StateEstimationAttitudeFilterGet(&tmp_uint8);
1242 
1243  if (tmp_uint8 == STATEESTIMATION_ATTITUDEFILTER_COMPLEMENTARYVELCOMPASS) {
1244  do_compass = true;
1245  }
1246  }
1247 
1248  if (do_compass) {
1249  AttitudeActualYawGet(&tmp);
1250  if (tmp < 0)
1251  tmp += 360;
1252  if (page->CompassHomeDir) {
1253  hud_draw_linear_compass(tmp, home_dir, 120, 180, GRAPHICS_X_MIDDLE, (int)page->CompassPos, 15, 30, 5, 8, 0);
1254  } else {
1255  hud_draw_linear_compass(tmp, -1, 120, 180, GRAPHICS_X_MIDDLE, (int)page->CompassPos, 15, 30, 5, 8, 0);
1256  }
1257  }
1258  }
1259 
1260  // Custom text
1261  if (page->CustomText) {
1262  memcpy((void *)tmp_str, (void *)(osd_settings.CustomText), ONSCREENDISPLAYSETTINGS_CUSTOMTEXT_NUMELEM);
1263  tmp_str[ONSCREENDISPLAYSETTINGS_CUSTOMTEXT_NUMELEM] = 0;
1264  write_string(tmp_str, page->CustomTextPosX, page->CustomTextPosY, 0, 0, TEXT_VA_TOP, (int)page->CustomTextAlign, 0,
1265  page->CustomTextFont);
1266  }
1267 
1268  // Home arrow
1269  if (has_nav && page->HomeArrow) {
1270  if (!page->Compass) {
1271  AttitudeActualYawGet(&tmp);
1272  }
1273  tmp = fmodf(home_dir -tmp, 360.f);
1274  draw_polygon(page->HomeArrowPosX, page->HomeArrowPosY, tmp, HOME_ARROW, NELEMENTS(HOME_ARROW), 0, 1);
1275  }
1276 
1277  // CPU utilization
1278  if (page->Cpu) {
1279  SystemStatsCPULoadGet(&tmp_uint8);
1280  sprintf(tmp_str, "CPU:%2d", tmp_uint8);
1281  write_string(tmp_str, page->CpuPosX, page->CpuPosY, 0, 0, TEXT_VA_TOP, (int)page->CpuAlign, 0, page->CpuFont);
1282  }
1283 
1284  // Flight mode
1285  if (page->FlightMode) {
1286  draw_flight_mode(page->FlightModePosX, page->FlightModePosY, 0, 0, TEXT_VA_TOP, (int)page->FlightModeAlign, 0,
1287  page->FlightModeFont);
1288  }
1289 
1290  // G Force
1291  if (page->GForce) {
1292  AccelsData accelsData;
1293  AccelsGet(&accelsData);
1294  // apply low pass filter to reduce noise bias
1295  static AccelsData accelsDataAcc = { 0 };
1296 
1297  accelsDataAcc.x = 0.8f * accelsDataAcc.x + 0.2f * accelsData.x;
1298  accelsDataAcc.y = 0.8f * accelsDataAcc.y + 0.2f * accelsData.y;
1299  accelsDataAcc.z = 0.8f * accelsDataAcc.z + 0.2f * accelsData.z;
1300 
1301  tmp = sqrtf(powf(accelsDataAcc.x, 2.f) + powf(accelsDataAcc.y, 2.f) + powf(accelsDataAcc.z, 2.f)) / 9.81f;
1302  sprintf(tmp_str, "%0.1fG", (double)tmp);
1303  write_string(tmp_str, page->GForcePosX, page->GForcePosY, 0, 0, TEXT_VA_TOP, (int)page->GForceAlign, 0,
1304  page->GForceFont);
1305  }
1306 
1307  // GPS
1308  if (has_gps && (page->GpsStatus || page->GpsLat || page->GpsLon || page->GpsMgrs)) {
1309  GPSPositionData gps_data;
1310  GPSPositionGet(&gps_data);
1311 
1312  draw_image(page->GpsStatusPosX, page->GpsStatusPosY - image_gps.height / 2, &image_gps);
1313 
1314  uint8_t pdop_1 = gps_data.PDOP;
1315  uint8_t pdop_2 = roundf(10 * (gps_data.PDOP - pdop_1));
1316 
1317  if (page->GpsStatus) {
1318  switch (gps_data.Status)
1319  {
1320  case GPSPOSITION_STATUS_NOFIX:
1321  sprintf(tmp_str, "NO");
1322  break;
1323  case GPSPOSITION_STATUS_FIX2D:
1324  sprintf(tmp_str, "2D %d %d.%d", (int)gps_data.Satellites, (int)pdop_1, pdop_2);
1325  break;
1326  case GPSPOSITION_STATUS_FIX3D:
1327  sprintf(tmp_str, "3D %d %d.%d", (int)gps_data.Satellites, (int)pdop_1, pdop_2);
1328  break;
1329  case GPSPOSITION_STATUS_DIFF3D:
1330  sprintf(tmp_str, "3D %d %d.%d", (int)gps_data.Satellites, (int)pdop_1, pdop_2);
1331  break;
1332  default:
1333  sprintf(tmp_str, "NOGPS");
1334  }
1335  write_string(tmp_str, page->GpsStatusPosX + image_gps.width -4, page->GpsStatusPosY, 0, 0, TEXT_VA_MIDDLE, TEXT_HA_LEFT,
1336  0, page->GpsStatusFont);
1337  }
1338 
1339  if (page->GpsLat) {
1340  sprintf(tmp_str, "%0.5f", (double)gps_data.Latitude / 10000000.0);
1341  write_string(tmp_str, page->GpsLatPosX, page->GpsLatPosY, 0, 0, TEXT_VA_TOP, (int)page->GpsLatAlign, 0,
1342  page->GpsLatFont);
1343  }
1344 
1345  if (page->GpsLon) {
1346  sprintf(tmp_str, "%0.5f", (double)gps_data.Longitude / 10000000.0);
1347  write_string(tmp_str, page->GpsLonPosX, page->GpsLonPosY, 0, 0, TEXT_VA_TOP, (int)page->GpsLonAlign, 0,
1348  page->GpsLonFont);
1349  }
1350 
1351  // MGRS location
1352  if (page->GpsMgrs) {
1353  static char mgrs_str[20] = {0};
1354 
1355  if (frame_counter % 5 == 0) {
1356  // the conversion to MGRS is computationally expensive, so we update it a bit slower
1357  tmp_int1 = Convert_Geodetic_To_MGRS((double)gps_data.Latitude * (double)DEG2RAD / 10000000.0,
1358  (double)gps_data.Longitude * (double)DEG2RAD / 10000000.0, 5, mgrs_str);
1359  if (tmp_int1 != 0)
1360  sprintf(mgrs_str, "MGRS ERR: %d", tmp_int1);
1361  }
1362  write_string(mgrs_str, page->GpsMgrsPosX, page->GpsMgrsPosY, 0, 0, TEXT_VA_TOP, (int)page->GpsMgrsAlign, 0,
1363  page->GpsMgrsFont);
1364  }
1365  }
1366 
1367  // Home distance (will be -1 if enabled but GPS is not enabled)
1368  if (home_dist >= 0) {
1369  if (home_dist < convert_distance_divider)
1370  sprintf(tmp_str, "%d%s", (int) home_dist, dist_unit_short);
1371  else {
1372  sprintf(tmp_str, "%0.2f%s", (double)(home_dist / convert_distance_divider), dist_unit_long);
1373  }
1374  if (page->HomeDistanceShowIcon) {
1375  draw_image(page->HomeDistancePosX, page->HomeDistancePosY - image_home.height / 2, &image_home);
1376  }
1377  write_string(tmp_str, page->HomeDistancePosX + image_home.width - 4, page->HomeDistancePosY, 0, 0, TEXT_VA_MIDDLE, TEXT_HA_LEFT,
1378  0, page->HomeDistanceFont);
1379  }
1380 
1381  // RSSI
1382  if (page->Rssi) {
1383  ManualControlCommandRssiGet(&tmp_int16);
1384  if (tmp_int16 > osd_settings.RssiWarnThreshold || blink) {
1385  sprintf(tmp_str, "%3d", tmp_int16);
1386  if (page->RssiShowIcon) { // XXX rename
1387  draw_image(page->RssiPosX, page->RssiPosY - image_rssi.height / 2, &image_rssi);
1388  }
1389  write_string(tmp_str, page->RssiPosX + image_rssi.width - 4, page->RssiPosY, 0, 0, TEXT_VA_MIDDLE, TEXT_HA_LEFT, 0,
1390  page->RssiFont);
1391  }
1392  }
1393 
1394  // Speed Scale
1395  if (page->SpeedScale) {
1396  tmp = 0.f;
1397  bool speed_valid = false;
1398  switch (page->SpeedScaleSource)
1399  {
1400  case ONSCREENDISPLAYPAGESETTINGS_SPEEDSCALESOURCE_NAV:
1401  if (VelocityActualHandle()) {
1402  VelocityActualNorthGet(&tmp);
1403  VelocityActualEastGet(&tmp1);
1404  tmp = sqrt(tmp * tmp + tmp1 * tmp1);
1405  speed_valid = true;
1406  }
1407  sprintf(tmp_str, "%s", "GND");
1408  break;
1409  case ONSCREENDISPLAYPAGESETTINGS_SPEEDSCALESOURCE_GPS:
1410  if (has_gps && GPSPositionHandle()) {
1411  GPSPositionGroundspeedGet(&tmp);
1412  uint8_t fix;
1413  GPSPositionStatusGet(&fix);
1414  speed_valid = fix != GPSPOSITION_STATUS_NOFIX;
1415  }
1416  sprintf(tmp_str, "%s", "GND");
1417  break;
1418  case ONSCREENDISPLAYPAGESETTINGS_SPEEDSCALESOURCE_AIRSPEED:
1419  if (AirspeedActualHandle()) {
1420  AirspeedActualTrueAirspeedGet(&tmp);
1421  speed_valid = true;
1422  }
1423  sprintf(tmp_str, "%s", "AIR");
1424  }
1425  if (speed_valid){
1426  if (page->SpeedScaleAlign == ONSCREENDISPLAYPAGESETTINGS_SPEEDSCALEALIGN_LEFT) {
1427  hud_draw_vertical_scale(tmp * convert_speed, 30, -1, page->SpeedScalePos, GRAPHICS_Y_MIDDLE, 120, 10, 20, 5, 8, 11,
1428  100, 0);
1429  write_string(tmp_str, page->SpeedScalePos + 10, 200, 0, 0, TEXT_VA_MIDDLE, TEXT_HA_LEFT, 0, FONT_OUTLINED8X8);
1430  } else {
1431  hud_draw_vertical_scale(tmp * convert_speed, 30, 1, page->SpeedScalePos, GRAPHICS_Y_MIDDLE, 120, 10, 20, 5, 8, 11, 100,
1432  0);
1433  write_string(tmp_str, page->SpeedScalePos - 30, 200, 0, 0, TEXT_VA_MIDDLE, TEXT_HA_LEFT, 0, FONT_OUTLINED8X8);
1434  }
1435  }
1436  }
1437 
1438  // Speed Numeric
1439  if (page->SpeedNumeric) {
1440  tmp = 0.f;
1441  bool speed_valid = false;
1442  switch (page->SpeedNumericSource)
1443  {
1444  case ONSCREENDISPLAYPAGESETTINGS_SPEEDNUMERICSOURCE_NAV:
1445  if (VelocityActualHandle()) {
1446  VelocityActualNorthGet(&tmp);
1447  VelocityActualEastGet(&tmp1);
1448  speed_valid = true;
1449  }
1450  tmp = sqrt(tmp * tmp + tmp1 * tmp1);
1451  break;
1452  case ONSCREENDISPLAYPAGESETTINGS_SPEEDNUMERICSOURCE_GPS:
1453  if (GPSVelocityHandle()) {
1454  GPSVelocityNorthGet(&tmp);
1455  GPSVelocityEastGet(&tmp1);
1456  tmp = sqrt(tmp * tmp + tmp1 * tmp1);
1457  speed_valid = has_gps;
1458  }
1459  break;
1460  case ONSCREENDISPLAYPAGESETTINGS_SPEEDNUMERICSOURCE_AIRSPEED:
1461  if (AirspeedActualHandle()) {
1462  AirspeedActualTrueAirspeedGet(&tmp);
1463  speed_valid = true;
1464  }
1465  }
1466  if (speed_valid) {
1467  sprintf(tmp_str, "%d", (int)(tmp * convert_speed));
1468  write_string(tmp_str, page->SpeedNumericPosX, page->SpeedNumericPosY, 0, 0, (int)page->SpeedNumericAlign, TEXT_HA_LEFT, 0,
1469  page->SpeedNumericFont);
1470  }
1471  }
1472 
1473  // Time
1474  if (page->Time) {
1475  uint32_t time;
1476  SystemStatsFlightTimeGet(&time);
1477 
1478  tmp_int16 = (time / 3600000); // hours
1479  if (tmp_int16 == 0) {
1480  tmp_int1 = time / 60000; // minutes
1481  tmp_int2 = (time / 1000) - 60 * tmp_int1; // seconds
1482  sprintf(tmp_str, "%02d:%02d", (int)tmp_int1, (int)tmp_int2);
1483  } else {
1484  tmp_int1 = time / 60000 - 60 * tmp_int16; // minutes
1485  tmp_int2 = (time / 1000) - 60 * tmp_int1 - 3600 * tmp_int16; // seconds
1486  sprintf(tmp_str, "%02d:%02d:%02d", (int)tmp_int16, (int)tmp_int1, (int)tmp_int2);
1487  }
1488  write_string(tmp_str, page->TimePosX, page->TimePosY, 0, 0, TEXT_VA_TOP, (int)page->TimeAlign, 0, page->TimeFont);
1489  }
1490 
1491  // Throttle
1492  if (page->Throttle) {
1493  StabilizationDesiredThrustGet(&tmp);
1494 
1495  int throttle = (100 * tmp + 0.5f);
1496 
1497  /* Cap to 3 digits */
1498  if (throttle < -99) {
1499  throttle = -99;
1500  }
1501 
1502  sprintf(tmp_str, "%d", throttle);
1503 
1504  write_string(tmp_str, page->ThrottlePosX, page->ThrottlePosY, 0, 0, TEXT_VA_TOP, (int)page->ThrottleAlign, 0,
1505  page->ThrottleFont);
1506  }
1507 
1508  // Video Transmitter Frequency
1509  if (page->VTXFreq && VTXInfoHandle()) {
1510  uint16_t freq;
1511  VTXInfoFrequencyGet(&freq);
1512  if (page->VTXFreqShowUnit) {
1513  sprintf(tmp_str, "%dMHz", freq);
1514  }
1515  else {
1516  sprintf(tmp_str, "%d", freq);
1517  }
1518  write_string(tmp_str, page->VTXFreqPosX, page->VTXFreqPosY, 0, 0, TEXT_VA_TOP, (int)page->VTXFreqAlign, 0,
1519  page->VTXFreqFont);
1520  }
1521 
1522  // Video Transmitter Power
1523  if (page->VTXPower && VTXInfoHandle()) {
1524  uint16_t power;
1525  VTXInfoPowerGet(&power);
1526  if (page->VTXPowerShowUnit) {
1527  sprintf(tmp_str, "%dmW", power);
1528  }
1529  else {
1530  sprintf(tmp_str, "%d", power);
1531  }
1532  write_string(tmp_str, page->VTXPowerPosX, page->VTXPowerPosY, 0, 0, TEXT_VA_TOP, (int)page->VTXPowerAlign, 0,
1533  page->VTXPowerFont);
1534  }
1535 }
1536 
1537 #define STATS_LINE_SPACING 11
1538 #define STATS_LINE_Y 40
1539 #define STATS_LINE_X (GRAPHICS_LEFT + 10)
1540 #define STATS_FONT FONT8X10
1541 
1543 {
1544  float tmp;
1545  char tmp_str[100] = { 0 };
1546  int y_pos = STATS_LINE_Y;
1547  FlightStatsData stats;
1548  FlightStatsGet(&stats);
1549 
1550  write_string("Flight Statistics", GRAPHICS_X_MIDDLE, 10, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, FONT12X18);
1551 
1552  if (has_nav) {
1553  tmp = convert_distance * stats.DistanceTravelled;
1554  if (tmp < convert_distance_divider)
1555  sprintf(tmp_str, "Distance traveled: %d %s", (int)tmp, dist_unit_short);
1556  else {
1557  sprintf(tmp_str, "Distance traveled: %0.2f %s", (double)(tmp / convert_distance_divider), dist_unit_long);
1558  }
1559  write_string(tmp_str, STATS_LINE_X, y_pos, 0, 0, TEXT_VA_TOP, TEXT_HA_LEFT, 0, STATS_FONT);
1560  y_pos += STATS_LINE_SPACING;
1561 
1562  tmp = convert_distance * stats.MaxDistanceToHome;
1563  if (tmp < convert_distance_divider)
1564  sprintf(tmp_str, "Maximum distance to home: %d %s", (int)tmp, dist_unit_short);
1565  else {
1566  sprintf(tmp_str, "Maximum distance to home: %0.2f %s", (double)(tmp / convert_distance_divider), dist_unit_long);
1567  }
1568  write_string(tmp_str, STATS_LINE_X, y_pos, 0, 0, TEXT_VA_TOP, TEXT_HA_LEFT, 0, STATS_FONT);
1569  y_pos += STATS_LINE_SPACING;
1570 
1571  tmp = convert_distance * stats.MaxAltitude;
1572  sprintf(tmp_str, "Maximum altitude: %0.2f %s", (double)tmp, dist_unit_short);
1573  write_string(tmp_str, STATS_LINE_X, y_pos, 0, 0, TEXT_VA_TOP, TEXT_HA_LEFT, 0, STATS_FONT);
1574  y_pos += 2 * STATS_LINE_SPACING;
1575 
1576  tmp = convert_speed * stats.MaxGroundSpeed;
1577  sprintf(tmp_str, "Maximum ground speed: %0.2f %s", (double)tmp, speed_unit);
1578  write_string(tmp_str, STATS_LINE_X, y_pos, 0, 0, TEXT_VA_TOP, TEXT_HA_LEFT, 0, STATS_FONT);
1579  y_pos += STATS_LINE_SPACING;
1580  }
1581 
1582  if (has_baro) {
1583  tmp = convert_distance * stats.MaxClimbRate;
1584  sprintf(tmp_str, "Maximum climb rate: %0.2f %s/s", (double)tmp, dist_unit_short);
1585  write_string(tmp_str, STATS_LINE_X, y_pos, 0, 0, TEXT_VA_TOP, TEXT_HA_LEFT, 0, STATS_FONT);
1586  y_pos += STATS_LINE_SPACING;
1587 
1588  tmp = convert_distance * stats.MaxDescentRate;
1589  sprintf(tmp_str, "Maximum descent rate: %0.2f %s/s", (double)tmp, dist_unit_short);
1590  write_string(tmp_str, STATS_LINE_X, y_pos, 0, 0, TEXT_VA_TOP, TEXT_HA_LEFT, 0, STATS_FONT);
1591  y_pos += 2 * STATS_LINE_SPACING;
1592  }
1593 
1594  sprintf(tmp_str, "Maximum roll rate: %d deg/s", stats.MaxRollRate);
1595  write_string(tmp_str, STATS_LINE_X, y_pos, 0, 0, TEXT_VA_TOP, TEXT_HA_LEFT, 0, STATS_FONT);
1596  y_pos += STATS_LINE_SPACING;
1597 
1598  sprintf(tmp_str, "Maximum pitch rate: %d deg/s", stats.MaxPitchRate);
1599  write_string(tmp_str, STATS_LINE_X, y_pos, 0, 0, TEXT_VA_TOP, TEXT_HA_LEFT, 0, STATS_FONT);
1600  y_pos += STATS_LINE_SPACING;
1601 
1602  sprintf(tmp_str, "Maximum yaw rate: %d deg/s", stats.MaxYawRate);
1603  write_string(tmp_str, STATS_LINE_X, y_pos, 0, 0, TEXT_VA_TOP, TEXT_HA_LEFT, 0, STATS_FONT);
1604  y_pos += 2 * STATS_LINE_SPACING;
1605 
1606  if (has_battery) {
1607  sprintf(tmp_str, "Consumed energy: %d mAh", stats.ConsumedEnergy);
1608  write_string(tmp_str, STATS_LINE_X, y_pos, 0, 0, TEXT_VA_TOP, TEXT_HA_LEFT, 0, STATS_FONT);
1609  y_pos += STATS_LINE_SPACING;
1610 
1611  sprintf(tmp_str, "Initial battery voltage: %0.1f V", (double)stats.InitialBatteryVoltage / 1000.);
1612  write_string(tmp_str, STATS_LINE_X, y_pos, 0, 0, TEXT_VA_TOP, TEXT_HA_LEFT, 0, STATS_FONT);
1613  y_pos += STATS_LINE_SPACING;
1614  }
1615  return y_pos;
1616 }
1617 
1619 {
1620  if (video_system == PIOS_VIDEO_SYSTEM_NTSC) {
1621  PIOS_Video_SetLevels(osd_settings.NTSCBlack, osd_settings.NTSCWhite);
1622  PIOS_Video_SetXScale(osd_settings.NTSCXScale);
1623  }
1624  else {
1625  PIOS_Video_SetLevels(osd_settings.PALBlack, osd_settings.PALWhite);
1627  }
1628 }
1629 
1634 {
1635  if (module_enabled) {
1636  onScreenDisplaySemaphore = PIOS_Semaphore_Create();
1637  if (!onScreenDisplaySemaphore)
1638  return -2;
1639 
1641  if (!taskHandle)
1642  return -3;
1643  TaskMonitorAdd(TASKINFO_RUNNING_ONSCREENDISPLAY, taskHandle);
1644 
1645 #if defined(PIOS_INCLUDE_WDG) && defined(OSD_USE_WDG)
1646  PIOS_WDG_RegisterFlag(PIOS_WDG_OSDGEN);
1647 #endif
1648  return 0;
1649  }
1650  return -1;
1651 }
1652 
1657 {
1658  uint8_t osd_state;
1659 
1660  if (OnScreenDisplaySettingsInitialize() == -1) {
1661  module_enabled = false;
1662  return -1;
1663  }
1664  OnScreenDisplaySettingsOSDEnabledGet(&osd_state);
1665 
1666  if (osd_state == ONSCREENDISPLAYSETTINGS_OSDENABLED_ENABLED) {
1667  module_enabled = true;
1668  } else {
1669  module_enabled = false;
1670  return 0;
1671  }
1672 
1673  ModuleSettingsData module_settings;
1674  ModuleSettingsGet(&module_settings);
1675 
1677  has_battery = module_settings.AdminState[MODULESETTINGS_ADMINSTATE_BATTERY];
1678 
1679  has_nav = false;
1680  if (StateEstimationHandle()) {
1681  uint8_t filter;
1682  StateEstimationNavigationFilterGet(&filter);
1683  if (filter != STATEESTIMATION_NAVIGATIONFILTER_NONE) {
1684  has_nav = true;
1685  }
1686  }
1687 
1688  if (OnScreenDisplayPageSettingsInitialize() == -1 \
1689  || OnScreenDisplayPageSettings2Initialize() == -1 \
1690  || OnScreenDisplayPageSettings3Initialize() == -1 \
1691  || OnScreenDisplayPageSettings4Initialize() == -1 ) {
1692 
1693  module_enabled = false;
1694  return -1;
1695  }
1696 
1697  /* Register callbacks for modified settings */
1698  OnScreenDisplaySettingsConnectCallbackCtx(UAVObjCbSetFlag, &osd_settings_updated);
1699 
1700  /* Register callbacks for modified pages */
1701  OnScreenDisplayPageSettingsConnectCallbackCtx(UAVObjCbSetFlag, &osd_page_updated);
1702  OnScreenDisplayPageSettings2ConnectCallbackCtx(UAVObjCbSetFlag, &osd_page_updated);
1703  OnScreenDisplayPageSettings3ConnectCallbackCtx(UAVObjCbSetFlag, &osd_page_updated);
1704  OnScreenDisplayPageSettings4ConnectCallbackCtx(UAVObjCbSetFlag, &osd_page_updated);
1705 
1706  return 0;
1707 }
1708 
1710 
1711 static float get_accessorydesired(int idx) {
1712  if (idx < 0) {
1713  return 0;
1714  }
1715 
1716  if (idx >= MANUALCONTROLCOMMAND_ACCESSORY_NUMELEM) {
1717  return 0;
1718  }
1719 
1720  float accessories[MANUALCONTROLCOMMAND_ACCESSORY_NUMELEM];
1721 
1722  ManualControlCommandAccessoryGet(accessories);
1723 
1724  return accessories[idx];
1725 }
1726 
1730 #define BLANK_TIME 2000
1731 #define INTRO_TIME 5500
1732 static void onScreenDisplayTask(__attribute__((unused)) void *parameters)
1733 {
1734  OnScreenDisplayPageSettingsData osd_page_settings;
1735 
1736  enum pios_video_system video_system_act = PIOS_VIDEO_SYSTEM_NONE;
1737  enum pios_video_system video_system_last = PIOS_VIDEO_SYSTEM_NONE;
1738  uint32_t now;
1739  uint32_t show_stats_start = 0;
1740  uint32_t show_stats_until = 0;
1741  uint8_t arm_status;
1742  uint8_t last_arm_status = FLIGHTSTATUS_ARMED_DISARMED;
1743  uint8_t current_page = 0;
1744  uint8_t last_page = -1;
1745  uint8_t page_when_stats_enabled = 0;
1746  float tmp;
1747 
1748  OnScreenDisplaySettingsGet(&osd_settings);
1749  home_baro_altitude = 0.;
1750 
1751  // Determine if sensors have been registered
1754 
1755  // blank
1756  while (PIOS_Thread_Systime() <= BLANK_TIME) {
1757  PIOS_Thread_Sleep(20);
1758  }
1759 
1760  video_active = true;
1761 
1762  PIOS_Thread_Sleep(20);
1763 
1764  // initialize interupts
1766 
1767  // initialize settings
1768  video_system_act = PIOS_Video_GetSystem();
1769  set_ntsc_pal_settings(video_system_act);
1772  enum pios_video_3d_mode video_3d_mode;
1773  switch(osd_settings.ThreeDMode) {
1774  case ONSCREENDISPLAYSETTINGS_THREEDMODE_SBS3D:
1775  video_3d_mode = PIOS_VIDEO_3D_SBS3D;
1776  break;
1777  default:
1778  video_3d_mode = PIOS_VIDEO_3D_DISABLED;
1779  }
1780  PIOS_Video_Set3DConfig(video_3d_mode, osd_settings.ThreeDRightEyeXShift);
1781 
1782  // intro
1783  while (PIOS_Thread_Systime() <= BLANK_TIME + INTRO_TIME) {
1784  if (PIOS_Semaphore_Take(onScreenDisplaySemaphore, 300) == true) {
1785  video_active = true;
1786 
1787  clearGraphics();
1788  video_system_act = PIOS_Video_GetSystem();
1789  if ( video_system_act == PIOS_VIDEO_SYSTEM_NTSC) {
1794  } else {
1799  }
1800  frame_counter++;
1801  // Accumulate baro altitude
1802  if (BaroAltitudeHandle()) {
1803  BaroAltitudeAltitudeGet(&tmp);
1804  home_baro_altitude += tmp;
1805  }
1806  } else {
1807  video_active = false;
1808  }
1809  }
1810 
1811  // Create average altitude
1813 
1814  while (1) {
1815  if (PIOS_Semaphore_Take(onScreenDisplaySemaphore, 400) == true) {
1816  video_active = true;
1817  now = PIOS_Thread_Systime();
1818 #ifdef DEBUG_TIMING
1819  in_ticks = PIOS_Thread_Systime();
1820  out_time = in_ticks - out_ticks;
1821 #endif
1822  video_system_act = PIOS_Video_GetSystem();
1823  if (osd_settings_updated) {
1824  OnScreenDisplaySettingsGet(&osd_settings);
1825  set_ntsc_pal_settings(video_system_act);
1828  enum pios_video_3d_mode video_3d_mode;
1829  switch(osd_settings.ThreeDMode) {
1830  case ONSCREENDISPLAYSETTINGS_THREEDMODE_SBS3D:
1831  video_3d_mode = PIOS_VIDEO_3D_SBS3D;
1832  break;
1833  default:
1834  video_3d_mode = PIOS_VIDEO_3D_DISABLED;
1835  }
1836  PIOS_Video_Set3DConfig(video_3d_mode, osd_settings.ThreeDRightEyeXShift);
1837 
1838  if (osd_settings.Units == ONSCREENDISPLAYSETTINGS_UNITS_IMPERIAL) {
1840  convert_distance_divider = 5280.f; // feet in a mile
1845  } else {
1846  convert_distance = 1.f;
1847  convert_distance_divider = 1000.f;
1852  }
1853 
1854  osd_settings_updated = false;
1855  }
1856 
1857  // update settings when video type changes
1858  if (video_system_act != video_system_last) {
1859  set_ntsc_pal_settings(video_system_act);
1860  }
1861 
1862  // decide whether to show blinking elements
1864 
1865  if (frame_counter % 5 == 0) {
1866  // determine current page to use
1867  float accessory = get_accessorydesired(osd_settings.PageSwitch);
1868  current_page = (uint8_t)roundf(((accessory + 1.0f) / 2.0f) * (osd_settings.NumPages - 1));
1869  current_page = osd_settings.PageConfig[current_page];
1870 
1871  if (current_page != last_page)
1872  osd_page_updated = true;
1873  }
1874 
1875  if (osd_page_updated) {
1876  switch (current_page) {
1877  case ONSCREENDISPLAYSETTINGS_PAGECONFIG_CUSTOM2:
1878  OnScreenDisplayPageSettings2Get((OnScreenDisplayPageSettings2Data*)&osd_page_settings);
1879  break;
1880  case ONSCREENDISPLAYSETTINGS_PAGECONFIG_CUSTOM3:
1881  OnScreenDisplayPageSettings3Get((OnScreenDisplayPageSettings3Data*)&osd_page_settings);
1882  break;
1883  case ONSCREENDISPLAYSETTINGS_PAGECONFIG_CUSTOM4:
1884  OnScreenDisplayPageSettings4Get((OnScreenDisplayPageSettings4Data*)&osd_page_settings);
1885  break;
1886  default:
1887  OnScreenDisplayPageSettingsGet(&osd_page_settings);
1888  }
1889  osd_page_updated = false;
1890  }
1891 
1892  // Show stats when we disarm
1893  FlightStatusArmedGet(&arm_status);
1894  if (arm_status == FLIGHTSTATUS_ARMED_DISARMED) {
1895  if (last_arm_status != FLIGHTSTATUS_ARMED_DISARMED) {
1896  switch (osd_settings.StatsDisplayDuration) {
1897  case ONSCREENDISPLAYSETTINGS_STATSDISPLAYDURATION_OFF:
1898  show_stats_until = 0;
1899  break;
1900  case ONSCREENDISPLAYSETTINGS_STATSDISPLAYDURATION_10S:
1901  show_stats_until = now + 10 * 1000 + STATS_DELAY_MS;
1902  break;
1903  case ONSCREENDISPLAYSETTINGS_STATSDISPLAYDURATION_20S:
1904  show_stats_until = now + 20 * 1000 + STATS_DELAY_MS;
1905  break;
1906  case ONSCREENDISPLAYSETTINGS_STATSDISPLAYDURATION_30S:
1907  show_stats_until = now + 30 * 1000 + STATS_DELAY_MS;
1908  break;
1909  }
1910  page_when_stats_enabled = current_page;
1911  show_stats_start = now + STATS_DELAY_MS;
1912  } else {
1913  if (show_stats_until > now) {
1914  if (frame_counter % 5 == 0 && current_page != page_when_stats_enabled) {
1915  // toggling the page switch gets rid of the stats display
1916  show_stats_until = 0;
1917  } else if (now >= show_stats_start) {
1918  current_page = ONSCREENDISPLAYSETTINGS_PAGECONFIG_STATISTICS;
1919  }
1920  }
1921  }
1922  }
1923 
1924  clearGraphics();
1925  switch (current_page) {
1926  case ONSCREENDISPLAYSETTINGS_PAGECONFIG_OFF:
1927  break;
1928  case ONSCREENDISPLAYSETTINGS_PAGECONFIG_STATISTICS:
1929  render_stats();
1930  break;
1931  case ONSCREENDISPLAYSETTINGS_PAGECONFIG_MENU:
1932 #ifdef OSD_USE_MENU
1933  if ((arm_status == FLIGHTSTATUS_ARMED_DISARMED) ||
1934  (osd_settings.DisableMenuWhenArmed == ONSCREENDISPLAYSETTINGS_DISABLEMENUWHENARMED_DISABLED)) {
1935  render_osd_menu();
1936  break;
1937  }
1938 #endif
1939  write_string("MENU DISABLED", GRAPHICS_X_MIDDLE, 50, 0, 0, TEXT_VA_TOP, TEXT_HA_CENTER, 0, 3);
1940  case ONSCREENDISPLAYSETTINGS_PAGECONFIG_CUSTOM1:
1941  case ONSCREENDISPLAYSETTINGS_PAGECONFIG_CUSTOM2:
1942  case ONSCREENDISPLAYSETTINGS_PAGECONFIG_CUSTOM3:
1943  case ONSCREENDISPLAYSETTINGS_PAGECONFIG_CUSTOM4:
1944  render_user_page(&osd_page_settings);
1945 
1946  break;
1947  }
1948 
1949  //drawBox(0, 0, 351, 240);
1950 
1951  frame_counter++;
1952  last_page = current_page;
1953  last_arm_status = arm_status;
1954  video_system_last = video_system_act;
1955 #ifdef DEBUG_TIMING
1956  out_ticks = PIOS_Thread_Systime();
1957  in_time = out_ticks - in_ticks;
1958  char tmp_str[50];
1959  sprintf(tmp_str, "%03d %03d", (int)in_time, (int)out_time);
1961 #endif
1962  } else {
1963  video_active = false;
1964  }
1965  }
1966 }
1967 
volatile bool video_active
OSD Utility Functions.
Definition: fonts.h:43
#define BLINK_INTERVAL_FRAMES
pios_video_system
Definition: pios_video.h:50
MODULE_INITCALL(OnScreenDisplayInitialize, OnScreenDisplayStart)
#define STATS_FONT
static WaypointData waypoint
Definition: pathplanner.c:56
uint32_t PIOS_Thread_Systime(void)
Definition: pios_thread.c:212
void write_string(char *str, int x, int y, int xs, int ys, int va, int ha, int flags, int font)
Definition: osd_utils.c:1344
static const struct Image image_rssi
Definition: images.h:2085
void draw_polygon(int16_t x, int16_t y, float angle, const point_t *points, uint8_t n_points, int mode, int mmode)
Definition: osd_utils.c:1396
static AccelsData accelsData
Definition: sensors.c:101
#define VSCALE_FONT
#define BLANK_TIME
void write_vline_lm(int x, int y0, int y1, int lmode, int mmode)
Definition: osd_utils.c:483
void write_vline_outlined(int x, int y0, int y1, int endcap0, int endcap1, int mode, int mmode)
Definition: osd_utils.c:508
void PIOS_Video_SetXScale(uint8_t x_scale)
#define TEXT_HA_LEFT
Definition: osd_utils.h:133
#define INTRO_TIME
void hud_draw_linear_compass(int v, int home_dir, int range, int width, int x, int y, int mintick_step, int majtick_step, int mintick_len, int majtick_len, __attribute__((unused)) int flags)
int sprintf(char *out, const char *format,...)
uint16_t frame_counter
#define NELEMENTS(x)
Definition: pios.h:192
void draw_image(uint16_t x, uint16_t y, const struct Image *image)
Definition: osd_utils.c:70
#define FONT12X18
Definition: fonts.h:54
struct _msp_pid_item pitch
Definition: msp_messages.h:97
static bool has_gps
OSD gen module, handles OSD draw. Parts from CL-OSD and SUPEROSD projects.
int Convert_Geodetic_To_MGRS(double Latitude, double Longitude, int Precision, char *MGRS)
Definition: mgrs.c:775
bool PIOS_Modules_IsEnabled(enum pios_modules module)
Definition: pios_modules.c:41
#define GRAPHICS_Y_MIDDLE
Definition: pios_video.h:134
uint8_t width
Definition: fonts.h:44
void drawBattery(uint16_t x, uint16_t y, uint8_t battery, uint16_t size)
static bool has_nav
uint8_t PIOS_Board_Revision(void)
enum pios_video_system PIOS_Video_GetSystem(void)
void write_rectangle_outlined(int x, int y, int width, int height, int mode, int mmode)
Definition: osd_utils.c:681
bool PIOS_WDG_RegisterFlag(uint16_t flag_requested)
Register a module against the watchdog.
Definition: pios_wdg.c:86
static bool blink
struct _msp_pid_item roll
Definition: msp_messages.h:96
#define MS_TO_MPH
void write_line_outlined_dashed(int x0, int y0, int x1, int y1, __attribute__((unused)) int endcap0, __attribute__((unused)) int endcap1, int mode, int mmode, int dots)
Definition: osd_utils.c:1029
const char * speed_unit
#define FONT_OUTLINED8X8
Definition: fonts.h:55
void set_ntsc_pal_settings(enum pios_video_system video_system)
static volatile bool osd_settings_updated
static OnScreenDisplaySettingsData osd_settings
void UAVObjCbSetFlag(const UAVObjEvent *objEv, void *ctx, void *obj, int len)
static const struct Image image_brainfpv
Definition: images.h:1836
#define sign(x)
This is but one definition of sign(.)
Definition: misc_math.h:44
pios_video_3d_mode
Definition: pios_video.h:45
void write_line_outlined(int x0, int y0, int x1, int y1, __attribute__((unused)) int endcap0, __attribute__((unused)) int endcap1, int mode, int mmode)
Definition: osd_utils.c:945
static const struct Image image_home
Definition: images.h:2073
void showVideoType(int16_t x, int16_t y)
uint16_t flags
Definition: uavtalk_priv.h:52
#define TEXT_HA_RIGHT
Definition: osd_utils.h:135
void introText(int16_t x, int16_t y)
static bool has_battery
uint8_t fix
Definition: msp_messages.h:96
const struct pios_board_info pios_board_info_blob
void draw_map_uav_center(int width_px, int height_px, int width_m, int height_m, bool show_wp, bool show_uav, bool show_tablet)
const char METRIC_SPEED_UNIT[]
static const struct Image image_dronin
Definition: images.h:2050
#define HUD_VSCALE_FLAG_NO_NEGATIVE
Definition: osd_utils.h:44
void PIOS_Video_SetYOffset(int8_t)
#define STATS_DELAY_MS
int32_t OnScreenDisplayInitialize(void)
int16_t x
Definition: osd_utils.h:163
void calc_text_dimensions(char *str, const struct FontEntry *font, int xs, int ys, struct FontDimensions *dim)
Definition: osd_utils.c:1308
static const struct Image image_gps
Definition: images.h:2097
#define TEXT_HA_CENTER
Definition: osd_utils.h:134
void PIOS_Video_Set3DConfig(enum pios_video_3d_mode mode, uint8_t right_eye_x_shift)
uint8_t * draw_buffer_level
struct pios_semaphore * onScreenDisplaySemaphore
static struct pios_thread * taskHandle
static bool module_enabled
#define MS_TO_KMH
static const struct Image image_droninbig
Definition: images.h:1518
bool PIOS_SENSORS_IsRegistered(enum pios_sensor_type type)
Checks if a sensor type is registered with the PIOS_SENSORS interface.
Definition: pios_sensors.c:88
void introGraphics(int16_t x, int16_t y)
struct _msp_pid_item pos
Definition: msp_messages.h:100
struct pios_semaphore * PIOS_Semaphore_Create(void)
Creates a binary semaphore.
#define BOOT_DISPLAY_TIME_MS
void draw_map_home_center(int width_px, int height_px, int width_m, int height_m, bool show_wp, bool show_home, bool show_tablet)
const char * dist_unit_short
void PIOS_Video_SetXOffset(int8_t)
void PIOS_Video_Init(const struct pios_video_cfg *cfg)
#define PITCH_STEP
OSD gen module, handles OSD draw. Parts from CL-OSD and SUPEROSD projects.
struct pios_thread * PIOS_Thread_Create(void(*fp)(void *), const char *namep, size_t stack_bytes, void *argp, enum pios_thread_prio_e prio)
Definition: pios_thread.c:89
#define GRAPHICS_RIGHT
Definition: pios_video.h:130
static float get_accessorydesired(int idx)
#define GRAPHICS_X_MIDDLE
Definition: pios_video.h:133
const char * dist_unit_long
void draw_alarms(int x, int y, int xs, int ys, int va, int ha, int flags, int font)
int32_t TaskMonitorAdd(TaskInfoRunningElem task, struct pios_thread *threadp)
Definition: taskmonitor.c:67
uint8_t i
Definition: msp_messages.h:97
static volatile bool osd_page_updated
struct _msp_pid_item yaw
Definition: msp_messages.h:98
const point_t HOME_ARROW[]
enum channel_mode mode
Definition: pios_servo.c:58
float convert_speed
static void onScreenDisplayTask(void *parameters)
uint8_t height
Definition: fonts.h:45
#define TEXT_VA_TOP
Definition: osd_utils.h:130
static bool has_baro
const char IMPERIAL_SPEED_UNIT[]
const char * AlarmBootReason(uint8_t reason)
void hud_draw_vertical_scale(int v, int range, int halign, int x, int y, int height, int mintick_step, int majtick_step, int mintick_len, int majtick_len, int boundtick_len, __attribute__((unused)) int max_val, int flags)
uint8_t * draw_buffer_mask
void write_hline_outlined(int x0, int x1, int y, int endcap0, int endcap1, int mode, int mmode)
Definition: osd_utils.c:397
int32_t OnScreenDisplayStart(void)
#define STACK_SIZE_BYTES
uint16_t height
Definition: images.h:1076
void PIOS_Thread_Sleep(uint32_t time_ms)
Definition: pios_thread.c:229
#define M_TO_FEET
const char METRIC_DIST_UNIT_SHORT[]
void clearGraphics()
Definition: osd_utils.c:60
void simple_artificial_horizon(float roll, float pitch, int16_t x, int16_t y, int16_t width, int16_t height, int8_t max_pitch, uint8_t n_pitch_steps, bool show_horizon, OnScreenDisplayPageSettingsCenterMarkOptions center_mark)
void PIOS_Video_SetLevels(uint8_t, uint8_t)
static EventStats stats
Definition: systemmod.c:132
float convert_distance_divider
tuple f
Definition: px_mkfw.py:81
int render_stats()
#define CENTER_WING
float home_baro_altitude
bool PIOS_Semaphore_Take(struct pios_semaphore *sema, uint32_t timeout_ms)
Takes binary semaphore.
const struct FontEntry * get_font_info(int font)
Definition: osd_utils.c:1290
Includes PiOS and core architecture components.
Include file of the WorldMagModel internal functionality.
void render_osd_menu()
#define FONT8X10
Definition: fonts.h:52
uint16_t UAVObjGetNumInstances(UAVObjHandle obj)
uint8_t unused[4]
Definition: bl_messages.h:70
OSD Menu.
void printFWVersion(int16_t x, int16_t y)
#define FONT_OUTLINED8X14
Definition: fonts.h:53
const char IMPERIAL_DIST_UNIT_LONG[]
float convert_distance
#define CENTER_BODY
Generic interface for sensors.
void write_hline_lm(int x0, int x1, int y, int lmode, int mmode)
Definition: osd_utils.c:372
int32_t AlarmString(SystemAlarmsData *alarm, char *buf, size_t buflen, bool blink, uint8_t *state)
#define TEXT_VA_MIDDLE
Definition: osd_utils.h:131
void render_user_page(OnScreenDisplayPageSettingsData *page)
const char digits[16]
void write_filled_rectangle_lm(int x, int y, int width, int height, int lmode, int mmode)
Definition: osd_utils.c:659
void draw_flight_mode(int x, int y, int xs, int ys, int va, int ha, int flags, int font)
#define CENTER_RUDDER
void lla_to_ned(int32_t lat, int32_t lon, float alt, float *NED)
Definition: osd_utils.c:1447
uint16_t width
Definition: images.h:1075
#define TASK_PRIORITY
#define STATS_LINE_X
const char METRIC_DIST_UNIT_LONG[]
void write_pixel_lm(int x, int y, int mmode, int lmode)
Definition: osd_utils.c:261
#define GRAPHICS_BOTTOM
Definition: pios_video.h:131
#define STATS_LINE_Y
typedef __attribute__
Definition: serial_4way.h:43
const char IMPERIAL_DIST_UNIT_SHORT[]
#define STATS_LINE_SPACING
enum arena_state state
static bool has_mag