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 }