1 // Copyright 2013 Gushcha Anton
2 /*
3 Boost Software License - Version 1.0 - August 17th, 2003
4 
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 // Written in D programming language
28 /**
29 *   Module providing instruments to work with serial port on Windows and Posix
30 *   OS. SerialPort class can enumerate available serial ports (works robust only
31 *   on Windows), check available baud rates.
32 *
33 *   Example:
34 *   --------
35 *   auto com = new SerialPort("COM1"); // ttyS0 on GNU/Linux, for instance
36 *   string test = "Hello, World!";
37 *   com.write(test.ptr);
38 *
39 *   ubyte[13] buff;
40 *   com.read(buff);
41 *   writeln(cast(string)buff);
42 *   --------
43 *
44 *   Note: Some points were extracted from Tango D2 library.
45 *   TODO: Serial port tweaking, asynchronous i/o.
46 */
47 module serial.device;
48 
49 import std.conv;
50 import std.stdio;
51 import std.string;
52 import core.time;
53 
54 version(Posix)
55 {
56     import core.sys.posix.unistd;
57     version(linux) {
58       import core.sys.linux.termios;
59       version(GNU) {
60         enum B57600  = 57600;
61       }
62     }
63     else version(OSX)
64     {
65       import core.sys.posix.termios;
66       enum B7200   = 7200;
67       enum B14400  = 14400;
68       enum B28800  = 28800;
69       enum B57600  = 57600;
70       enum B76800  = 76800;
71       enum B115200 = 115200;
72       enum B230400 = 230400;
73     }
74     else
75     {
76       import core.sys.posix.termios;
77     }
78 
79     import core.sys.posix.fcntl;
80     import core.sys.posix.sys.select;
81     import std.algorithm;
82     import std.file;
83 
84     alias core.sys.posix.unistd.read posixRead;
85     alias core.sys.posix.unistd.write posixWrite;
86     alias core.sys.posix.unistd.close posixClose;
87 }
88 version(Windows)
89 {
90     import core.sys.windows.windows;
91     import std.bitmanip;
92 }
93 
94 /**
95 *   Represents allowed baud rate speeds for serial port.
96 */
97 enum BaudRate : uint
98 {
99     BR_0      = 0,
100     BR_50     = 50,
101     BR_75     = 75,
102     BR_110    = 110,
103     BR_134    = 134,
104     BR_150    = 150,
105     BR_200    = 200,
106     BR_300    = 300,
107     BR_600    = 600,
108     BR_1200   = 1200,
109     BR_1800   = 1800,
110     BR_2400   = 2400,
111     BR_4800   = 4800,
112     BR_9600   = 9600,
113     BR_19200  = 19_200,
114     BR_38400  = 38_400,
115     BR_57600  = 57_600,
116     BR_115200 = 115_200,
117     BR_230400 = 230_400,
118     BR_UNKNOWN  // refers to unknown baud rate
119 }
120 
121 /**
122 *   Thrown when trying to setup serial port with
123 *   unsupported baud rate by current OS.
124 */
125 class SpeedUnsupportedException : Exception
126 {
127     BaudRate speed;
128 
129     this(BaudRate spd)
130     {
131         speed = spd;
132         super(text("Speed ", spd, " is unsupported!"));
133    }
134 }
135 
136 // The members of enums should be camelCased
137 // http://dlang.org/dstyle.html
138 enum Parity
139 {
140    none,
141    odd,
142    even
143 }
144 
145 class ParityUnsupportedException : Exception
146 {
147    Parity parity;
148 
149    this(Parity p)
150    {
151       parity = p;
152       super(text("Parity ", p, " is unsupported!"));
153    }
154 }
155 
156 enum DataBits
157 {
158    data5,
159    data6,
160    data7,
161    data8
162 }
163 
164 class DataBitsUnsupportedException : Exception
165 {
166    DataBits dataBits;
167 
168    this(DataBits d)
169    {
170       dataBits = d;
171       super(text("DataBits ", d, " is unsupported!"));
172    }
173 }
174 
175 enum StopBits
176 {
177    one,
178    onePointFive,
179    two
180 }
181 
182 class StopBitsUnsupportedException : Exception
183 {
184    StopBits stopBits;
185 
186    this(StopBits d)
187    {
188       stopBits = d;
189       super(text("StopBits ", d, " is unsupported!"));
190     }
191 }
192 
193 /**
194 *   Thrown when setting up new serial port parameters has failed.
195 */
196 class InvalidParametersException : Exception
197 {
198     string port;
199 
200     @safe pure nothrow this(string port, string file = __FILE__, size_t line = __LINE__)
201     {
202         this.port = port;
203         super("One of serial port "~ port ~ " parameters is invalid!", file, line);
204     }
205 }
206 
207 /**
208 *   Thrown when tried to open invalid serial port file.
209 */
210 class InvalidDeviceException : Exception
211 {
212     string port;
213 
214     @safe pure nothrow this(string port, string file = __FILE__, size_t line = __LINE__)
215     {
216         this.port = port;
217         super(text("Failed to open serial port with name ", port, "!"), file, line);
218     }
219 }
220 
221 /**
222 *   Thrown when trying to accept closed device.
223 */
224 class DeviceClosedException : Exception
225 {
226     @safe pure nothrow this(string file = __FILE__, size_t line = __LINE__)
227     {
228         super("Tried to access closed device!", file, line);
229     }
230 }
231 
232 /**
233 *	Thrown when read/write operations failed due timeout.
234 *
235 *   Exception carries useful info about number of read/wrote bytes until
236 *   timeout occurs. It takes sense only for Windows, as posix version
237 *   won't start reading until the port is empty.
238 */
239 class TimeoutException : Exception
240 {
241     /**
242     *   Transfers problematic $(B port) name and $(B size) of read/wrote bytes until
243     *   timeout.
244     */
245 	@safe pure nothrow this(string port, size_t size, string file = __FILE__, size_t line = __LINE__)
246 	{
247         this.size = size;
248 		super("Timeout is expired for serial port '"~port~"'", file, line);
249 	}
250 
251     size_t size;
252 }
253 
254 /**
255 *   Thrown when reading from serial port is failed.
256 */
257 class DeviceReadException : Exception
258 {
259     string port;
260 
261     @safe pure nothrow this(string port, string file = __FILE__, size_t line = __LINE__)
262     {
263         this.port = port;
264         super("Failed to read from serial port with name " ~ port, file, line);
265     }
266 }
267 
268 /**
269 *   Thrown when writing to serial port is failed.
270 */
271 class DeviceWriteException : Exception
272 {
273     string port;
274 
275     @safe pure nothrow this(string port, string file = __FILE__, size_t line = __LINE__)
276     {
277         this.port = port;
278         super("Failed to write to serial port with name " ~ port, file, line);
279     }
280 }
281 
282 version(Windows)
283 {
284     enum NOPARITY    = 0x0;
285     enum EVENPARITY  = 0x2;
286     enum MARKPARITY  = 0x3;
287     enum ODDPARITY   = 0x1;
288     enum SPACEPARITY = 0x4;
289 
290     enum ONESTOPBIT   = 0x0;
291     enum ONE5STOPBITS = 0x1;
292     enum TWOSTOPBITS  = 0x2;
293 
294     struct DCB
295     {
296         DWORD DCBlength;
297         DWORD BaudRate;
298 
299         mixin(bitfields!(
300         DWORD, "fBinary",           1,
301         DWORD, "fParity",           1,
302         DWORD, "fOutxCtsFlow",      1,
303         DWORD, "fOutxDsrFlow",      1,
304         DWORD, "fDtrControl",       2,
305         DWORD, "fDsrSensitivity",   1,
306         DWORD, "fTXContinueOnXoff", 1,
307         DWORD, "fOutX",             1,
308         DWORD, "fInX",              1,
309         DWORD, "fErrorChar",        1,
310         DWORD, "fNull",             1,
311         DWORD, "fRtsControl",       2,
312         DWORD, "fAbortOnError",     1,
313         DWORD, "fDummy2",           17));
314 
315         WORD  wReserved;
316         WORD  XonLim;
317         WORD  XoffLim;
318         BYTE  ByteSize;
319         BYTE  Parity;
320         BYTE  StopBits;
321         ubyte  XonChar;
322         ubyte  XoffChar;
323         ubyte  ErrorChar;
324         ubyte  EofChar;
325         ubyte  EvtChar;
326         WORD  wReserved1;
327     }
328 
329     struct COMMTIMEOUTS
330     {
331     	DWORD ReadIntervalTimeout;
332     	DWORD ReadTotalTimeoutMultiplier;
333     	DWORD ReadTotalTimeoutConstant;
334     	DWORD WriteTotalTimeoutMultiplier;
335     	DWORD WriteTotalTimeoutConstant;
336     }
337 
338     extern(Windows)
339     {
340         bool GetCommState(HANDLE hFile, DCB* lpDCB);
341         bool SetCommState(HANDLE hFile, DCB* lpDCB);
342         bool SetCommTimeouts(HANDLE hFile, COMMTIMEOUTS* lpCommTimeouts);
343     }
344 }
345 
346 /**
347 *   Main class encapsulating platform dependent files handles and
348 *   algorithms to work with serial port.
349 *
350 *   You can open serial port only once, after calling close any
351 *   nonstatic method will throw DeviceClosedException.
352 *
353 *   Note: Serial port enumerating is robust only on Windows, due
354 *   other platform doesn't strictly bound serial port names.
355 */
356 class SerialPort
357 {
358     /**
359     *   Creates new serial port instance.
360     *
361     *   Params:
362     *   port =  Port name. On Posix, it should be reffer to device file
363     *           like /dev/ttyS<N>. On Windows, port name should be like
364     *           COM<N> or any other.
365     *
366     *   Throws: InvalidParametersException, InvalidDeviceException
367     *
368     */
369     this(string port)
370     {
371         setup(port);
372     }
373 
374     /**
375     *   Creates new serial port instance.
376     *
377     *   Params:
378     *   port =  Port name. On Posix, it should be reffer to device file
379     *           like /dev/ttyS<N>. On Windows, port name should be like
380     *           COM<N> or any other.
381     *   readTimeout  = Setups constant timeout on read operations.
382     *   writeTimeout = Setups constant timeout on write operations. In posix is ignored.
383     *
384     *   Throws: InvalidParametersException, InvalidDeviceException
385     */
386     this(string port, Duration readTimeout, Duration writeTimeout)
387     {
388     	readTimeoutConst = readTimeout;
389     	writeTimeoutConst = writeTimeout;
390     	this(port);
391     }
392 
393     /**
394     *   Creates new serial port instance.
395     *
396     *   Params:
397     *   port =  Port name. On Posix, it should be reffer to device file
398     *           like /dev/ttyS<N>. On Windows, port name should be like
399     *           COM<N> or any other.
400     *   readTimeoutConst  = Setups constant timeout on read operations.
401     *   writeTimeoutConst = Setups constant timeout on write operations. In posix is ignored.
402     *   readTimeoutMult   = Setups timeout on read operations depending on buffer size.
403     *   writeTimeoutMult  = Setups timeout on write operations depending on buffer size.
404     *                       In posix is ignored.
405     *
406     *   Note: Total timeout is calculated as timeoutMult*buff.length + timeoutConst.
407     *   Throws: InvalidParametersException, InvalidDeviceException
408     */
409     this(string port, Duration readTimeoutMult, Duration readTimeoutConst,
410     		 		  Duration writeTimeoutMult, Duration writeTimeoutConst)
411     {
412     	this.readTimeoutMult = readTimeoutMult;
413     	this.readTimeoutConst = readTimeoutConst;
414     	this.writeTimeoutMult = writeTimeoutMult;
415     	this.writeTimeoutConst = writeTimeoutConst;
416     	this(port);
417     }
418 
419     ~this()
420     {
421         close();
422     }
423 
424     /**
425     *   Converts serial port to it port name.
426     *   Example: "ttyS0", "ttyS1", "COM1", "CNDA1".
427     */
428     override string toString()
429     {
430         return port;
431     }
432 
433     /**
434     *   Set the baud rate for this serial port. Speed values are usually
435     *   restricted to be 1200 * i ^ 2.
436     *
437     *   Note: that for Posix, the specification only mandates speeds up
438     *   to 38400, excluding speeds such as 7200, 14400 and 28800.
439     *   Most Posix systems have chosen to support at least higher speeds
440     *   though.
441     *
442     *   Throws: SpeedUnsupportedException if speed is unsupported by current system.
443     */
444     SerialPort speed(BaudRate speed) @property
445     {
446         if (closed) throw new DeviceClosedException();
447 
448         version(Posix)
449         {
450             speed_t baud = convertPosixSpeed(speed);
451 
452             termios options;
453             tcgetattr(handle, &options);
454             cfsetispeed(&options, baud);
455             cfsetospeed(&options, baud);
456 
457             tcsetattr(handle, TCSANOW, &options);
458         }
459         version(Windows)
460         {
461             DCB config;
462             GetCommState(handle, &config);
463             config.BaudRate = cast(DWORD)speed;
464             if(!SetCommState(handle, &config))
465             {
466                 throw new SpeedUnsupportedException(speed);
467             }
468         }
469 
470         return this;
471     }
472 
473     /**
474     *   Returns current port speed. Can return BR_UNKNONW baud rate
475     *   if speed was changed not by speed property or wreid errors are occured.
476     */
477     BaudRate speed() @property
478     {
479         if (closed) throw new DeviceClosedException();
480 
481         version(Posix)
482         {
483             termios options;
484             tcgetattr(handle, &options);
485             speed_t baud = cfgetospeed(&options);
486             return getBaudSpeed(cast(uint)baud);
487         }
488         version(Windows)
489         {
490             DCB config;
491             GetCommState(handle, &config);
492             return getBaudSpeed(cast(uint)config.BaudRate);
493         }
494     }
495 
496     /**
497     *  Set the parity-checking protocol.
498     */
499    SerialPort parity(Parity parity) @property
500    {
501       if (closed) throw new DeviceClosedException();
502 
503       version(Posix)
504       {
505          termios options;
506          tcgetattr(handle, &options);
507          final switch (parity) {
508             case Parity.none:
509                options.c_cflag &= ~PARENB;
510                break;
511             case Parity.odd:
512                options.c_cflag |= (PARENB | PARODD);
513                break;
514             case Parity.even:
515                options.c_cflag &= ~PARODD;
516                options.c_cflag |= PARENB;
517                break;
518          }
519          tcsetattr(handle, TCSANOW, &options);
520       }
521       version(Windows)
522       {
523          DCB config;
524          GetCommState(handle, &config);
525          final switch (parity) {
526             case Parity.none:
527                config.Parity = NOPARITY;
528                break;
529             case Parity.odd:
530                config.Parity = ODDPARITY;
531                break;
532             case Parity.even:
533                config.Parity = EVENPARITY;
534                break;
535          }
536 
537          if(!SetCommState(handle, &config))
538          {
539             throw new ParityUnsupportedException(parity);
540          }
541       }
542 
543       return this;
544    }
545 
546    /**
547     *   Returns current parity .
548     */
549    Parity parity() @property
550    {
551       if (closed) throw new DeviceClosedException();
552 
553       version(Posix)
554       {
555          termios options;
556          tcgetattr(handle, &options);
557          if ((options.c_cflag & PARENB) == 0) {
558             return Parity.none;
559          } else if ((options.c_cflag & PARODD) == PARODD) {
560             return Parity.odd;
561          } else {
562             return Parity.even;
563          }
564       }
565       version(Windows)
566       {
567          DCB config;
568          GetCommState(handle, &config);
569          switch (config.Parity) {
570             default:
571             case NOPARITY: return Parity.none;
572             case ODDPARITY: return Parity.odd;
573             case EVENPARITY: return Parity.even;
574          }
575       }
576    }
577 
578 
579    /**
580     *  Set the number of stop bits
581     */
582    SerialPort stopBits(StopBits stop) @property
583    {
584       if (closed) throw new DeviceClosedException();
585 
586       version(Posix)
587       {
588          termios options;
589          tcgetattr(handle, &options);
590          final switch (stop) {
591             case StopBits.one:
592                options.c_cflag &= ~CSTOPB;
593                break;
594             case StopBits.onePointFive:
595             case StopBits.two:
596                options.c_cflag |= CSTOPB;
597                break;
598          }
599          tcsetattr(handle, TCSANOW, &options);
600       }
601       version(Windows)
602       {
603          DCB config;
604          GetCommState(handle, &config);
605          final switch (stop) {
606             case StopBits.one:
607                config.StopBits = ONESTOPBIT;
608                break;
609             case StopBits.onePointFive:
610                config.StopBits = ONESTOPBIT;
611                break;
612             case StopBits.two:
613                config.StopBits = TWOSTOPBITS;
614                break;
615          }
616 
617          if(!SetCommState(handle, &config))
618          {
619             throw new StopBitsUnsupportedException(stop);
620          }
621       }
622       return this;
623    }
624 
625    StopBits stopBits() @property
626    {
627       if (closed) throw new DeviceClosedException();
628 
629       version(Posix)
630       {
631          termios options;
632          tcgetattr(handle, &options);
633          if ((options.c_cflag & CSTOPB) == CSTOPB) {
634             return StopBits.two;
635          } else {
636             return StopBits.one;
637          }
638       }
639       version(Windows)
640       {
641          DCB config;
642          GetCommState(handle, &config);
643          switch (config.StopBits)
644          {
645             case ONESTOPBIT: return StopBits.one;
646             default: return StopBits.two;
647          }
648       }
649    }
650 
651    /**
652     * Sets the standard length of data bits per byte
653     */
654    SerialPort dataBits(DataBits data) @property
655    {
656       if (closed) throw new DeviceClosedException();
657 
658       version(Posix)
659       {
660          termios options;
661          tcgetattr(handle, &options);
662 
663          options.c_cflag &= ~CSIZE;
664          final switch (data) {
665             case DataBits.data5:
666                options.c_cflag |= CS5;
667                break;
668             case DataBits.data6:
669                options.c_cflag |= CS6;
670                break;
671             case DataBits.data7:
672                options.c_cflag |= CS7;
673                break;
674             case DataBits.data8:
675                options.c_cflag |= CS8;
676                break;
677          }
678          tcsetattr(handle, TCSANOW, &options);
679       }
680 
681       version(Windows)
682       {
683          DCB config;
684          GetCommState(handle, &config);
685          final switch (data) {
686             case DataBits.data5:
687                config.ByteSize = 5;
688                break;
689             case DataBits.data6:
690                config.ByteSize = 6;
691                break;
692             case DataBits.data7:
693                config.ByteSize = 7;
694                break;
695             case DataBits.data8:
696                config.ByteSize = 8;
697                break;
698          }
699 
700          if(!SetCommState(handle, &config))
701          {
702             throw new DataBitsUnsupportedException(data);
703          }
704       }
705       return this;
706    }
707 
708    DataBits dataBits() @property
709    {
710       if (closed) throw new DeviceClosedException();
711 
712       version(Posix)
713       {
714          termios options;
715          tcgetattr(handle, &options);
716          if ((options.c_cflag & CS8) == CS8)
717          {
718             return DataBits.data8;
719          }
720          else if ((options.c_cflag & CS7) == CS7)
721          {
722             return DataBits.data7;
723          }
724          else if ((options.c_cflag & CS6) == CS6)
725          {
726             return DataBits.data6;
727          }
728          else
729          {
730             return DataBits.data5;
731          }
732       }
733       version(Windows)
734       {
735          DCB config;
736          GetCommState(handle, &config);
737          switch (config.ByteSize)
738          {
739             case 5: return DataBits.data5;
740             case 6: return DataBits.data6;
741             case 7: return DataBits.data7;
742             default:
743             case 8: return DataBits.data8;
744          }
745       }
746    }
747 
748    /**
749     *   Iterates over all bauds rate and tries to setup port with it.
750     *   Returns: array of successfully setuped baud rates for current
751     *   serial port.
752     */
753     BaudRate[] getBaudRates()
754     {
755         if (closed) throw new DeviceClosedException();
756         BaudRate currSpeed = speed;
757         BaudRate[] ret;
758         foreach(baud; __traits(allMembers, BaudRate))
759         {
760             auto baudRate = mixin("BaudRate."~baud);
761             if(baudRate != BaudRate.BR_UNKNOWN)
762             {
763                 try
764                 {
765                     speed = baudRate;
766                     ret ~= baudRate;
767                 }
768                 catch(SpeedUnsupportedException e)
769                 {
770 
771                 }
772             }
773         }
774 
775         speed = currSpeed;
776         return ret;
777     }
778 
779     /**
780     *   Tries to enumerate all serial ports. While this usually works on
781     *   Windows, it's more problematic on other OS. Posix provides no way
782     *   to list serial ports, and the only option is searching through
783     *   "/dev".
784     *
785     *   Because there's no naming standard for the device files, this method
786     *   must be ported for each OS. This method is also unreliable because
787     *   the user could have created invalid device files, or deleted them.
788     *
789     *   Returns:
790     *   A string array of all the serial ports that could be found, in
791     *   alphabetical order. Every string is formatted as a valid argument
792     *   to the constructor, but the port may not be accessible.
793     */
794     static string[] ports()
795     {
796         string[] ports;
797         version(Windows)
798         {
799             // try to open COM1..255
800             immutable pre = `\\.\COM`;
801             for(int i = 1; i <= 255; ++i)
802             {
803                 HANDLE port = CreateFileA(text(pre, i).toStringz, GENERIC_READ | GENERIC_WRITE, 0, null, OPEN_EXISTING, 0, null);
804                 if(port != INVALID_HANDLE_VALUE)
805                 {
806                     ports ~= text("COM", i);
807                     CloseHandle(port);
808                 }
809             }
810         }
811         version(Posix)
812         {
813             bool comFilter(DirEntry entry)
814             {
815                 bool isInRange(T, U)(T val, U lower, U upper)
816                 {
817                     auto cval = val.to!U;
818                     return cval >= lower && cval <= upper;
819                 }
820 
821                 version(linux)
822                 {
823                     return (entry.name.countUntil("ttyUSB") == 5
824                            || entry.name.countUntil("ttyS") == 5);
825                 }
826                 version(darwin)
827                 {
828                     return entry.name.countUntil("cu") == 5;
829                 }
830                 version(FreeBSD)
831                 {
832                     return (entry.name.countUntil("cuaa") == 5
833                            || entry.name.countUntil("cuad") == 5);
834                 }
835                 version(openbsd)
836                 {
837                     return entry.name.countUntil("tty") == 5;
838                 }
839                 version(solaris)
840                 {
841                     return entry.name.countUntil("tty") == 5
842                            && isInRange(entry.name[$-1], 'a', 'z');
843                 }
844             }
845 
846             auto portFiles = filter!(comFilter)(dirEntries("/dev",SpanMode.shallow));
847             foreach(entry; portFiles)
848             {
849                 ports ~= entry.name;
850             }
851         }
852         return ports;
853     }
854 
855     /**
856     *   Closing underlying serial port. You shouldn't use port after
857     *   it closing.
858     */
859     void close()
860     {
861         version(Windows)
862         {
863             if(handle !is null)
864             {
865                 CloseHandle(handle);
866                 handle = null;
867             }
868         }
869         version(Posix)
870         {
871             if(handle != -1)
872             {
873                 posixClose(handle);
874                 handle = -1;
875             }
876         }
877     }
878 
879     /**
880     *   Returns true if serial port was closed.
881     */
882     bool closed() @property
883     {
884         version(Windows)
885             return handle is null;
886         version(Posix)
887             return handle == -1;
888     }
889 
890     /**
891     *   Writes down array of bytes to serial port.
892     *
893     *   Throws: TimeoutException (Windows only)
894     */
895     void write(const(void[]) arr)
896     {
897         if (closed) throw new DeviceClosedException();
898 
899         version(Windows)
900         {
901             uint written;
902             if(!WriteFile(handle, arr.ptr,
903                 cast(uint)arr.length, &written, null))
904                     throw new DeviceWriteException(port);
905             if(arr.length != written)
906                 throw new TimeoutException(port, written);
907         }
908         version(Posix)
909         {
910             size_t totalWritten;
911             while(totalWritten < arr.length)
912             {
913                 ssize_t result = posixWrite(handle, arr[totalWritten..$].ptr, arr.length - totalWritten);
914                 if(result < 0)
915                     throw new DeviceWriteException(port);
916                 totalWritten += cast(size_t)result;
917             }
918         }
919     }
920 
921     /**
922     *   Fills up provided array with bytes from com port.
923     *   Returns: actual number of readed bytes.
924     *   Throws: DeviceReadException, TimeoutException
925     */
926     size_t read(void[] arr)
927     {
928         if (closed) throw new DeviceClosedException();
929 
930         version(Windows)
931         {
932             uint readed;
933             if(!ReadFile(handle, arr.ptr, cast(uint)arr.length, &readed, null))
934                 throw new DeviceReadException(port);
935             if(arr.length != readed)
936                 throw new TimeoutException(port, cast(size_t)readed);
937             return cast(size_t)readed;
938         }
939         version(Posix)
940         {
941             fd_set selectSet;
942             FD_ZERO(&selectSet);
943             FD_SET(handle, &selectSet);
944 
945             Duration totalReadTimeout = arr.length * readTimeoutMult + readTimeoutConst;
946 
947             timeval timeout;
948             timeout.tv_sec = cast(int)(totalReadTimeout.total!"seconds");
949             enum US_PER_MS = 1000;
950             timeout.tv_usec = cast(int)(totalReadTimeout.split().msecs * US_PER_MS);
951 
952             auto rv = select(handle + 1, &selectSet, null, null, &timeout);
953             if(rv == -1)
954             {
955                throw new DeviceReadException(port);
956             } else if(rv == 0)
957             {
958                throw new TimeoutException(port, 0);
959             }
960 
961             ssize_t result = posixRead(handle, arr.ptr, arr.length);
962             if(result < 0)
963             {
964                 throw new DeviceReadException(port);
965             }
966             return cast(size_t)result;
967         }
968     }
969 
970     protected
971     {
972         version(Windows)
973         {
974             private void setup(string port)
975             {
976                 this.port = port;
977                 handle = CreateFileA((`\\.\` ~ port).toStringz, GENERIC_READ | GENERIC_WRITE, 0, null, OPEN_EXISTING, 0, null);
978                 if(handle is INVALID_HANDLE_VALUE)
979                 {
980                     throw new InvalidDeviceException(port);
981                 }
982 
983                 DCB config;
984                 GetCommState(handle, &config);
985                 config.BaudRate = 9600;
986                 config.ByteSize = 8;
987                 config.Parity = NOPARITY;
988                 config.StopBits = ONESTOPBIT;
989                 config.fBinary = 1;
990                 config.fParity = 1;
991 
992                 COMMTIMEOUTS timeouts;
993                 timeouts.ReadIntervalTimeout         = 0;
994 		        timeouts.ReadTotalTimeoutMultiplier  = cast(DWORD)readTimeoutMult.total!"msecs";
995 		        timeouts.ReadTotalTimeoutConstant    = cast(DWORD)readTimeoutConst.total!"msecs";
996 		        timeouts.WriteTotalTimeoutMultiplier = cast(DWORD)writeTimeoutMult.total!"msecs";
997 		        timeouts.WriteTotalTimeoutConstant   = cast(DWORD)writeTimeoutConst.total!"msecs";
998 		        if (SetCommTimeouts(handle, &timeouts) == 0)
999 		        {
1000 		        	throw new InvalidParametersException(port);
1001 		        }
1002 
1003                 if(!SetCommState(handle, &config))
1004                 {
1005                     throw new InvalidParametersException(port);
1006                 }
1007             }
1008         }
1009 
1010         version(Posix)
1011         {
1012             private static __gshared speed_t[BaudRate] posixBRTable;
1013             shared static this()
1014             {
1015                 posixBRTable = [
1016                     BaudRate.BR_0 : B0,
1017                     BaudRate.BR_50 : B50,
1018                     BaudRate.BR_75 : B75,
1019                     BaudRate.BR_110 : B110,
1020                     BaudRate.BR_134 : B134,
1021                     BaudRate.BR_150 : B150,
1022                     BaudRate.BR_200 : B200,
1023                     BaudRate.BR_300 : B300,
1024                     BaudRate.BR_600 : B600,
1025                     BaudRate.BR_1200 : B1200,
1026                     BaudRate.BR_1800 : B1800,
1027                     BaudRate.BR_2400 : B2400,
1028                     BaudRate.BR_4800 : B4800,
1029                     BaudRate.BR_9600 : B9600,
1030                     BaudRate.BR_19200 : B19200,
1031                     BaudRate.BR_38400 : B38400,
1032                     BaudRate.BR_57600 : B57600,
1033                     BaudRate.BR_115200 : B115200,
1034                     BaudRate.BR_230400 : B230400
1035                 ];
1036             }
1037 
1038             speed_t convertPosixSpeed(BaudRate baud)
1039             {
1040                 if(baud in posixBRTable) return posixBRTable[baud];
1041                 throw new SpeedUnsupportedException(baud);
1042             }
1043 
1044             void setup(string file)
1045             {
1046                 if(file.length == 0) throw new InvalidDeviceException(file);
1047 
1048             	port = file;
1049 
1050                 handle = open(file.toStringz(), O_RDWR | O_NOCTTY | O_NONBLOCK);
1051                 if(handle == -1)
1052                 {
1053                     throw new InvalidDeviceException(file);
1054                 }
1055                 if(fcntl(handle, F_SETFL, 0) == -1)  // disable O_NONBLOCK
1056                 {
1057                     throw new InvalidDeviceException(file);
1058                 }
1059 
1060                 termios options;
1061                 if(tcgetattr(handle, &options) == -1)
1062                 {
1063                     throw new InvalidDeviceException(file);
1064                 }
1065                 cfsetispeed(&options, B0); // same as output baud rate
1066                 cfsetospeed(&options, B9600);
1067                 makeRaw(options); // disable echo and special characters
1068                 tcsetattr(handle, TCSANOW, &options);
1069             }
1070 
1071             void makeRaw (ref termios options)
1072             {
1073                 options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP |
1074                                      INLCR | IGNCR | ICRNL | IXON);
1075                 options.c_oflag &= ~OPOST;
1076                 options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
1077                 options.c_cflag &= ~(CSIZE | PARENB);
1078                 options.c_cflag |= CS8;
1079             }
1080         }
1081 
1082         private static __gshared BaudRate[uint] baudRatetoUint;
1083         shared static this()
1084         {
1085             version(Windows)
1086             {
1087                 baudRatetoUint = [
1088                     0 : BaudRate.BR_0,
1089                     50 : BaudRate.BR_50,
1090                     75 : BaudRate.BR_75,
1091                     110 : BaudRate.BR_110,
1092                     134 : BaudRate.BR_134,
1093                     150 : BaudRate.BR_150,
1094                     200 : BaudRate.BR_200,
1095                     300 : BaudRate.BR_300,
1096                     600 : BaudRate.BR_600,
1097                     1200 : BaudRate.BR_1200,
1098                     1800 : BaudRate.BR_1800,
1099                     2400 : BaudRate.BR_2400,
1100                     4800 : BaudRate.BR_4800,
1101                     9600 : BaudRate.BR_9600,
1102                     38_400 : BaudRate.BR_38400,
1103                     57_600 : BaudRate.BR_57600,
1104                     115_200 : BaudRate.BR_115200,
1105                     230_400 : BaudRate.BR_230400
1106                 ];
1107             }
1108             version(linux)
1109             {
1110                 baudRatetoUint = [
1111                     B0 : BaudRate.BR_0,
1112                     B50 : BaudRate.BR_50,
1113                     B75 : BaudRate.BR_75,
1114                     B110 : BaudRate.BR_110,
1115                     B134 : BaudRate.BR_134,
1116                     B150 : BaudRate.BR_150,
1117                     B200 : BaudRate.BR_200,
1118                     B300 : BaudRate.BR_300,
1119                     B600 : BaudRate.BR_600,
1120                     B1200 : BaudRate.BR_1200,
1121                     B1800 : BaudRate.BR_1800,
1122                     B2400 : BaudRate.BR_2400,
1123                     B4800 : BaudRate.BR_4800,
1124                     B9600 : BaudRate.BR_9600,
1125                     B38400 : BaudRate.BR_38400,
1126                     B57600 : BaudRate.BR_57600,
1127                     B115200 : BaudRate.BR_115200,
1128                     B230400 : BaudRate.BR_230400
1129                 ];
1130             }
1131         }
1132 
1133         static BaudRate getBaudSpeed(uint value)
1134         {
1135             if(value in baudRatetoUint) return baudRatetoUint[value];
1136             return BaudRate.BR_UNKNOWN;
1137         }
1138     }
1139 
1140     private
1141     {
1142         /// Port name
1143         string port;
1144         /// Port handle
1145         version(Posix)
1146             int handle = -1;
1147         version(Windows)
1148             HANDLE handle = null;
1149 
1150         Duration readTimeoutMult;
1151         Duration readTimeoutConst;
1152         Duration writeTimeoutMult;
1153         Duration writeTimeoutConst;
1154     }
1155 }