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