1 /*----------------------------------------------------------------------------+
2  |                                                                            |
3  |                 HEYU CM17A "Firecracker" Support                           |
4  |             Copyright 2005, 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 #ifdef HASCM17A  /* Compile only if configured for CM17A */
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <ctype.h>
40 #include <string.h>
41 #include "x10.h"
42 #include "process.h"
43 
44 #ifdef        SCO
45 #define _SVID3 /* required for correct termio handling */
46 #undef  _IBCS2 /* conflicts with SVID3  */
47 #endif
48 
49 #ifdef ATTSVR4
50 #include <sys/time.h>
51 #endif
52 
53 #include <time.h>
54 #include <unistd.h>
55 #include <signal.h>
56 
57 #ifdef LINUX
58 #include <asm/ioctls.h>
59 #   ifdef OLDLINUX
60 #include <linux/serial_reg.h>
61 #   endif
62 #include <linux/serial.h>
63 #include <sys/ioctl.h>
64 #include <unistd.h>
65 #include <syslog.h>
66 #else
67 #    if (defined(POSIX) || defined(FREEBSD) || defined(OPENBSD))
68 #include <sys/ttycom.h>
69 #    else
70 #         ifdef SCO
71 #include <sys/ttycom.h>
72 #         else
73 #              ifdef DARWIN
74 #include <sys/ttycom.h>
75 #              else
76 #include <sys/ttycom.h>
77 #              endif
78 #         endif
79 #    endif
80 #endif
81 
82 #if (defined(OSF) || defined(DARWIN) || defined(NETBSD))
83 #include <sys/ioctl.h>
84 #endif
85 
86 #ifdef HASSELECT
87 #include <sys/time.h>
88 #include <sys/types.h>
89 #endif
90 
91 
92 extern int verbose;
93 extern int sptty;
94 extern int tty;
95 extern int ttylock(), munlock();
96 extern int i_am_relay;
97 
98 extern CONFIG config;
99 extern CONFIG *configp;
100 
101 #define FC_STANDBY (TIOCM_RTS | TIOCM_DTR)
102 #define FC_RESET   ~FC_STANDBY
103 #define FC_ONE     TIOCM_RTS
104 #define FC_ZERO    TIOCM_DTR
105 
106 #define HC_MASK      0xF000
107 #define FUNC_MASK    0x00B8
108 #define UNIT_MASK    0x0458
109 #define NOSW_MASK    0x0003
110 #define OLD_MASK     0x0800
111 #define NOTX10_MASK  ~(HC_MASK|FUNC_MASK|UNIT_MASK|NOSW_MASK|OLD_MASK)
112 
113 /* The CM17A will normally repeat its RF code    */
114 /* transmission 5 times (configp->def_rf_bursts)   */
115 /* at intervals of about 110 milliseconds        */
116 /* (configp->rf_burst_spacing).                    */
117 /* By resetting the unit in one of the intervals */
118 /* between transmissions the number of bursts    */
119 /* can be set from 1 through 5.                  */
120 /* It appears there is an effective window of    */
121 /* about 40-50  milliseconds at the end of an    */
122 /* interval when the reset will cut off          */
123 /* subsequent bursts without interfering with    */
124 /* the current burst.                            */
125 /* Time the reset at OFFSET milliseconds before  */
126 /* the start of the next interval.               */
127 
128 #define OFFSET        10
129 
130 /* On many systems the nanosleep() function will  */
131 /* have a minimum resolution of 10 milliseconds   */
132 /* and a minimum time of 20 milliseconds.         */
133 /* The tweak (configp->rf_timer_tweak) attempts to  */
134 /* compensate for this to keep within the         */
135 /* effective window.                              */
136 
137 /* RF Codes for X10 Housecodes (A-P) */
138 unsigned int hc2rfcode[16] = {
139    0x6000, 0x7000, 0x4000, 0x5000,
140    0x8000, 0x9000, 0xA000, 0xB000,
141    0xE000, 0xF000, 0xC000, 0xD000,
142    0x0000, 0x1000, 0x2000, 0x3000,
143 };
144 
145 /* Housecode for RF code (upper nybble) */
146 static char *rf2hc = "MNOPCDABEFGHKLIJ";
147 
148 /* RF Codes for X10 _encoded_ unit codes */
149 unsigned int rf_unit_code[16] = {
150    0x0440, 0x0040, 0x0008, 0x0408,
151    0x0448, 0x0048, 0x0000, 0x0400,
152    0x0450, 0x0050, 0x0018, 0x0418,
153    0x0458, 0x0058, 0x0010, 0x0410,
154 };
155 
156 /* RF Function names for monitor */
157 char *rf_func_name[] = {
158    "fAllOff", "fLightsOn", "fOn", "fOff",
159    "fDim", "fBright", "fLightsOff",
160    "fdimbo", "freset", "fArb"
161 };
162 
163 /* RF Function Codes:               */
164 /* AllOff, LightsOn, On, Off,       */
165 /* Dim,    Bright, LightsOff        */
166 unsigned int rf_func_code[] = {
167    0x0080, 0x0090, 0x0000, 0x0020,
168    0x0098, 0x0088, 0x00a0,
169    0x0000, 0x0000, 0x0000
170 };
171 
172 /* Undocumented "No Switch" bits for TM751 and RR501    */
173 /* Transceivers.  If set, the built-in appliance module */
174 /* relay will not respond to the On, Off, and AllOff RF */
175 /* signals, and will just quietly transceive them.      */
176 unsigned int rf_nosw_code[] = {
177    0x0001, 0x0000, 0x0002, 0x0002,
178    0x0000, 0x0000, 0x0000, 0x0000,
179    0x0000, 0x0000,
180 };
181 
182 static unsigned long cm17a_loopcount;
183 
184 static volatile unsigned long countdown;
185 static volatile int dummy = 1;
186 /*----------------------------------------------------------------------------+
187  | Microsecond timer using timing loop.                                       |
188  +----------------------------------------------------------------------------*/
looptimer(void)189 static void looptimer ( void )
190 {
191    countdown = cm17a_loopcount;
192 
193    while (dummy && countdown--);
194    return;
195 }
196 
197 /*----------------------------------------------------------------------------+
198  | Microsecond timer using standard timing functions.  These don't have       |
199  | sufficient resolution for fast RF mode in most Unix-like kernels.          |
200  +----------------------------------------------------------------------------*/
stdtimer(void)201 static void stdtimer ( void )
202 {
203    long microsec = configp->cm17a_bit_delay;
204 
205 #ifdef NSLEEP
206    struct timestruc_t tspec;
207 
208    tspec.tv_sec = microsec / 1000000L;
209    tspec.tv_nsec = 1000L * (microsec % 1000000L);
210 
211    while ( nsleep( &tspec, &tspec ) == -1 );
212    return;
213 #else
214 #ifdef ATTSVR4
215    struct timeval tspec;
216 
217    tspec.tv_sec = microsec / 1000000;
218    tspec.tv_usec = microsec % 1000000;
219    while ( usleep(tspec.tv_usec) == -1 );
220 #else
221    struct timespec tspec;
222 
223    tspec.tv_sec = microsec / 1000000L;
224    tspec.tv_nsec = 1000L * (microsec % 1000000L);
225 
226    while ( nanosleep( &tspec, &tspec ) == -1 );
227 #endif /* ATTSVR4 */
228    return;
229 #endif
230 }
231 
232 
233 /*----------------------------------------------------------------------------+
234  | Delay after normal CM17A command.                                          |
235  +----------------------------------------------------------------------------*/
rf_post_delay(void)236 void rf_post_delay(void)
237 {
238    millisleep(configp->rf_post_delay);
239    return;
240 }
241 
242 /*----------------------------------------------------------------------------+
243  | Delay after 'farb' CM17A command                                           |
244  +----------------------------------------------------------------------------*/
rf_farb_delay(void)245 void rf_farb_delay(void)
246 {
247    millisleep(configp->rf_farb_delay);
248    return;
249 }
250 
251 /*----------------------------------------------------------------------------+
252  | Delay after 'farw' CM17A command                                           |
253  +----------------------------------------------------------------------------*/
rf_farw_delay(void)254 void rf_farw_delay(void)
255 {
256    millisleep(configp->rf_farw_delay);
257    return;
258 }
259 
260 /*----------------------------------------------------------------------------+
261  | Delay after 'flux' CM17A command                                           |
262  +----------------------------------------------------------------------------*/
rf_flux_delay(long msec)263 void rf_flux_delay( long msec )
264 {
265    millisleep(msec);
266    return;
267 }
268 
269 /*----------------------------------------------------------------------------+
270  | Reset CM17A to the power-up state - with no log message.                   |
271  +----------------------------------------------------------------------------*/
reset_cm17a_quiet(void)272 int reset_cm17a_quiet ( void )
273 {
274    int status, retcode;
275 
276    retcode = ioctl(tty, TIOCMGET, &status);
277    status &= FC_RESET;
278    retcode |= ioctl(tty, TIOCMSET, &status);
279    millisleep(10);
280 
281    retcode = ioctl(tty, TIOCMGET, &status);
282    status = (status & FC_RESET) | FC_STANDBY;
283    retcode |= ioctl(tty, TIOCMSET, &status);
284    millisleep(500);
285 
286    return retcode;
287 }
288 
289 /*----------------------------------------------------------------------------+
290  | Reset CM17A to the power-up state.                                         |
291  +----------------------------------------------------------------------------*/
reset_cm17a(void)292 int reset_cm17a( void )
293 {
294    send_x10state_command(ST_RESETRF, 0);
295 
296    return reset_cm17a_quiet();
297 }
298 
299 /*----------------------------------------------------------------------------+
300  | Actuate the CM17A by appropriately toggling the RTS and DTR lines.         |
301  +----------------------------------------------------------------------------*/
write_cm17a(unsigned int rfword,int bursts,unsigned char rfmode)302 int write_cm17a( unsigned int rfword, int bursts, unsigned char rfmode )
303 {
304    unsigned char buffer[16];
305    unsigned char data, mask;
306    int           status, signal;
307    int           i, j, k, retcode, groups, presleep;
308    void          (*delay)();
309 
310    if ( rfmode == RF_FAST && configp->timer_loopcount > 0 ) {
311       cm17a_loopcount =
312          configp->cm17a_bit_delay * (configp->timer_loopcount / 1000000L);
313       delay = looptimer;
314    }
315    else {
316       delay = stdtimer;
317    }
318 
319    if ( rfmode == RF_FAST ) {
320       groups = (bursts - 1) / (int)configp->def_rf_bursts + 1;
321       bursts = (bursts - 1) % (int)configp->def_rf_bursts + 1;
322       presleep = (configp->rf_burst_spacing * configp->def_rf_bursts)
323                    - OFFSET - configp->rf_timer_tweak;
324    }
325    else {
326       groups = 1;
327       bursts = min(bursts, (int)configp->def_rf_bursts);
328       presleep = 0;
329    }
330 
331    buffer[0] = 0xD5;
332    buffer[1] = 0xAA;
333    buffer[2] = (rfword & 0xff00) >> 8;
334    buffer[3] = rfword & 0xff;
335    buffer[4] = 0xAD;
336 
337    retcode = ioctl(tty, TIOCMGET, &status);
338    status |= FC_STANDBY;
339    retcode |= ioctl(tty, TIOCMSET, &status);
340    delay();
341 
342    for ( i = 0; i < groups; i++ ) {
343       if ( i > 0 )
344          millisleep( presleep );
345       for ( j = 0; j < 5; j++ ) {
346          data = buffer[j];
347          mask = 0x80;
348          for ( k = 0; k < 8; k++ ) {
349             delay(); /* Put delay at beginning of loop */
350             signal = (data & mask) ? FC_ONE : FC_ZERO ;
351 #if 0
352             status &= FC_RESET;
353 	    status |= signal;
354 #endif
355             status = (status & FC_RESET) | signal;
356 	    retcode |= ioctl(tty, TIOCMSET, &status);
357 	    delay();
358 #if 0
359 	    status |= FC_STANDBY;
360 #endif
361             status = (status & FC_RESET) | FC_STANDBY;
362 	    retcode |= ioctl(tty, TIOCMSET, &status);
363 	    mask = mask >> 1;
364          }
365       }
366    }
367 
368    /* Timed reset here limits the number of RF bursts */
369    if ( bursts > 0 ) {
370       millisleep((configp->rf_burst_spacing * bursts)
371            - OFFSET - configp->rf_timer_tweak);
372       status &= FC_RESET;
373       retcode |= ioctl(tty, TIOCMSET, &status);
374       millisleep(10);
375       status |= FC_STANDBY;
376       retcode |= ioctl(tty, TIOCMSET, &status);
377       millisleep(10);
378    }
379 
380    return retcode;
381 }
382 
383 /*----------------------------------------------------------------------------+
384  |
385  +----------------------------------------------------------------------------*/
xlate_rf(unsigned char type,char ** fname,unsigned int rfword,char * hcp,int * unitp,unsigned char * nosw)386 void xlate_rf( unsigned char type, char **fname, unsigned int rfword,
387 		     char *hcp, int *unitp, unsigned char *nosw )
388 {
389    int j;
390 
391    *fname = rf_func_name[type];
392    if ( type == 9 )
393       return;
394 
395    *hcp = rf2hc[(rfword & HC_MASK) >> 12];
396    *nosw = rfword & NOSW_MASK;
397 
398    rfword &= UNIT_MASK;
399    *unitp = 0;
400    for ( j = 0; j < 16; j++ ) {
401       if ( rfword == rf_unit_code[j] ) {
402          *unitp = code2unit(j);
403 	 break;
404       }
405    }
406    return;
407 }
408 
409 /*----------------------------------------------------------------------------+
410  | Display the CM17A command in the monitor/logfile                           |
411  |                                                                            |
412  | *** This really doesn't work very well, especially with multiple RF        |
413  | commands.  The uncertain delay between transmission of the RF and the      |
414  | reporting of the received power line signal by the CM11A often results in  |
415  | the RF signals not properly interleaved with the resulting power line      |
416  | signals.  It can be corrected only by setting an unreasonably long         |
417  | post-delay.                                                                |
418  +----------------------------------------------------------------------------*/
display_rf_xmit(unsigned char type,unsigned int rfword,int bursts)419 int display_rf_xmit ( unsigned char type, unsigned int rfword, int bursts )
420 {
421    extern int sptty;
422 
423    int ignoret;
424 
425    static unsigned char template[10] = {
426       0xff,0xff,0xff,6,ST_COMMAND,ST_XMITRF,0,0,0,0};
427 
428    if ( configp->disp_rf_xmit == NO )
429       return 0;
430 
431    template[6] = type;
432    template[7] = (rfword & 0xFF00) >> 8;
433    template[8] = rfword & 0xFF;
434    template[9] = (unsigned char)bursts;
435 
436    ignoret = write(sptty, template, 10);
437 
438    return 0;
439 }
440 
441 
442 #else  /* Stubs */
xlate_rf(unsigned char type,char ** fname,unsigned int rfword,char * hcp,int * unitp,unsigned char * nosw)443 void xlate_rf( unsigned char type, char **fname, unsigned int rfword,
444 		     char *hcp, int *unitp, unsigned char *nosw ) {}
445 #endif  /* End of HASCM17A code */
446 
447 
448 
449