altos/telelco-v3: Minor UI tweaks
[fw/altos] / src / telelco-v3.0 / ao_lco_v3.c
1 /*
2  * Copyright © 2012 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 #include <ao.h>
20 #include <ao_lco.h>
21 #include <ao_event.h>
22 #include <ao_quadrature.h>
23 #include <ao_radio_cmac.h>
24 #include <ao_st7565.h>
25 #include <ao_adc_single.h>
26 #include <ao_pwm.h>
27
28 #define WIDTH   AO_ST7565_WIDTH
29 #define HEIGHT  AO_ST7565_HEIGHT
30 #define STRIDE  AO_BITMAP_STRIDE(WIDTH)
31
32 static uint32_t image[STRIDE * HEIGHT];
33
34 static struct ao_bitmap fb = {
35         .base = image,
36         .stride = STRIDE,
37         .width = WIDTH,
38         .height = HEIGHT,
39         .damage = AO_BOX_INIT,
40 };
41
42 static const struct ao_transform logo_transform = {
43         .x_scale = 48, .x_off = 2,
44         .y_scale = 48, .y_off = 0,
45 };
46
47 static const struct ao_transform show_transform = {
48         .x_scale = 36, .x_off = 100,
49         .y_scale = 36, .y_off = 0,
50 };
51
52 #define BIG_FONT BitstreamVeraSans_Roman_58_font
53 #define VOLT_FONT BitstreamVeraSans_Roman_58_font
54 #define SMALL_FONT BitstreamVeraSans_Roman_12_font
55 #define TINY_FONT BitstreamVeraSans_Roman_10_font
56 #define LOGO_FONT BenguiatGothicStd_Bold_26_font
57
58 #define LABEL_Y         (int16_t) (SMALL_FONT.ascent)
59 #define VALUE_Y         (int16_t) (LABEL_Y + 5 + BIG_FONT.ascent)
60
61 #define SEP_X           82
62 #define SEP_WIDTH       2
63
64 #define BOX_X           (SEP_X / 2)
65 #define PAD_X           ((WIDTH + SEP_X + SEP_WIDTH) / 2)
66
67 #define VALUE_LABEL_X   64
68 #define RSSI_LABEL_X    15
69
70 #define SCAN_X          (WIDTH - 100) / 2
71 #define SCAN_Y          50
72 #define SCAN_HEIGHT     3
73 #define FOUND_Y         63
74 #define FOUND_X         3
75 #define FOUND_WIDTH     (WIDTH - 6)
76 #define CONTRAST_LABEL_X        37
77 #define CONTRAST_WIDTH  100
78 #define CONTRAST_X      (WIDTH - CONTRAST_WIDTH) / 2
79 #define CONTRAST_Y      20
80 #define CONTRAST_HEIGHT 20
81 #define CONTRAST_VALUE_X        64
82 #define CONTRAST_VALUE_Y        (CONTRAST_Y + CONTRAST_HEIGHT + SMALL_FONT.ascent + 3)
83 #define BACKLIGHT_LABEL_X       37
84 #define BACKLIGHT_WIDTH 100
85 #define BACKLIGHT_X     (WIDTH - BACKLIGHT_WIDTH) / 2
86 #define BACKLIGHT_Y     20
87 #define BACKLIGHT_HEIGHT        20
88 #define BACKLIGHT_VALUE_X       64
89 #define BACKLIGHT_VALUE_Y       (BACKLIGHT_Y + BACKLIGHT_HEIGHT + SMALL_FONT.ascent + 3)
90 #define INFO_START_Y    ((int16_t) (SMALL_FONT.ascent + 2))
91 #define INFO_STEP_Y     ((int16_t) (SMALL_FONT.ascent + 3))
92
93 #define AO_LCO_DRAG_RACE_START_TIME     AO_SEC_TO_TICKS(5)
94 #define AO_LCO_DRAG_RACE_STOP_TIME      AO_SEC_TO_TICKS(2)
95
96 /* UI values */
97 static uint8_t  ao_lco_select_mode;
98 static uint8_t  ao_lco_event_debug;
99
100 #define PRINTE(...) do { if (!ao_lco_debug && !ao_lco_event_debug) break; printf ("\r%5lu %s: ", (unsigned long) ao_tick_count, __func__); printf(__VA_ARGS__); flush(); } while(0)
101 #define AO_LCO_SELECT_BOX       0
102 #define AO_LCO_SELECT_PAD       1
103
104 static uint8_t  ao_lco_display_mutex;
105
106 static void
107 _ao_center_text(int16_t x, int16_t y, const struct ao_font *font, const char *str)
108 {
109         int16_t width = ao_text_width(font, str);
110         ao_text(&fb, font, x - width/2, y, str, AO_BLACK, AO_COPY);
111 }
112
113 static void
114 _ao_lco_show_pad(int8_t pad)
115 {
116         char    str[5];
117
118         _ao_center_text(PAD_X, LABEL_Y, &SMALL_FONT, "Pad");
119         snprintf(str, sizeof(str), "%d", pad);
120         _ao_center_text(PAD_X, VALUE_Y, &BIG_FONT, str);
121 }
122
123 static void
124 _ao_lco_show_box(int16_t box)
125 {
126         char    str[7];
127
128         _ao_center_text(BOX_X, LABEL_Y, &SMALL_FONT, "Bank");
129         snprintf(str, sizeof(str), "%d", box);
130         _ao_center_text(BOX_X, VALUE_Y, &BIG_FONT, str);
131 }
132
133 static void
134 _ao_lco_show_voltage(uint16_t decivolts, const char *label)
135 {
136         char    str[7];
137
138         PRINTD("voltage %d\n", decivolts);
139         _ao_center_text(WIDTH/2, LABEL_Y, &SMALL_FONT, label);
140         snprintf(str, sizeof(str), "%d.%d", decivolts / 10, decivolts % 10);
141         _ao_center_text(WIDTH/2, VALUE_Y, &BIG_FONT, str);
142 }
143
144 static void
145 _ao_lco_batt_voltage(void)
146 {
147         struct ao_adc   packet;
148         int16_t         decivolt;
149
150         ao_adc_single_get(&packet);
151         decivolt = ao_battery_decivolt(packet.v_batt);
152         _ao_lco_show_voltage((uint16_t) decivolt, "LCO Battery");
153         ao_st7565_update(&fb);
154 }
155
156 static void
157 _ao_lco_show_contrast(void)
158 {
159         char buf[8];
160         uint8_t brightness = ao_st7565_get_brightness();
161         int16_t contrast = (int16_t) (brightness * CONTRAST_WIDTH / AO_LCO_MAX_CONTRAST);
162
163         _ao_center_text(WIDTH/2, LABEL_Y, &SMALL_FONT, "Contrast");
164         ao_rect(&fb, CONTRAST_X, CONTRAST_Y, contrast, CONTRAST_HEIGHT, AO_BLACK, AO_COPY);
165         snprintf(buf, sizeof(buf), "%d %%", brightness * 100 / AO_LCO_MAX_CONTRAST);
166         _ao_center_text(WIDTH/2, CONTRAST_VALUE_Y, &SMALL_FONT, buf);
167 }
168
169 static void
170 _ao_lco_show_backlight(void)
171 {
172         char buf[8];
173         int32_t backlight = ao_lco_get_backlight();
174         int16_t value = (int16_t) (backlight * BACKLIGHT_WIDTH / AO_LCO_MAX_BACKLIGHT);
175
176         _ao_center_text(WIDTH/2, LABEL_Y, &SMALL_FONT, "Backlight");
177         ao_rect(&fb, BACKLIGHT_X, BACKLIGHT_Y, value, BACKLIGHT_HEIGHT, AO_BLACK, AO_COPY);
178         snprintf(buf, sizeof(buf), "%ld %%", backlight * 100 / AO_LCO_MAX_BACKLIGHT);
179         _ao_center_text(WIDTH/2, BACKLIGHT_VALUE_Y, &SMALL_FONT, buf);
180 }
181
182 static int16_t info_y;
183
184 static void
185 _ao_lco_info(const char *format, ...)
186 {
187         va_list a;
188         char    buf[20];
189         va_start(a, format);
190         vsnprintf(buf, sizeof(buf), format, a);
191         va_end(a);
192         ao_text(&fb, &SMALL_FONT, 0, info_y, buf, AO_BLACK, AO_COPY);
193         info_y += INFO_STEP_Y;
194 }
195
196 static void
197 _ao_lco_show_info(void)
198 {
199         info_y = INFO_START_Y;
200         ao_logo_poly(&fb, &show_transform, AO_BLACK, AO_COPY);
201         _ao_lco_info("%s", ao_product);
202         _ao_lco_info("Version: %s", ao_version);
203         _ao_lco_info("Serial: %d", ao_serial_number);
204         _ao_lco_info("Callsign: %s", ao_config.callsign);
205         _ao_lco_info("Frequency: %ld.%03d",
206                      ao_config.frequency / 1000,
207                      (int) (ao_config.frequency % 1000));
208 }
209
210 static void
211 _ao_lco_show_rssi(void)
212 {
213         char label[20];
214         int16_t width;
215         snprintf(label, sizeof(label), "Bank %d RSSI", ao_lco_box);
216         width = ao_text_width(&SMALL_FONT, label);
217         ao_text(&fb, &SMALL_FONT, VALUE_LABEL_X - width / 2, LABEL_Y, label, AO_BLACK, AO_COPY);
218         if (!(ao_lco_valid[ao_lco_box] & AO_LCO_VALID_LAST))
219                 strcpy(label, "---");
220         else
221                 snprintf(label, sizeof(label), "%d", ao_radio_cmac_rssi);
222         width = ao_text_width(&VOLT_FONT, label);
223         ao_text(&fb, &VOLT_FONT, VALUE_LABEL_X - width / 2, VALUE_Y, label, AO_BLACK, AO_COPY);
224 }
225
226 static void
227 _ao_lco_show_pad_battery(void)
228 {
229         char label[20];
230         snprintf(label, sizeof(label), "Bank %d Battery", ao_lco_box);
231         _ao_lco_show_voltage(ao_pad_query.battery, label);
232 }
233
234 void
235 ao_lco_show(void)
236 {
237         ao_mutex_get(&ao_lco_display_mutex);
238         ao_rect(&fb, 0, 0, WIDTH, HEIGHT, AO_WHITE, AO_COPY);
239         switch (ao_lco_box) {
240         case AO_LCO_LCO_VOLTAGE:
241                 _ao_lco_batt_voltage();
242                 break;
243         case AO_LCO_CONTRAST:
244                 _ao_lco_show_contrast();
245                 break;
246         case AO_LCO_BACKLIGHT:
247                 _ao_lco_show_backlight();
248                 break;
249         case AO_LCO_INFO:
250                 _ao_lco_show_info();
251                 break;
252         default:
253                 switch (ao_lco_pad) {
254                 case AO_LCO_PAD_RSSI:
255                         _ao_lco_show_rssi();
256                         break;
257                 case AO_LCO_PAD_VOLTAGE:
258                         _ao_lco_show_pad_battery();
259                         break;
260                 default:
261                         _ao_lco_show_pad(ao_lco_pad);
262                         _ao_lco_show_box(ao_lco_box);
263                         ao_rect(&fb, SEP_X, 0, SEP_WIDTH, HEIGHT, AO_BLACK, AO_COPY);
264                 }
265                 break;
266         }
267         ao_st7565_update(&fb);
268         ao_mutex_put(&ao_lco_display_mutex);
269 }
270
271 static void
272 ao_lco_set_select(void)
273 {
274         if (ao_lco_armed) {
275                 ao_led_off(AO_LED_PAD);
276                 ao_led_off(AO_LED_BOX);
277         } else {
278                 switch (ao_lco_select_mode) {
279                 case AO_LCO_SELECT_PAD:
280                         ao_led_off(AO_LED_BOX);
281                         ao_led_on(AO_LED_PAD);
282                         break;
283                 case AO_LCO_SELECT_BOX:
284                         ao_led_off(AO_LED_PAD);
285                         ao_led_on(AO_LED_BOX);
286                         break;
287                 default:
288                         break;
289                 }
290         }
291 }
292
293
294 void
295 ao_lco_set_contrast(int32_t contrast)
296 {
297         ao_st7565_set_brightness((uint8_t) contrast);
298 }
299
300 int32_t
301 ao_lco_get_contrast(void)
302 {
303         return (int32_t) ao_st7565_get_brightness();
304 }
305
306 static uint16_t ao_backlight;
307
308 void
309 ao_lco_set_backlight(int32_t backlight)
310 {
311         ao_backlight = (uint16_t) backlight;
312         ao_pwm_set(AO_LCD_BL_PWM_CHAN, ao_backlight);
313 }
314
315 int32_t
316 ao_lco_get_backlight(void)
317 {
318         return (int32_t) ao_backlight;
319 }
320
321 static struct ao_task   ao_lco_drag_task;
322
323 static void
324 ao_lco_drag_monitor(void)
325 {
326         AO_TICK_TYPE    delay = ~0UL;
327         AO_TICK_TYPE    now;
328
329         ao_beep_for(AO_BEEP_MID, AO_MS_TO_TICKS(200));
330         for (;;) {
331                 PRINTD("Drag monitor count %d delay %lu\n", ao_lco_drag_beep_count, (unsigned long) delay);
332                 if (delay == (AO_TICK_TYPE) ~0)
333                         ao_sleep(&ao_lco_drag_beep_count);
334                 else
335                         ao_sleep_for(&ao_lco_drag_beep_count, delay);
336
337                 delay = ~0UL;
338                 now = ao_time();
339                 delay = ao_lco_drag_warn_check(now, delay);
340                 delay = ao_lco_drag_beep_check(now, delay);
341         }
342 }
343
344 static void
345 ao_lco_input(void)
346 {
347         static struct ao_event  event;
348
349         for (;;) {
350                 ao_event_get(&event);
351                 PRINTE("event type %d unit %d value %ld\n",
352                        event.type, event.unit, (long) event.value);
353                 switch (event.type) {
354                 case AO_EVENT_QUADRATURE:
355                         switch (event.unit) {
356                         case AO_QUADRATURE_SELECT:
357                                 if (!ao_lco_armed) {
358                                         switch (ao_lco_select_mode) {
359                                         case AO_LCO_SELECT_PAD:
360                                                 ao_lco_step_pad((int8_t) event.value);
361                                                 break;
362                                         case AO_LCO_SELECT_BOX:
363                                                 ao_lco_step_box((int8_t) event.value);
364                                                 break;
365                                         default:
366                                                 break;
367                                         }
368                                 }
369                                 break;
370                         }
371                         break;
372                 case AO_EVENT_BUTTON:
373                         switch (event.unit) {
374                         case AO_BUTTON_ARM:
375                                 ao_lco_set_armed((uint8_t) event.value);
376                                 ao_lco_set_select();
377                                 break;
378                         case AO_BUTTON_FIRE:
379                                 if (ao_lco_armed)
380                                         ao_lco_set_firing((uint8_t) event.value);
381                                 break;
382                         case AO_BUTTON_DRAG_SELECT:
383                                 if (event.value)
384                                         ao_lco_toggle_drag();
385                                 break;
386                         case AO_BUTTON_DRAG_MODE:
387                                 if (event.value)
388                                         ao_lco_drag_enable();
389                                 else
390                                         ao_lco_drag_disable();
391                                 break;
392                         case AO_BUTTON_ENCODER_SELECT:
393                                 if (event.value) {
394                                         if (!ao_lco_armed) {
395                                                 ao_lco_select_mode = 1 - ao_lco_select_mode;
396                                                 ao_lco_set_select();
397                                         }
398                                 }
399                                 break;
400                         }
401                         break;
402                 }
403         }
404 }
405
406 /*
407  * Light up everything for a second at power on to let the user
408  * visually inspect the system for correct operation
409  */
410 static void
411 ao_lco_display_test(void)
412 {
413         ao_led_on(AO_LEDS_AVAILABLE);
414         ao_rect(&fb, 0, 0, WIDTH, HEIGHT, AO_BLACK, AO_COPY);
415         ao_st7565_update(&fb);
416         ao_delay(AO_MS_TO_TICKS(250));
417         ao_led_off(AO_LEDS_AVAILABLE);
418 }
419
420 static struct ao_task ao_lco_input_task;
421 static struct ao_task ao_lco_monitor_task;
422 static struct ao_task ao_lco_arm_warn_task;
423 static struct ao_task ao_lco_igniter_status_task;
424
425 static int16_t  found_width;
426 #define MAX_FOUND       32
427 static int16_t  found_boxes[MAX_FOUND];
428 static uint8_t  nfound;
429
430 void
431 ao_lco_search_start(void)
432 {
433         ao_rect(&fb, 0, 0, WIDTH, HEIGHT, AO_WHITE, AO_COPY);
434         ao_logo(&fb, &logo_transform, &LOGO_FONT, AO_BLACK, AO_COPY);
435         found_width = 0;
436         nfound = 0;
437 }
438
439 void
440 ao_lco_search_box_check(int16_t box)
441 {
442         if (box > 0)
443                 ao_rect(&fb, SCAN_X, SCAN_Y, box, SCAN_HEIGHT, AO_BLACK, AO_COPY);
444         ao_st7565_update(&fb);
445 }
446
447 void
448 ao_lco_search_box_present(int16_t box)
449 {
450         char    str[8];
451         int16_t width;
452         int16_t box_top = FOUND_Y - TINY_FONT.ascent;
453         int16_t x;
454         uint8_t n;
455
456         snprintf(str, sizeof(str), "%s%u", nfound ? ", " : "", box);
457         width = ao_text_width(&TINY_FONT, str);
458         while (found_width + width > FOUND_WIDTH || nfound == MAX_FOUND)
459         {
460                 snprintf(str, sizeof(str), "%u, ", found_boxes[0]);
461                 found_width -= ao_text_width(&TINY_FONT, str);
462                 memmove(&found_boxes[0], &found_boxes[1], (nfound - 1) * sizeof (int16_t));
463                 nfound--;
464         }
465         found_boxes[nfound++] = box;
466
467         ao_rect(&fb, FOUND_X, FOUND_Y - TINY_FONT.ascent, FOUND_WIDTH, HEIGHT - box_top, AO_WHITE, AO_COPY);
468         x = FOUND_X;
469         for (n = 0; n < nfound; n++) {
470                 snprintf(str, sizeof(str), "%s%u", n ? ", " : "", found_boxes[n]);
471                 int16_t next_x = ao_text(&fb, &TINY_FONT, x, FOUND_Y, str, AO_BLACK, AO_COPY);
472                 x = next_x;
473         }
474         found_width = x - FOUND_X;
475 }
476
477 void
478 ao_lco_search_done(void)
479 {
480         ao_st7565_update(&fb);
481 }
482
483 static void
484 ao_lco_main(void)
485 {
486         ao_lco_display_test();
487         ao_lco_search();
488         ao_add_task(&ao_lco_input_task, ao_lco_input, "lco input");
489         ao_add_task(&ao_lco_arm_warn_task, ao_lco_arm_warn, "lco arm warn");
490         ao_add_task(&ao_lco_igniter_status_task, ao_lco_igniter_status, "lco igniter status");
491         ao_add_task(&ao_lco_drag_task, ao_lco_drag_monitor, "drag race");
492         ao_lco_monitor();
493 }
494
495 #if DEBUG
496 static void
497 ao_lco_set_debug(void)
498 {
499         uint32_t r = ao_cmd_decimal();
500         if (ao_cmd_status == ao_cmd_success){
501                 ao_lco_debug = r & 1;
502                 ao_lco_event_debug = (r & 2) >> 1;
503         }
504 }
505
506 const struct ao_cmds ao_lco_cmds[] = {
507         { ao_lco_set_debug,     "D <0 off, 1 on>\0Debug" },
508         { ao_lco_search,        "s\0Search for pad boxes" },
509         { ao_lco_pretend,       "p\0Pretend there are lots of pad boxes" },
510         { 0, NULL }
511 };
512 #endif
513
514 void
515 ao_lco_init(void)
516 {
517         ao_add_task(&ao_lco_monitor_task, ao_lco_main, "lco monitor");
518 #if DEBUG
519         ao_cmd_register(&ao_lco_cmds[0]);
520 #endif
521 }