altos/telefireone-v1.0: Track ao_led_init API change
[fw/altos] / src / drivers / ao_lco_bits.c
1 /*
2  * Copyright © 2018 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
15 #include <ao.h>
16 #include <ao_lco.h>
17 #include <ao_radio_cmac.h>
18
19 uint8_t         ao_lco_debug;
20
21 uint8_t         ao_lco_pad;
22 int16_t         ao_lco_box;
23
24 uint8_t         ao_lco_armed;                                   /* arm active */
25 uint8_t         ao_lco_firing;                                  /* fire active */
26
27 uint8_t         ao_lco_min_box, ao_lco_max_box;
28
29 #if AO_LCO_DRAG
30 uint8_t         ao_lco_drag_race;
31 #endif
32
33 struct ao_pad_query     ao_pad_query;                           /* latest query response */
34
35 static uint8_t          ao_lco_channels[AO_PAD_MAX_BOXES];      /* pad channels available on each box */
36 static uint16_t         ao_lco_tick_offset[AO_PAD_MAX_BOXES];   /* offset from local to remote tick count */
37 static uint8_t          ao_lco_selected[AO_PAD_MAX_BOXES];      /* pads selected to fire */
38
39 #define AO_LCO_VALID_LAST       1
40 #define AO_LCO_VALID_EVER       2
41
42 static uint8_t          ao_lco_valid[AO_PAD_MAX_BOXES];         /* AO_LCO_VALID bits per box */
43
44 static const AO_LED_TYPE        continuity_led[AO_LED_CONTINUITY_NUM] = {
45 #ifdef AO_LED_CONTINUITY_0
46         AO_LED_CONTINUITY_0,
47 #endif
48 #ifdef AO_LED_CONTINUITY_1
49         AO_LED_CONTINUITY_1,
50 #endif
51 #ifdef AO_LED_CONTINUITY_2
52         AO_LED_CONTINUITY_2,
53 #endif
54 #ifdef AO_LED_CONTINUITY_3
55         AO_LED_CONTINUITY_3,
56 #endif
57 #ifdef AO_LED_CONTINUITY_4
58         AO_LED_CONTINUITY_4,
59 #endif
60 #ifdef AO_LED_CONTINUITY_5
61         AO_LED_CONTINUITY_5,
62 #endif
63 #ifdef AO_LED_CONTINUITY_6
64         AO_LED_CONTINUITY_6,
65 #endif
66 #ifdef AO_LED_CONTINUITY_7
67         AO_LED_CONTINUITY_7,
68 #endif
69 };
70
71 /* Set LEDs to match remote box status */
72 void
73 ao_lco_igniter_status(void)
74 {
75         uint8_t         c;
76         uint8_t         t = 0;
77
78         for (;;) {
79 #if AO_LCO_DRAG
80                 if (ao_lco_drag_race)
81                         ao_sleep_for(&ao_pad_query, AO_MS_TO_TICKS(50));
82                 else
83 #endif
84                         ao_sleep(&ao_pad_query);
85                 PRINTD("RSSI %d VALID %d\n", ao_radio_cmac_rssi, ao_lco_valid[ao_lco_box]);
86                 if (!(ao_lco_valid[ao_lco_box] & AO_LCO_VALID_LAST)) {
87                         ao_led_on(AO_LED_RED);
88                         ao_led_off(AO_LED_GREEN|AO_LED_AMBER);
89                         continue;
90                 }
91                 if (ao_radio_cmac_rssi < -90) {
92                         ao_led_on(AO_LED_AMBER);
93                         ao_led_off(AO_LED_RED|AO_LED_GREEN);
94                 } else {
95                         ao_led_on(AO_LED_GREEN);
96                         ao_led_off(AO_LED_RED|AO_LED_AMBER);
97                 }
98                 if (ao_pad_query.arm_status)
99                         ao_led_on(AO_LED_REMOTE_ARM);
100                 else
101                         ao_led_off(AO_LED_REMOTE_ARM);
102
103                 for (c = 0; c < AO_LED_CONTINUITY_NUM; c++) {
104                         uint8_t status;
105
106                         if (ao_pad_query.channels & (1 << c))
107                                 status = ao_pad_query.igniter_status[c];
108                         else
109                                 status = AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN;
110
111 #if AO_LCO_DRAG
112                         if (ao_lco_drag_race && (ao_lco_selected[ao_lco_box] & (1 << c))) {
113                                 uint8_t on = 0;
114                                 if (status == AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN) {
115                                         if (t)
116                                                 on = 1;
117                                 } else {
118                                         if (t == 1)
119                                                 on = 1;
120                                 }
121                                 if (on)
122                                         ao_led_on(continuity_led[c]);
123                                 else
124                                         ao_led_off(continuity_led[c]);
125                         } else
126 #endif
127                         {
128                                 if (status == AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN)
129                                         ao_led_on(continuity_led[c]);
130                                 else
131                                         ao_led_off(continuity_led[c]);
132                         }
133                 }
134                 t = (t + 1) & 3;
135         }
136 }
137
138 uint8_t
139 ao_lco_pad_present(uint8_t box, uint8_t pad)
140 {
141         /* voltage measurement is always valid */
142         if (pad == AO_LCO_PAD_VOLTAGE)
143                 return 1;
144         if (!ao_lco_channels[box])
145                 return 0;
146         if (pad > AO_PAD_MAX_CHANNELS)
147                 return 0;
148         return (ao_lco_channels[box] >> (pad - 1)) & 1;
149 }
150
151 uint8_t
152 ao_lco_pad_first(uint8_t box)
153 {
154         uint8_t pad;
155
156         for (pad = 1; pad <= AO_PAD_MAX_CHANNELS; pad++)
157                 if (ao_lco_pad_present(box, pad))
158                         return pad;
159         return 0;
160 }
161
162 static uint8_t
163 ao_lco_get_channels(uint8_t box, struct ao_pad_query *query)
164 {
165         int8_t                  r;
166
167         r = ao_lco_query(box, query, &ao_lco_tick_offset[box]);
168         if (r == AO_RADIO_CMAC_OK) {
169                 ao_lco_channels[box] = query->channels;
170                 ao_lco_valid[box] = AO_LCO_VALID_LAST | AO_LCO_VALID_EVER;
171         } else
172                 ao_lco_valid[box] &= ~AO_LCO_VALID_LAST;
173         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]);
174         ao_wakeup(&ao_pad_query);
175         return ao_lco_valid[box];
176 }
177
178 void
179 ao_lco_update(void)
180 {
181         uint8_t previous_valid = ao_lco_valid[ao_lco_box];
182
183         if (ao_lco_get_channels(ao_lco_box, &ao_pad_query) & AO_LCO_VALID_LAST) {
184                 if (!(previous_valid & AO_LCO_VALID_EVER)) {
185                         if (ao_lco_pad != AO_LCO_PAD_VOLTAGE)
186                                 ao_lco_set_pad(ao_lco_pad_first(ao_lco_box));
187                 }
188                 if (ao_lco_pad == AO_LCO_PAD_VOLTAGE)
189                         ao_lco_show();
190         }
191 }
192
193 uint8_t ao_lco_box_mask[AO_LCO_MASK_SIZE(AO_PAD_MAX_BOXES)];
194
195 static void
196 ao_lco_box_reset_present(void)
197 {
198         ao_lco_min_box = 0xff;
199         ao_lco_max_box = 0x00;
200         memset(ao_lco_box_mask, 0, sizeof (ao_lco_box_mask));
201 }
202
203 static void
204 ao_lco_box_set_present(uint8_t box)
205 {
206         if (box < ao_lco_min_box)
207                 ao_lco_min_box = box;
208         if (box > ao_lco_max_box)
209                 ao_lco_max_box = box;
210         if (box >= AO_PAD_MAX_BOXES)
211                 return;
212         ao_lco_box_mask[AO_LCO_MASK_ID(box)] |= 1 << AO_LCO_MASK_SHIFT(box);
213 }
214
215 void
216 ao_lco_set_pad(uint8_t new_pad)
217 {
218         ao_lco_pad = new_pad;
219         ao_lco_show();
220 }
221
222 void
223 ao_lco_set_box(uint16_t new_box)
224 {
225         ao_lco_box = new_box;
226         if (ao_lco_box < AO_PAD_MAX_BOXES)
227                 ao_lco_channels[ao_lco_box] = 0;
228         ao_lco_pad = 1;
229         ao_lco_show();
230 }
231
232 void
233 ao_lco_step_pad(int8_t dir)
234 {
235         int8_t  new_pad;
236
237         new_pad = ao_lco_pad;
238         do {
239                 new_pad += dir;
240                 if (new_pad > AO_PAD_MAX_CHANNELS)
241                         new_pad = AO_LCO_PAD_VOLTAGE;
242                 if (new_pad < 0)
243                         new_pad = AO_PAD_MAX_CHANNELS;
244                 if (new_pad == ao_lco_pad)
245                         break;
246         } while (!ao_lco_pad_present(ao_lco_box, new_pad));
247         ao_lco_set_pad(new_pad);
248 }
249
250 void
251 ao_lco_set_armed(uint8_t armed)
252 {
253         ao_lco_armed = armed;
254         PRINTD("Armed %d\n", ao_lco_armed);
255         if (ao_lco_armed) {
256 #if AO_LCO_DRAG
257                 if (ao_lco_drag_race) {
258                         uint8_t box;
259
260                         for (box = ao_lco_min_box; box <= ao_lco_max_box; box++)
261                                 if (ao_lco_selected[box])
262                                         break;
263                         if (box > ao_lco_max_box)
264                                 ao_lco_armed = 0;
265                 } else
266 #endif
267                 {
268                         memset(ao_lco_selected, 0, sizeof (ao_lco_selected));
269                         if (ao_lco_pad != 0)
270                                 ao_lco_selected[ao_lco_box] = (1 << (ao_lco_pad - 1));
271                         else
272                                 ao_lco_armed = 0;
273                 }
274         }
275         ao_wakeup(&ao_lco_armed);
276 }
277
278 void
279 ao_lco_set_firing(uint8_t firing)
280 {
281         ao_lco_firing = firing;
282         PRINTD("Firing %d\n", ao_lco_firing);
283         ao_wakeup(&ao_lco_armed);
284 }
285
286 void
287 ao_lco_search(void)
288 {
289         int8_t          r;
290         int8_t          try;
291         uint8_t         box;
292         uint8_t         boxes = 0;
293
294         ao_lco_box_reset_present();
295         ao_lco_show_box(0);
296         ao_lco_show_pad(0);
297         for (box = 0; box < AO_PAD_MAX_BOXES; box++) {
298                 if ((box % 10) == 0)
299                         ao_lco_show_box(box);
300                 for (try = 0; try < 3; try++) {
301                         ao_lco_tick_offset[box] = 0;
302                         r = ao_lco_query(box, &ao_pad_query, &ao_lco_tick_offset[box]);
303                         PRINTD("box %d result %d offset %d\n", box, r, ao_lco_tick_offset[box]);
304                         if (r == AO_RADIO_CMAC_OK) {
305                                 ++boxes;
306                                 ao_lco_box_set_present(box);
307                                 ao_lco_show_pad(boxes % 10);
308                                 ao_delay(AO_MS_TO_TICKS(30));
309                                 break;
310                         }
311                 }
312         }
313         if (ao_lco_min_box <= ao_lco_max_box)
314                 ao_lco_box = ao_lco_min_box;
315         else
316                 ao_lco_min_box = ao_lco_max_box = ao_lco_box = 0;
317         memset(ao_lco_valid, 0, sizeof (ao_lco_valid));
318         memset(ao_lco_channels, 0, sizeof (ao_lco_channels));
319         ao_lco_set_box(ao_lco_min_box);
320 }
321
322 void
323 ao_lco_monitor(void)
324 {
325         uint16_t                delay;
326         uint8_t                 box;
327
328         for (;;) {
329                 PRINTD("monitor armed %d firing %d\n",
330                        ao_lco_armed, ao_lco_firing);
331
332                 if (ao_lco_armed && ao_lco_firing) {
333                         ao_lco_ignite(AO_PAD_FIRE);
334                 } else {
335                         ao_lco_update();
336                         if (ao_lco_armed) {
337                                 for (box = ao_lco_min_box; box <= ao_lco_max_box; box++) {
338                                         if (ao_lco_selected[box]) {
339                                                 PRINTD("Arming box %d pads %x\n",
340                                                        box, ao_lco_selected[box]);
341                                                 if (ao_lco_valid[box] & AO_LCO_VALID_EVER) {
342                                                         ao_lco_arm(box, ao_lco_selected[box], ao_lco_tick_offset[box]);
343                                                         ao_delay(AO_MS_TO_TICKS(10));
344                                                 }
345                                         }
346                                 }
347                         }
348                 }
349                 if (ao_lco_armed && ao_lco_firing)
350                         delay = AO_MS_TO_TICKS(100);
351                 else
352                         delay = AO_SEC_TO_TICKS(1);
353                 ao_sleep_for(&ao_lco_armed, delay);
354         }
355 }
356
357 #if AO_LCO_DRAG
358
359 uint8_t                 ao_lco_drag_beep_count;
360 static uint8_t          ao_lco_drag_beep_on;
361 static uint16_t         ao_lco_drag_beep_time;
362 static uint16_t         ao_lco_drag_warn_time;
363
364 #define AO_LCO_DRAG_BEEP_TIME   AO_MS_TO_TICKS(50)
365 #define AO_LCO_DRAG_WARN_TIME   AO_SEC_TO_TICKS(5)
366
367 /* Request 'beeps' additional drag race beeps */
368 void
369 ao_lco_drag_add_beeps(uint8_t beeps)
370 {
371         PRINTD("beep %d\n", beeps);
372         if (ao_lco_drag_beep_count == 0)
373                 ao_lco_drag_beep_time = ao_time();
374         ao_lco_drag_beep_count += beeps;
375         ao_wakeup(&ao_lco_drag_beep_count);
376 }
377
378 /* Toggle current pad in drag set */
379 void
380 ao_lco_toggle_drag(void)
381 {
382         if (ao_lco_drag_race && ao_lco_pad != AO_LCO_PAD_VOLTAGE) {
383                 ao_lco_selected[ao_lco_box] ^= (1 << (ao_lco_pad - 1));
384                 PRINTD("Toggle box %d pad %d (pads now %x) to drag race\n",
385                        ao_lco_pad, ao_lco_box, ao_lco_selected[ao_lco_box]);
386                 ao_lco_drag_add_beeps(ao_lco_pad);
387         }
388 }
389
390 /* Check whether it's time to change the beeper status, then either
391  * turn it on or off as necessary and bump the remaining beep counts
392  */
393
394 uint16_t
395 ao_lco_drag_beep_check(uint16_t now, uint16_t delay)
396 {
397         PRINTD("beep check count %d delta %d\n",
398                ao_lco_drag_beep_count,
399                (int16_t) (now - ao_lco_drag_beep_time));
400         if (ao_lco_drag_beep_count) {
401                 if ((int16_t) (now - ao_lco_drag_beep_time) >= 0) {
402                         if (ao_lco_drag_beep_on) {
403                                 ao_beep(0);
404                                 PRINTD("beep stop\n");
405                                 ao_lco_drag_beep_on = 0;
406                                 if (ao_lco_drag_beep_count) {
407                                         --ao_lco_drag_beep_count;
408                                         if (ao_lco_drag_beep_count)
409                                                 ao_lco_drag_beep_time = now + AO_LCO_DRAG_BEEP_TIME;
410                                 }
411                         } else {
412                                 ao_beep(AO_BEEP_HIGH);
413                                 PRINTD("beep start\n");
414                                 ao_lco_drag_beep_on = 1;
415                                 ao_lco_drag_beep_time = now + AO_LCO_DRAG_BEEP_TIME;
416                         }
417                 }
418         }
419
420         if (ao_lco_drag_beep_count) {
421                 uint16_t beep_delay = 0;
422
423                 if (ao_lco_drag_beep_time > now)
424                         beep_delay = ao_lco_drag_beep_time - now;
425
426                 if (delay > beep_delay)
427                         delay = beep_delay;
428         }
429         return delay;
430 }
431
432 void
433 ao_lco_drag_enable(void)
434 {
435         if (!ao_lco_drag_race) {
436                 PRINTD("Drag enable\n");
437                 ao_lco_drag_race = 1;
438                 memset(ao_lco_selected, 0, sizeof (ao_lco_selected));
439 #ifdef AO_LED_DRAG
440                 ao_led_on(AO_LED_DRAG);
441 #endif
442                 ao_lco_drag_add_beeps(5);
443                 ao_lco_show();
444         }
445 }
446
447 void
448 ao_lco_drag_disable(void)
449 {
450         if (ao_lco_drag_race) {
451                 PRINTD("Drag disable\n");
452                 ao_lco_drag_race = 0;
453 #ifdef AO_LED_DRAG
454                 ao_led_off(AO_LED_DRAG);
455 #endif
456                 memset(ao_lco_selected, 0, sizeof (ao_lco_selected));
457                 ao_lco_drag_add_beeps(2);
458                 ao_lco_show();
459         }
460 }
461
462 /* add a beep if it's time to warn the user that drag race mode is
463  * active
464  */
465
466 uint16_t
467 ao_lco_drag_warn_check(uint16_t now, uint16_t delay)
468 {
469         if (ao_lco_drag_race) {
470                 uint16_t        warn_delay;
471
472                 if ((int16_t) (now - ao_lco_drag_warn_time) >= 0) {
473                         ao_lco_drag_add_beeps(1);
474                         ao_lco_drag_warn_time = now + AO_LCO_DRAG_WARN_TIME;
475                 }
476                 warn_delay = ao_lco_drag_warn_time - now;
477                 if (delay > warn_delay)
478                         delay = warn_delay;
479         }
480         return delay;
481 }
482 #endif /* AO_LCO_DRAG */
483
484 /* task function for beeping while arm is active */
485 void
486 ao_lco_arm_warn(void)
487 {
488         for (;;) {
489                 while (!ao_lco_armed) {
490 #ifdef AO_LED_FIRE
491                         ao_led_off(AO_LED_FIRE);
492 #endif
493                         ao_sleep(&ao_lco_armed);
494                 }
495 #ifdef AO_LED_FIRE
496                 ao_led_on(AO_LED_FIRE);
497 #endif
498                 ao_beep_for(AO_BEEP_MID, AO_MS_TO_TICKS(200));
499                 ao_delay(AO_MS_TO_TICKS(200));
500         }
501 }