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