| 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.SerialPort; |
|---|
| 12 |
|
|---|
| 13 |
import tango.core.Array : sort; |
|---|
| 14 |
import |
|---|
| 15 |
tango.core.Exception, |
|---|
| 16 |
tango.io.DeviceConduit, |
|---|
| 17 |
tango.stdc.stringz, |
|---|
| 18 |
tango.sys.Common, |
|---|
| 19 |
tango.text.Regex; |
|---|
| 20 |
|
|---|
| 21 |
version(Windows) { |
|---|
| 22 |
import Integer = tango.text.convert.Integer; |
|---|
| 23 |
import tango.stdc.stringz; |
|---|
| 24 |
} else version(Posix) { |
|---|
| 25 |
import |
|---|
| 26 |
tango.io.FilePath, |
|---|
| 27 |
tango.stdc.posix.termios; |
|---|
| 28 |
} |
|---|
| 29 |
|
|---|
| 30 |
/******************************************************************************* |
|---|
| 31 |
|
|---|
| 32 |
Enables applications to use a serial port (aka COM-port, ttyS). |
|---|
| 33 |
Usage is similar to that of FileConduit: |
|---|
| 34 |
--- |
|---|
| 35 |
auto serPort = new SerialPort("ttyS0"); |
|---|
| 36 |
serPort.speed = 38400; |
|---|
| 37 |
serPort.write("Hello world!"); |
|---|
| 38 |
serPort.close(); |
|---|
| 39 |
---- |
|---|
| 40 |
|
|---|
| 41 |
*******************************************************************************/ |
|---|
| 42 |
class SerialPort : DeviceConduit |
|---|
| 43 |
{ |
|---|
| 44 |
private static char[][] _ports; |
|---|
| 45 |
|
|---|
| 46 |
/******************************************************************************* |
|---|
| 47 |
|
|---|
| 48 |
Tries to enumerate all serial ports. While this usually works on |
|---|
| 49 |
Windows, it's more problematic on other OS. Posix provides no way to |
|---|
| 50 |
list serial ports, and the only option is searching through "/dev". |
|---|
| 51 |
Because there's no naming standard for the device files, this method |
|---|
| 52 |
must be ported for each OS. This method is also unreliable because |
|---|
| 53 |
the user could have created invalid device files, or deleted them. |
|---|
| 54 |
|
|---|
| 55 |
Returns: |
|---|
| 56 |
A string array of all the serial ports that could be found, in |
|---|
| 57 |
alphabetical order. Every string is formatted as a valid argument to |
|---|
| 58 |
the constructor, but the port may not be accessible. |
|---|
| 59 |
|
|---|
| 60 |
*******************************************************************************/ |
|---|
| 61 |
public static char[][] ports() |
|---|
| 62 |
{ |
|---|
| 63 |
if(_ports !is null) { |
|---|
| 64 |
return _ports; |
|---|
| 65 |
} |
|---|
| 66 |
version(Windows) { |
|---|
| 67 |
// try opening COM1...COM255 |
|---|
| 68 |
for(int i = 1; i <= 255; ++i) { |
|---|
| 69 |
char[] p = `\\.\COM` ~ Integer.toString(i); |
|---|
| 70 |
HANDLE port = CreateFileA(toStringz(p), GENERIC_READ | GENERIC_WRITE, 0, null, OPEN_EXISTING, 0, null); |
|---|
| 71 |
if(port != INVALID_HANDLE_VALUE) { |
|---|
| 72 |
_ports ~= p[`\\.\`.length..$]; // cut the leading \\.\ |
|---|
| 73 |
CloseHandle(port); |
|---|
| 74 |
} |
|---|
| 75 |
} |
|---|
| 76 |
} else version(Posix) { |
|---|
| 77 |
version(linux) { |
|---|
| 78 |
auto serPattern = new RegExpT!(char)("^tty(:?USB|S)[0-9]+$"); |
|---|
| 79 |
} else version(freebsd) { // untested |
|---|
| 80 |
auto serPattern = new RegExpT!(char)("^cua[ad][0-9]+$"); |
|---|
| 81 |
} else version(openbsd) { // untested |
|---|
| 82 |
auto serPattern = new RegExpT!(char)("^tty[0-9]{2}$"); |
|---|
| 83 |
} else version(solaris) { // untested |
|---|
| 84 |
auto serPattern = new RegExpT!(char)("^tty[a-z]$"); |
|---|
| 85 |
} else { |
|---|
| 86 |
return null; |
|---|
| 87 |
} |
|---|
| 88 |
auto dev = FilePath("/dev"); |
|---|
| 89 |
FilePath[] serPorts = dev.toList((FilePath path, bool isFolder) { |
|---|
| 90 |
return !isFolder && serPattern.test(path.name); |
|---|
| 91 |
}); |
|---|
| 92 |
_ports.length = serPorts.length; |
|---|
| 93 |
foreach(i, path; serPorts) { |
|---|
| 94 |
_ports[i] = path.name; |
|---|
| 95 |
} |
|---|
| 96 |
} |
|---|
| 97 |
sort(_ports); |
|---|
| 98 |
return _ports; |
|---|
| 99 |
} |
|---|
| 100 |
|
|---|
| 101 |
private char[] str; |
|---|
| 102 |
|
|---|
| 103 |
/******************************************************************************* |
|---|
| 104 |
|
|---|
| 105 |
Create a new SerialPort instance. The port will be opened and set to |
|---|
| 106 |
raw mode with 9600-8N1. |
|---|
| 107 |
|
|---|
| 108 |
Params: |
|---|
| 109 |
port = A string identifying the port. On Posix, this must be a device |
|---|
| 110 |
file like /dev/ttyS0. If the input doesn't begin with "/", |
|---|
| 111 |
"/dev/" is automatically prepended, so "ttyS0" is sufficent. |
|---|
| 112 |
On Windows, this must be a device name like COM1. |
|---|
| 113 |
|
|---|
| 114 |
*******************************************************************************/ |
|---|
| 115 |
public this(char[] port) |
|---|
| 116 |
{ |
|---|
| 117 |
create(port); |
|---|
| 118 |
} |
|---|
| 119 |
|
|---|
| 120 |
public ~this() |
|---|
| 121 |
{ |
|---|
| 122 |
detach(); |
|---|
| 123 |
} |
|---|
| 124 |
|
|---|
| 125 |
/******************************************************************************* |
|---|
| 126 |
|
|---|
| 127 |
Sets the baud rate of this port. Usually, the baud rate can only be set |
|---|
| 128 |
to fixed values (common values are 1200 * 2^n). |
|---|
| 129 |
|
|---|
| 130 |
*******************************************************************************/ |
|---|
| 131 |
public SerialPort speed(uint speed) |
|---|
| 132 |
{ |
|---|
| 133 |
version(Posix) { |
|---|
| 134 |
speed_t *baud = speed in baudRates; |
|---|
| 135 |
if(baud is null) { |
|---|
| 136 |
throw new IOException("Invalid baud rate."); |
|---|
| 137 |
} |
|---|
| 138 |
|
|---|
| 139 |
termios options; |
|---|
| 140 |
tcgetattr(handle, &options); |
|---|
| 141 |
cfsetospeed(&options, *baud); |
|---|
| 142 |
tcsetattr(handle, TCSANOW, &options); |
|---|
| 143 |
} |
|---|
| 144 |
version(Win32) { |
|---|
| 145 |
DCB config; |
|---|
| 146 |
GetCommState(handle, &config); |
|---|
| 147 |
config.BaudRate = speed; |
|---|
| 148 |
if(!SetCommState(handle, &config)) error(); |
|---|
| 149 |
} |
|---|
| 150 |
return this; |
|---|
| 151 |
} |
|---|
| 152 |
|
|---|
| 153 |
version(Win32) { |
|---|
| 154 |
private void create(char[] port) |
|---|
| 155 |
{ |
|---|
| 156 |
str = port; |
|---|
| 157 |
handle = CreateFileA((`\\.\` ~ port).toStringz(), GENERIC_READ | GENERIC_WRITE, 0, null, OPEN_EXISTING, 0, null); |
|---|
| 158 |
if(handle == INVALID_HANDLE_VALUE) { |
|---|
| 159 |
error(); |
|---|
| 160 |
} |
|---|
| 161 |
DCB config; |
|---|
| 162 |
GetCommState(handle, &config); |
|---|
| 163 |
config.BaudRate = 9600; |
|---|
| 164 |
config.ByteSize = 8; |
|---|
| 165 |
config.Parity = NOPARITY; |
|---|
| 166 |
config.StopBits = ONESTOPBIT; |
|---|
| 167 |
config.flag0 |= bm_DCB_fBinary | bm_DCB_fParity; |
|---|
| 168 |
if(!SetCommState(handle, &config)) error(); |
|---|
| 169 |
} |
|---|
| 170 |
} |
|---|
| 171 |
|
|---|
| 172 |
version(Posix) { |
|---|
| 173 |
private static speed_t[uint] baudRates; |
|---|
| 174 |
|
|---|
| 175 |
static this() |
|---|
| 176 |
{ |
|---|
| 177 |
baudRates[50] = B50; |
|---|
| 178 |
baudRates[75] = B75; |
|---|
| 179 |
baudRates[110] = B110; |
|---|
| 180 |
baudRates[134] = B134; |
|---|
| 181 |
baudRates[150] = B150; |
|---|
| 182 |
baudRates[200] = B200; |
|---|
| 183 |
baudRates[300] = B300; |
|---|
| 184 |
baudRates[600] = B600; |
|---|
| 185 |
baudRates[1200] = B1200; |
|---|
| 186 |
baudRates[1800] = B1800; |
|---|
| 187 |
baudRates[2400] = B2400; |
|---|
| 188 |
baudRates[9600] = B9600; |
|---|
| 189 |
baudRates[4800] = B4800; |
|---|
| 190 |
baudRates[19200] = B19200; |
|---|
| 191 |
baudRates[38400] = B38400; |
|---|
| 192 |
} |
|---|
| 193 |
|
|---|
| 194 |
private void create(char[] file) |
|---|
| 195 |
{ |
|---|
| 196 |
if(file.length == 0) throw new IOException("Empty port name"); |
|---|
| 197 |
if(file[0] != '/') file = "/dev/" ~ file; |
|---|
| 198 |
|
|---|
| 199 |
if(file.length > 5 && file[0..5] == "/dev/") |
|---|
| 200 |
str = file[5..$]; |
|---|
| 201 |
else |
|---|
| 202 |
str = "SerialPort@" ~ file; |
|---|
| 203 |
|
|---|
| 204 |
handle = posix.open(file.toStringz(), O_RDWR | O_NOCTTY | O_NONBLOCK); |
|---|
| 205 |
if(handle == -1) { |
|---|
| 206 |
error(); |
|---|
| 207 |
} |
|---|
| 208 |
if(posix.fcntl(handle, F_SETFL, 0) == -1) { // disable O_NONBLOCK |
|---|
| 209 |
error(); |
|---|
| 210 |
} |
|---|
| 211 |
|
|---|
| 212 |
termios options; |
|---|
| 213 |
if(tcgetattr(handle, &options) == -1) { |
|---|
| 214 |
error(); |
|---|
| 215 |
} |
|---|
| 216 |
cfsetispeed(&options, B0); // same as output baud rate |
|---|
| 217 |
cfsetospeed(&options, B9600); |
|---|
| 218 |
makeRaw(&options); // disable echo and special characters |
|---|
| 219 |
tcsetattr(handle, TCSANOW, &options); |
|---|
| 220 |
} |
|---|
| 221 |
|
|---|
| 222 |
private void makeRaw(termios *options) |
|---|
| 223 |
{ |
|---|
| 224 |
options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP |
|---|
| 225 |
| INLCR | IGNCR | ICRNL | IXON); |
|---|
| 226 |
options.c_oflag &= ~OPOST; |
|---|
| 227 |
options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); |
|---|
| 228 |
options.c_cflag &= ~(CSIZE | PARENB); |
|---|
| 229 |
options.c_cflag |= CS8; |
|---|
| 230 |
} |
|---|
| 231 |
} |
|---|
| 232 |
|
|---|
| 233 |
/******************************************************************************* |
|---|
| 234 |
|
|---|
| 235 |
Returns a string describing this serial port. |
|---|
| 236 |
For example: "ttyS0", "COM1", "cuad0" |
|---|
| 237 |
|
|---|
| 238 |
*******************************************************************************/ |
|---|
| 239 |
override public char[] toString() |
|---|
| 240 |
{ |
|---|
| 241 |
return str; |
|---|
| 242 |
} |
|---|
| 243 |
} |
|---|