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