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.
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
34 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
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"
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"
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"};
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
)
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);
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");
118 sscanf(value
, "%u", &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
);
128 ast_verb(4, "AlarmReceiver: database_increment write error\n");
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
)
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 */
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
)
163 struct ast_frame
*f
, wf
;
166 unsigned char offset
[AST_FRIENDLY_OFFSET
];
167 unsigned char buf
[640];
172 if (ast_waitfor(chan
, -1) < 0) {
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
;
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
);
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
);
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
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
)
229 struct timeval lastdigittime
;
231 lastdigittime
= ast_tvnow();
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
);
241 if ((r
= ast_waitfor(chan
, -1) < 0)) {
242 ast_debug(1, "Waitfor returned %d\n", r
);
253 /* If they hung up, leave */
254 if ((f
->frametype
== AST_FRAME_CONTROL
) && (f
->subclass
== AST_CONTROL_HANGUP
)) {
256 chan
->hangupcause
= f
->seqno
;
263 /* if not DTMF, just do it again */
264 if (f
->frametype
!= AST_FRAME_DTMF
) {
269 digit_string
[i
++] = f
->subclass
; /* save digit */
273 /* If we have all the digits we expect, leave */
277 lastdigittime
= ast_tvnow();
280 digit_string
[i
] = '\0'; /* Nul terminate the end of the digit string */
285 * Write the metadata to the log file
287 static int write_metadata( FILE *logfile
, char *signalling_type
, struct ast_channel
*chan
)
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
);
303 ast_shrink_phone_number(cl
);
305 /* Get the current time */
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");
315 res
= fprintf(logfile
, "PROTOCOL=%s\n", signalling_type
);
318 res
= fprintf(logfile
, "CALLINGFROM=%s\n", (!cl
) ? "<unknown>" : cl
);
321 res
= fprintf(logfile
, "CALLERNAME=%s\n", (!cn
) ? "<unknown>" : cn
);
324 res
= fprintf(logfile
, "TIMESTAMP=%s\n\n", timestamp
);
327 res
= fprintf(logfile
, "[events]\n\n");
330 ast_verb(3, "AlarmReceiver: can't write metadata\n");
331 ast_debug(1,"AlarmReceiver: can't write metadata\n");
339 * Write a single event to the log file
341 static int write_event( FILE *logfile
, event_node_t
*event
)
345 if (fprintf(logfile
, "%s\n", event
->data
) < 0)
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
)
359 char workstring
[sizeof(event_spool_dir
)+sizeof(event_file
)] = "";
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
);
374 ast_verb(3, "AlarmReceiver: can't make temporary file\n");
375 ast_debug(1,"AlarmReceiver: can't make temporary file\n");
380 logfile
= fdopen(fd
, "w");
383 res
= write_metadata(logfile
, signalling_type
, chan
);
385 while ((!res
) && (elp
!= NULL
)) {
386 res
= write_event(logfile
, elp
);
390 if (fflush(logfile
) == EOF
)
393 if (fclose(logfile
) == EOF
)
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
)
416 event_node_t
*enew
, *elp
;
417 int got_some_digits
= 0;
418 int events_received
= 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");
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
);
435 res
= ast_safe_sleep(chan
, 100);
437 ast_verb(4, "AlarmReceiver: Sending 2300Hz 100ms burst (ACK)\n");
438 res
= send_tone_burst(chan
, 2300.0, 100, tldn
);
442 res
= receive_dtmf_digits(chan
, event
, sizeof(event
) - 1, fdto
, sdto
);
444 if (events_received
== 0) {
445 /* Hangup with no events received should be logged in the DB */
446 database_increment("no-events-received");
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");
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;
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
])
485 checksum
+= digit_weights
[i
];
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;
497 database_increment("checksum-errors");
498 ast_verb(2, "AlarmReceiver: Nonzero checksum\n");
499 ast_debug(1, "AlarmReceiver: Nonzero checksum\n");
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");
516 /* Queue the Event */
517 if (!(enew
= ast_calloc(1, sizeof(*enew
)))) {
523 ast_copy_string(enew
->data
, event
, sizeof(enew
->data
));
526 * Insert event onto end of list
531 for(elp
= *ehead
; elp
->next
!= NULL
; elp
= elp
->next
)
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 */
544 res
= ast_safe_sleep(chan
, 200);
546 /* Send the kissoff tone */
548 res
= send_tone_burst(chan
, 1400.0, 900, tldn
);
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
)
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
);
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
);
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
)))
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 */
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
);
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
);) {
628 * Load the configuration from the configuration file
630 static int load_config(void)
632 struct ast_config
*cfg
;
634 struct ast_flags config_flags
= { 0 };
636 /* Read in the config file */
637 cfg
= ast_config_load(ALMRCV_CONFIG
, config_flags
);
640 ast_verb(4, "AlarmReceiver: No config file\n");
643 p
= ast_variable_retrieve(cfg
, "general", "eventcmd");
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");
650 toneloudness
= atoi(p
);
651 if(toneloudness
< 100)
653 if(toneloudness
> 8192)
656 p
= ast_variable_retrieve(cfg
, "general", "fdtimeout");
661 if(fdtimeout
> 10000)
665 p
= ast_variable_retrieve(cfg
, "general", "sdtimeout");
674 p
= ast_variable_retrieve(cfg
, "general", "logindividualevents");
676 log_individual_events
= ast_true(p
);
678 p
= ast_variable_retrieve(cfg
, "general", "eventspooldir");
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");
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");
692 ast_copy_string(db_family
, p
, sizeof(db_family
));
693 db_family
[sizeof(db_family
) - 1] = '\0';
695 ast_config_destroy(cfg
);
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)
711 if (ast_register_application(app
, alarmreceiver_exec
, synopsis
, descrip
))
712 return AST_MODULE_LOAD_FAILURE
;
713 return AST_MODULE_LOAD_SUCCESS
;
715 return AST_MODULE_LOAD_DECLINE
;
718 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY
, "Alarm Receiver for Asterisk");