altos/telelco-v0: Test display, show batt voltage at boot
[fw/altos] / src / drivers / ao_lco.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_seven_segment.h>
23 #include <ao_quadrature.h>
24 #include <ao_lco_func.h>
25 #include <ao_radio_cmac.h>
26 #if HAS_ADC_SINGLE
27 #include <ao_adc_single.h>
28 #endif
29
30 #define DEBUG   1
31
32 #if DEBUG
33 static uint8_t  ao_lco_debug;
34 #define PRINTD(...) do { if (!ao_lco_debug) break; printf ("\r%5u %s: ", ao_tick_count, __func__); printf(__VA_ARGS__); flush(); } while(0)
35 #else
36 #define PRINTD(...) 
37 #endif
38
39 #define AO_LCO_PAD_DIGIT        0
40 #define AO_LCO_BOX_DIGIT_1      1
41 #define AO_LCO_BOX_DIGIT_10     2
42
43 #define AO_LCO_DRAG_RACE_START_TIME     AO_SEC_TO_TICKS(5)
44 #define AO_LCO_DRAG_RACE_STOP_TIME      AO_SEC_TO_TICKS(2)
45
46 #define AO_LCO_VALID_LAST       1
47 #define AO_LCO_VALID_EVER       2
48
49 static uint8_t  ao_lco_min_box, ao_lco_max_box;
50 static uint8_t  ao_lco_selected[AO_PAD_MAX_BOXES];
51 static uint8_t  ao_lco_valid[AO_PAD_MAX_BOXES];
52 static uint8_t  ao_lco_channels[AO_PAD_MAX_BOXES];
53 static uint16_t ao_lco_tick_offset[AO_PAD_MAX_BOXES];
54
55 /* UI values */
56 static uint8_t  ao_lco_armed;
57 static uint8_t  ao_lco_firing;
58 static uint16_t ao_lco_fire_tick;
59 static uint8_t  ao_lco_fire_down;
60 static uint8_t  ao_lco_drag_race;
61 static uint8_t  ao_lco_pad;
62 static int16_t  ao_lco_box;
63
64 #define AO_LCO_BOX_DRAG         0x1000
65
66 static struct ao_pad_query      ao_pad_query;
67
68 static uint8_t  ao_lco_display_mutex;
69
70 static void
71 ao_lco_set_pad(uint8_t pad)
72 {
73         ao_mutex_get(&ao_lco_display_mutex);
74         ao_seven_segment_set(AO_LCO_PAD_DIGIT, pad | (ao_lco_drag_race << 4));
75         ao_mutex_put(&ao_lco_display_mutex);
76 }
77
78 #define SEVEN_SEGMENT_d         ((0 << 0) |     \
79                                  (0 << 1) |     \
80                                  (1 << 2) |     \
81                                  (1 << 3) |     \
82                                  (1 << 4) |     \
83                                  (1 << 5) |     \
84                                  (1 << 6))
85
86
87 #define SEVEN_SEGMENT_r         ((0 << 0) |     \
88                                  (0 << 1) |     \
89                                  (0 << 2) |     \
90                                  (1 << 3) |     \
91                                  (1 << 4) |     \
92                                  (0 << 5) |     \
93                                  (0 << 6))
94
95 static void
96 ao_lco_set_box(uint16_t box)
97 {
98         ao_mutex_get(&ao_lco_display_mutex);
99         if (box == AO_LCO_BOX_DRAG) {
100                 ao_seven_segment_direct(AO_LCO_BOX_DIGIT_10, SEVEN_SEGMENT_d | (ao_lco_drag_race << 7));
101                 ao_seven_segment_direct(AO_LCO_BOX_DIGIT_1, SEVEN_SEGMENT_r | (ao_lco_drag_race << 7));
102         } else {
103                 ao_seven_segment_set(AO_LCO_BOX_DIGIT_1, box % 10 | (ao_lco_drag_race << 4));
104                 ao_seven_segment_set(AO_LCO_BOX_DIGIT_10, box / 10 | (ao_lco_drag_race << 4));
105         }
106         ao_mutex_put(&ao_lco_display_mutex);
107 }
108
109 static void
110 ao_lco_set_voltage(uint16_t decivolts)
111 {
112         uint8_t tens, ones, tenths;
113
114         tenths = decivolts % 10;
115         ones = (decivolts / 10) % 10;
116         tens = (decivolts / 100) % 10;
117         ao_mutex_get(&ao_lco_display_mutex);
118         ao_seven_segment_set(AO_LCO_PAD_DIGIT, tenths);
119         ao_seven_segment_set(AO_LCO_BOX_DIGIT_1, ones | 0x10);
120         ao_seven_segment_set(AO_LCO_BOX_DIGIT_10, tens);
121         ao_mutex_put(&ao_lco_display_mutex);
122 }
123
124 static void
125 ao_lco_set_display(void)
126 {
127         if (ao_lco_pad == 0 && ao_lco_box != AO_LCO_BOX_DRAG) {
128                 ao_lco_set_voltage(ao_pad_query.battery);
129         } else {
130                 if (ao_lco_box == AO_LCO_BOX_DRAG)
131                         ao_lco_set_pad(ao_lco_drag_race);
132                 else
133                         ao_lco_set_pad(ao_lco_pad);
134                 ao_lco_set_box(ao_lco_box);
135         }
136 }
137
138 #define MASK_SIZE(n)    (((n) + 7) >> 3)
139 #define MASK_ID(n)      ((n) >> 3)
140 #define MASK_SHIFT(n)   ((n) & 7)
141
142 static uint8_t  ao_lco_box_mask[MASK_SIZE(AO_PAD_MAX_BOXES)];
143
144 static uint8_t
145 ao_lco_box_present(uint16_t box)
146 {
147         if (box == AO_LCO_BOX_DRAG)
148                 return 1;
149
150         if (box >= AO_PAD_MAX_BOXES)
151                 return 0;
152         return (ao_lco_box_mask[MASK_ID(box)] >> MASK_SHIFT(box)) & 1;
153 }
154
155 static uint8_t
156 ao_lco_pad_present(uint8_t box, uint8_t pad)
157 {
158         /* voltage measurement is always valid */
159         if (pad == 0)
160                 return 1;
161         if (!ao_lco_channels[box])
162                 return 0;
163         if (pad > AO_PAD_MAX_CHANNELS)
164                 return 0;
165         return (ao_lco_channels[box] >> (pad - 1)) & 1;
166 }
167
168 static uint8_t
169 ao_lco_pad_first(uint8_t box)
170 {
171         uint8_t pad;
172
173         for (pad = 1; pad <= AO_PAD_MAX_CHANNELS; pad++)
174                 if (ao_lco_pad_present(box, pad))
175                         return pad;
176         return 0;
177 }
178
179 static struct ao_task   ao_lco_drag_task;
180 static uint8_t          ao_lco_drag_active;
181 static uint8_t          ao_lco_drag_beep_count;
182 static uint8_t          ao_lco_drag_beep_on;
183 static uint16_t         ao_lco_drag_beep_time;
184 static uint16_t         ao_lco_drag_warn_time;
185
186 #define AO_LCO_DRAG_BEEP_TIME   AO_MS_TO_TICKS(50)
187 #define AO_LCO_DRAG_WARN_TIME   AO_SEC_TO_TICKS(5)
188
189 static void
190 ao_lco_drag_beep_start(void)
191 {
192         ao_beep(AO_BEEP_HIGH);
193         PRINTD("beep start\n");
194         ao_lco_drag_beep_on = 1;
195         ao_lco_drag_beep_time = ao_time() + AO_LCO_DRAG_BEEP_TIME;
196 }
197
198 static void
199 ao_lco_drag_beep_stop(void)
200 {
201         ao_beep(0);
202         PRINTD("beep stop\n");
203         ao_lco_drag_beep_on = 0;
204         if (ao_lco_drag_beep_count) {
205                 --ao_lco_drag_beep_count;
206                 if (ao_lco_drag_beep_count)
207                         ao_lco_drag_beep_time = ao_time() + AO_LCO_DRAG_BEEP_TIME;
208         }
209 }
210
211 static void
212 ao_lco_drag_beep(uint8_t beeps)
213 {
214         PRINTD("beep %d\n", beeps);
215         if (!ao_lco_drag_beep_count)
216                 ao_lco_drag_beep_start();
217         ao_lco_drag_beep_count += beeps;
218 }
219
220 static uint16_t
221 ao_lco_drag_beep_check(uint16_t now, uint16_t delay)
222 {
223         PRINTD("beep check count %d delta %d\n",
224                ao_lco_drag_beep_count,
225                (int16_t) (now - ao_lco_drag_beep_time));
226         if (ao_lco_drag_beep_count) {
227                 if ((int16_t) (now - ao_lco_drag_beep_time) >= 0) {
228                         if (ao_lco_drag_beep_on)
229                                 ao_lco_drag_beep_stop();
230                         else
231                                 ao_lco_drag_beep_start();
232                 }
233         }
234
235         if (ao_lco_drag_beep_count) {
236                 if (delay > AO_LCO_DRAG_BEEP_TIME)
237                         delay = AO_LCO_DRAG_BEEP_TIME;
238         }
239         return delay;
240 }
241
242 static void
243 ao_lco_drag_enable(void)
244 {
245         PRINTD("Drag enable\n");
246         ao_lco_drag_race = 1;
247         memset(ao_lco_selected, 0, sizeof (ao_lco_selected));
248         ao_lco_drag_beep(5);
249         ao_lco_set_display();
250         ao_lco_fire_down = 0;
251 }
252
253 static void
254 ao_lco_drag_disable(void)
255 {
256         PRINTD("Drag disable\n");
257         ao_lco_drag_race = 0;
258         memset(ao_lco_selected, 0, sizeof (ao_lco_selected));
259         ao_lco_drag_beep(2);
260         ao_lco_set_display();
261         ao_lco_fire_down = 0;
262 }
263
264 static uint16_t
265 ao_lco_drag_button_check(uint16_t now, uint16_t delay)
266 {
267         uint16_t        button_delay = ~0;
268
269         /*
270          * Check to see if the button has been held down long enough
271          * to switch in/out of drag race mode
272          */
273         if (ao_lco_fire_down) {
274                 if (ao_lco_drag_race) {
275                         if ((int16_t) (now - ao_lco_fire_tick) >= AO_LCO_DRAG_RACE_STOP_TIME)
276                                 ao_lco_drag_disable();
277                         else
278                                 button_delay = ao_lco_fire_tick + AO_LCO_DRAG_RACE_STOP_TIME - now;
279                 } else {
280                         if ((int16_t) (now - ao_lco_fire_tick) >= AO_LCO_DRAG_RACE_START_TIME)
281                                 ao_lco_drag_enable();
282                         else
283                                 button_delay = ao_lco_fire_tick + AO_LCO_DRAG_RACE_START_TIME - now;
284                 }
285                 if (delay > button_delay)
286                         delay = button_delay;
287         }
288         return delay;
289 }
290
291 static uint16_t
292 ao_lco_drag_warn_check(uint16_t now, uint16_t delay)
293 {
294         uint16_t        warn_delay = ~0;
295
296         if (ao_lco_drag_race) {
297                 if ((int16_t) (now - ao_lco_drag_warn_time) >= 0) {
298                         ao_lco_drag_beep(1);
299                         ao_lco_drag_warn_time = now + AO_LCO_DRAG_WARN_TIME;
300                 }
301                 warn_delay = ao_lco_drag_warn_time - now;
302         }
303         if (delay > warn_delay)
304                 delay = warn_delay;
305         return delay;
306 }
307
308 static void
309 ao_lco_drag_monitor(void)
310 {
311         uint16_t        delay = ~0;
312         uint16_t        now;
313
314         for (;;) {
315                 PRINTD("Drag monitor active %d delay %d\n", ao_lco_drag_active, delay);
316                 if (delay == (uint16_t) ~0)
317                         ao_sleep(&ao_lco_drag_active);
318                 else
319                         ao_sleep_for(&ao_lco_drag_active, delay);
320
321                 delay = ~0;
322                 if (!ao_lco_drag_active)
323                         continue;
324
325                 now = ao_time();
326                 delay = ao_lco_drag_button_check(now, delay);
327                 delay = ao_lco_drag_warn_check(now, delay);
328                 delay = ao_lco_drag_beep_check(now, delay);
329
330                 /* check to see if there's anything left to do here */
331                 if (!ao_lco_fire_down && !ao_lco_drag_race && !ao_lco_drag_beep_count) {
332                         delay = ~0;
333                         ao_lco_drag_active = 0;
334                 }
335         }
336 }
337
338 static void
339 ao_lco_input(void)
340 {
341         static struct ao_event  event;
342         int8_t          dir, new_pad;
343         int16_t         new_box;
344
345         ao_beep_for(AO_BEEP_MID, AO_MS_TO_TICKS(200));
346         for (;;) {
347                 ao_event_get(&event);
348                 PRINTD("event type %d unit %d value %d\n",
349                        event.type, event.unit, event.value);
350                 switch (event.type) {
351                 case AO_EVENT_QUADRATURE:
352                         switch (event.unit) {
353                         case AO_QUADRATURE_PAD:
354                                 if (!ao_lco_armed) {
355                                         dir = (int8_t) event.value;
356                                         new_pad = ao_lco_pad;
357                                         do {
358                                                 new_pad += dir;
359                                                 if (new_pad > AO_PAD_MAX_CHANNELS)
360                                                         new_pad = 0;
361                                                 if (new_pad < 0)
362                                                         new_pad = AO_PAD_MAX_CHANNELS;
363                                                 if (new_pad == ao_lco_pad)
364                                                         break;
365                                         } while (!ao_lco_pad_present(ao_lco_box, new_pad));
366                                         if (new_pad != ao_lco_pad) {
367                                                 ao_lco_pad = new_pad;
368                                                 ao_lco_set_display();
369                                         }
370                                 }
371                                 break;
372                         case AO_QUADRATURE_BOX:
373                                 if (!ao_lco_armed) {
374                                         dir = (int8_t) event.value;
375                                         new_box = ao_lco_box;
376                                         do {
377                                                 if (new_box == AO_LCO_BOX_DRAG) {
378                                                         if (dir < 0)
379                                                                 new_box = ao_lco_max_box;
380                                                         else
381                                                                 new_box = ao_lco_min_box;
382                                                 } else {
383                                                         new_box += dir;
384                                                         if (new_box > ao_lco_max_box)
385                                                                 new_box = AO_LCO_BOX_DRAG;
386                                                         else if (new_box < ao_lco_min_box)
387                                                                 new_box = AO_LCO_BOX_DRAG;
388                                                 }
389                                                 if (new_box == ao_lco_box)
390                                                         break;
391                                         } while (!ao_lco_box_present(new_box));
392                                         if (ao_lco_box != new_box) {
393                                                 ao_lco_box = new_box;
394                                                 ao_lco_pad = 1;
395                                                 if (ao_lco_box != AO_LCO_BOX_DRAG)
396                                                         ao_lco_channels[ao_lco_box] = 0;
397                                                 ao_lco_set_display();
398                                         }
399                                 }
400                                 break;
401                         }
402                         break;
403                 case AO_EVENT_BUTTON:
404                         switch (event.unit) {
405                         case AO_BUTTON_ARM:
406                                 ao_lco_armed = event.value;
407                                 PRINTD("Armed %d\n", ao_lco_armed);
408                                 if (ao_lco_armed) {
409                                         if (ao_lco_drag_race) {
410                                                 uint8_t box;
411
412                                                 for (box = ao_lco_min_box; box <= ao_lco_max_box; box++) {
413                                                         if (ao_lco_selected[box]) {
414                                                                 ao_wakeup(&ao_lco_armed);
415                                                                 break;
416                                                         }
417                                                 }
418                                         } else {
419                                                 memset(ao_lco_selected, 0, sizeof (ao_lco_selected));
420                                                 if (ao_lco_pad != 0 && ao_lco_box != AO_LCO_BOX_DRAG)
421                                                         ao_lco_selected[ao_lco_box] = (1 << (ao_lco_pad - 1));
422                                                 else
423                                                         ao_lco_armed = 0;
424                                         }
425                                 }
426                                 ao_wakeup(&ao_lco_armed);
427                                 break;
428                         case AO_BUTTON_FIRE:
429                                 if (ao_lco_armed) {
430                                         ao_lco_fire_down = 0;
431                                         ao_lco_firing = event.value;
432                                         PRINTD("Firing %d\n", ao_lco_firing);
433                                         ao_wakeup(&ao_lco_armed);
434                                 } else {
435                                         if (event.value) {
436                                                 if (ao_lco_box == AO_LCO_BOX_DRAG) {
437                                                         ao_lco_fire_down = 1;
438                                                         ao_lco_fire_tick = ao_time();
439                                                         ao_lco_drag_active = 1;
440                                                 }
441                                                 if (ao_lco_drag_race) {
442                                                         if (ao_lco_pad != 0 && ao_lco_box != AO_LCO_BOX_DRAG) {
443                                                                 ao_lco_selected[ao_lco_box] ^= (1 << (ao_lco_pad - 1));
444                                                                 PRINTD("Toggle box %d pad %d (pads now %x) to drag race\n",
445                                                                        ao_lco_pad, ao_lco_box, ao_lco_selected[ao_lco_box]);
446                                                                 ao_lco_drag_beep(ao_lco_pad);
447                                                         }
448                                                 }
449                                                 ao_wakeup(&ao_lco_drag_active);
450                                         } else {
451                                                 ao_lco_fire_down = 0;
452                                                 if (ao_lco_drag_active)
453                                                         ao_wakeup(&ao_lco_drag_active);
454                                         }
455                                 }
456                                 break;
457                         }
458                         break;
459                 }
460         }
461 }
462
463 static AO_LED_TYPE      continuity_led[AO_LED_CONTINUITY_NUM] = {
464 #ifdef AO_LED_CONTINUITY_0
465         AO_LED_CONTINUITY_0,
466 #endif
467 #ifdef AO_LED_CONTINUITY_1
468         AO_LED_CONTINUITY_1,
469 #endif
470 #ifdef AO_LED_CONTINUITY_2
471         AO_LED_CONTINUITY_2,
472 #endif
473 #ifdef AO_LED_CONTINUITY_3
474         AO_LED_CONTINUITY_3,
475 #endif
476 #ifdef AO_LED_CONTINUITY_4
477         AO_LED_CONTINUITY_4,
478 #endif
479 #ifdef AO_LED_CONTINUITY_5
480         AO_LED_CONTINUITY_5,
481 #endif
482 #ifdef AO_LED_CONTINUITY_6
483         AO_LED_CONTINUITY_6,
484 #endif
485 #ifdef AO_LED_CONTINUITY_7
486         AO_LED_CONTINUITY_7,
487 #endif
488 };
489
490 static uint8_t
491 ao_lco_get_channels(uint8_t box, struct ao_pad_query *query)
492 {
493         int8_t                  r;
494
495         r = ao_lco_query(box, query, &ao_lco_tick_offset[box]);
496         if (r == AO_RADIO_CMAC_OK) {
497                 ao_lco_channels[box] = query->channels;
498                 ao_lco_valid[box] = AO_LCO_VALID_LAST | AO_LCO_VALID_EVER;
499         } else
500                 ao_lco_valid[box] &= ~AO_LCO_VALID_LAST;
501         PRINTD("ao_lco_get_channels(%d) rssi %d valid %d ret %d offset %d\n", box, ao_radio_cmac_rssi, ao_lco_valid[box], r, ao_lco_tick_offset[box]);
502         ao_wakeup(&ao_pad_query);
503         return ao_lco_valid[box];
504 }
505
506 static void
507 ao_lco_update(void)
508 {
509         if (ao_lco_box != AO_LCO_BOX_DRAG) {
510                 uint8_t previous_valid = ao_lco_valid[ao_lco_box];
511
512                 if (ao_lco_get_channels(ao_lco_box, &ao_pad_query) & AO_LCO_VALID_LAST) {
513                         if (!(previous_valid & AO_LCO_VALID_EVER)) {
514                                 if (ao_lco_pad != 0)
515                                         ao_lco_pad = ao_lco_pad_first(ao_lco_box);
516                                 ao_lco_set_display();
517                         }
518                         if (ao_lco_pad == 0)
519                                 ao_lco_set_display();
520                 }
521         }
522 }
523
524 static void
525 ao_lco_box_reset_present(void)
526 {
527         ao_lco_min_box = 0xff;
528         ao_lco_max_box = 0x00;
529         memset(ao_lco_box_mask, 0, sizeof (ao_lco_box_mask));
530 }
531
532 static void
533 ao_lco_box_set_present(uint8_t box)
534 {
535         if (box < ao_lco_min_box)
536                 ao_lco_min_box = box;
537         if (box > ao_lco_max_box)
538                 ao_lco_max_box = box;
539         if (box >= AO_PAD_MAX_BOXES)
540                 return;
541         ao_lco_box_mask[MASK_ID(box)] |= 1 << MASK_SHIFT(box);
542 }
543
544 static void
545 ao_lco_search(void)
546 {
547         int8_t          r;
548         int8_t          try;
549         uint8_t         box;
550         uint8_t         boxes = 0;
551
552         ao_lco_box_reset_present();
553         ao_lco_set_pad(0);
554         for (box = 0; box < AO_PAD_MAX_BOXES; box++) {
555                 if ((box % 10) == 0)
556                         ao_lco_set_box(box);
557                 for (try = 0; try < 3; try++) {
558                         ao_lco_tick_offset[box] = 0;
559                         r = ao_lco_query(box, &ao_pad_query, &ao_lco_tick_offset[box]);
560                         PRINTD("box %d result %d offset %d\n", box, r, ao_lco_tick_offset[box]);
561                         if (r == AO_RADIO_CMAC_OK) {
562                                 ++boxes;
563                                 ao_lco_box_set_present(box);
564                                 ao_lco_set_pad(boxes % 10);
565                                 ao_delay(AO_MS_TO_TICKS(30));
566                                 break;
567                         }
568                 }
569         }
570         if (ao_lco_min_box <= ao_lco_max_box)
571                 ao_lco_box = ao_lco_min_box;
572         else
573                 ao_lco_min_box = ao_lco_max_box = ao_lco_box = 0;
574         memset(ao_lco_valid, 0, sizeof (ao_lco_valid));
575         memset(ao_lco_channels, 0, sizeof (ao_lco_channels));
576         ao_lco_pad = 1;
577         ao_lco_set_display();
578 }
579
580 static void
581 ao_lco_igniter_status(void)
582 {
583         uint8_t         c;
584         uint8_t         t = 0;
585
586         for (;;) {
587                 ao_sleep(&ao_pad_query);
588                 PRINTD("RSSI %d VALID %d\n", ao_radio_cmac_rssi, ao_lco_box == AO_LCO_BOX_DRAG ? -1 : ao_lco_valid[ao_lco_box]);
589                 if (ao_lco_box == AO_LCO_BOX_DRAG) {
590                         ao_led_off(AO_LED_RED|AO_LED_GREEN|AO_LED_AMBER);
591                         for (c = 0; c < AO_LED_CONTINUITY_NUM; c++)
592                                 ao_led_off(continuity_led[c]);
593                 } else {
594                         if (!(ao_lco_valid[ao_lco_box] & AO_LCO_VALID_LAST)) {
595                                 ao_led_on(AO_LED_RED);
596                                 ao_led_off(AO_LED_GREEN|AO_LED_AMBER);
597                                 continue;
598                         }
599                         if (ao_radio_cmac_rssi < -90) {
600                                 ao_led_on(AO_LED_AMBER);
601                                 ao_led_off(AO_LED_RED|AO_LED_GREEN);
602                         } else {
603                                 ao_led_on(AO_LED_GREEN);
604                                 ao_led_off(AO_LED_RED|AO_LED_AMBER);
605                         }
606                         if (ao_pad_query.arm_status)
607                                 ao_led_on(AO_LED_REMOTE_ARM);
608                         else
609                                 ao_led_off(AO_LED_REMOTE_ARM);
610
611                         for (c = 0; c < AO_LED_CONTINUITY_NUM; c++) {
612                                 uint8_t status;
613
614                                 if (ao_lco_drag_race) {
615                                         if (ao_lco_selected[ao_lco_box] & (1 << c) && t)
616                                                 ao_led_on(continuity_led[c]);
617                                         else
618                                                 ao_led_off(continuity_led[c]);
619                                 } else {
620                                         if (ao_pad_query.channels & (1 << c))
621                                                 status = ao_pad_query.igniter_status[c];
622                                         else
623                                                 status = AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN;
624                                         if (status == AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN)
625                                                 ao_led_on(continuity_led[c]);
626                                         else
627                                                 ao_led_off(continuity_led[c]);
628                                 }
629                         }
630                         t = 1-t;
631                 }
632         }
633 }
634
635 static void
636 ao_lco_arm_warn(void)
637 {
638         for (;;) {
639                 while (!ao_lco_armed)
640                         ao_sleep(&ao_lco_armed);
641                 ao_beep_for(AO_BEEP_MID, AO_MS_TO_TICKS(200));
642                 ao_delay(AO_MS_TO_TICKS(200));
643         }
644 }
645
646 /*
647  * Light up everything for a second at power on to let the user
648  * visually inspect the system for correct operation
649  */
650 static void
651 ao_lco_display_test()
652 {
653         ao_mutex_get(&ao_lco_display_mutex);
654         ao_seven_segment_set(AO_LCO_PAD_DIGIT, 8 | 0x10);
655         ao_seven_segment_set(AO_LCO_BOX_DIGIT_1, 8 | 0x10);
656         ao_seven_segment_set(AO_LCO_BOX_DIGIT_10, 8 | 0x10);
657         ao_mutex_put(&ao_lco_display_mutex);
658         ao_led_on(LEDS_AVAILABLE);
659         ao_delay(AO_MS_TO_TICKS(1000));
660         ao_led_off(LEDS_AVAILABLE);
661 }
662
663 #if HAS_ADC_SINGLE
664 static void
665 ao_lco_batt_voltage(void)
666 {
667         struct ao_adc   packet;
668         int16_t         decivolt;
669
670         ao_adc_single_get(&packet);
671         decivolt = ao_battery_decivolt(packet.v_batt);
672         ao_lco_set_voltage(decivolt);
673         ao_delay(AO_MS_TO_TICKS(1000));
674 }
675 #endif
676
677 static struct ao_task ao_lco_input_task;
678 static struct ao_task ao_lco_monitor_task;
679 static struct ao_task ao_lco_arm_warn_task;
680 static struct ao_task ao_lco_igniter_status_task;
681
682 static void
683 ao_lco_monitor(void)
684 {
685         uint16_t                delay;
686         uint8_t                 box;
687
688         ao_lco_display_test();
689 #if HAS_ADC_SINGLE
690         ao_lco_batt_voltage();
691 #endif
692         ao_lco_search();
693         ao_add_task(&ao_lco_input_task, ao_lco_input, "lco input");
694         ao_add_task(&ao_lco_arm_warn_task, ao_lco_arm_warn, "lco arm warn");
695         ao_add_task(&ao_lco_igniter_status_task, ao_lco_igniter_status, "lco igniter status");
696         ao_add_task(&ao_lco_drag_task, ao_lco_drag_monitor, "drag race");
697         for (;;) {
698                 PRINTD("monitor armed %d firing %d\n",
699                        ao_lco_armed, ao_lco_firing);
700
701                 if (ao_lco_armed && ao_lco_firing) {
702                         ao_lco_ignite(AO_PAD_FIRE);
703                 } else {
704                         ao_lco_update();
705                         if (ao_lco_armed) {
706                                 for (box = ao_lco_min_box; box <= ao_lco_max_box; box++) {
707                                         if (ao_lco_selected[box]) {
708                                                 PRINTD("Arming box %d pads %x\n",
709                                                        box, ao_lco_selected[box]);
710                                                 if (ao_lco_valid[box] & AO_LCO_VALID_EVER) {
711                                                         ao_lco_arm(box, ao_lco_selected[box], ao_lco_tick_offset[box]);
712                                                         ao_delay(AO_MS_TO_TICKS(10));
713                                                 }
714                                         }
715                                 }
716                         }
717                 }
718                 if (ao_lco_armed && ao_lco_firing)
719                         delay = AO_MS_TO_TICKS(100);
720                 else
721                         delay = AO_SEC_TO_TICKS(1);
722                 ao_sleep_for(&ao_lco_armed, delay);
723         }
724 }
725
726 #if DEBUG
727 void
728 ao_lco_set_debug(void)
729 {
730         ao_cmd_decimal();
731         if (ao_cmd_status == ao_cmd_success)
732                 ao_lco_debug = ao_cmd_lex_i != 0;
733 }
734
735 __code struct ao_cmds ao_lco_cmds[] = {
736         { ao_lco_set_debug,     "D <0 off, 1 on>\0Debug" },
737         { ao_lco_search,        "s\0Search for pad boxes" },
738         { 0, NULL }
739 };
740 #endif
741
742 void
743 ao_lco_init(void)
744 {
745         ao_add_task(&ao_lco_monitor_task, ao_lco_monitor, "lco monitor");
746 #if DEBUG
747         ao_cmd_register(&ao_lco_cmds[0]);
748 #endif
749 }