1 /*
2  * SpanDSP - a series of DSP components for telephony
3  *
4  * ademco_contactid.c - Ademco ContactID alarm protocol
5  *
6  * Written by Steve Underwood <steveu@coppice.org>
7  *
8  * Copyright (C) 2012 Steve Underwood
9  *
10  * All rights reserved.
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU Lesser General Public License version 2.1,
14  * as published by the Free Software Foundation.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this program; if not, write to the Free Software
23  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24  */
25 
26 /*! \page ademco_contactid_tests_page Ademco ContactID tests
27 \section ademco_contactid_tests_page_sec_1 What does it do?
28 
29 \section ademco_contactid_tests_page_sec_2 How does it work?
30 */
31 
32 #if defined(HAVE_CONFIG_H)
33 #include "config.h"
34 #endif
35 
36 #include <unistd.h>
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <assert.h>
41 #include <sndfile.h>
42 
43 #include "spandsp.h"
44 #include "spandsp-sim.h"
45 
46 #define SAMPLES_PER_CHUNK           160
47 
48 #define OUTPUT_FILE_NAME            "ademco_contactid.wav"
49 
50 #define MITEL_DIR                   "../test-data/mitel/"
51 #define BELLCORE_DIR                "../test-data/bellcore/"
52 
53 const char *bellcore_files[] =
54 {
55     MITEL_DIR    "mitel-cm7291-talkoff.wav",
56     BELLCORE_DIR "tr-tsy-00763-1.wav",
57     BELLCORE_DIR "tr-tsy-00763-2.wav",
58     BELLCORE_DIR "tr-tsy-00763-3.wav",
59     BELLCORE_DIR "tr-tsy-00763-4.wav",
60     BELLCORE_DIR "tr-tsy-00763-5.wav",
61     BELLCORE_DIR "tr-tsy-00763-6.wav",
62     ""
63 };
64 
65 static const ademco_contactid_report_t reports[] =
66 {
67     {0x1234, 0x18, 0x1, 0x131, 0x1, 0x15},
68     {0x1234, 0x18, 0x3, 0x131, 0x1, 0x15},
69     {0x1234, 0x18, 0x1, 0x401, 0x2, 0x3},
70     {0x1234, 0x18, 0x3, 0x401, 0x3, 0x5},
71     {0x1234, 0x56, 0x7, 0x890, 0xBC, 0xDEF},
72     {0x1234, 0x56, 0x7, 0x89A, 0xBC, 0xDEF}     /* This one is bad, as it contains a hex 'A' */
73 };
74 static int reports_entry = 0;
75 
76 static int16_t amp[1000000];
77 
78 bool tx_callback_reported = false;
79 bool rx_callback_reported = false;
80 
81 bool sending_complete = false;
82 
83 SNDFILE *outhandle;
84 
talkoff_tx_callback(void * user_data,int tone,int level,int duration)85 static void talkoff_tx_callback(void *user_data, int tone, int level, int duration)
86 {
87     printf("Ademco sender report %d\n", tone);
88     tx_callback_reported = true;
89 }
90 
mitel_cm7291_side_2_and_bellcore_tests(void)91 static int mitel_cm7291_side_2_and_bellcore_tests(void)
92 {
93     int j;
94     SNDFILE *inhandle;
95     int frames;
96     ademco_contactid_sender_state_t *sender;
97     logging_state_t *logging;
98 
99     if ((sender = ademco_contactid_sender_init(NULL, talkoff_tx_callback, NULL)) == NULL)
100         return -1;
101     logging = ademco_contactid_sender_get_logging_state(sender);
102     span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
103     span_log_set_tag(logging, "Ademco-tx");
104 
105     tx_callback_reported = false;
106 
107     /* The remainder of the Mitel tape is the talk-off test */
108     /* Here we use the Bellcore test tapes (much tougher), in six
109        files - 1 from each side of the original 3 cassette tapes */
110     /* Bellcore say you should get no more than 470 false detections with
111        a good receiver. Dialogic claim 20. Of course, we can do better than
112        that, eh? */
113     printf("Talk-off test\n");
114     for (j = 0;  bellcore_files[j][0];  j++)
115     {
116         if ((inhandle = sf_open_telephony_read(bellcore_files[j], 1)) == NULL)
117         {
118             printf("    Cannot open speech file '%s'\n", bellcore_files[j]);
119             return -1;
120         }
121         while ((frames = sf_readf_short(inhandle, amp, SAMPLE_RATE)))
122         {
123             ademco_contactid_sender_rx(sender, amp, frames);
124         }
125         if (sf_close_telephony(inhandle))
126         {
127             printf("    Cannot close speech file '%s'\n", bellcore_files[j]);
128             return -1;
129         }
130         printf("    File %d gave %d false hits.\n", j + 1, 0);
131     }
132     if (tx_callback_reported)
133     {
134         printf("    Failed\n");
135         return -1;
136     }
137     printf("    Passed\n");
138     ademco_contactid_sender_free(sender);
139     return 0;
140 }
141 /*- End of function --------------------------------------------------------*/
142 
rx_callback(void * user_data,const ademco_contactid_report_t * report)143 static void rx_callback(void *user_data, const ademco_contactid_report_t *report)
144 {
145     printf("Ademco Contact ID message:\n");
146     printf("    Account %X\n", report->acct);
147     printf("    Message type %X\n", report->mt);
148     printf("    Qualifier %X\n", report->q);
149     printf("    Event %X\n", report->xyz);
150     printf("    Group/partition %X\n", report->gg);
151     printf("    User/Zone information %X\n", report->ccc);
152     if (memcmp(&reports[reports_entry], report, sizeof(*report)))
153     {
154         printf("Report mismatch\n");
155         exit(2);
156     }
157     rx_callback_reported = true;
158 }
159 /*- End of function --------------------------------------------------------*/
160 
tx_callback(void * user_data,int tone,int level,int duration)161 static void tx_callback(void *user_data, int tone, int level, int duration)
162 {
163     ademco_contactid_sender_state_t *sender;
164 
165     sender = (ademco_contactid_sender_state_t *) user_data;
166     printf("Ademco sender report %d\n", tone);
167     switch (tone)
168     {
169     case -1:
170         /* We are connected and ready to send */
171         ademco_contactid_sender_put(sender, &reports[reports_entry]);
172         break;
173     case 1:
174         /* We have succeeded in sending, and are ready to send another message. */
175         if (++reports_entry < 5)
176             ademco_contactid_sender_put(sender, &reports[reports_entry]);
177         else
178             sending_complete = true;
179         break;
180     case 0:
181         /* Sending failed after retries */
182         sending_complete = true;
183         break;
184     }
185 }
186 /*- End of function --------------------------------------------------------*/
187 
end_to_end_tests(void)188 static int end_to_end_tests(void)
189 {
190     ademco_contactid_receiver_state_t *receiver;
191     ademco_contactid_sender_state_t *sender;
192     logging_state_t *logging;
193     codec_munge_state_t *munge;
194     awgn_state_t noise_source;
195     int16_t amp[SAMPLES_PER_CHUNK];
196     int16_t sndfile_buf[2*SAMPLES_PER_CHUNK];
197     int samples;
198     int i;
199     int j;
200 
201     printf("End to end tests\n");
202 
203     if ((outhandle = sf_open_telephony_write(OUTPUT_FILE_NAME, 2)) == NULL)
204     {
205         fprintf(stderr, "    Cannot open audio file '%s'\n", OUTPUT_FILE_NAME);
206         exit(2);
207     }
208 
209     if ((receiver = ademco_contactid_receiver_init(NULL, rx_callback, NULL)) == NULL)
210         return -1;
211     ademco_contactid_receiver_set_realtime_callback(receiver, rx_callback, receiver);
212 
213     logging = ademco_contactid_receiver_get_logging_state(receiver);
214     span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
215     span_log_set_tag(logging, "Ademco-rx");
216 
217     if ((sender = ademco_contactid_sender_init(NULL, tx_callback, NULL)) == NULL)
218         return -1;
219     ademco_contactid_sender_set_realtime_callback(sender, tx_callback, sender);
220     logging = ademco_contactid_sender_get_logging_state(sender);
221     span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
222     span_log_set_tag(logging, "Ademco-tx");
223 
224     awgn_init_dbm0(&noise_source, 1234567, -50);
225     munge = codec_munge_init(MUNGE_CODEC_ALAW, 0);
226 
227     sending_complete = false;
228     rx_callback_reported = false;
229 
230     for (i = 0;  i < 1000;  i++)
231     {
232         samples = ademco_contactid_sender_tx(sender, amp, SAMPLES_PER_CHUNK);
233         for (j = samples;  j < SAMPLES_PER_CHUNK;  j++)
234             amp[j] = 0;
235         for (j = 0;  j < SAMPLES_PER_CHUNK;  j++)
236             sndfile_buf[2*j] = amp[j];
237         /* There is no point in impairing this signal. It is just DTMF tones, which
238            will work as wel as the DTMF detector beign used. */
239         ademco_contactid_receiver_rx(receiver, amp, SAMPLES_PER_CHUNK);
240 
241         samples = ademco_contactid_receiver_tx(receiver, amp, SAMPLES_PER_CHUNK);
242         for (j = samples;  j < SAMPLES_PER_CHUNK;  j++)
243             amp[j] = 0;
244 
245         /* We add AWGN and codec impairments to the signal, to stress the tone detector. */
246         codec_munge(munge, amp, SAMPLES_PER_CHUNK);
247         for (j = 0;  j < SAMPLES_PER_CHUNK;  j++)
248         {
249             sndfile_buf[2*j + 1] = amp[j];
250             /* Add noise to the tones */
251             amp[j] += awgn(&noise_source);
252         }
253         codec_munge(munge, amp, SAMPLES_PER_CHUNK);
254         ademco_contactid_sender_rx(sender, amp, SAMPLES_PER_CHUNK);
255 
256         sf_writef_short(outhandle, sndfile_buf, SAMPLES_PER_CHUNK);
257     }
258     codec_munge_free(munge);
259     if (!rx_callback_reported)
260     {
261         fprintf(stderr, "    Report not received\n");
262         return -1;
263     }
264 
265     if (sf_close_telephony(outhandle))
266     {
267         fprintf(stderr, "    Cannot close audio file '%s'\n", OUTPUT_FILE_NAME);
268         return -1;
269     }
270     printf("    Passed\n");
271     ademco_contactid_sender_free(sender);
272     ademco_contactid_receiver_free(receiver);
273     return 0;
274 }
275 /*- End of function --------------------------------------------------------*/
276 
encode_decode_tests(void)277 static int encode_decode_tests(void)
278 {
279     char buf[100];
280     ademco_contactid_receiver_state_t *receiver;
281     ademco_contactid_sender_state_t *sender;
282     logging_state_t *logging;
283     ademco_contactid_report_t result;
284     int i;
285 
286     printf("Encode and decode tests\n");
287 
288     if ((receiver = ademco_contactid_receiver_init(NULL, NULL, NULL)) == NULL)
289         return 2;
290     logging = ademco_contactid_receiver_get_logging_state(receiver);
291     span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
292     span_log_set_tag(logging, "Ademco-rx");
293 
294     if ((sender = ademco_contactid_sender_init(NULL, NULL, NULL)) == NULL)
295         return 2;
296     logging = ademco_contactid_sender_get_logging_state(sender);
297     span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);
298     span_log_set_tag(logging, "Ademco-tx");
299 
300     for (i = 0;  i < 5;  i++)
301     {
302         if (encode_msg(buf, &reports[i]) < 0)
303         {
304             printf("Bad encode message\n");
305             return -1;
306         }
307         printf("'%s'\n", buf);
308         if (decode_msg(&result, buf))
309         {
310             printf("Bad decode message\n");
311             return -1;
312         }
313         ademco_contactid_receiver_log_msg(receiver, &result);
314         printf("\n");
315         if (memcmp(&reports[i], &result, sizeof(result)))
316         {
317             printf("Received message does not match the one sent\n");
318             return -1;
319         }
320     }
321 
322     if (encode_msg(buf, &reports[5]) >= 0)
323     {
324         printf("Incorrectly good message\n");
325         return -1;
326     }
327     printf("'%s'\n", buf);
328     printf("\n");
329     printf("    Passed\n");
330     ademco_contactid_sender_free(sender);
331     ademco_contactid_receiver_free(receiver);
332     return 0;
333 }
334 /*- End of function --------------------------------------------------------*/
335 
decode_file(const char * file)336 static void decode_file(const char *file)
337 {
338     //SPAN_DECLARE(int) decode_msg(ademco_contactid_report_t *report, const char buf[])
339 }
340 /*- End of function --------------------------------------------------------*/
341 
main(int argc,char * argv[])342 int main(int argc, char *argv[])
343 {
344     int opt;
345     const char *decode_test_file;
346 
347     decode_test_file = NULL;
348     while ((opt = getopt(argc, argv, "d:")) != -1)
349     {
350         switch (opt)
351         {
352         case 'd':
353             decode_test_file = optarg;
354             break;
355         default:
356             //usage();
357             exit(2);
358             break;
359         }
360     }
361 
362     if (decode_test_file)
363     {
364         decode_file(decode_test_file);
365         return 0;
366     }
367 
368     if (encode_decode_tests())
369     {
370         printf("Tests failed\n");
371         return 2;
372     }
373 
374     if (mitel_cm7291_side_2_and_bellcore_tests())
375     {
376         printf("Tests failed\n");
377         return 2;
378     }
379 
380     if (end_to_end_tests())
381     {
382         printf("Tests failed\n");
383         return 2;
384     }
385 
386     printf("Tests passed\n");
387     return 0;
388 }
389 /*- End of function --------------------------------------------------------*/
390 /*- End of file ------------------------------------------------------------*/
391