Let's also include aclocal.m4
[asterisk-bristuff.git] / apps / app_alarmreceiver.c
blob8afce25d577b80c515b7a0a8a730d405b8e9253d
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2004 - 2005 Steve Rodgers
6 * Steve Rodgers <hwstar@rodgers.sdcoxmail.com>
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.
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.
19 /*! \file
20 * \brief Central Station Alarm receiver for Ademco Contact ID
21 * \author Steve Rodgers <hwstar@rodgers.sdcoxmail.com>
23 * *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
25 * Use at your own risk. Please consult the GNU GPL license document included with Asterisk. *
27 * *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
29 * \ingroup applications
30 */
32 #include "asterisk.h"
34 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36 #include <string.h>
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <math.h>
40 #include <sys/wait.h>
41 #include <unistd.h>
42 #include <sys/time.h>
44 #include "asterisk/lock.h"
45 #include "asterisk/file.h"
46 #include "asterisk/logger.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/pbx.h"
49 #include "asterisk/module.h"
50 #include "asterisk/translate.h"
51 #include "asterisk/ulaw.h"
52 #include "asterisk/options.h"
53 #include "asterisk/app.h"
54 #include "asterisk/dsp.h"
55 #include "asterisk/config.h"
56 #include "asterisk/localtime.h"
57 #include "asterisk/callerid.h"
58 #include "asterisk/astdb.h"
59 #include "asterisk/utils.h"
61 #define ALMRCV_CONFIG "alarmreceiver.conf"
62 #define ADEMCO_CONTACT_ID "ADEMCO_CONTACT_ID"
64 struct event_node{
65 char data[17];
66 struct event_node *next;
69 typedef struct event_node event_node_t;
71 static char *app = "AlarmReceiver";
73 static char *synopsis = "Provide support for receiving alarm reports from a burglar or fire alarm panel";
74 static char *descrip =
75 " AlarmReceiver(): Only 1 signalling format is supported at this time: Ademco\n"
76 "Contact ID. This application should be called whenever there is an alarm\n"
77 "panel calling in to dump its events. The application will handshake with the\n"
78 "alarm panel, and receive events, validate them, handshake them, and store them\n"
79 "until the panel hangs up. Once the panel hangs up, the application will run the\n"
80 "system command specified by the eventcmd setting in alarmreceiver.conf and pipe\n"
81 "the events to the standard input of the application. The configuration file also\n"
82 "contains settings for DTMF timing, and for the loudness of the acknowledgement\n"
83 "tones.\n";
85 /* Config Variables */
87 static int fdtimeout = 2000;
88 static int sdtimeout = 200;
89 static int toneloudness = 4096;
90 static int log_individual_events = 0;
91 static char event_spool_dir[128] = {'\0'};
92 static char event_app[128] = {'\0'};
93 static char db_family[128] = {'\0'};
94 static char time_stamp_format[128] = {"%a %b %d, %Y @ %H:%M:%S %Z"};
96 /* Misc variables */
98 static char event_file[14] = "/event-XXXXXX";
101 * Attempt to access a database variable and increment it,
102 * provided that the user defined db-family in alarmreceiver.conf
103 * The alarmreceiver app will write statistics to a few variables
104 * in this family if it is defined. If the new key doesn't exist in the
105 * family, then create it and set its value to 1.
108 static void database_increment( char *key )
110 int res = 0;
111 unsigned v;
112 char value[16];
115 if (ast_strlen_zero(db_family))
116 return; /* If not defined, don't do anything */
118 res = ast_db_get(db_family, key, value, sizeof(value) - 1);
120 if(res){
121 if(option_verbose >= 4)
122 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Creating database entry %s and setting to 1\n", key);
123 /* Guess we have to create it */
124 res = ast_db_put(db_family, key, "1");
125 return;
128 sscanf(value, "%u", &v);
129 v++;
131 if(option_verbose >= 4)
132 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: New value for %s: %u\n", key, v);
134 snprintf(value, sizeof(value), "%u", v);
136 res = ast_db_put(db_family, key, value);
138 if((res)&&(option_verbose >= 4))
139 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: database_increment write error\n");
141 return;
146 * Build a MuLaw data block for a single frequency tone
149 static void make_tone_burst(unsigned char *data, float freq, float loudness, int len, int *x)
151 int i;
152 float val;
154 for(i = 0; i < len; i++){
155 val = loudness * sin((freq * 2.0 * M_PI * (*x)++)/8000.0);
156 data[i] = AST_LIN2MU((int)val);
159 /* wrap back around from 8000 */
161 if (*x >= 8000) *x = 0;
162 return;
166 * Send a single tone burst for a specifed duration and frequency.
167 * Returns 0 if successful
170 static int send_tone_burst(struct ast_channel *chan, float freq, int duration, int tldn)
172 int res = 0;
173 int i = 0;
174 int x = 0;
175 struct ast_frame *f, wf;
177 struct {
178 unsigned char offset[AST_FRIENDLY_OFFSET];
179 unsigned char buf[640];
180 } tone_block;
182 for(;;)
185 if (ast_waitfor(chan, -1) < 0){
186 res = -1;
187 break;
190 f = ast_read(chan);
191 if (!f){
192 res = -1;
193 break;
196 if (f->frametype == AST_FRAME_VOICE) {
197 wf.frametype = AST_FRAME_VOICE;
198 wf.subclass = AST_FORMAT_ULAW;
199 wf.offset = AST_FRIENDLY_OFFSET;
200 wf.mallocd = 0;
201 wf.data = tone_block.buf;
202 wf.datalen = f->datalen;
203 wf.samples = wf.datalen;
205 make_tone_burst(tone_block.buf, freq, (float) tldn, wf.datalen, &x);
207 i += wf.datalen / 8;
208 if (i > duration) {
209 ast_frfree(f);
210 break;
212 if (ast_write(chan, &wf)){
213 if(option_verbose >= 4)
214 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Failed to write frame on %s\n", chan->name);
215 ast_log(LOG_WARNING, "AlarmReceiver Failed to write frame on %s\n",chan->name);
216 res = -1;
217 ast_frfree(f);
218 break;
222 ast_frfree(f);
224 return res;
228 * Receive a string of DTMF digits where the length of the digit string is known in advance. Do not give preferential
229 * treatment to any digit value, and allow separate time out values to be specified for the first digit and all subsequent
230 * digits.
232 * Returns 0 if all digits successfully received.
233 * Returns 1 if a digit time out occurred
234 * Returns -1 if the caller hung up or there was a channel error.
238 static int receive_dtmf_digits(struct ast_channel *chan, char *digit_string, int length, int fdto, int sdto)
240 int res = 0;
241 int i = 0;
242 int r;
243 struct ast_frame *f;
244 struct timeval lastdigittime;
246 lastdigittime = ast_tvnow();
247 for(;;){
248 /* if outa time, leave */
249 if (ast_tvdiff_ms(ast_tvnow(), lastdigittime) >
250 ((i > 0) ? sdto : fdto)){
251 if(option_verbose >= 4)
252 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: DTMF Digit Timeout on %s\n", chan->name);
254 ast_log(LOG_DEBUG,"AlarmReceiver: DTMF timeout on chan %s\n",chan->name);
256 res = 1;
257 break;
260 if ((r = ast_waitfor(chan, -1) < 0)) {
261 ast_log(LOG_DEBUG, "Waitfor returned %d\n", r);
262 continue;
265 f = ast_read(chan);
267 if (f == NULL){
268 res = -1;
269 break;
272 /* If they hung up, leave */
273 if ((f->frametype == AST_FRAME_CONTROL) &&
274 (f->subclass == AST_CONTROL_HANGUP)){
275 ast_frfree(f);
276 res = -1;
277 break;
280 /* if not DTMF, just do it again */
281 if (f->frametype != AST_FRAME_DTMF){
282 ast_frfree(f);
283 continue;
286 digit_string[i++] = f->subclass; /* save digit */
288 ast_frfree(f);
290 /* If we have all the digits we expect, leave */
291 if(i >= length)
292 break;
294 lastdigittime = ast_tvnow();
297 digit_string[i] = '\0'; /* Nul terminate the end of the digit string */
298 return res;
303 * Write the metadata to the log file
306 static int write_metadata( FILE *logfile, char *signalling_type, struct ast_channel *chan)
308 int res = 0;
309 time_t t;
310 struct tm now;
311 char *cl,*cn;
312 char workstring[80];
313 char timestamp[80];
315 /* Extract the caller ID location */
316 if (chan->cid.cid_num)
317 ast_copy_string(workstring, chan->cid.cid_num, sizeof(workstring));
318 workstring[sizeof(workstring) - 1] = '\0';
320 ast_callerid_parse(workstring, &cn, &cl);
321 if (cl)
322 ast_shrink_phone_number(cl);
325 /* Get the current time */
327 time(&t);
328 ast_localtime(&t, &now, NULL);
330 /* Format the time */
332 strftime(timestamp, sizeof(timestamp), time_stamp_format, &now);
335 res = fprintf(logfile, "\n\n[metadata]\n\n");
337 if(res >= 0)
338 res = fprintf(logfile, "PROTOCOL=%s\n", signalling_type);
340 if(res >= 0)
341 res = fprintf(logfile, "CALLINGFROM=%s\n", (!cl) ? "<unknown>" : cl);
343 if(res >- 0)
344 res = fprintf(logfile, "CALLERNAME=%s\n", (!cn) ? "<unknown>" : cn);
346 if(res >= 0)
347 res = fprintf(logfile, "TIMESTAMP=%s\n\n", timestamp);
349 if(res >= 0)
350 res = fprintf(logfile, "[events]\n\n");
352 if(res < 0){
353 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: can't write metadata\n");
355 ast_log(LOG_DEBUG,"AlarmReceiver: can't write metadata\n");
357 else
358 res = 0;
360 return res;
364 * Write a single event to the log file
367 static int write_event( FILE *logfile, event_node_t *event)
369 int res = 0;
371 if( fprintf(logfile, "%s\n", event->data) < 0)
372 res = -1;
374 return res;
379 * If we are configured to log events, do so here.
383 static int log_events(struct ast_channel *chan, char *signalling_type, event_node_t *event)
386 int res = 0;
387 char workstring[sizeof(event_spool_dir)+sizeof(event_file)] = "";
388 int fd;
389 FILE *logfile;
390 event_node_t *elp = event;
392 if (!ast_strlen_zero(event_spool_dir)) {
394 /* Make a template */
396 ast_copy_string(workstring, event_spool_dir, sizeof(workstring));
397 strncat(workstring, event_file, sizeof(workstring) - strlen(workstring) - 1);
399 /* Make the temporary file */
401 fd = mkstemp(workstring);
403 if(fd == -1){
404 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: can't make temporary file\n");
405 ast_log(LOG_DEBUG,"AlarmReceiver: can't make temporary file\n");
406 res = -1;
409 if(!res){
410 logfile = fdopen(fd, "w");
411 if(logfile){
412 /* Write the file */
413 res = write_metadata(logfile, signalling_type, chan);
414 if(!res)
415 while((!res) && (elp != NULL)){
416 res = write_event(logfile, elp);
417 elp = elp->next;
419 if(!res){
420 if(fflush(logfile) == EOF)
421 res = -1;
422 if(!res){
423 if(fclose(logfile) == EOF)
424 res = -1;
428 else
429 res = -1;
433 return res;
437 * This function implements the logic to receive the Ademco contact ID format.
439 * The function will return 0 when the caller hangs up, else a -1 if there was a problem.
442 static int receive_ademco_contact_id( struct ast_channel *chan, void *data, int fdto, int sdto, int tldn, event_node_t **ehead)
444 int i,j;
445 int res = 0;
446 int checksum;
447 char event[17];
448 event_node_t *enew, *elp;
449 int got_some_digits = 0;
450 int events_received = 0;
451 int ack_retries = 0;
453 static char digit_map[15] = "0123456789*#ABC";
454 static unsigned char digit_weights[15] = {10,1,2,3,4,5,6,7,8,9,11,12,13,14,15};
456 database_increment("calls-received");
458 /* Wait for first event */
460 if(option_verbose >= 4)
461 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Waiting for first event from panel\n");
463 while(res >= 0){
465 if(got_some_digits == 0){
467 /* Send ACK tone sequence */
470 if(option_verbose >= 4)
471 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Sending 1400Hz 100ms burst (ACK)\n");
474 res = send_tone_burst(chan, 1400.0, 100, tldn);
476 if(!res)
477 res = ast_safe_sleep(chan, 100);
479 if(!res){
480 if(option_verbose >= 4)
481 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Sending 2300Hz 100ms burst (ACK)\n");
483 res = send_tone_burst(chan, 2300.0, 100, tldn);
488 if( res >= 0)
489 res = receive_dtmf_digits(chan, event, sizeof(event) - 1, fdto, sdto);
491 if (res < 0){
493 if(events_received == 0)
494 /* Hangup with no events received should be logged in the DB */
495 database_increment("no-events-received");
496 else{
497 if(ack_retries){
498 if(option_verbose >= 4)
499 ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: ACK retries during this call: %d\n", ack_retries);
501 database_increment("ack-retries");
504 if(option_verbose >= 4)
505 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: App exiting...\n");
506 res = -1;
507 break;
510 if(res != 0){
511 /* Didn't get all of the digits */
512 if(option_verbose >= 2)
513 ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Incomplete string: %s, trying again...\n", event);
515 if(!got_some_digits){
516 got_some_digits = (!ast_strlen_zero(event)) ? 1 : 0;
517 ack_retries++;
519 continue;
522 got_some_digits = 1;
524 if(option_verbose >= 2)
525 ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Received Event %s\n", event);
526 ast_log(LOG_DEBUG, "AlarmReceiver: Received event: %s\n", event);
528 /* Calculate checksum */
530 for(j = 0, checksum = 0; j < 16; j++){
531 for(i = 0 ; i < sizeof(digit_map) ; i++){
532 if(digit_map[i] == event[j])
533 break;
536 if(i == 16)
537 break;
539 checksum += digit_weights[i];
542 if(i == 16){
543 if(option_verbose >= 2)
544 ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Bad DTMF character %c, trying again\n", event[j]);
545 continue; /* Bad character */
548 /* Checksum is mod(15) of the total */
550 checksum = checksum % 15;
552 if (checksum) {
553 database_increment("checksum-errors");
554 if (option_verbose >= 2)
555 ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Nonzero checksum\n");
556 ast_log(LOG_DEBUG, "AlarmReceiver: Nonzero checksum\n");
557 continue;
560 /* Check the message type for correctness */
562 if(strncmp(event + 4, "18", 2)){
563 if(strncmp(event + 4, "98", 2)){
564 database_increment("format-errors");
565 if(option_verbose >= 2)
566 ast_verbose(VERBOSE_PREFIX_2 "AlarmReceiver: Wrong message type\n");
567 ast_log(LOG_DEBUG, "AlarmReceiver: Wrong message type\n");
568 continue;
572 events_received++;
574 /* Queue the Event */
575 if (!(enew = ast_calloc(1, sizeof(*enew)))) {
576 res = -1;
577 break;
580 enew->next = NULL;
581 ast_copy_string(enew->data, event, sizeof(enew->data));
584 * Insert event onto end of list
587 if(*ehead == NULL){
588 *ehead = enew;
590 else{
591 for(elp = *ehead; elp->next != NULL; elp = elp->next)
594 elp->next = enew;
597 if(res > 0)
598 res = 0;
600 /* Let the user have the option of logging the single event before sending the kissoff tone */
602 if((res == 0) && (log_individual_events))
603 res = log_events(chan, ADEMCO_CONTACT_ID, enew);
605 /* Wait 200 msec before sending kissoff */
607 if(res == 0)
608 res = ast_safe_sleep(chan, 200);
610 /* Send the kissoff tone */
612 if(res == 0)
613 res = send_tone_burst(chan, 1400.0, 900, tldn);
617 return res;
622 * This is the main function called by Asterisk Core whenever the App is invoked in the extension logic.
623 * This function will always return 0.
626 static int alarmreceiver_exec(struct ast_channel *chan, void *data)
628 int res = 0;
629 struct ast_module_user *u;
630 event_node_t *elp, *efree;
631 char signalling_type[64] = "";
633 event_node_t *event_head = NULL;
635 u = ast_module_user_add(chan);
637 /* Set write and read formats to ULAW */
639 if(option_verbose >= 4)
640 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Setting read and write formats to ULAW\n");
642 if (ast_set_write_format(chan,AST_FORMAT_ULAW)){
643 ast_log(LOG_WARNING, "AlarmReceiver: Unable to set write format to Mu-law on %s\n",chan->name);
644 ast_module_user_remove(u);
645 return -1;
648 if (ast_set_read_format(chan,AST_FORMAT_ULAW)){
649 ast_log(LOG_WARNING, "AlarmReceiver: Unable to set read format to Mu-law on %s\n",chan->name);
650 ast_module_user_remove(u);
651 return -1;
654 /* Set default values for this invokation of the application */
656 ast_copy_string(signalling_type, ADEMCO_CONTACT_ID, sizeof(signalling_type));
659 /* Answer the channel if it is not already */
661 if(option_verbose >= 4)
662 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Answering channel\n");
664 if (chan->_state != AST_STATE_UP) {
666 res = ast_answer(chan);
668 if (res) {
669 ast_module_user_remove(u);
670 return -1;
674 /* Wait for the connection to settle post-answer */
676 if(option_verbose >= 4)
677 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: Waiting for connection to stabilize\n");
679 res = ast_safe_sleep(chan, 1250);
681 /* Attempt to receive the events */
683 if(!res){
685 /* Determine the protocol to receive in advance */
686 /* Note: Ademco contact is the only one supported at this time */
687 /* Others may be added later */
689 if(!strcmp(signalling_type, ADEMCO_CONTACT_ID))
690 receive_ademco_contact_id(chan, data, fdtimeout, sdtimeout, toneloudness, &event_head);
691 else
692 res = -1;
697 /* Events queued by receiver, write them all out here if so configured */
699 if((!res) && (log_individual_events == 0)){
700 res = log_events(chan, signalling_type, event_head);
705 * Do we exec a command line at the end?
708 if((!res) && (!ast_strlen_zero(event_app)) && (event_head)){
709 ast_log(LOG_DEBUG,"Alarmreceiver: executing: %s\n", event_app);
710 ast_safe_system(event_app);
714 * Free up the data allocated in our linked list
717 for(elp = event_head; (elp != NULL);){
718 efree = elp;
719 elp = elp->next;
720 free(efree);
724 ast_module_user_remove(u);
726 return 0;
730 * Load the configuration from the configuration file
733 static int load_config(void)
735 struct ast_config *cfg;
736 const char *p;
738 /* Read in the config file */
740 cfg = ast_config_load(ALMRCV_CONFIG);
742 if(!cfg){
744 if(option_verbose >= 4)
745 ast_verbose(VERBOSE_PREFIX_4 "AlarmReceiver: No config file\n");
746 return 0;
748 else{
751 p = ast_variable_retrieve(cfg, "general", "eventcmd");
753 if(p){
754 ast_copy_string(event_app, p, sizeof(event_app));
755 event_app[sizeof(event_app) - 1] = '\0';
758 p = ast_variable_retrieve(cfg, "general", "loudness");
759 if(p){
760 toneloudness = atoi(p);
761 if(toneloudness < 100)
762 toneloudness = 100;
763 if(toneloudness > 8192)
764 toneloudness = 8192;
766 p = ast_variable_retrieve(cfg, "general", "fdtimeout");
767 if(p){
768 fdtimeout = atoi(p);
769 if(fdtimeout < 1000)
770 fdtimeout = 1000;
771 if(fdtimeout > 10000)
772 fdtimeout = 10000;
775 p = ast_variable_retrieve(cfg, "general", "sdtimeout");
776 if(p){
777 sdtimeout = atoi(p);
778 if(sdtimeout < 110)
779 sdtimeout = 110;
780 if(sdtimeout > 4000)
781 sdtimeout = 4000;
785 p = ast_variable_retrieve(cfg, "general", "logindividualevents");
786 if(p){
787 log_individual_events = ast_true(p);
791 p = ast_variable_retrieve(cfg, "general", "eventspooldir");
793 if(p){
794 ast_copy_string(event_spool_dir, p, sizeof(event_spool_dir));
795 event_spool_dir[sizeof(event_spool_dir) - 1] = '\0';
798 p = ast_variable_retrieve(cfg, "general", "timestampformat");
800 if(p){
801 ast_copy_string(time_stamp_format, p, sizeof(time_stamp_format));
802 time_stamp_format[sizeof(time_stamp_format) - 1] = '\0';
805 p = ast_variable_retrieve(cfg, "general", "db-family");
807 if(p){
808 ast_copy_string(db_family, p, sizeof(db_family));
809 db_family[sizeof(db_family) - 1] = '\0';
811 ast_config_destroy(cfg);
813 return 1;
818 * These functions are required to implement an Asterisk App.
822 static int unload_module(void)
824 int res;
826 res = ast_unregister_application(app);
828 ast_module_user_hangup_all();
830 return res;
833 static int load_module(void)
835 if(load_config())
836 return ast_register_application(app, alarmreceiver_exec, synopsis, descrip);
837 else
838 return AST_MODULE_LOAD_DECLINE;
841 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Alarm Receiver for Asterisk");