Optionally display the value of several variables within the Status command.
[asterisk-bristuff.git] / apps / app_alarmreceiver.c
blob5230681f2145031ca65b46d3fdc5e811d420da91
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
32 #include "asterisk.h"
34 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36 #include <math.h>
37 #include <sys/wait.h>
38 #include <sys/time.h>
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"
55 #define ALMRCV_CONFIG "alarmreceiver.conf"
56 #define ADEMCO_CONTACT_ID "ADEMCO_CONTACT_ID"
58 struct event_node{
59 char data[17];
60 struct event_node *next;
63 typedef struct event_node event_node_t;
65 static char *app = "AlarmReceiver";
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";
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"};
89 /* Misc variables */
90 static char event_file[14] = "/event-XXXXXX";
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.
99 static void database_increment( char *key )
101 int res = 0;
102 unsigned v;
103 char value[16];
106 if (ast_strlen_zero(db_family))
107 return; /* If not defined, don't do anything */
109 res = ast_db_get(db_family, key, value, sizeof(value) - 1);
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;
118 sscanf(value, "%u", &v);
119 v++;
121 ast_verb(4, "AlarmReceiver: New value for %s: %u\n", key, v);
123 snprintf(value, sizeof(value), "%u", v);
125 res = ast_db_put(db_family, key, value);
127 if (res)
128 ast_verb(4, "AlarmReceiver: database_increment write error\n");
130 return;
135 * Build a MuLaw data block for a single frequency tone
137 static void make_tone_burst(unsigned char *data, float freq, float loudness, int len, int *x)
139 int i;
140 float val;
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);
147 /* wrap back around from 8000 */
149 if (*x >= 8000)
150 *x = 0;
151 return;
155 * Send a single tone burst for a specifed duration and frequency.
156 * Returns 0 if successful
158 static int send_tone_burst(struct ast_channel *chan, float freq, int duration, int tldn)
160 int res = 0;
161 int i = 0;
162 int x = 0;
163 struct ast_frame *f, wf;
165 struct {
166 unsigned char offset[AST_FRIENDLY_OFFSET];
167 unsigned char buf[640];
168 } tone_block;
170 for (;;) {
172 if (ast_waitfor(chan, -1) < 0) {
173 res = -1;
174 break;
177 f = ast_read(chan);
178 if (!f) {
179 res = -1;
180 break;
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;
192 make_tone_burst(tone_block.buf, freq, (float) tldn, wf.datalen, &x);
194 i += wf.datalen / 8;
195 if (i > duration) {
196 ast_frfree(f);
197 break;
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;
208 ast_frfree(f);
210 return res;
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.
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.
223 static int receive_dtmf_digits(struct ast_channel *chan, char *digit_string, int length, int fdto, int sdto)
225 int res = 0;
226 int i = 0;
227 int r;
228 struct ast_frame *f;
229 struct timeval lastdigittime;
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;
241 if ((r = ast_waitfor(chan, -1) < 0)) {
242 ast_debug(1, "Waitfor returned %d\n", r);
243 continue;
246 f = ast_read(chan);
248 if (f == NULL) {
249 res = -1;
250 break;
253 /* If they hung up, leave */
254 if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
255 if (f->seqno) {
256 chan->hangupcause = f->seqno;
258 ast_frfree(f);
259 res = -1;
260 break;
263 /* if not DTMF, just do it again */
264 if (f->frametype != AST_FRAME_DTMF) {
265 ast_frfree(f);
266 continue;
269 digit_string[i++] = f->subclass; /* save digit */
271 ast_frfree(f);
273 /* If we have all the digits we expect, leave */
274 if(i >= length)
275 break;
277 lastdigittime = ast_tvnow();
280 digit_string[i] = '\0'; /* Nul terminate the end of the digit string */
281 return res;
285 * Write the metadata to the log file
287 static int write_metadata( FILE *logfile, char *signalling_type, struct ast_channel *chan)
289 int res = 0;
290 struct timeval t;
291 struct ast_tm now;
292 char *cl,*cn;
293 char workstring[80];
294 char timestamp[80];
296 /* Extract the caller ID location */
297 if (chan->cid.cid_num)
298 ast_copy_string(workstring, chan->cid.cid_num, sizeof(workstring));
299 workstring[sizeof(workstring) - 1] = '\0';
301 ast_callerid_parse(workstring, &cn, &cl);
302 if (cl)
303 ast_shrink_phone_number(cl);
305 /* Get the current time */
306 t = ast_tvnow();
307 ast_localtime(&t, &now, NULL);
309 /* Format the time */
310 ast_strftime(timestamp, sizeof(timestamp), time_stamp_format, &now);
312 res = fprintf(logfile, "\n\n[metadata]\n\n");
314 if (res >= 0)
315 res = fprintf(logfile, "PROTOCOL=%s\n", signalling_type);
317 if (res >= 0)
318 res = fprintf(logfile, "CALLINGFROM=%s\n", (!cl) ? "<unknown>" : cl);
320 if (res >- 0)
321 res = fprintf(logfile, "CALLERNAME=%s\n", (!cn) ? "<unknown>" : cn);
323 if (res >= 0)
324 res = fprintf(logfile, "TIMESTAMP=%s\n\n", timestamp);
326 if (res >= 0)
327 res = fprintf(logfile, "[events]\n\n");
329 if (res < 0) {
330 ast_verb(3, "AlarmReceiver: can't write metadata\n");
331 ast_debug(1,"AlarmReceiver: can't write metadata\n");
332 } else
333 res = 0;
335 return res;
339 * Write a single event to the log file
341 static int write_event( FILE *logfile, event_node_t *event)
343 int res = 0;
345 if (fprintf(logfile, "%s\n", event->data) < 0)
346 res = -1;
348 return res;
352 * If we are configured to log events, do so here.
355 static int log_events(struct ast_channel *chan, char *signalling_type, event_node_t *event)
358 int res = 0;
359 char workstring[sizeof(event_spool_dir)+sizeof(event_file)] = "";
360 int fd;
361 FILE *logfile;
362 event_node_t *elp = event;
364 if (!ast_strlen_zero(event_spool_dir)) {
366 /* Make a template */
367 ast_copy_string(workstring, event_spool_dir, sizeof(workstring));
368 strncat(workstring, event_file, sizeof(workstring) - strlen(workstring) - 1);
370 /* Make the temporary file */
371 fd = mkstemp(workstring);
373 if (fd == -1) {
374 ast_verb(3, "AlarmReceiver: can't make temporary file\n");
375 ast_debug(1,"AlarmReceiver: can't make temporary file\n");
376 res = -1;
379 if (!res) {
380 logfile = fdopen(fd, "w");
381 if (logfile) {
382 /* Write the file */
383 res = write_metadata(logfile, signalling_type, chan);
384 if (!res)
385 while ((!res) && (elp != NULL)) {
386 res = write_event(logfile, elp);
387 elp = elp->next;
389 if (!res) {
390 if (fflush(logfile) == EOF)
391 res = -1;
392 if (!res) {
393 if (fclose(logfile) == EOF)
394 res = -1;
397 } else
398 res = -1;
402 return res;
406 * This function implements the logic to receive the Ademco contact ID format.
408 * The function will return 0 when the caller hangs up, else a -1 if there was a problem.
410 static int receive_ademco_contact_id( struct ast_channel *chan, void *data, int fdto, int sdto, int tldn, event_node_t **ehead)
412 int i, j;
413 int res = 0;
414 int checksum;
415 char event[17];
416 event_node_t *enew, *elp;
417 int got_some_digits = 0;
418 int events_received = 0;
419 int ack_retries = 0;
421 static char digit_map[15] = "0123456789*#ABC";
422 static unsigned char digit_weights[15] = {10,1,2,3,4,5,6,7,8,9,11,12,13,14,15};
424 database_increment("calls-received");
426 /* Wait for first event */
427 ast_verb(4, "AlarmReceiver: Waiting for first event from panel\n");
429 while (res >= 0) {
430 if (got_some_digits == 0) {
431 /* Send ACK tone sequence */
432 ast_verb(4, "AlarmReceiver: Sending 1400Hz 100ms burst (ACK)\n");
433 res = send_tone_burst(chan, 1400.0, 100, tldn);
434 if (!res)
435 res = ast_safe_sleep(chan, 100);
436 if (!res) {
437 ast_verb(4, "AlarmReceiver: Sending 2300Hz 100ms burst (ACK)\n");
438 res = send_tone_burst(chan, 2300.0, 100, tldn);
441 if ( res >= 0)
442 res = receive_dtmf_digits(chan, event, sizeof(event) - 1, fdto, sdto);
443 if (res < 0) {
444 if (events_received == 0) {
445 /* Hangup with no events received should be logged in the DB */
446 database_increment("no-events-received");
447 } else {
448 if (ack_retries) {
449 ast_verb(4, "AlarmReceiver: ACK retries during this call: %d\n", ack_retries);
450 database_increment("ack-retries");
453 ast_verb(4, "AlarmReceiver: App exiting...\n");
454 res = -1;
455 break;
458 if (res != 0) {
459 /* Didn't get all of the digits */
460 ast_verb(2, "AlarmReceiver: Incomplete string: %s, trying again...\n", event);
462 if (!got_some_digits) {
463 got_some_digits = (!ast_strlen_zero(event)) ? 1 : 0;
464 ack_retries++;
466 continue;
469 got_some_digits = 1;
471 ast_verb(2, "AlarmReceiver: Received Event %s\n", event);
472 ast_debug(1, "AlarmReceiver: Received event: %s\n", event);
474 /* Calculate checksum */
476 for (j = 0, checksum = 0; j < 16; j++) {
477 for (i = 0; i < sizeof(digit_map); i++) {
478 if (digit_map[i] == event[j])
479 break;
482 if (i == 16)
483 break;
485 checksum += digit_weights[i];
487 if (i == 16) {
488 ast_verb(2, "AlarmReceiver: Bad DTMF character %c, trying again\n", event[j]);
489 continue; /* Bad character */
492 /* Checksum is mod(15) of the total */
494 checksum = checksum % 15;
496 if (checksum) {
497 database_increment("checksum-errors");
498 ast_verb(2, "AlarmReceiver: Nonzero checksum\n");
499 ast_debug(1, "AlarmReceiver: Nonzero checksum\n");
500 continue;
503 /* Check the message type for correctness */
505 if (strncmp(event + 4, "18", 2)) {
506 if (strncmp(event + 4, "98", 2)) {
507 database_increment("format-errors");
508 ast_verb(2, "AlarmReceiver: Wrong message type\n");
509 ast_debug(1, "AlarmReceiver: Wrong message type\n");
510 continue;
514 events_received++;
516 /* Queue the Event */
517 if (!(enew = ast_calloc(1, sizeof(*enew)))) {
518 res = -1;
519 break;
522 enew->next = NULL;
523 ast_copy_string(enew->data, event, sizeof(enew->data));
526 * Insert event onto end of list
528 if (*ehead == NULL)
529 *ehead = enew;
530 else {
531 for(elp = *ehead; elp->next != NULL; elp = elp->next)
533 elp->next = enew;
536 if (res > 0)
537 res = 0;
539 /* Let the user have the option of logging the single event before sending the kissoff tone */
540 if ((res == 0) && (log_individual_events))
541 res = log_events(chan, ADEMCO_CONTACT_ID, enew);
542 /* Wait 200 msec before sending kissoff */
543 if (res == 0)
544 res = ast_safe_sleep(chan, 200);
546 /* Send the kissoff tone */
547 if (res == 0)
548 res = send_tone_burst(chan, 1400.0, 900, tldn);
551 return res;
555 * This is the main function called by Asterisk Core whenever the App is invoked in the extension logic.
556 * This function will always return 0.
558 static int alarmreceiver_exec(struct ast_channel *chan, void *data)
560 int res = 0;
561 event_node_t *elp, *efree;
562 char signalling_type[64] = "";
563 event_node_t *event_head = NULL;
565 /* Set write and read formats to ULAW */
566 ast_verb(4, "AlarmReceiver: Setting read and write formats to ULAW\n");
568 if (ast_set_write_format(chan,AST_FORMAT_ULAW)) {
569 ast_log(LOG_WARNING, "AlarmReceiver: Unable to set write format to Mu-law on %s\n",chan->name);
570 return -1;
573 if (ast_set_read_format(chan,AST_FORMAT_ULAW)) {
574 ast_log(LOG_WARNING, "AlarmReceiver: Unable to set read format to Mu-law on %s\n",chan->name);
575 return -1;
578 /* Set default values for this invocation of the application */
579 ast_copy_string(signalling_type, ADEMCO_CONTACT_ID, sizeof(signalling_type));
581 /* Answer the channel if it is not already */
582 ast_verb(4, "AlarmReceiver: Answering channel\n");
583 if (chan->_state != AST_STATE_UP) {
584 if ((res = ast_answer(chan)))
585 return -1;
588 /* Wait for the connection to settle post-answer */
589 ast_verb(4, "AlarmReceiver: Waiting for connection to stabilize\n");
590 res = ast_safe_sleep(chan, 1250);
592 /* Attempt to receive the events */
593 if (!res) {
594 /* Determine the protocol to receive in advance */
595 /* Note: Ademco contact is the only one supported at this time */
596 /* Others may be added later */
597 if(!strcmp(signalling_type, ADEMCO_CONTACT_ID))
598 receive_ademco_contact_id(chan, data, fdtimeout, sdtimeout, toneloudness, &event_head);
599 else
600 res = -1;
603 /* Events queued by receiver, write them all out here if so configured */
604 if ((!res) && (log_individual_events == 0))
605 res = log_events(chan, signalling_type, event_head);
608 * Do we exec a command line at the end?
610 if ((!res) && (!ast_strlen_zero(event_app)) && (event_head)) {
611 ast_debug(1,"Alarmreceiver: executing: %s\n", event_app);
612 ast_safe_system(event_app);
616 * Free up the data allocated in our linked list
618 for (elp = event_head; (elp != NULL);) {
619 efree = elp;
620 elp = elp->next;
621 ast_free(efree);
624 return 0;
628 * Load the configuration from the configuration file
630 static int load_config(void)
632 struct ast_config *cfg;
633 const char *p;
634 struct ast_flags config_flags = { 0 };
636 /* Read in the config file */
637 cfg = ast_config_load(ALMRCV_CONFIG, config_flags);
639 if (!cfg) {
640 ast_verb(4, "AlarmReceiver: No config file\n");
641 return 0;
642 } else {
643 p = ast_variable_retrieve(cfg, "general", "eventcmd");
644 if (p) {
645 ast_copy_string(event_app, p, sizeof(event_app));
646 event_app[sizeof(event_app) - 1] = '\0';
648 p = ast_variable_retrieve(cfg, "general", "loudness");
649 if (p) {
650 toneloudness = atoi(p);
651 if(toneloudness < 100)
652 toneloudness = 100;
653 if(toneloudness > 8192)
654 toneloudness = 8192;
656 p = ast_variable_retrieve(cfg, "general", "fdtimeout");
657 if (p) {
658 fdtimeout = atoi(p);
659 if(fdtimeout < 1000)
660 fdtimeout = 1000;
661 if(fdtimeout > 10000)
662 fdtimeout = 10000;
665 p = ast_variable_retrieve(cfg, "general", "sdtimeout");
666 if (p) {
667 sdtimeout = atoi(p);
668 if(sdtimeout < 110)
669 sdtimeout = 110;
670 if(sdtimeout > 4000)
671 sdtimeout = 4000;
674 p = ast_variable_retrieve(cfg, "general", "logindividualevents");
675 if (p)
676 log_individual_events = ast_true(p);
678 p = ast_variable_retrieve(cfg, "general", "eventspooldir");
679 if (p) {
680 ast_copy_string(event_spool_dir, p, sizeof(event_spool_dir));
681 event_spool_dir[sizeof(event_spool_dir) - 1] = '\0';
684 p = ast_variable_retrieve(cfg, "general", "timestampformat");
685 if (p) {
686 ast_copy_string(time_stamp_format, p, sizeof(time_stamp_format));
687 time_stamp_format[sizeof(time_stamp_format) - 1] = '\0';
690 p = ast_variable_retrieve(cfg, "general", "db-family");
691 if (p) {
692 ast_copy_string(db_family, p, sizeof(db_family));
693 db_family[sizeof(db_family) - 1] = '\0';
695 ast_config_destroy(cfg);
697 return 1;
701 * These functions are required to implement an Asterisk App.
703 static int unload_module(void)
705 return ast_unregister_application(app);
708 static int load_module(void)
710 if (load_config()) {
711 if (ast_register_application(app, alarmreceiver_exec, synopsis, descrip))
712 return AST_MODULE_LOAD_FAILURE;
713 return AST_MODULE_LOAD_SUCCESS;
714 } else
715 return AST_MODULE_LOAD_DECLINE;
718 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Alarm Receiver for Asterisk");