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