1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2008 Robin Kreis. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6 
7         author:         Robin Kreis
8 
9 *******************************************************************************/
10 
11 module tango.io.device.SerialPort;
12 
13 private import  tango.core.Array : sort;
14 
15 private import  tango.core.Exception,
16                 tango.io.device.Device,
17                 tango.stdc.stringz,
18                 tango.sys.Common;
19 
20 version(Windows)
21 {
22 private import Integer = tango.text.convert.Integer;
23 private import tango.stdc.stringz;
24 }
25 else
26 version(Posix)
27 {
28 private import  tango.io.FilePath,
29                 tango.stdc.posix.termios;
30 }
31 
32 /*******************************************************************************
33 
34         Enables applications to use a serial port (aka COM-port, ttyS).
35         Usage is similar to that of File:
36         ---
37         auto serCond = new SerialPort("ttyS0");
38         serCond.speed = 38400;
39         serCond.write("Hello world!");
40         serCond.close();
41         ----
42 
43 *******************************************************************************/
44 
45 class SerialPort : Device
46 {
47     private const(char)[]              str;
48     private __gshared const(char)[][]  _ports;
49 
50     /***************************************************************************
51 
52             Create a new SerialPort instance. The port will be opened and
53             set to raw mode with 9600-8N1.
54 
55             Params:
56             port = A string identifying the port. On Posix, this must be a
57                    device file like /dev/ttyS0. If the input doesn't begin
58                    with "/", "/dev/" is automatically prepended, so "ttyS0"
59                    is sufficent. On Windows, this must be a device name like
60                    COM1.
61 
62     ***************************************************************************/
63 
64     this (const(char)[] port)
65     {
66         create (port);
67     }
68 
69     /***************************************************************************
70 
71             Returns a string describing this serial port.
72             For example: "ttyS0", "COM1", "cuad0".
73 
74     ***************************************************************************/
75 
76     override string toString ()
77     {
78         return str.idup;
79     }
80 
81     /***************************************************************************
82 
83             Sets the baud rate of this port. Usually, the baud rate can
84             only be set to fixed values (common values are 1200 * 2^n).
85 
86             Note that for Posix, the specification only mandates speeds up
87             to 38400, excluding speeds such as 7200, 14400 and 28800.
88             Most Posix systems have chosen to support at least higher speeds
89             though.
90 
91             See_also: maxSpeed
92 
93             Throws: IOException if speed is unsupported.
94 
95     ***************************************************************************/
96 
97     SerialPort speed (uint speed)
98     {
99         version(Posix) {
100             speed_t *baud = speed in baudRates;
101             if(baud is null) {
102                 throw new IOException("Invalid baud rate.");
103             }
104 
105             termios options;
106             tcgetattr(handle, &options);
107             cfsetospeed(&options, *baud);
108             tcsetattr(handle, TCSANOW, &options);
109         }
110         version(Win32) {
111             DCB config;
112             GetCommState(io.handle, &config);
113             config.BaudRate = speed;
114             if(!SetCommState(io.handle, &config)) error();
115         }
116         return this;
117     }
118 
119     /***************************************************************************
120 
121             Tries to enumerate all serial ports. While this usually works on
122             Windows, it's more problematic on other OS. Posix provides no way
123             to list serial ports, and the only option is searching through
124             "/dev".
125 
126             Because there's no naming standard for the device files, this method
127             must be ported for each OS. This method is also unreliable because
128             the user could have created invalid device files, or deleted them.
129 
130             Returns:
131             A string array of all the serial ports that could be found, in
132             alphabetical order. Every string is formatted as a valid argument
133             to the constructor, but the port may not be accessible.
134 
135     ***************************************************************************/
136 
137     static const(char)[][] ports ()
138     {
139         if(_ports !is null) {
140             return _ports;
141         }
142         version(Windows) {
143             // try opening COM1...COM255
144             auto pre = `\\.\COM`;
145             char[11] p = void;
146             char[3] num = void;
147             p[0..pre.length] = pre;
148             for(int i = 1; i <= 255; ++i) {
149                 char[] portNum = Integer.format(num, i);
150                 p[pre.length..pre.length + portNum.length] = portNum;
151                 p[pre.length + portNum.length] = '\0';
152                 HANDLE port = CreateFileA(p.ptr, GENERIC_READ | GENERIC_WRITE, 0, null, OPEN_EXISTING, 0, null);
153                 if(port != INVALID_HANDLE_VALUE) {
154                     _ports ~= p[`\\.\`.length..$].dup; // cut the leading \\.\
155                     CloseHandle(port);
156                 }
157             }
158         } else version(Posix) {
159             auto dev = FilePath("/dev".dup);
160             FilePath[] serPorts = dev.toList((FilePath path, bool isFolder) {
161                 if(isFolder) return false;
162                 version(linux) {
163                     auto r = rest(path.name, "ttyUSB");
164                     if(r is null) r = rest(path.name, "ttyS");
165                     if(r.length == 0) return false;
166                     return isInRange(r, '0', '9');
167                 } else version(OSX) { // untested
168                     auto r = rest(path.name, "cu");
169                     if(r.length == 0) return false;
170                     return true;
171                 } else version(FreeBSD) { // untested
172                     auto r = rest(path.name, "cuaa");
173                     if(r is null) r = rest(path.name, "cuad");
174                     if(r.length == 0) return false;
175                     return isInRange(r, '0', '9');
176                 } else version(openbsd) { // untested
177                     auto r = rest(path.name, "tty");
178                     if(r.length != 2) return false;
179                     return isInRange(r, '0', '9');
180                 } else version(solaris) { // untested
181                     auto r = rest(path.name, "tty");
182                     if(r.length != 1) return false;
183                     return isInRange(r, 'a', 'z');
184                 } else {
185                     return false;
186                 }
187             });
188             _ports.length = serPorts.length;
189             foreach(i, path; serPorts) {
190                 _ports[i] = path.name;
191             }
192         }
193         sort(_ports);
194         return _ports;
195     }
196 
197     version(Win32) {
198         private void create (const(char)[] port)
199         {
200             str = port;
201             io.handle = CreateFileA((`\\.\` ~ port).toStringz(), GENERIC_READ | GENERIC_WRITE, 0, null, OPEN_EXISTING, 0, null);
202             if(io.handle is INVALID_HANDLE_VALUE) {
203                 error();
204             }
205             DCB config;
206             GetCommState(io.handle, &config);
207             config.BaudRate = 9600;
208             config.ByteSize = 8;
209             config.Parity = NOPARITY;
210             config.StopBits = ONESTOPBIT;
211             config.flag0 |= bm_DCB_fBinary | bm_DCB_fParity;
212             if(!SetCommState(io.handle, &config)) error();
213         }
214     }
215 
216     version(Posix) {
217         private __gshared speed_t[uint] baudRates;
218 
219         shared static this()
220         {
221             baudRates[50] = B50;
222             baudRates[75] = B75;
223             baudRates[110] = B110;
224             baudRates[134] = B134;
225             baudRates[150] = B150;
226             baudRates[200] = B200;
227             baudRates[300] = B300;
228             baudRates[600] = B600;
229             baudRates[1200] = B1200;
230             baudRates[1800] = B1800;
231             baudRates[2400] = B2400;
232             baudRates[9600] = B9600;
233             baudRates[4800] = B4800;
234             baudRates[19200] = B19200;
235             baudRates[38400] = B38400;
236 
237             version( linux )
238             {
239                 baudRates[57600] = B57600;
240                 baudRates[115200] = B115200;
241                 baudRates[230400] = B230400;
242                 baudRates[460800] = B460800;
243                 baudRates[500000] = B500000;
244                 baudRates[576000] = B576000;
245                 baudRates[921600] = B921600;
246                 baudRates[1000000] = B1000000;
247                 baudRates[1152000] = B1152000;
248                 baudRates[1500000] = B1500000;
249                 baudRates[2000000] = B2000000;
250                 baudRates[2500000] = B2500000;
251                 baudRates[3000000] = B3000000;
252                 baudRates[3500000] = B3500000;
253                 baudRates[4000000] = B4000000;
254             }
255             else version( FreeBSD )
256             {
257                 baudRates[7200] = B7200;
258                 baudRates[14400] = B14400;
259                 baudRates[28800] = B28800;
260                 baudRates[57600] = B57600;
261                 baudRates[76800] = B76800;
262                 baudRates[115200] = B115200;
263                 baudRates[230400] = B230400;
264                 baudRates[460800] = B460800;
265                 baudRates[921600] = B921600;
266             }
267             else version( solaris )
268             {
269                 baudRates[57600] = B57600;
270                 baudRates[76800] = B76800;
271                 baudRates[115200] = B115200;
272                 baudRates[153600] = B153600;
273                 baudRates[230400] = B230400;
274                 baudRates[307200] = B307200;
275                 baudRates[460800] = B460800;
276             }
277             else version(OSX)
278             {
279                 baudRates[7200] = B7200;
280                 baudRates[14400] = B14400;
281                 baudRates[28800] = B28800;
282                 baudRates[57600] = B57600;
283                 baudRates[76800] = B76800;
284                 baudRates[115200] = B115200;
285                 baudRates[230400] = B230400;
286             }
287         }
288 
289         private void create (const(char)[] file)
290         {
291             if(file.length == 0) throw new IOException("Empty port name");
292             if(file[0] != '/') file = "/dev/" ~ file;
293 
294             if(file.length > 5 && file[0..5] == "/dev/")
295                 str = file[5..$];
296             else
297                 str = "SerialPort@" ~ file;
298 
299             handle = posix.open(file.toStringz(), O_RDWR | O_NOCTTY | O_NONBLOCK);
300             if(handle == -1) {
301                 error();
302             }
303             if(posix.fcntl(handle, F_SETFL, 0) == -1) { // disable O_NONBLOCK
304                 error();
305             }
306 
307             termios options;
308             if(tcgetattr(handle, &options) == -1) {
309                 error();
310             }
311             cfsetispeed(&options, B0); // same as output baud rate
312             cfsetospeed(&options, B9600);
313             makeRaw(&options); // disable echo and special characters
314             tcsetattr(handle, TCSANOW, &options);
315         }
316 
317         private void makeRaw (termios *options)
318         {
319             options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
320                     | INLCR | IGNCR | ICRNL | IXON);
321             options.c_oflag &= ~OPOST;
322             options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
323             options.c_cflag &= ~(CSIZE | PARENB);
324             options.c_cflag |= CS8;
325         }
326 
327 
328         private static inout(char)[] rest (inout(char)[] str, in char[] prefix) {
329             if(str.length < prefix.length) return null;
330             if(str[0..prefix.length] != prefix) return null;
331             return str[prefix.length..$];
332         }
333 
334         private static bool isInRange (const(char)[] str, char lower, char upper) {
335             foreach(c; str) {
336                 if(c < lower || c > upper) return false;
337             }
338             return true;
339         }
340     }
341 }
342