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