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 }