1 /*----------------------------------------------------------------------------+
2  |                                                                            |
3  |              HEYU CM10 "IBM Home Director HD16" Support                    |
4  |               Copyright 2006 Charles W. Sullivan                           |
5  |                                                                            |
6  |                                                                            |
7  | As used herein, HEYU is a trademark of Daniel B. Suthers.                  |
8  | X10, CM11A, and ActiveHome are trademarks of X-10 (USA) Inc.               |
9  | The author is not affiliated with either entity.                           |
10  |                                                                            |
11  | Charles W. Sullivan                                                        |
12  | Co-author and Maintainer                                                   |
13  | Greensboro, North Carolina                                                 |
14  | Email ID: cwsulliv01                                                       |
15  | Email domain: -at- heyu -dot- org                                          |
16  |                                                                            |
17  +----------------------------------------------------------------------------*/
18 
19 /*
20  *   This program is free software: you can redistribute it and/or modify
21  *   it under the terms of the GNU General Public License as published by
22  *   the Free Software Foundation, either version 3 of the License, or
23  *   (at your option) any later version.
24  *
25  *   This program is distributed in the hope that it will be useful,
26  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
27  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28  *   GNU General Public License for more details.
29  *
30  *   You should have received a copy of the GNU General Public License
31  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
32  *
33  */
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <ctype.h>
38 #include <string.h>
39 #include "x10.h"
40 #include "process.h"
41 
42 #ifdef        SCO
43 #define _SVID3 /* required for correct termio handling */
44 #undef  _IBCS2 /* conflicts with SVID3  */
45 #endif
46 
47 #include <time.h>
48 #include <unistd.h>
49 #include <signal.h>
50 
51 #ifdef LINUX
52 #include <asm/ioctls.h>
53 #   ifdef OLDLINUX
54 #include <linux/serial_reg.h>
55 #   endif
56 #include <linux/serial.h>
57 #include <sys/ioctl.h>
58 #include <unistd.h>
59 #include <syslog.h>
60 #else
61 #    if (defined(DARWIN) || defined(FREEBSD))
62 #include <termios.h>
63 #    else
64 #         ifdef SCO
65 #include <sys/termio.h>
66 #         else
67 #              if (defined(POSIX) || defined(OPENBSD))
68 #include <sys/termios.h>
69 #              else
70 #include <termio.h>
71 #              endif
72 #         endif
73 #    endif
74 #endif
75 
76 #if (defined(OSF) || defined(DARWIN) || defined(NETBSD))
77 #include <sys/ioctl.h>
78 #endif
79 
80 #ifdef HASSELECT
81 #include <sys/time.h>
82 #include <sys/types.h>
83 #endif
84 
85 
86 extern int verbose;
87 extern int sptty;
88 extern int tty;
89 extern int ttylock(), munlock();
90 extern int i_am_relay;
91 extern int xwrite(), exread();
92 extern int check4poll();
93 
94 extern CONFIG config;
95 extern CONFIG *configp;
96 
97 /* On CM10A versions having a 6-pin RJ11 connector and cable,        */
98 /* if the serial port RTS line is turned off for more than about 20  */
99 /* milliseconds, then turned on again, a CM10A will write a bunch    */
100 /* of characters to the serial port.  This is what I'm seeing with   */
101 /* the unit I have.  Their interpretation is currently unknown.      */
102 
103 unsigned char cm10a_standard_response[29] =
104     {0x00,0x78,0x1e,0x00,0x00,0x80,0x78,0x00,
105      0xf0,0xf8,0x00,0x78,0xfe,0x80,0x80,0x00,
106      0x78,0x00,0x00,0x78,0x00,0x00,0x78,0x1e,
107      0x80,0x78,0x1e,0x78,0x1e};
108 
109 /* Example CM10 macro from protocol, equivalent to:   */
110 /*   trigger C1 off mac1                              */
111 /*   macro mac1 0 dim A1-3 11; on A4                  */
112 /*   trigger C2 on  mac2                              */
113 /*   macro mac2 0 on A1; dim A2,3 6; off A4           */
114 
115 static unsigned char example[] =
116   {0xfb, 0x26,0x0a,0x04,0x66,0x2e,0x04,0x0b,0x02,0x6a,0x02,
117          0x26,0x8c,0x02,0x66,0x02,0x03,0x6e,0x42,0x06,0x02,0x6a,0x03,};
118 
119 
120 /*----------------------------------------------------------------------------+
121  | Turn the RTS serial line off.                                              |
122  +----------------------------------------------------------------------------*/
turn_rts_off(void)123 int turn_rts_off( void )
124 {
125    int status, retcode;
126 
127    retcode = ioctl(tty, TIOCMGET, &status);
128    status &= ~TIOCM_RTS;
129    retcode |= ioctl(tty, TIOCMSET, &status);
130 
131    return retcode;
132 }
133 
c_turn_rts_off(int argc,char * argv[])134 int c_turn_rts_off( int argc, char *argv[] )
135 {
136    if ( turn_rts_off() != 0 ) {
137       fprintf(stderr, "Unable to turn RTS line Off.\n");
138       return 1;
139    }
140    return 0;
141 }
142 
143 
144 /*----------------------------------------------------------------------------+
145  | Turn the RTS serial line on.                                               |
146  +----------------------------------------------------------------------------*/
turn_rts_on(void)147 int turn_rts_on( void )
148 {
149    int status, retcode;
150 
151    retcode = ioctl(tty, TIOCMGET, &status);
152    status |= TIOCM_RTS;
153    retcode |= ioctl(tty, TIOCMSET, &status);
154 
155    return retcode;
156 }
157 
c_turn_rts_on(int argc,char * argv[])158 int c_turn_rts_on( int argc, char *argv[] )
159 {
160    if ( turn_rts_on() != 0 ) {
161       fprintf(stderr, "Unable to turn RTS line On.\n");
162       return 1;
163    }
164    return 0;
165 }
166 
167 
168 /*----------------------------------------------------------------------------+
169  | Toggle the RTS serial line off, then back on                               |
170  +----------------------------------------------------------------------------*/
toggle_rts(void)171 int toggle_rts( void )
172 {
173    int retcode;
174 
175    retcode = turn_rts_off();
176 
177    millisleep(100);
178 
179    retcode |= turn_rts_on();
180 
181    return retcode;
182 }
183 
184 /*----------------------------------------------------------------------------+
185  | Ask the CM10A to identify itself.                                          |
186  +----------------------------------------------------------------------------*/
c_cm10a_ident(int argc,char * argv[])187 int c_cm10a_ident ( int argc, char *argv[] )
188 {
189     unsigned char buf[50];
190     unsigned char *bp;
191     int      j, count, nread, left;
192     extern void millisleep();
193 
194     if ( (toggle_rts()) != 0 ) {
195        fprintf(stderr, "Unable to toggle RTS line.\n");
196        return 1;
197     }
198 
199     bp = buf;
200     nread = 0; left = 30;
201     for ( j = 0; j < 3; j++ ) {
202        count = exread(sptty, bp, left, 1);
203        nread += count;
204        if ( nread == 29 ) {
205           if ( memcmp(buf, cm10a_standard_response, 29) == 0 ) {
206              printf("CM10 is connected.\n");
207 	     check4poll(0,1);
208              return 0;
209           }
210           else {
211              fprintf(stderr, "Non-standard CM10A response.\n");
212 	     check4poll(0,1);
213              return 1;
214           }
215        }
216        bp += count;
217        left -= count;
218        millisleep(10);
219    }
220 
221    if ( nread == 0 ) {
222      fprintf(stderr, "No response from CM10A\n");
223    }
224    else {
225      fprintf(stderr, "Invalid response, %d bytes returned.\n", nread);
226    }
227 
228    return 1;
229 }
230 
231 /*----------------------------------------------------------------------------+
232  | For versions of the CM10A with a 6-pin RJ11 connector, the factory         |
233  | CM10A cable has a jumper between DB-9 pins 4 (DTR) and 6 (DSR),            |
234  | neither of which are connected to the interface.  These function verify    |
235  | that the cable is connected at the PC end at least.                        |
236  +----------------------------------------------------------------------------*/
237 /*----------------------------------------------------------------------------+
238  | This function just reads the serial status, normally assuming that the DTR |
239  | line is active.  It returns 0 if both the DTR and DSR lines are active;    |
240  | 1 if the DTR is active and the DSR is not; -1 if the DTR is not.           |
241  +----------------------------------------------------------------------------*/
cm10a_cable_check(void)242 int cm10a_cable_check ( void )
243 {
244    int status;
245 
246    ioctl(tty, TIOCMGET, &status);
247 
248    if ( !(status & TIOCM_DTR) )
249       return -1;
250 
251    if ( !(status & TIOCM_DSR) )
252       return 1;
253 
254    return 0;
255 }
256 
257 /*----------------------------------------------------------------------------+
258  | This function toggles the DTS line.                                        |
259  | It returns 0 if the DSR activity follows the DTR activity.                 |
260  | Otherwise it returns 1 if DSR remains inactive when the DTR is toggled or  |
261  | -1 if the DSR remains active regardless.                                   |
262  +----------------------------------------------------------------------------*/
cm10a_cable_check_full(void)263 int cm10a_cable_check_full ( void )
264 {
265    int status, savestatus;
266 
267    ioctl(tty, TIOCMGET, &status);
268    savestatus = status;
269 
270    status &= ~TIOCM_DTR;
271    ioctl(tty, TIOCMSET, &status);
272    millisleep(10);
273    ioctl(tty, TIOCMGET, &status);
274    if ( status & TIOCM_DSR ) {
275       ioctl(tty, TIOCMSET, &savestatus);
276       return -1;
277    }
278 
279    status |= TIOCM_DTR;
280    ioctl(tty, TIOCMSET, &status);
281    millisleep(10);
282    ioctl(tty, TIOCMGET, &status);
283    if ( !(status & TIOCM_DSR) ) {
284       ioctl(tty, TIOCMSET, &savestatus);
285       return 1;
286    }
287 
288    ioctl(tty, TIOCMSET, &savestatus);
289 
290    return 0;
291 }
292 
293 
294 /*----------------------------------------------------------------------------+
295  |  Initialize CM10A interface by uploading a dummy macro block.              |
296  +----------------------------------------------------------------------------*/
c_cm10a_init(int argc,char * argv[])297 int c_cm10a_init ( int argc, char *argv[] )
298 {
299     unsigned char macrodata[50];
300     unsigned char buf[3];
301     unsigned int j, n, code;
302     unsigned char cksum;
303     extern int usage(), xwrite(), xread(), exread(), check4poll();
304 
305     int ignoret;
306 
307     if ( !(configp->device_type & DEV_CM10A) && i_am_relay != 1 ) {
308        fprintf(stderr,
309 	 "Heyu not configured for CM10A - see man page heyu(1)\n");
310        return 1;
311     }
312 
313     if( argc > 2  )
314        usage(E_2MANY);
315 
316 #if 0
317     if ( cm10a_cable_check() == 1 ) {
318        if ( i_am_relay != 1 )
319           fprintf(stderr, "CM10A is not connected.\n");
320        return 1;
321     }
322 #endif
323 
324     macrodata[0] = 0xfb;          /* CM10A init code */
325     for ( j = 1; j < 43; j++ ) {
326        macrodata[j] = 0;
327     }
328 
329     example[0] = example[0];  /* Keep compiler happy */
330 
331     /* For testing, use example CM10 macro data from protocol */
332 #if 0
333     memcpy(macrodata, example, sizeof(example));
334 #endif
335 
336 
337     cksum = checksum(macrodata + 1, 42);
338 
339     code = 0;
340 
341 #if 0
342     xwrite(tty, (char *) macrodata, 43);
343     if ( i_am_relay )
344        n = xread(tty, buf, 1, 1);
345     else
346        n = exread(sptty, buf, 1, 1);
347 
348     if ( n != 1 )
349        code |= 0x10;
350     else if ( buf[0] != cksum )
351        code |= 0x01;
352 
353     xwrite(tty, "\0", 1);   /* WRMI */
354     if ( i_am_relay )
355        n = xread(tty, buf, 1, 1);
356     else
357        n = exread(sptty, buf, 1, 1);
358 #endif
359 
360     if ( i_am_relay ) {
361        ignoret = write(tty, (char *) macrodata, 43);
362        n = xread(tty, buf, 1, 2);
363     }
364     else {
365        xwrite(tty, (char *) macrodata, 43);
366        n = exread(sptty, buf, 1, 1);
367     }
368 
369     if ( n != 1 )
370        code |= 0x10;
371     else if ( buf[0] != cksum )
372        code |= 0x01;
373 
374     if ( i_am_relay ) {
375        ignoret = write(tty, "\0", 1);   /* WRMI */
376        n = xread(tty, buf, 1, 2);
377     }
378     else {
379        xwrite(tty, "\0", 1);   /* WRMI */
380        n = exread(sptty, buf, 1, 1);
381     }
382 
383     if ( n != 1 )
384        code |= 0x20;
385     else if ( buf[0] != 0x55 )
386        code |= 0x02;
387 
388     if ( argc == 2 )
389        check4poll(0,1);		/* zero means to discard data */
390 
391     if ( i_am_relay != 1 )
392        (void) printf("CM10A initialized.\n");
393 
394     if ( i_am_relay )
395        return code;
396 
397     return 0;
398 }
399 
400 
c_test_serial_port(int argc,char * argv[])401 int c_test_serial_port ( int argc, char *argv[] )
402 {
403    int  j, k, nlist, retcode;
404    int  all_lines, status, savestatus, result1, result2;
405    int  delay = 10;
406 
407    static struct tiocm_list {
408       int line;
409       char *name;
410    } list[] = {
411      {TIOCM_LE,  "LE" },
412      {TIOCM_DTR, "DTR"},
413      {TIOCM_RTS, "RTS"},
414      {TIOCM_ST,  "ST" },
415      {TIOCM_SR,  "SR" },
416      {TIOCM_CTS, "CTS"},
417      {TIOCM_CD,  "CD" },
418      {TIOCM_RI,  "RI" },
419      {TIOCM_DSR, "DSR"},
420    };
421    nlist = (sizeof(list)/sizeof(struct tiocm_list));
422 
423    all_lines = 0;
424    for ( j = 0; j < nlist; j++ )
425       all_lines |= list[j].line;
426 
427    ioctl(tty, TIOCMGET, &status);
428    savestatus = status;
429 
430    if ( status & TIOCM_CTS ) {
431       printf("CTS initially On\n");
432    }
433    else {
434       printf("CTS initially Off\n");
435    }
436 
437    status |= TIOCM_CTS;
438    retcode = ioctl(tty, TIOCMBIS, &status);
439    millisleep(delay);
440    status |= TIOCM_CTS;
441    retcode |= ioctl(tty, TIOCMSET, &status);
442    millisleep(delay);
443    retcode |= ioctl(tty, TIOCMGET, &status);
444 
445    if ( status & TIOCM_CTS ) {
446       printf("CTS finally On\n");
447    }
448    else {
449       printf("CTS finally Off\n");
450    }
451 
452    printf("retcode = %x\n", retcode);
453 
454    /* Test individual lines for toggleability */
455    for ( j = 0; j < nlist; j++ ) {
456       retcode = 0;
457       status &= ~list[j].line;
458       retcode |= ioctl(tty, TIOCMSET, &status);
459       if ( delay )
460          millisleep(delay);
461       retcode |= ioctl(tty, TIOCMGET, &status);
462       result1 = status & list[j].line;
463       status |= list[j].line;
464       retcode |= ioctl(tty, TIOCMSET, &status);
465       if ( delay )
466          millisleep(delay);
467       retcode |= ioctl(tty, TIOCMGET, &status);
468       result2 = status & list[j].line;
469       if ( retcode != 0 )
470          printf("Bad retcode\n");
471       if ( result2 != result1 )
472          printf("%-3s can be controlled by PC.\n", list[j].name);
473    }
474 
475    /* Test for linked lines */
476    for ( j = 0; j < nlist; j++ ) {
477       retcode = 0;
478       status &= ~all_lines;
479       retcode |= ioctl(tty, TIOCMSET, &status);
480       if ( delay )
481          millisleep(delay);
482       retcode |= ioctl(tty, TIOCMGET, &status);
483       result1 = status & all_lines;
484       status &= ~all_lines;
485       status |= list[j].line;
486       retcode |= ioctl(tty, TIOCMSET, &status);
487       if ( delay )
488          millisleep(delay);
489       retcode |= ioctl(tty, TIOCMGET, &status);
490       result2 = (status ^ result1) & ~list[j].line & all_lines;
491       if ( retcode != 0 )
492          printf("Bad retcode\n");
493       for ( k = 0; k < nlist; k++ ) {
494          if ( result2 & list[k].line )
495             printf("%-3s is linked to %-3s\n", list[k].name, list[j].name);
496       }
497    }
498    for ( j = 0; j < nlist; j++ ) {
499       retcode = 0;
500       status |= all_lines;
501       retcode |= ioctl(tty, TIOCMSET, &status);
502       if ( delay )
503          millisleep(delay);
504       retcode |= ioctl(tty, TIOCMGET, &status);
505       result1 = status & all_lines;
506       status |= all_lines;
507       status &= ~list[j].line;
508       retcode |= ioctl(tty, TIOCMSET, &status);
509       if ( delay )
510          millisleep(delay);
511       retcode |= ioctl(tty, TIOCMGET, &status);
512       result2 = (status ^ result1) & ~list[j].line & all_lines;
513       if ( retcode != 0 )
514          printf("Bad retcode\n");
515       for ( k = 0; k < nlist; k++ ) {
516          if ( result2 & list[k].line )
517             printf("%-3s is revlinked to %-3s\n", list[k].name, list[j].name);
518       }
519    }
520 
521    ioctl(tty, TIOCMSET, &savestatus);
522 
523    return 0;
524 }
525 
526 
527 
528 
529 
530