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