altos/telelco-v2.0: Only switch encoder mode on push, not release
[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 (event.value) {
426                                         if (!ao_lco_armed) {
427                                                 ao_lco_select_mode = 1 - ao_lco_select_mode;
428                                                 ao_lco_set_select();
429                                         }
430                                 }
431                                 break;
432                         }
433                         break;
434                 }
435         }
436 }
437
438 static AO_LED_TYPE      continuity_led[AO_LED_CONTINUITY_NUM] = {
439 #ifdef AO_LED_CONTINUITY_0
440         AO_LED_CONTINUITY_0,
441 #endif
442 #ifdef AO_LED_CONTINUITY_1
443         AO_LED_CONTINUITY_1,
444 #endif
445 #ifdef AO_LED_CONTINUITY_2
446         AO_LED_CONTINUITY_2,
447 #endif
448 #ifdef AO_LED_CONTINUITY_3
449         AO_LED_CONTINUITY_3,
450 #endif
451 #ifdef AO_LED_CONTINUITY_4
452         AO_LED_CONTINUITY_4,
453 #endif
454 #ifdef AO_LED_CONTINUITY_5
455         AO_LED_CONTINUITY_5,
456 #endif
457 #ifdef AO_LED_CONTINUITY_6
458         AO_LED_CONTINUITY_6,
459 #endif
460 #ifdef AO_LED_CONTINUITY_7
461         AO_LED_CONTINUITY_7,
462 #endif
463 };
464
465 static uint8_t
466 ao_lco_get_channels(uint8_t box, struct ao_pad_query *query)
467 {
468         int8_t                  r;
469
470         r = ao_lco_query(box, query, &ao_lco_tick_offset[box]);
471         if (r == AO_RADIO_CMAC_OK) {
472                 ao_lco_channels[box] = query->channels;
473                 ao_lco_valid[box] = AO_LCO_VALID_LAST | AO_LCO_VALID_EVER;
474         } else
475                 ao_lco_valid[box] &= ~AO_LCO_VALID_LAST;
476         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]);
477         ao_wakeup(&ao_pad_query);
478         return ao_lco_valid[box];
479 }
480
481 static void
482 ao_lco_update(void)
483 {
484         uint8_t previous_valid = ao_lco_valid[ao_lco_box];
485
486         if (ao_lco_get_channels(ao_lco_box, &ao_pad_query) & AO_LCO_VALID_LAST) {
487                 if (!(previous_valid & AO_LCO_VALID_EVER)) {
488                         if (ao_lco_pad != 0)
489                                 ao_lco_pad = ao_lco_pad_first(ao_lco_box);
490                         ao_lco_set_display();
491                 }
492                 if (ao_lco_pad == 0)
493                         ao_lco_set_display();
494         }
495 }
496
497 static void
498 ao_lco_box_reset_present(void)
499 {
500         ao_lco_min_box = 0xff;
501         ao_lco_max_box = 0x00;
502         memset(ao_lco_box_mask, 0, sizeof (ao_lco_box_mask));
503 }
504
505 static void
506 ao_lco_box_set_present(uint8_t box)
507 {
508         if (box < ao_lco_min_box)
509                 ao_lco_min_box = box;
510         if (box > ao_lco_max_box)
511                 ao_lco_max_box = box;
512         if (box >= AO_PAD_MAX_BOXES)
513                 return;
514         ao_lco_box_mask[MASK_ID(box)] |= 1 << MASK_SHIFT(box);
515 }
516
517 static void
518 ao_lco_search(void)
519 {
520         int8_t          r;
521         int8_t          try;
522         uint8_t         box;
523         uint8_t         boxes = 0;
524
525         ao_lco_box_reset_present();
526         ao_lco_set_pad(0);
527         for (box = 0; box < AO_PAD_MAX_BOXES; box++) {
528                 if ((box % 10) == 0)
529                         ao_lco_set_box(box);
530                 for (try = 0; try < 3; try++) {
531                         ao_lco_tick_offset[box] = 0;
532                         r = ao_lco_query(box, &ao_pad_query, &ao_lco_tick_offset[box]);
533                         PRINTD("box %d result %d offset %d\n", box, r, ao_lco_tick_offset[box]);
534                         if (r == AO_RADIO_CMAC_OK) {
535                                 ++boxes;
536                                 ao_lco_box_set_present(box);
537                                 ao_lco_set_pad(boxes % 10);
538                                 ao_delay(AO_MS_TO_TICKS(30));
539                                 break;
540                         }
541                 }
542         }
543         if (ao_lco_min_box <= ao_lco_max_box)
544                 ao_lco_box = ao_lco_min_box;
545         else
546                 ao_lco_min_box = ao_lco_max_box = ao_lco_box = 0;
547         memset(ao_lco_valid, 0, sizeof (ao_lco_valid));
548         memset(ao_lco_channels, 0, sizeof (ao_lco_channels));
549         ao_lco_pad = 1;
550         ao_lco_set_display();
551 }
552
553 static void
554 ao_lco_igniter_status(void)
555 {
556         uint8_t         c;
557         uint8_t         t = 0;
558
559         for (;;) {
560                 ao_sleep(&ao_pad_query);
561                 PRINTD("RSSI %d VALID %d\n", ao_radio_cmac_rssi, ao_lco_valid[ao_lco_box]);
562                 if (!(ao_lco_valid[ao_lco_box] & AO_LCO_VALID_LAST)) {
563                         ao_led_on(AO_LED_RED);
564                         ao_led_off(AO_LED_GREEN|AO_LED_AMBER);
565                         continue;
566                 }
567                 if (ao_radio_cmac_rssi < -90) {
568                         ao_led_on(AO_LED_AMBER);
569                         ao_led_off(AO_LED_RED|AO_LED_GREEN);
570                 } else {
571                         ao_led_on(AO_LED_GREEN);
572                         ao_led_off(AO_LED_RED|AO_LED_AMBER);
573                 }
574                 if (ao_pad_query.arm_status)
575                         ao_led_on(AO_LED_REMOTE_ARM);
576                 else
577                         ao_led_off(AO_LED_REMOTE_ARM);
578
579                 for (c = 0; c < AO_LED_CONTINUITY_NUM; c++) {
580                         uint8_t status;
581
582                         if (ao_lco_drag_race) {
583                                 if (ao_lco_selected[ao_lco_box] & (1 << c) && t)
584                                         ao_led_on(continuity_led[c]);
585                                 else
586                                         ao_led_off(continuity_led[c]);
587                         } else {
588                                 if (ao_pad_query.channels & (1 << c))
589                                         status = ao_pad_query.igniter_status[c];
590                                 else
591                                         status = AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN;
592                                 if (status == AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN)
593                                         ao_led_on(continuity_led[c]);
594                                 else
595                                         ao_led_off(continuity_led[c]);
596                         }
597                 }
598                 t = 1-t;
599         }
600 }
601
602 static void
603 ao_lco_arm_warn(void)
604 {
605         for (;;) {
606                 while (!ao_lco_armed) {
607                         ao_led_off(AO_LED_FIRE);
608                         ao_sleep(&ao_lco_armed);
609                 }
610                 ao_led_on(AO_LED_FIRE);
611                 ao_beep_for(AO_BEEP_MID, AO_MS_TO_TICKS(200));
612                 ao_delay(AO_MS_TO_TICKS(200));
613         }
614 }
615
616 static struct ao_task ao_lco_input_task;
617 static struct ao_task ao_lco_monitor_task;
618 static struct ao_task ao_lco_arm_warn_task;
619 static struct ao_task ao_lco_igniter_status_task;
620
621 static void
622 ao_lco_monitor(void)
623 {
624         uint16_t                delay;
625         uint8_t                 box;
626
627         ao_lco_search();
628         ao_add_task(&ao_lco_input_task, ao_lco_input, "lco input");
629         ao_add_task(&ao_lco_arm_warn_task, ao_lco_arm_warn, "lco arm warn");
630         ao_add_task(&ao_lco_igniter_status_task, ao_lco_igniter_status, "lco igniter status");
631         ao_add_task(&ao_lco_drag_task, ao_lco_drag_monitor, "drag race");
632         for (;;) {
633                 PRINTD("monitor armed %d firing %d\n",
634                        ao_lco_armed, ao_lco_firing);
635
636                 if (ao_lco_armed && ao_lco_firing) {
637                         ao_lco_ignite(AO_PAD_FIRE);
638                 } else {
639                         ao_lco_update();
640                         if (ao_lco_armed) {
641                                 for (box = ao_lco_min_box; box <= ao_lco_max_box; box++) {
642                                         if (ao_lco_selected[box]) {
643                                                 PRINTD("Arming box %d pads %x\n",
644                                                        box, ao_lco_selected[box]);
645                                                 if (ao_lco_valid[box] & AO_LCO_VALID_EVER) {
646                                                         ao_lco_arm(box, ao_lco_selected[box], ao_lco_tick_offset[box]);
647                                                         ao_delay(AO_MS_TO_TICKS(10));
648                                                 }
649                                         }
650                                 }
651                         }
652                 }
653                 if (ao_lco_armed && ao_lco_firing)
654                         delay = AO_MS_TO_TICKS(100);
655                 else
656                         delay = AO_SEC_TO_TICKS(1);
657                 ao_sleep_for(&ao_lco_armed, delay);
658         }
659 }
660
661 #if DEBUG
662 void
663 ao_lco_set_debug(void)
664 {
665         ao_cmd_decimal();
666         if (ao_cmd_status == ao_cmd_success)
667                 ao_lco_debug = ao_cmd_lex_i != 0;
668 }
669
670 __code struct ao_cmds ao_lco_cmds[] = {
671         { ao_lco_set_debug,     "D <0 off, 1 on>\0Debug" },
672         { ao_lco_search,        "s\0Search for pad boxes" },
673         { 0, NULL }
674 };
675 #endif
676
677 void
678 ao_lco_init(void)
679 {
680         ao_add_task(&ao_lco_monitor_task, ao_lco_monitor, "lco monitor");
681 #if DEBUG
682         ao_cmd_register(&ao_lco_cmds[0]);
683 #endif
684 }