1 /*
2  *
3  * BottleRocket command handling functions.  Only x10_br_out is available to
4  * other programs.  Rest are for it's use.
5  *
6  * (c) 1999 Tymm Twillman (tymm@acm.org).  Free Software.  LGPL applies.
7  *  No warranties expressed or implied.
8  *
9  * This is for interfacing with the X10 Dynamite wireless transmitter for X10
10  *  home automation hardware.
11 */
12 #ifdef __cplusplus
13 extern C {
14 #endif
15 
16 
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #include <unistd.h>
22 #include <sys/ioctl.h>
23 #include <stdio.h>
24 #include <fcntl.h>
25 #include <sys/types.h>
26 #include <sys/time.h>
27 #include <limits.h>
28 
29 #ifdef HAVE_ERRNO_H
30 #include <errno.h>
31 #else
32 extern int errno;
33 #endif
34 
35 #ifdef HAVE_TERMIOS_H
36 #include <termios.h>
37 #endif
38 
39 #ifdef HAVE_SYS_TERMIOS_H
40 #include <sys/termios.h>
41 #endif
42 
43 #include "br_cmd.h"
44 #include "br_translate.h"
45 
46 #ifndef TIOCM_FOR_0
47 #define TIOCM_FOR_0 TIOCM_DTR
48 #endif
49 
50 #ifndef TIOCM_FOR_1
51 #define TIOCM_FOR_1 TIOCM_RTS
52 #endif
53 
54 /*
55  * These values should be good for pretty much everyone, but you can
56  *   try increasing them if you have problems.  Values are in uSec;
57  *   PreCmdDelay is the amount of time to hold output lines in
58  *   "clock" position before a command, PostCmdDelay is how long
59  *   to stay in "clock" position after a command, and InterBitDelay
60  *   is how long each bit/clock wiggled out the serial port should
61  *   last.
62  */
63 
64 int PreCmdDelay = 300000;   /* empirically found... */
65 int PostCmdDelay = 300000;
66 int InterBitDelay = 1400;
67 
usec_sleep(long usecs)68 static int usec_sleep(long usecs)
69 {
70     /*
71      * Sleep for a little while.  Using select() so we don't busy-
72      *  wait for this long.
73      */
74 
75     struct timeval sleeptime;
76     int tmperrno;
77 
78 
79     sleeptime.tv_sec = usecs / 1000000;
80     sleeptime.tv_usec = usecs % 1000000;
81 
82     if (select(0, NULL, NULL, NULL, &sleeptime) < 0) {
83 	tmperrno = errno;
84 	perror("select");
85 	errno = tmperrno;
86         return -1;
87     }
88 
89     return 0;
90 }
91 
usec_delay(long usecs)92 static int usec_delay(long usecs)
93 {
94     struct timeval endtime;
95     struct timeval currtime;
96     int tmperrno;
97 
98     /*
99      * This way of doing things stolen from Firecracker, by Chris Yokum.
100      *   Much better.  What was I thinking before?
101      */
102 
103     if (gettimeofday(&endtime, NULL) < 0) {
104         tmperrno = errno;
105         perror("gettimeofday");
106         errno = tmperrno;
107         return -1;
108     }
109 
110     endtime.tv_usec += usecs;
111 
112     if (endtime.tv_usec > 1000000) {
113         endtime.tv_sec++;
114         endtime.tv_usec -= 1000000;
115     }
116 
117     do {
118         if (gettimeofday(&currtime, NULL) < 0) {
119             tmperrno = errno;
120             perror("gettimeofday");
121             errno = tmperrno;
122             return -1;
123         }
124     } while (timercmp(&endtime, &currtime, >));
125 
126     return 0;
127 }
128 
129 
bits_out(const int fd,const int bits)130 static int bits_out(const int fd, const int bits)
131 {
132     /*
133      * Send out one command bit; set RTS or DTR (but only one) depending on
134      *  value.
135      *
136      * This now assumes that both RTS and DTR are high; it just clears the one
137      *  that we don't want set for this bit.
138      */
139 
140     int out;
141     int tmperrno;
142 
143 
144     out = (bits) ? TIOCM_FOR_0:TIOCM_FOR_1;
145 
146 #ifdef DEBUG
147     /*
148      * I print these things out funny because that's how I started doing
149      *  it and so too bad if it looks weird.
150      */
151 
152     printf("%d", out >> 1);
153 #endif
154 
155     /* Set RTS, DTR to desired settings */
156 
157     if (ioctl(fd, TIOCMBIC, &out) < 0) {
158         tmperrno = errno;
159         perror("ioctl");
160         errno = tmperrno;
161         return -1;
162     }
163 
164     if (usec_delay(InterBitDelay) < 0)
165         return -1;
166 
167     return 0;
168 }
169 
clock_out(const int fd)170 static int clock_out(const int fd)
171 {
172     /*
173      * Send out a "clock pulse" -- both RTS and DTR set; used before/after
174      *  command (long pulse) and between command bits (short)
175      */
176 
177     int out = TIOCM_FOR_0 | TIOCM_FOR_1;
178     int tmperrno;
179 
180 
181 #ifdef DEBUG
182     printf("%d", out >> 1);
183 #endif
184 
185     if (ioctl(fd, TIOCMBIS, &out) < 0) {
186         tmperrno = errno;
187         perror("ioctl");
188         errno = tmperrno;
189         return -1;
190     }
191 
192     if (usec_delay(InterBitDelay) < 0)
193         return -1;
194 
195     return 0;
196 }
197 
198 
x10_br_out(int fd,unsigned char unit,int cmd)199 int x10_br_out(int fd, unsigned char unit, int cmd)
200 {
201 
202     /*
203      * Put together the commands to send out.  The basic start and end of
204      *  each command is the same; just fill in the little bits in the middle
205      *
206      * Yeah, it's pretty nasty; I'll probably get around to cleaning this
207      *  up soon, but until then you just have to suffer.
208      */
209 
210     unsigned char cmd_seq[5] = { 0xd5, 0xaa, 0x00, 0x00, 0xad };
211 
212     register int i;
213     register int j;
214     unsigned char byte;
215     int out;
216     int housecode;
217     int device;
218     int serial_state;
219     int tmperrno;
220 #ifdef USE_CLOCAL
221     struct termios termios;
222     struct termios tmp_termios;
223 #endif
224 
225     /*
226      * Make sure to set the numeric part of the device address to 0
227      *  for dim/bright (they only work per housecode)
228      */
229 
230     if ((cmd == DIM) || (cmd == BRIGHT))
231         unit &= 0xf0;
232 
233 #ifdef USE_CLOCAL
234 
235     if (tcgetattr(fd, &termios) < 0) {
236         tmperrno = errno;
237         perror("ioctl");
238         errno = tmperrno;
239         return -1;
240     }
241 
242     tmp_termios = termios;
243 
244     tmp_termios.c_cflag |= CLOCAL;
245 
246     if (tcsetattr(fd, TCSANOW, &tmp_termios) < 0) {
247         tmperrno = errno;
248         perror("ioctl");
249         errno = tmperrno;
250         return -1;
251     }
252 
253 #endif
254 
255     /*
256      * Save current state of bits we don't want to touch in serial
257      *  register
258      */
259 
260     if (ioctl(fd, TIOCMGET, &serial_state) < 0) {
261         tmperrno = errno;
262         perror("ioctl");
263         errno = tmperrno;
264         return -1;
265     }
266 
267     /* Save state of lines to be mucked with
268      */
269 
270     serial_state &= (TIOCM_FOR_0 | TIOCM_FOR_1);
271 
272     /* Figure out which ones we're going to want to clear
273      *  when finished (they'll both be high after the last
274      *  clock_out)
275      */
276 
277     serial_state ^= (TIOCM_FOR_0 | TIOCM_FOR_1);
278 
279     /*
280      * Open with a clock pulse to let the receiver get its wits about
281      */
282 
283 
284     housecode = unit >> 4;
285     device = unit & 0x0f;
286 
287     if ((cmd > MAX_CMD) || (cmd < 0))
288         return -1;
289 
290     /*
291      * Slap together the variable part of a command
292      */
293 
294     cmd_seq[2] |= housecode_table[housecode] << 4 | device_table[device][0];
295     cmd_seq[3] |= device_table[device][1] | cmd_table[cmd];
296 
297     /*
298      * Set lines to clock and wait, to make sure receiver is ready
299      */
300 
301     if (clock_out(fd) < 0)
302         return -1;
303 
304     if (usec_sleep(PreCmdDelay) < 0)
305         return -1;
306 
307     for (j = 0; j < 5; j++) {
308         byte = cmd_seq[j];
309 
310 #ifdef UGLY_DEBUG
311         printf("sending byte: %02x\n", (unsigned int)byte);
312 #endif
313 
314         /*
315          * Roll out the bits, following each one by a "clock".
316          */
317 
318         for (i = 0; i < 8; i++) {
319             out = (byte & 0x80) ? 1:0;
320             byte <<= 1;
321             if ((bits_out(fd, out) < 0) || (clock_out(fd) < 0))
322                 return -1;
323         }
324     }
325 
326     /*
327      * Close with a clock pulse and wait a bit to allow command to complete
328      */
329 
330     if (clock_out(fd) < 0)
331         return -1;
332 
333     if (usec_sleep(PostCmdDelay) < 0)
334         return -1;
335 
336    if (ioctl(fd, TIOCMBIC, &serial_state) < 0) {
337         tmperrno = errno;
338         perror("ioctl");
339         errno = tmperrno;
340         return -1;
341     }
342 
343 #ifdef USE_CLOCAL
344     if (tcsetattr(fd, TCSANOW, &termios) < 0) {
345         tmperrno = errno;
346         perror("tcsetattr");
347         errno = tmperrno;
348         return -1;
349     }
350 #endif
351 
352     return 0;
353 }
354