There she goes! First commit from me to trunk \o/
[asterisk/asterisk.git] / apps / app_alarmreceiver.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C)  2004 - 2005 Steve Rodgers
5  *
6  * Steve Rodgers <hwstar@rodgers.sdcoxmail.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  * \brief Central Station Alarm receiver for Ademco Contact ID
21  * \author Steve Rodgers <hwstar@rodgers.sdcoxmail.com>
22  *
23  * *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
24  *
25  * Use at your own risk. Please consult the GNU GPL license document included with Asterisk.         *
26  *
27  * *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
28  *
29  * \ingroup applications
30  */
31
32 #include "asterisk.h"
33
34 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
35
36 #include <math.h>
37 #include <sys/wait.h>
38 #include <sys/time.h>
39
40 #include "asterisk/lock.h"
41 #include "asterisk/file.h"
42 #include "asterisk/channel.h"
43 #include "asterisk/pbx.h"
44 #include "asterisk/module.h"
45 #include "asterisk/translate.h"
46 #include "asterisk/ulaw.h"
47 #include "asterisk/app.h"
48 #include "asterisk/dsp.h"
49 #include "asterisk/config.h"
50 #include "asterisk/localtime.h"
51 #include "asterisk/callerid.h"
52 #include "asterisk/astdb.h"
53 #include "asterisk/utils.h"
54
55 #define ALMRCV_CONFIG "alarmreceiver.conf"
56 #define ADEMCO_CONTACT_ID "ADEMCO_CONTACT_ID"
57
58 struct event_node{
59         char data[17];
60         struct event_node *next;
61 };
62
63 typedef struct event_node event_node_t;
64
65 static char *app = "AlarmReceiver";
66
67 static char *synopsis = "Provide support for receiving alarm reports from a burglar or fire alarm panel";
68 static char *descrip =
69 "  AlarmReceiver(): Only 1 signalling format is supported at this time: Ademco\n"
70 "Contact ID. This application should be called whenever there is an alarm\n"
71 "panel calling in to dump its events. The application will handshake with the\n"
72 "alarm panel, and receive events, validate them, handshake them, and store them\n"
73 "until the panel hangs up. Once the panel hangs up, the application will run the\n"
74 "system command specified by the eventcmd setting in alarmreceiver.conf and pipe\n"
75 "the events to the standard input of the application. The configuration file also\n"
76 "contains settings for DTMF timing, and for the loudness of the acknowledgement\n"
77 "tones.\n";
78
79 /* Config Variables */
80 static int fdtimeout = 2000;
81 static int sdtimeout = 200;
82 static int toneloudness = 4096;
83 static int log_individual_events = 0;
84 static char event_spool_dir[128] = {'\0'};
85 static char event_app[128] = {'\0'};
86 static char db_family[128] = {'\0'};
87 static char time_stamp_format[128] = {"%a %b %d, %Y @ %H:%M:%S %Z"};
88
89 /* Misc variables */
90 static char event_file[14] = "/event-XXXXXX";
91
92 /*
93 * Attempt to access a database variable and increment it,
94 * provided that the user defined db-family in alarmreceiver.conf
95 * The alarmreceiver app will write statistics to a few variables
96 * in this family if it is defined. If the new key doesn't exist in the
97 * family, then create it and set its value to 1.
98 */
99 static void database_increment( char *key )
100 {
101         int res = 0;
102         unsigned v;
103         char value[16];
104         
105         
106         if (ast_strlen_zero(db_family))
107                 return; /* If not defined, don't do anything */
108         
109         res = ast_db_get(db_family, key, value, sizeof(value) - 1);
110         
111         if (res) {
112                 ast_verb(4, "AlarmReceiver: Creating database entry %s and setting to 1\n", key);
113                 /* Guess we have to create it */
114                 res = ast_db_put(db_family, key, "1");
115                 return;
116         }
117         
118         sscanf(value, "%u", &v);
119         v++;
120
121         ast_verb(4, "AlarmReceiver: New value for %s: %u\n", key, v);
122
123         snprintf(value, sizeof(value), "%u", v);
124
125         res = ast_db_put(db_family, key, value);
126
127         if (res)
128                 ast_verb(4, "AlarmReceiver: database_increment write error\n");
129
130         return;
131 }
132
133
134 /*
135 * Build a MuLaw data block for a single frequency tone
136 */
137 static void make_tone_burst(unsigned char *data, float freq, float loudness, int len, int *x)
138 {
139         int     i;
140         float   val;
141
142         for (i = 0; i < len; i++) {
143                 val = loudness * sin((freq * 2.0 * M_PI * (*x)++)/8000.0);
144                 data[i] = AST_LIN2MU((int)val);
145         }
146
147         /* wrap back around from 8000 */
148
149         if (*x >= 8000)
150                 *x = 0;
151         return;
152 }
153
154 /*
155 * Send a single tone burst for a specifed duration and frequency.
156 * Returns 0 if successful
157 */
158 static int send_tone_burst(struct ast_channel *chan, float freq, int duration, int tldn)
159 {
160         int res = 0;
161         int i = 0;
162         int x = 0;
163         struct ast_frame *f, wf;
164         
165         struct {
166                 unsigned char offset[AST_FRIENDLY_OFFSET];
167                 unsigned char buf[640];
168         } tone_block;
169
170         for (;;) {
171
172                 if (ast_waitfor(chan, -1) < 0) {
173                         res = -1;
174                         break;
175                 }
176
177                 f = ast_read(chan);
178                 if (!f) {
179                         res = -1;
180                         break;
181                 }
182
183                 if (f->frametype == AST_FRAME_VOICE) {
184                         wf.frametype = AST_FRAME_VOICE;
185                         wf.subclass = AST_FORMAT_ULAW;
186                         wf.offset = AST_FRIENDLY_OFFSET;
187                         wf.mallocd = 0;
188                         wf.data = tone_block.buf;
189                         wf.datalen = f->datalen;
190                         wf.samples = wf.datalen;
191                         
192                         make_tone_burst(tone_block.buf, freq, (float) tldn, wf.datalen, &x);
193
194                         i += wf.datalen / 8;
195                         if (i > duration) {
196                                 ast_frfree(f);
197                                 break;
198                         }
199                         if (ast_write(chan, &wf)) {
200                                 ast_verb(4, "AlarmReceiver: Failed to write frame on %s\n", chan->name);
201                                 ast_log(LOG_WARNING, "AlarmReceiver Failed to write frame on %s\n",chan->name);
202                                 res = -1;
203                                 ast_frfree(f);
204                                 break;
205                         }
206                 }
207
208                 ast_frfree(f);
209         }
210         return res;
211 }
212
213 /*
214 * Receive a string of DTMF digits where the length of the digit string is known in advance. Do not give preferential
215 * treatment to any digit value, and allow separate time out values to be specified for the first digit and all subsequent
216 * digits.
217 *
218 * Returns 0 if all digits successfully received.
219 * Returns 1 if a digit time out occurred
220 * Returns -1 if the caller hung up or there was a channel error.
221 *
222 */
223 static int receive_dtmf_digits(struct ast_channel *chan, char *digit_string, int length, int fdto, int sdto)
224 {
225         int res = 0;
226         int i = 0;
227         int r;
228         struct ast_frame *f;
229         struct timeval lastdigittime;
230
231         lastdigittime = ast_tvnow();
232         for (;;) {
233                 /* if outa time, leave */
234                 if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) > ((i > 0) ? sdto : fdto)) {
235                         ast_verb(4, "AlarmReceiver: DTMF Digit Timeout on %s\n", chan->name);
236                         ast_debug(1,"AlarmReceiver: DTMF timeout on chan %s\n",chan->name);
237                         res = 1;
238                         break;
239                 }
240
241                 if ((r = ast_waitfor(chan, -1) < 0)) {
242                         ast_debug(1, "Waitfor returned %d\n", r);
243                         continue;
244                 }
245
246                 f = ast_read(chan);
247
248                 if (f == NULL) {
249                         res = -1;
250                         break;
251                 }
252
253                 /* If they hung up, leave */
254                 if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
255                         ast_frfree(f);
256                         res = -1;
257                         break;
258                 }
259
260                 /* if not DTMF, just do it again */
261                 if (f->frametype != AST_FRAME_DTMF) {
262                         ast_frfree(f);
263                         continue;
264                 }
265
266                 digit_string[i++] = f->subclass;  /* save digit */
267
268                 ast_frfree(f);
269
270                 /* If we have all the digits we expect, leave */
271                 if(i >= length)
272                         break;
273
274                 lastdigittime = ast_tvnow();
275         }
276
277         digit_string[i] = '\0'; /* Nul terminate the end of the digit string */
278         return res;
279 }
280
281 /*
282 * Write the metadata to the log file
283 */
284 static int write_metadata( FILE *logfile, char *signalling_type, struct ast_channel *chan)
285 {
286         int res = 0;
287         struct timeval t;
288         struct ast_tm now;
289         char *cl,*cn;
290         char workstring[80];
291         char timestamp[80];
292         
293         /* Extract the caller ID location */
294         if (chan->cid.cid_num)
295                 ast_copy_string(workstring, chan->cid.cid_num, sizeof(workstring));
296         workstring[sizeof(workstring) - 1] = '\0';
297
298         ast_callerid_parse(workstring, &cn, &cl);
299         if (cl)
300                 ast_shrink_phone_number(cl);
301
302         /* Get the current time */
303         t = ast_tvnow();
304         ast_localtime(&t, &now, NULL);
305
306         /* Format the time */
307         ast_strftime(timestamp, sizeof(timestamp), time_stamp_format, &now);
308
309         res = fprintf(logfile, "\n\n[metadata]\n\n");
310
311         if (res >= 0)
312                 res = fprintf(logfile, "PROTOCOL=%s\n", signalling_type);
313
314         if (res >= 0)
315                 res = fprintf(logfile, "CALLINGFROM=%s\n", (!cl) ? "<unknown>" : cl);
316
317         if (res >- 0)
318                 res = fprintf(logfile, "CALLERNAME=%s\n", (!cn) ? "<unknown>" : cn);
319
320         if (res >= 0)
321                 res = fprintf(logfile, "TIMESTAMP=%s\n\n", timestamp);
322
323         if (res >= 0)
324                 res = fprintf(logfile, "[events]\n\n");
325
326         if (res < 0) {
327                 ast_verb(3, "AlarmReceiver: can't write metadata\n");
328                 ast_debug(1,"AlarmReceiver: can't write metadata\n");
329         } else
330                 res = 0;
331
332         return res;
333 }
334
335 /*
336 * Write a single event to the log file
337 */
338 static int write_event( FILE *logfile,  event_node_t *event)
339 {
340         int res = 0;
341
342         if (fprintf(logfile, "%s\n", event->data) < 0)
343                 res = -1;
344
345         return res;
346 }
347
348 /*
349 * If we are configured to log events, do so here.
350 *
351 */
352 static int log_events(struct ast_channel *chan,  char *signalling_type, event_node_t *event)
353 {
354
355         int res = 0;
356         char workstring[sizeof(event_spool_dir)+sizeof(event_file)] = "";
357         int fd;
358         FILE *logfile;
359         event_node_t *elp = event;
360         
361         if (!ast_strlen_zero(event_spool_dir)) {
362                 
363                 /* Make a template */
364                 ast_copy_string(workstring, event_spool_dir, sizeof(workstring));
365                 strncat(workstring, event_file, sizeof(workstring) - strlen(workstring) - 1);
366                 
367                 /* Make the temporary file */
368                 fd = mkstemp(workstring);
369                 
370                 if (fd == -1) {
371                         ast_verb(3, "AlarmReceiver: can't make temporary file\n");
372                         ast_debug(1,"AlarmReceiver: can't make temporary file\n");
373                         res = -1;
374                 }
375
376                 if (!res) {
377                         logfile = fdopen(fd, "w");
378                         if (logfile) {
379                                 /* Write the file */
380                                 res = write_metadata(logfile, signalling_type, chan);
381                                 if (!res)
382                                         while ((!res) && (elp != NULL)) {
383                                                 res = write_event(logfile, elp);
384                                                 elp = elp->next;
385                                         }
386                                 if (!res) {
387                                         if (fflush(logfile) == EOF)
388                                                 res = -1;
389                                         if (!res) {
390                                                 if (fclose(logfile) == EOF)
391                                                         res = -1;
392                                         }
393                                 }
394                         } else
395                                 res = -1;
396                 }
397         }
398
399         return res;
400 }
401
402 /*
403 * This function implements the logic to receive the Ademco contact ID  format.
404 *
405 * The function will return 0 when the caller hangs up, else a -1 if there was a problem.
406 */
407 static int receive_ademco_contact_id( struct ast_channel *chan, void *data, int fdto, int sdto, int tldn, event_node_t **ehead)
408 {
409         int i, j;
410         int res = 0;
411         int checksum;
412         char event[17];
413         event_node_t *enew, *elp;
414         int got_some_digits = 0;
415         int events_received = 0;
416         int ack_retries = 0;
417         
418         static char digit_map[15] = "0123456789*#ABC";
419         static unsigned char digit_weights[15] = {10,1,2,3,4,5,6,7,8,9,11,12,13,14,15};
420
421         database_increment("calls-received");
422
423         /* Wait for first event */
424         ast_verb(4, "AlarmReceiver: Waiting for first event from panel\n");
425
426         while (res >= 0) {
427                 if (got_some_digits == 0) {
428                         /* Send ACK tone sequence */
429                         ast_verb(4, "AlarmReceiver: Sending 1400Hz 100ms burst (ACK)\n");
430                         res = send_tone_burst(chan, 1400.0, 100, tldn);
431                         if (!res)
432                                 res = ast_safe_sleep(chan, 100);
433                         if (!res) {
434                                 ast_verb(4, "AlarmReceiver: Sending 2300Hz 100ms burst (ACK)\n");
435                                 res = send_tone_burst(chan, 2300.0, 100, tldn);
436                         }
437                 }
438                 if ( res >= 0)
439                         res = receive_dtmf_digits(chan, event, sizeof(event) - 1, fdto, sdto);
440                 if (res < 0) {
441                         if (events_received == 0) {
442                                 /* Hangup with no events received should be logged in the DB */
443                                 database_increment("no-events-received");
444                         } else {
445                                 if (ack_retries) {
446                                         ast_verb(4, "AlarmReceiver: ACK retries during this call: %d\n", ack_retries);
447                                         database_increment("ack-retries");
448                                 }
449                         }
450                         ast_verb(4, "AlarmReceiver: App exiting...\n");
451                         res = -1;
452                         break;
453                 }
454
455                 if (res != 0) {
456                         /* Didn't get all of the digits */
457                         ast_verb(2, "AlarmReceiver: Incomplete string: %s, trying again...\n", event);
458
459                         if (!got_some_digits) {
460                                 got_some_digits = (!ast_strlen_zero(event)) ? 1 : 0;
461                                 ack_retries++;
462                         }
463                         continue;
464                 }
465
466                 got_some_digits = 1;
467
468                 ast_verb(2, "AlarmReceiver: Received Event %s\n", event);
469                 ast_debug(1, "AlarmReceiver: Received event: %s\n", event);
470
471                 /* Calculate checksum */
472
473                 for (j = 0, checksum = 0; j < 16; j++) {
474                         for (i = 0; i < sizeof(digit_map); i++) {
475                                 if (digit_map[i] == event[j])
476                                         break;
477                         }
478
479                         if (i == 16)
480                                 break;
481
482                         checksum += digit_weights[i];
483                 }
484                 if (i == 16) {
485                         ast_verb(2, "AlarmReceiver: Bad DTMF character %c, trying again\n", event[j]);
486                         continue; /* Bad character */
487                 }
488
489                 /* Checksum is mod(15) of the total */
490
491                 checksum = checksum % 15;
492
493                 if (checksum) {
494                         database_increment("checksum-errors");
495                         ast_verb(2, "AlarmReceiver: Nonzero checksum\n");
496                         ast_debug(1, "AlarmReceiver: Nonzero checksum\n");
497                         continue;
498                 }
499
500                 /* Check the message type for correctness */
501
502                 if (strncmp(event + 4, "18", 2)) {
503                         if (strncmp(event + 4, "98", 2)) {
504                                 database_increment("format-errors");
505                                 ast_verb(2, "AlarmReceiver: Wrong message type\n");
506                                 ast_debug(1, "AlarmReceiver: Wrong message type\n");
507                         continue;
508                         }
509                 }
510
511                 events_received++;
512
513                 /* Queue the Event */
514                 if (!(enew = ast_calloc(1, sizeof(*enew)))) {
515                         res = -1;
516                         break;
517                 }
518
519                 enew->next = NULL;
520                 ast_copy_string(enew->data, event, sizeof(enew->data));
521
522                 /*
523                 * Insert event onto end of list
524                 */
525                 if (*ehead == NULL)
526                         *ehead = enew;
527                 else {
528                         for(elp = *ehead; elp->next != NULL; elp = elp->next)
529                         ;
530                         elp->next = enew;
531                 }
532
533                 if (res > 0)
534                         res = 0;
535
536                 /* Let the user have the option of logging the single event before sending the kissoff tone */
537                 if ((res == 0) && (log_individual_events))
538                         res = log_events(chan, ADEMCO_CONTACT_ID, enew);
539                 /* Wait 200 msec before sending kissoff */
540                 if (res == 0)
541                         res = ast_safe_sleep(chan, 200);
542
543                 /* Send the kissoff tone */
544                 if (res == 0)
545                         res = send_tone_burst(chan, 1400.0, 900, tldn);
546         }
547
548         return res;
549 }
550
551 /*
552 * This is the main function called by Asterisk Core whenever the App is invoked in the extension logic.
553 * This function will always return 0.
554 */
555 static int alarmreceiver_exec(struct ast_channel *chan, void *data)
556 {
557         int res = 0;
558         event_node_t *elp, *efree;
559         char signalling_type[64] = "";
560         event_node_t *event_head = NULL;
561
562         /* Set write and read formats to ULAW */
563         ast_verb(4, "AlarmReceiver: Setting read and write formats to ULAW\n");
564
565         if (ast_set_write_format(chan,AST_FORMAT_ULAW)) {
566                 ast_log(LOG_WARNING, "AlarmReceiver: Unable to set write format to Mu-law on %s\n",chan->name);
567                 return -1;
568         }
569
570         if (ast_set_read_format(chan,AST_FORMAT_ULAW)) {
571                 ast_log(LOG_WARNING, "AlarmReceiver: Unable to set read format to Mu-law on %s\n",chan->name);
572                 return -1;
573         }
574
575         /* Set default values for this invocation of the application */
576         ast_copy_string(signalling_type, ADEMCO_CONTACT_ID, sizeof(signalling_type));
577
578         /* Answer the channel if it is not already */
579         ast_verb(4, "AlarmReceiver: Answering channel\n");
580         if (chan->_state != AST_STATE_UP) {
581                 if ((res = ast_answer(chan)))
582                         return -1;
583         }
584
585         /* Wait for the connection to settle post-answer */
586         ast_verb(4, "AlarmReceiver: Waiting for connection to stabilize\n");
587         res = ast_safe_sleep(chan, 1250);
588
589         /* Attempt to receive the events */
590         if (!res) {
591                 /* Determine the protocol to receive in advance */
592                 /* Note: Ademco contact is the only one supported at this time */
593                 /* Others may be added later */
594                 if(!strcmp(signalling_type, ADEMCO_CONTACT_ID))
595                         receive_ademco_contact_id(chan, data, fdtimeout, sdtimeout, toneloudness, &event_head);
596                 else
597                         res = -1;
598         }
599
600         /* Events queued by receiver, write them all out here if so configured */
601         if ((!res) && (log_individual_events == 0))
602                 res = log_events(chan, signalling_type, event_head);
603
604         /*
605         * Do we exec a command line at the end?
606         */
607         if ((!res) && (!ast_strlen_zero(event_app)) && (event_head)) {
608                 ast_debug(1,"Alarmreceiver: executing: %s\n", event_app);
609                 ast_safe_system(event_app);
610         }
611
612         /*
613         * Free up the data allocated in our linked list
614         */
615         for (elp = event_head; (elp != NULL);) {
616                 efree = elp;
617                 elp = elp->next;
618                 ast_free(efree);
619         }
620
621         return 0;
622 }
623
624 /*
625 * Load the configuration from the configuration file
626 */
627 static int load_config(void)
628 {
629         struct ast_config *cfg;
630         const char *p;
631         struct ast_flags config_flags = { 0 };
632
633         /* Read in the config file */
634         cfg = ast_config_load(ALMRCV_CONFIG, config_flags);
635
636         if (!cfg) {
637                 ast_verb(4, "AlarmReceiver: No config file\n");
638                 return 0;
639         } else {
640                 p = ast_variable_retrieve(cfg, "general", "eventcmd");
641                 if (p) {
642                         ast_copy_string(event_app, p, sizeof(event_app));
643                         event_app[sizeof(event_app) - 1] = '\0';
644                 }
645                 p = ast_variable_retrieve(cfg, "general", "loudness");
646                 if (p) {
647                         toneloudness = atoi(p);
648                         if(toneloudness < 100)
649                                 toneloudness = 100;
650                         if(toneloudness > 8192)
651                                 toneloudness = 8192;
652                 }
653                 p = ast_variable_retrieve(cfg, "general", "fdtimeout");
654                 if (p) {
655                         fdtimeout = atoi(p);
656                         if(fdtimeout < 1000)
657                                 fdtimeout = 1000;
658                         if(fdtimeout > 10000)
659                                 fdtimeout = 10000;
660                 }
661
662                 p = ast_variable_retrieve(cfg, "general", "sdtimeout");
663                 if (p) {
664                         sdtimeout = atoi(p);
665                         if(sdtimeout < 110)
666                                 sdtimeout = 110;
667                         if(sdtimeout > 4000)
668                                 sdtimeout = 4000;
669                 }
670
671                 p = ast_variable_retrieve(cfg, "general", "logindividualevents");
672                 if (p)
673                         log_individual_events = ast_true(p);
674
675                 p = ast_variable_retrieve(cfg, "general", "eventspooldir");
676                 if (p) {
677                         ast_copy_string(event_spool_dir, p, sizeof(event_spool_dir));
678                         event_spool_dir[sizeof(event_spool_dir) - 1] = '\0';
679                 }
680
681                 p = ast_variable_retrieve(cfg, "general", "timestampformat");
682                 if (p) {
683                         ast_copy_string(time_stamp_format, p, sizeof(time_stamp_format));
684                         time_stamp_format[sizeof(time_stamp_format) - 1] = '\0';
685                 }
686
687                 p = ast_variable_retrieve(cfg, "general", "db-family");
688                 if (p) {
689                         ast_copy_string(db_family, p, sizeof(db_family));
690                         db_family[sizeof(db_family) - 1] = '\0';
691                 }
692                 ast_config_destroy(cfg);
693         }
694         return 1;
695 }
696
697 /*
698 * These functions are required to implement an Asterisk App.
699 */
700 static int unload_module(void)
701 {
702         return ast_unregister_application(app);
703 }
704
705 static int load_module(void)
706 {
707         if (load_config()) {
708                 if (ast_register_application(app, alarmreceiver_exec, synopsis, descrip))
709                         return AST_MODULE_LOAD_FAILURE;
710                 return AST_MODULE_LOAD_SUCCESS;
711         } else
712                 return AST_MODULE_LOAD_DECLINE;
713 }
714
715 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Alarm Receiver for Asterisk");