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