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