altos: Allow for pad boxes with different sensor configurations
[fw/altos] / src / drivers / ao_pad.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_pad.h>
20 #include <ao_74hc165.h>
21 #include <ao_radio_cmac.h>
22
23 static __xdata uint8_t ao_pad_ignite;
24 static __xdata struct ao_pad_command    command;
25 static __xdata struct ao_pad_query      query;
26 static __pdata uint8_t  ao_pad_armed;
27 static __pdata uint16_t ao_pad_arm_time;
28 static __pdata uint8_t  ao_pad_box;
29 static __xdata uint8_t  ao_pad_disabled;
30 static __pdata uint16_t ao_pad_packet_time;
31
32 #ifndef AO_PAD_RSSI_MINIMUM
33 #define AO_PAD_RSSI_MINIMUM     -90
34 #endif
35
36 #define DEBUG   1
37
38 #if DEBUG
39 static __pdata uint8_t  ao_pad_debug;
40 #define PRINTD(...) (ao_pad_debug ? (printf(__VA_ARGS__), 0) : 0)
41 #define FLUSHD()    (ao_pad_debug ? (flush(), 0) : 0)
42 #else
43 #define PRINTD(...)
44 #define FLUSHD()
45 #endif
46
47 static void
48 ao_siren(uint8_t v)
49 {
50 #ifdef AO_SIREN
51         ao_gpio_set(AO_SIREN_PORT, AO_SIREN_PIN, AO_SIREN, v);
52 #else
53 #if HAS_BEEP
54         ao_beep(v ? AO_BEEP_MID : 0);
55 #else
56         (void) v;
57 #endif
58 #endif
59 }
60
61 static void
62 ao_strobe(uint8_t v)
63 {
64 #ifdef AO_STROBE
65         ao_gpio_set(AO_STROBE_PORT, AO_STROBE_PIN, AO_STROBE, v);
66 #else
67         (void) v;
68 #endif
69 }
70
71 static void
72 ao_pad_run(void)
73 {
74         AO_PORT_TYPE    pins;
75
76         for (;;) {
77                 while (!ao_pad_ignite)
78                         ao_sleep(&ao_pad_ignite);
79                 /*
80                  * Actually set the pad bits
81                  */
82                 pins = 0;
83 #if AO_PAD_NUM > 0
84                 if (ao_pad_ignite & (1 << 0))
85                         pins |= (1 << AO_PAD_PIN_0);
86 #endif
87 #if AO_PAD_NUM > 1
88                 if (ao_pad_ignite & (1 << 1))
89                         pins |= (1 << AO_PAD_PIN_1);
90 #endif
91 #if AO_PAD_NUM > 2
92                 if (ao_pad_ignite & (1 << 2))
93                         pins |= (1 << AO_PAD_PIN_2);
94 #endif
95 #if AO_PAD_NUM > 3
96                 if (ao_pad_ignite & (1 << 3))
97                         pins |= (1 << AO_PAD_PIN_3);
98 #endif
99                 PRINTD("ignite pins 0x%x\n", pins);
100                 ao_gpio_set_bits(AO_PAD_PORT, pins);
101                 while (ao_pad_ignite) {
102                         ao_pad_ignite = 0;
103
104                         ao_delay(AO_PAD_FIRE_TIME);
105                 }
106                 ao_gpio_clr_bits(AO_PAD_PORT, pins);
107                 PRINTD("turn off pins 0x%x\n", pins);
108         }
109 }
110
111 #define AO_PAD_ARM_SIREN_INTERVAL       200
112
113 #ifndef AO_PYRO_R_PYRO_SENSE
114 #define AO_PYRO_R_PYRO_SENSE    100
115 #define AO_PYRO_R_SENSE_GND     27
116 #define AO_FIRE_R_POWER_FET     100
117 #define AO_FIRE_R_FET_SENSE     100
118 #define AO_FIRE_R_SENSE_GND     27
119 #endif
120
121 static void
122 ao_pad_monitor(void)
123 {
124         uint8_t                 c;
125         uint8_t                 sample;
126         __pdata uint8_t         prev = 0, cur = 0;
127         __pdata uint8_t         beeping = 0;
128         __xdata volatile struct ao_data *packet;
129         __pdata uint16_t        arm_beep_time = 0;
130
131         sample = ao_data_head;
132         for (;;) {
133                 __pdata int16_t                 pyro;
134                 ao_arch_critical(
135                         while (sample == ao_data_head)
136                                 ao_sleep((void *) DATA_TO_XDATA(&ao_data_head));
137                         );
138
139
140                 packet = &ao_data_ring[sample];
141                 sample = ao_data_ring_next(sample);
142
143                 pyro = packet->adc.pyro;
144
145 #define VOLTS_TO_PYRO(x) ((int16_t) ((x) * ((1.0 * AO_PYRO_R_SENSE_GND) / \
146                                             (1.0 * (AO_PYRO_R_SENSE_GND + AO_PYRO_R_PYRO_SENSE)) / 3.3 * AO_ADC_MAX)))
147
148
149 #define VOLTS_TO_FIRE(x) ((int16_t) ((x) * ((1.0 * AO_FIRE_R_SENSE_GND) / \
150                                             (1.0 * (AO_FIRE_R_SENSE_GND + AO_FIRE_R_FET_SENSE)) / 3.3 * AO_ADC_MAX)))
151
152                 /* convert ADC value to voltage in tenths, then add .2 for the diode drop */
153                 query.battery = (packet->adc.batt + 96) / 192 + 2;
154                 cur = 0;
155                 if (pyro > VOLTS_TO_PYRO(10)) {
156                         query.arm_status = AO_PAD_ARM_STATUS_ARMED;
157                         cur |= AO_LED_ARMED;
158 #if AO_FIRE_R_POWER_FET
159                 } else if (pyro > VOLTS_TO_PYRO(5)) {
160                         if ((ao_time() % 100) < 50)
161                                 cur |= AO_LED_ARMED;
162                         query.arm_status = AO_PAD_ARM_STATUS_UNKNOWN;
163                         arm_beep_time = 0;
164 #endif
165                 } else {
166                         query.arm_status = AO_PAD_ARM_STATUS_DISARMED;
167                         arm_beep_time = 0;
168                 }
169                 if ((ao_time() - ao_pad_packet_time) > AO_SEC_TO_TICKS(2))
170                         cur |= AO_LED_RED;
171                 else if (ao_radio_cmac_rssi < AO_PAD_RSSI_MINIMUM)
172                         cur |= AO_LED_AMBER;
173                 else
174                         cur |= AO_LED_GREEN;
175
176                 for (c = 0; c < AO_PAD_NUM; c++) {
177                         int16_t         sense = packet->adc.sense[c];
178                         uint8_t status = AO_PAD_IGNITER_STATUS_UNKNOWN;
179
180                         /*
181                          *      pyro is run through a divider, so pyro = v_pyro * 27 / 127 ~= v_pyro / 20
182                          *      v_pyro = pyro * 127 / 27
183                          *
184                          *              v_pyro \
185                          *      100k            igniter
186                          *              output /
187                          *      100k           \
188                          *              sense   relay
189                          *      27k            /
190                          *              gnd ---
191                          *
192                          *              v_pyro \
193                          *      200k            igniter
194                          *              output /
195                          *      200k           \
196                          *              sense   relay
197                          *      22k            /
198                          *              gnd ---
199                          *
200                          *      If the relay is closed, then sense will be 0
201                          *      If no igniter is present, then sense will be v_pyro * 27k/227k = pyro * 127 / 227 ~= pyro/2
202                          *      If igniter is present, then sense will be v_pyro * 27k/127k ~= v_pyro / 20 = pyro
203                          */
204
205 #if AO_FIRE_R_POWER_FET
206                         if (sense <= pyro / 8) {
207                                 status = AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_CLOSED;
208                                 if ((ao_time() % 100) < 50)
209                                         cur |= AO_LED_CONTINUITY(c);
210                         } else
211                         if (pyro / 8 * 3 <= sense && sense <= pyro / 8 * 5)
212                                 status = AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN;
213                         else if (pyro / 8 * 7 <= sense) {
214                                 status = AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN;
215                                 cur |= AO_LED_CONTINUITY(c);
216                         }
217 #else
218                         if (sense >= pyro / 8 * 5) {
219                                 status = AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN;
220                                 cur |= AO_LED_CONTINUITY(c);
221                         } else {
222                                 status = AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN;
223                         }
224 #endif
225                         query.igniter_status[c] = status;
226                 }
227                 if (cur != prev) {
228                         PRINTD("change leds from %02x to %02x\n",
229                                prev, cur);
230                         FLUSHD();
231                         ao_led_set(cur);
232                         prev = cur;
233                 }
234
235                 if (ao_pad_armed && (int16_t) (ao_time() - ao_pad_arm_time) > AO_PAD_ARM_TIME)
236                         ao_pad_armed = 0;
237
238                 if (ao_pad_armed) {
239                         ao_strobe(1);
240                         ao_siren(1);
241                         beeping = 1;
242                 } else if (query.arm_status == AO_PAD_ARM_STATUS_ARMED && !beeping) {
243                         if (arm_beep_time == 0) {
244                                 arm_beep_time = AO_PAD_ARM_SIREN_INTERVAL;
245                                 beeping = 1;
246                                 ao_siren(1);
247                         }
248                         --arm_beep_time;
249                 } else if (beeping) {
250                         beeping = 0;
251                         ao_siren(0);
252                         ao_strobe(0);
253                 }
254         }
255 }
256
257 void
258 ao_pad_disable(void)
259 {
260         if (!ao_pad_disabled) {
261                 ao_pad_disabled = 1;
262                 ao_radio_recv_abort();
263         }
264 }
265
266 void
267 ao_pad_enable(void)
268 {
269         ao_pad_disabled = 0;
270         ao_wakeup (&ao_pad_disabled);
271 }
272
273 #if HAS_74HC165
274 static uint8_t
275 ao_pad_read_box(void)
276 {
277         uint8_t         byte = ao_74hc165_read();
278         uint8_t         h, l;
279
280         h = byte >> 4;
281         l = byte & 0xf;
282         return h * 10 + l;
283 }
284 #endif
285
286 #if HAS_FIXED_PAD_BOX
287 #define ao_pad_read_box()       ao_config.pad_box
288 #endif
289
290 #ifdef PAD_BOX
291 #define ao_pad_read_box()       PAD_BOX
292 #endif
293
294 static void
295 ao_pad(void)
296 {
297         int16_t time_difference;
298         int8_t  ret;
299
300         ao_pad_box = 0;
301         ao_led_set(0);
302         for (;;) {
303                 FLUSHD();
304                 while (ao_pad_disabled)
305                         ao_sleep(&ao_pad_disabled);
306                 ret = ao_radio_cmac_recv(&command, sizeof (command), 0);
307                 PRINTD ("cmac_recv %d %d\n", ret, ao_radio_cmac_rssi);
308                 if (ret != AO_RADIO_CMAC_OK)
309                         continue;
310                 ao_pad_packet_time = ao_time();
311
312                 ao_pad_box = ao_pad_read_box();
313
314                 PRINTD ("tick %d box %d (me %d) cmd %d channels %02x\n",
315                         command.tick, command.box, ao_pad_box, command.cmd, command.channels);
316
317                 switch (command.cmd) {
318                 case AO_LAUNCH_ARM:
319                         if (command.box != ao_pad_box) {
320                                 PRINTD ("box number mismatch\n");
321                                 break;
322                         }
323
324                         if (command.channels & ~(AO_PAD_ALL_CHANNELS))
325                                 break;
326
327                         time_difference = command.tick - ao_time();
328                         PRINTD ("arm tick %d local tick %d\n", command.tick, ao_time());
329                         if (time_difference < 0)
330                                 time_difference = -time_difference;
331                         if (time_difference > 10) {
332                                 PRINTD ("time difference too large %d\n", time_difference);
333                                 break;
334                         }
335                         PRINTD ("armed\n");
336                         ao_pad_armed = command.channels;
337                         ao_pad_arm_time = ao_time();
338                         break;
339
340                 case AO_LAUNCH_QUERY:
341                         if (command.box != ao_pad_box) {
342                                 PRINTD ("box number mismatch\n");
343                                 break;
344                         }
345
346                         query.tick = ao_time();
347                         query.box = ao_pad_box;
348                         query.channels = AO_PAD_ALL_CHANNELS;
349                         query.armed = ao_pad_armed;
350                         PRINTD ("query tick %d box %d channels %02x arm %d arm_status %d igniter %d,%d,%d,%d\n",
351                                 query.tick, query.box, query.channels, query.armed,
352                                 query.arm_status,
353                                 query.igniter_status[0],
354                                 query.igniter_status[1],
355                                 query.igniter_status[2],
356                                 query.igniter_status[3]);
357                         ao_radio_cmac_send(&query, sizeof (query));
358                         break;
359                 case AO_LAUNCH_FIRE:
360                         if (!ao_pad_armed) {
361                                 PRINTD ("not armed\n");
362                                 break;
363                         }
364                         if ((uint16_t) (ao_time() - ao_pad_arm_time) > AO_SEC_TO_TICKS(20)) {
365                                 PRINTD ("late pad arm_time %d time %d\n",
366                                         ao_pad_arm_time, ao_time());
367                                 break;
368                         }
369                         PRINTD ("ignite\n");
370                         ao_pad_ignite = ao_pad_armed;
371                         ao_pad_arm_time = ao_time();
372                         ao_wakeup(&ao_pad_ignite);
373                         break;
374                 }
375         }
376 }
377
378 void
379 ao_pad_test(void)
380 {
381         uint8_t c;
382
383         printf ("Arm switch: ");
384         switch (query.arm_status) {
385         case AO_PAD_ARM_STATUS_ARMED:
386                 printf ("Armed\n");
387                 break;
388         case AO_PAD_ARM_STATUS_DISARMED:
389                 printf ("Disarmed\n");
390                 break;
391         case AO_PAD_ARM_STATUS_UNKNOWN:
392                 printf ("Unknown\n");
393                 break;
394         }
395
396         for (c = 0; c < AO_PAD_NUM; c++) {
397                 printf ("Pad %d: ", c);
398                 switch (query.igniter_status[c]) {
399                 case AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_CLOSED:     printf ("No igniter. Relay closed\n"); break;
400                 case AO_PAD_IGNITER_STATUS_NO_IGNITER_RELAY_OPEN:       printf ("No igniter. Relay open\n"); break;
401                 case AO_PAD_IGNITER_STATUS_GOOD_IGNITER_RELAY_OPEN:     printf ("Good igniter. Relay open\n"); break;
402                 case AO_PAD_IGNITER_STATUS_UNKNOWN:                     printf ("Unknown\n"); break;
403                 }
404         }
405 }
406
407 void
408 ao_pad_manual(void)
409 {
410         uint8_t ignite;
411         int     repeat;
412         ao_cmd_white();
413         if (!ao_match_word("DoIt"))
414                 return;
415         ao_cmd_decimal();
416         if (ao_cmd_status != ao_cmd_success)
417                 return;
418         ignite = 1 << ao_cmd_lex_i;
419         ao_cmd_decimal();
420         if (ao_cmd_status != ao_cmd_success) {
421                 repeat = 1;
422                 ao_cmd_status = ao_cmd_success;
423         } else
424                 repeat = ao_cmd_lex_i;
425         while (repeat-- > 0) {
426                 ao_pad_ignite = ignite;
427                 ao_wakeup(&ao_pad_ignite);
428                 ao_delay(AO_PAD_FIRE_TIME>>1);
429         }
430 }
431
432 static __xdata struct ao_task ao_pad_task;
433 static __xdata struct ao_task ao_pad_ignite_task;
434 static __xdata struct ao_task ao_pad_monitor_task;
435
436 #if DEBUG
437 void
438 ao_pad_set_debug(void)
439 {
440         ao_cmd_decimal();
441         if (ao_cmd_status == ao_cmd_success)
442                 ao_pad_debug = ao_cmd_lex_i != 0;
443 }
444
445
446 static void
447 ao_pad_alarm_debug(void)
448 {
449         uint8_t which, value;
450         ao_cmd_decimal();
451         if (ao_cmd_status != ao_cmd_success)
452                 return;
453         which = ao_cmd_lex_i;
454         ao_cmd_decimal();
455         if (ao_cmd_status != ao_cmd_success)
456                 return;
457         value = ao_cmd_lex_i;
458         printf ("Set %s to %d\n", which ? "siren" : "strobe", value);
459         if (which)
460                 ao_siren(value);
461         else
462                 ao_strobe(value);
463 }
464 #endif
465
466 __code struct ao_cmds ao_pad_cmds[] = {
467         { ao_pad_test,  "t\0Test pad continuity" },
468         { ao_pad_manual,        "i <key> <n>\0Fire igniter. <key> is doit with D&I" },
469 #if DEBUG
470         { ao_pad_set_debug,     "D <0 off, 1 on>\0Debug" },
471         { ao_pad_alarm_debug,   "S <0 strobe, 1 siren> <0 off, 1 on>\0Set alarm output" },
472 #endif
473         { 0, NULL }
474 };
475
476 void
477 ao_pad_init(void)
478 {
479 #if AO_PAD_NUM > 0
480         ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_0, AO_PAD_0, 0);
481 #endif
482 #if AO_PAD_NUM > 1
483         ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_1, AO_PAD_1, 0);
484 #endif
485 #if AO_PAD_NUM > 2
486         ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_2, AO_PAD_2, 0);
487 #endif
488 #if AO_PAD_NUM > 3
489         ao_enable_output(AO_PAD_PORT, AO_PAD_PIN_3, AO_PAD_3, 0);
490 #endif
491 #ifdef AO_STROBE
492         ao_enable_output(AO_STROBE_PORT, AO_STROBE_PIN, AO_STROBE, 0);
493 #endif
494 #ifdef AO_SIREN
495         ao_enable_output(AO_SIREN_PORT, AO_SIREN_PIN, AO_SIREN, 0);
496 #endif
497         ao_cmd_register(&ao_pad_cmds[0]);
498         ao_add_task(&ao_pad_task, ao_pad, "pad listener");
499         ao_add_task(&ao_pad_ignite_task, ao_pad_run, "pad igniter");
500         ao_add_task(&ao_pad_monitor_task, ao_pad_monitor, "pad monitor");
501 }