altos/telelco: Show box voltage with pad knob instead of firing button
[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 static uint8_t  ao_lco_min_box, ao_lco_max_box;
40 static uint8_t  ao_lco_pad;
41 static uint8_t  ao_lco_box;
42 static uint8_t  ao_lco_armed;
43 static uint8_t  ao_lco_firing;
44 static uint8_t  ao_lco_valid;
45 static uint8_t  ao_lco_got_channels;
46 static uint16_t ao_lco_tick_offset;
47
48 static struct ao_pad_query      ao_pad_query;
49
50 static void
51 ao_lco_set_pad(uint8_t pad)
52 {
53         ao_seven_segment_set(AO_LCO_PAD_DIGIT, pad);
54 }
55
56 static void
57 ao_lco_set_box(uint8_t box)
58 {
59         ao_seven_segment_set(AO_LCO_BOX_DIGIT_1, box % 10);
60         ao_seven_segment_set(AO_LCO_BOX_DIGIT_10, box / 10);
61 }
62
63 static void
64 ao_lco_set_voltage(uint16_t decivolts)
65 {
66         uint8_t tens, ones, tenths;
67
68         tenths = decivolts % 10;
69         ones = (decivolts / 10) % 10;
70         tens = (decivolts / 100) % 10;
71         ao_seven_segment_set(AO_LCO_PAD_DIGIT, tenths);
72         ao_seven_segment_set(AO_LCO_BOX_DIGIT_1, ones | 0x10);
73         ao_seven_segment_set(AO_LCO_BOX_DIGIT_10, tens);
74 }
75
76 static void
77 ao_lco_set_display(void)
78 {
79         if (ao_lco_pad == 0) {
80                 ao_lco_set_voltage(ao_pad_query.battery);
81         } else {
82                 ao_lco_set_pad(ao_lco_pad);
83                 ao_lco_set_box(ao_lco_box);
84         }
85 }
86
87 #define MASK_SIZE(n)    (((n) + 7) >> 3)
88 #define MASK_ID(n)      ((n) >> 3)
89 #define MASK_SHIFT(n)   ((n) & 7)
90
91 static uint8_t  ao_lco_box_mask[MASK_SIZE(AO_PAD_MAX_BOXES)];
92
93 static uint8_t
94 ao_lco_box_present(uint8_t box)
95 {
96         if (box >= AO_PAD_MAX_BOXES)
97                 return 0;
98         return (ao_lco_box_mask[MASK_ID(box)] >> MASK_SHIFT(box)) & 1;
99 }
100
101 static uint8_t
102 ao_lco_pad_present(uint8_t pad)
103 {
104         if (!ao_lco_got_channels || !ao_pad_query.channels)
105                 return pad == 0;
106         /* voltage measurement is always valid */
107         if (pad == 0)
108                 return 1;
109         if (pad > AO_PAD_MAX_CHANNELS)
110                 return 0;
111         return (ao_pad_query.channels >> (pad - 1)) & 1;
112 }
113
114 static uint8_t
115 ao_lco_pad_first(void)
116 {
117         uint8_t pad;
118
119         for (pad = 1; pad <= AO_PAD_MAX_CHANNELS; pad++)
120                 if (ao_lco_pad_present(pad))
121                         return pad;
122         return 0;
123 }
124
125 static void
126 ao_lco_input(void)
127 {
128         static struct ao_event  event;
129         int8_t  dir, new_box, new_pad;
130
131         ao_beep_for(AO_BEEP_MID, AO_MS_TO_TICKS(200));
132         for (;;) {
133                 ao_event_get(&event);
134                 PRINTD("event type %d unit %d value %d\n",
135                        event.type, event.unit, event.value);
136                 switch (event.type) {
137                 case AO_EVENT_QUADRATURE:
138                         switch (event.unit) {
139                         case AO_QUADRATURE_PAD:
140                                 if (!ao_lco_armed) {
141                                         dir = (int8_t) event.value;
142                                         new_pad = ao_lco_pad;
143                                         do {
144                                                 new_pad += dir;
145                                                 if (new_pad > AO_PAD_MAX_CHANNELS)
146                                                         new_pad = 0;
147                                                 if (new_pad < 0)
148                                                         new_pad = AO_PAD_MAX_CHANNELS;
149                                                 if (new_pad == ao_lco_pad)
150                                                         break;
151                                         } while (!ao_lco_pad_present(new_pad));
152                                         if (new_pad != ao_lco_pad) {
153                                                 ao_lco_pad = new_pad;
154                                                 ao_lco_set_display();
155                                         }
156                                 }
157                                 break;
158                         case AO_QUADRATURE_BOX:
159                                 if (!ao_lco_armed) {
160                                         dir = (int8_t) event.value;
161                                         new_box = ao_lco_box;
162                                         do {
163                                                 new_box += dir;
164                                                 if (new_box > ao_lco_max_box)
165                                                         new_box = ao_lco_min_box;
166                                                 else if (new_box < ao_lco_min_box)
167                                                         new_box = ao_lco_max_box;
168                                                 if (new_box == ao_lco_box)
169                                                         break;
170                                         } while (!ao_lco_box_present(new_box));
171                                         if (ao_lco_box != new_box) {
172                                                 ao_lco_box = new_box;
173                                                 ao_lco_pad = 1;
174                                                 ao_lco_got_channels = 0;
175                                                 ao_lco_set_display();
176                                         }
177                                 }
178                                 break;
179                         }
180                         break;
181                 case AO_EVENT_BUTTON:
182                         switch (event.unit) {
183                         case AO_BUTTON_ARM:
184                                 ao_lco_armed = event.value;
185                                 PRINTD("Armed %d\n", ao_lco_armed);
186                                 ao_wakeup(&ao_lco_armed);
187                                 break;
188                         case AO_BUTTON_FIRE:
189                                 if (ao_lco_armed) {
190                                         ao_lco_firing = event.value;
191                                         PRINTD("Firing %d\n", ao_lco_firing);
192                                         ao_wakeup(&ao_lco_armed);
193                                 }
194                                 break;
195                         }
196                         break;
197                 }
198         }
199 }
200
201 static AO_LED_TYPE      continuity_led[AO_LED_CONTINUITY_NUM] = {
202 #ifdef AO_LED_CONTINUITY_0
203         AO_LED_CONTINUITY_0,
204 #endif
205 #ifdef AO_LED_CONTINUITY_1
206         AO_LED_CONTINUITY_1,
207 #endif
208 #ifdef AO_LED_CONTINUITY_2
209         AO_LED_CONTINUITY_2,
210 #endif
211 #ifdef AO_LED_CONTINUITY_3
212         AO_LED_CONTINUITY_3,
213 #endif
214 #ifdef AO_LED_CONTINUITY_4
215         AO_LED_CONTINUITY_4,
216 #endif
217 #ifdef AO_LED_CONTINUITY_5
218         AO_LED_CONTINUITY_5,
219 #endif
220 #ifdef AO_LED_CONTINUITY_6
221         AO_LED_CONTINUITY_6,
222 #endif
223 #ifdef AO_LED_CONTINUITY_7
224         AO_LED_CONTINUITY_7,
225 #endif
226 };
227
228 static void
229 ao_lco_update(void)
230 {
231         int8_t                  r;
232         uint8_t                 c;
233
234         r = ao_lco_query(ao_lco_box, &ao_pad_query, &ao_lco_tick_offset);
235         if (r == AO_RADIO_CMAC_OK) {
236                 c = ao_lco_got_channels;
237                 ao_lco_got_channels = 1;
238                 ao_lco_valid = 1;
239                 if (!c) {
240                         if (ao_lco_pad != 0)
241                                 ao_lco_pad = ao_lco_pad_first();
242                         ao_lco_set_display();
243                 }
244                 if (ao_lco_pad == 0)
245                         ao_lco_set_display();
246         } else
247                 ao_lco_valid = 0;
248
249 #if 0
250         PRINTD("lco_query success arm_status %d i0 %d i1 %d i2 %d i3 %d\n",
251                query.arm_status,
252                query.igniter_status[0],
253                query.igniter_status[1],
254                query.igniter_status[2],
255                query.igniter_status[3]);
256 #endif
257         PRINTD("ao_lco_update valid %d\n", ao_lco_valid);
258         ao_wakeup(&ao_pad_query);
259 }
260
261 static void
262 ao_lco_box_reset_present(void)
263 {
264         ao_lco_min_box = 0xff;
265         ao_lco_max_box = 0x00;
266         memset(ao_lco_box_mask, 0, sizeof (ao_lco_box_mask));
267 }
268
269 static void
270 ao_lco_box_set_present(uint8_t box)
271 {
272         if (box < ao_lco_min_box)
273                 ao_lco_min_box = box;
274         if (box > ao_lco_max_box)
275                 ao_lco_max_box = box;
276         if (box >= AO_PAD_MAX_BOXES)
277                 return;
278         ao_lco_box_mask[MASK_ID(box)] |= 1 << MASK_SHIFT(box);
279 }
280
281 static void
282 ao_lco_search(void)
283 {
284         uint16_t        tick_offset;
285         int8_t          r;
286         int8_t          try;
287         uint8_t         box;
288         uint8_t         boxes = 0;
289
290         ao_lco_box_reset_present();
291         ao_lco_set_pad(0);
292         for (box = 0; box < AO_PAD_MAX_BOXES; box++) {
293                 if ((box % 10) == 0)
294                         ao_lco_set_box(box);
295                 for (try = 0; try < 3; try++) {
296                         tick_offset = 0;
297                         r = ao_lco_query(box, &ao_pad_query, &tick_offset);
298                         PRINTD("box %d result %d\n", box, r);
299                         if (r == AO_RADIO_CMAC_OK) {
300                                 ++boxes;
301                                 ao_lco_box_set_present(box);
302                                 ao_lco_set_pad(boxes % 10);
303                                 ao_delay(AO_MS_TO_TICKS(30));
304                                 break;
305                         }
306                 }
307         }
308         if (ao_lco_min_box <= ao_lco_max_box)
309                 ao_lco_box = ao_lco_min_box;
310         else
311                 ao_lco_min_box = ao_lco_max_box = ao_lco_box = 0;
312         ao_lco_valid = 0;
313         ao_lco_got_channels = 0;
314         ao_lco_pad = 1;
315         ao_lco_set_display();
316 }
317
318 static void
319 ao_lco_igniter_status(void)
320 {
321         uint8_t         c;
322
323         for (;;) {
324                 ao_sleep(&ao_pad_query);
325                 PRINTD("RSSI %d VALID %d\n", ao_radio_cmac_rssi, ao_lco_valid);
326                 if (!ao_lco_valid) {
327                         ao_led_on(AO_LED_RED);
328                         ao_led_off(AO_LED_GREEN|AO_LED_AMBER);
329                         continue;
330                 }
331                 if (ao_radio_cmac_rssi < -90) {
332                         ao_led_on(AO_LED_AMBER);
333                         ao_led_off(AO_LED_RED|AO_LED_GREEN);
334                 } else {
335                         ao_led_on(AO_LED_GREEN);
336                         ao_led_off(AO_LED_RED|AO_LED_AMBER);
337                 }
338                 if (ao_pad_query.arm_status)
339                         ao_led_on(AO_LED_REMOTE_ARM);
340                 else
341                         ao_led_off(AO_LED_REMOTE_ARM);
342                 for (c = 0; c < AO_LED_CONTINUITY_NUM; c++) {
343                         uint8_t status;
344
345                         if (ao_pad_query.channels & (1 << c))
346                                 status = ao_pad_query.igniter_status[c];
347                         else
348                                 status = AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN;
349                         if (status == AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN)
350                                 ao_led_on(continuity_led[c]);
351                         else
352                                 ao_led_off(continuity_led[c]);
353                 }
354         }
355 }
356
357 static void
358 ao_lco_arm_warn(void)
359 {
360         for (;;) {
361                 while (!ao_lco_armed)
362                         ao_sleep(&ao_lco_armed);
363                 ao_beep_for(AO_BEEP_MID, AO_MS_TO_TICKS(200));
364                 ao_delay(AO_MS_TO_TICKS(200));
365         }
366 }
367
368 static struct ao_task ao_lco_input_task;
369 static struct ao_task ao_lco_monitor_task;
370 static struct ao_task ao_lco_arm_warn_task;
371 static struct ao_task ao_lco_igniter_status_task;
372
373 static void
374 ao_lco_monitor(void)
375 {
376         uint16_t                delay;
377
378         ao_lco_search();
379         ao_add_task(&ao_lco_input_task, ao_lco_input, "lco input");
380         ao_add_task(&ao_lco_arm_warn_task, ao_lco_arm_warn, "lco arm warn");
381         ao_add_task(&ao_lco_igniter_status_task, ao_lco_igniter_status, "lco igniter status");
382         for (;;) {
383                 PRINTD("monitor armed %d firing %d offset %d\n",
384                        ao_lco_armed, ao_lco_firing, ao_lco_tick_offset);
385
386                 if (ao_lco_armed && ao_lco_firing) {
387                         PRINTD("Firing box %d pad %d: valid %d\n",
388                                ao_lco_box, ao_lco_pad, ao_lco_valid);
389                         if (!ao_lco_valid)
390                                 ao_lco_update();
391                         if (ao_lco_valid && ao_lco_pad)
392                                 ao_lco_ignite(ao_lco_box, 1 << (ao_lco_pad - 1), ao_lco_tick_offset);
393                 } else if (ao_lco_armed) {
394                         PRINTD("Arming box %d pad %d\n",
395                                ao_lco_box, ao_lco_pad);
396                         if (!ao_lco_valid)
397                                 ao_lco_update();
398                         if (ao_lco_pad) {
399                                 ao_lco_arm(ao_lco_box, 1 << (ao_lco_pad - 1), ao_lco_tick_offset);
400                                 ao_delay(AO_MS_TO_TICKS(30));
401                                 ao_lco_update();
402                         }
403                 } else {
404                         ao_lco_update();
405                 }
406                 if (ao_lco_armed && ao_lco_firing)
407                         delay = AO_MS_TO_TICKS(100);
408                 else
409                         delay = AO_SEC_TO_TICKS(1);
410                 ao_sleep_for(&ao_lco_armed, delay);
411         }
412 }
413
414 #if DEBUG
415 void
416 ao_lco_set_debug(void)
417 {
418         ao_cmd_decimal();
419         if (ao_cmd_status == ao_cmd_success)
420                 ao_lco_debug = ao_cmd_lex_i != 0;
421 }
422
423 __code struct ao_cmds ao_lco_cmds[] = {
424         { ao_lco_set_debug,     "D <0 off, 1 on>\0Debug" },
425         { ao_lco_search,        "s\0Search for pad boxes" },
426         { 0, NULL }
427 };
428 #endif
429
430 void
431 ao_lco_init(void)
432 {
433         ao_add_task(&ao_lco_monitor_task, ao_lco_monitor, "lco monitor");
434 #if DEBUG
435         ao_cmd_register(&ao_lco_cmds[0]);
436 #endif
437 }