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