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