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