├── .appveyor.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dub.sdl ├── example └── monitor │ ├── .gitignore │ ├── dub.sdl │ └── source │ └── app.d ├── icon.png ├── icon.svg └── source └── serialport ├── base.d ├── block.d ├── config.d ├── exception.d ├── fiberready.d ├── nonblock.d ├── package.d ├── types.d ├── ut.d └── util.d /.appveyor.yml: -------------------------------------------------------------------------------- 1 | platform: x64 2 | environment: 3 | matrix: 4 | - DC: dmd 5 | DVersion: beta 6 | arch: x64 7 | - DC: dmd 8 | DVersion: beta 9 | arch: x86 10 | - DC: dmd 11 | DVersion: stable 12 | arch: x64 13 | - DC: dmd 14 | DVersion: stable 15 | arch: x86 16 | - DC: ldc 17 | DVersion: beta 18 | arch: x86 19 | - DC: ldc 20 | DVersion: beta 21 | arch: x64 22 | - DC: ldc 23 | DVersion: stable 24 | arch: x86 25 | - DC: ldc 26 | DVersion: stable 27 | arch: x64 28 | 29 | skip_tags: false 30 | 31 | branches: 32 | only: 33 | - master 34 | 35 | #before_build: 36 | # - bcdedit.exe -set loadoptions DDISABLE_INTEGRITY_CHECKS 37 | # - bcdedit.exe -set TESTSIGNING ON 38 | # - ps: sleep 5 39 | # - ps: restart-computer -f 40 | # - ps: sleep 5 41 | # - choco install com0com 42 | # - cd "C:\Program Files (x86)\com0com\" 43 | # - .\setupc.exe --silent install PortName=COM33 PortName=COM34 44 | 45 | install: 46 | - ps: function ResolveLatestDMD 47 | { 48 | $version = $env:DVersion; 49 | if($version -eq "stable") { 50 | $latest = (Invoke-WebRequest "http://downloads.dlang.org/releases/LATEST").toString(); 51 | $url = "http://downloads.dlang.org/releases/2.x/$($latest)/dmd.$($latest).windows.7z"; 52 | }elseif($version -eq "beta") { 53 | $latest = (Invoke-WebRequest "http://downloads.dlang.org/pre-releases/LATEST").toString(); 54 | $latestVersion = $latest.split("-")[0].split("~")[0]; 55 | $url = "http://downloads.dlang.org/pre-releases/2.x/$($latestVersion)/dmd.$($latest).windows.7z"; 56 | }else { 57 | $url = "http://downloads.dlang.org/releases/2.x/$($version)/dmd.$($version).windows.7z"; 58 | } 59 | $env:PATH += ";C:\dmd2\windows\bin;"; 60 | return $url; 61 | } 62 | - ps: function ResolveLatestLDC 63 | { 64 | $version = $env:DVersion; 65 | $arch = $env:arch; 66 | if($version -eq "stable") { 67 | $latest = (Invoke-WebRequest "https://ldc-developers.github.io/LATEST").toString().replace("`n","").replace("`r",""); 68 | $url = "https://github.com/ldc-developers/ldc/releases/download/v$($latest)/ldc2-$($latest)-windows-$($arch).7z"; 69 | }elseif($version -eq "beta") { 70 | $latest = (Invoke-WebRequest "https://ldc-developers.github.io/LATEST_BETA").toString().replace("`n","").replace("`r",""); 71 | $url = "https://github.com/ldc-developers/ldc/releases/download/v$($latest)/ldc2-$($latest)-windows-$($arch).7z"; 72 | } else { 73 | $latest = $version; 74 | $url = "https://github.com/ldc-developers/ldc/releases/download/v$($version)/ldc2-$($version)-windows-$($arch).7z"; 75 | } 76 | $env:PATH += ";C:\ldc2-$($latest)-windows-$($arch)\bin"; 77 | $env:DC = "ldc2"; 78 | return $url; 79 | } 80 | - ps: function SetUpDCompiler 81 | { 82 | $env:toolchain = "msvc"; 83 | if($env:DC -eq "dmd"){ 84 | echo "downloading ..."; 85 | $url = ResolveLatestDMD; 86 | echo $url; 87 | Invoke-WebRequest $url -OutFile "c:\dmd.7z"; 88 | echo "finished."; 89 | pushd c:\\; 90 | 7z x dmd.7z > $null; 91 | popd; 92 | }elseif($env:DC -eq "ldc"){ 93 | echo "downloading ..."; 94 | $url = ResolveLatestLDC; 95 | echo $url; 96 | Invoke-WebRequest $url -OutFile "c:\ldc.zip"; 97 | echo "finished."; 98 | pushd c:\\; 99 | 7z x ldc.zip > $null; 100 | popd; 101 | } 102 | } 103 | - ps: SetUpDCompiler 104 | 105 | build_script: 106 | - ps: if($env:arch -eq "x86"){ 107 | $env:compilersetupargs = "x86"; 108 | $env:Darch = "x86"; 109 | $env:DConf = "m32"; 110 | }elseif($env:arch -eq "x64"){ 111 | $env:compilersetupargs = "amd64"; 112 | $env:Darch = "x86_64"; 113 | $env:DConf = "m64"; 114 | } 115 | - ps: $env:compilersetup = "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall"; 116 | - '"%compilersetup%" %compilersetupargs%' 117 | 118 | test_script: 119 | - echo %PLATFORM% 120 | - echo %Darch% 121 | - echo %DC% 122 | - echo %PATH% 123 | - '%DC% --version' 124 | - dub test --arch=%Darch% --compiler=%DC% 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | dub.selections.json 3 | docs* 4 | doc 5 | __dummy.html 6 | *.a 7 | *.o 8 | *.lib 9 | *.dll 10 | *.obj 11 | *.exe 12 | *-test-* 13 | *~ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | 4 | os: 5 | - linux 6 | 7 | git: 8 | depth: 1 9 | 10 | addons: 11 | apt: 12 | packages: 13 | - socat 14 | 15 | language: d 16 | d: 17 | # actual 18 | - dmd-2.088.1 19 | - ldc-1.18.0 20 | # newer 21 | - dmd-nightly 22 | - ldc-beta 23 | # used 24 | - dmd-2.087.1 25 | - ldc-1.17.0 26 | # old 27 | - dmd-2.078.3 28 | - ldc-1.8.0 29 | # other 30 | - dmd-2.082.1 31 | - dmd-2.081.1 32 | - ldc-1.11.0 33 | - dmd-2.080.1 34 | - dmd-2.079.1 35 | - ldc-1.10.0 36 | - ldc-1.9.0 37 | 38 | matrix: 39 | allow_failures: 40 | - d: dmd-nightly 41 | os: linux 42 | - d: ldc-beta 43 | os: linux 44 | 45 | script: 46 | - dub test --compiler=${DC} --build=unittest-cov 47 | 48 | after_success: 49 | - if [[ "$TRAVIS_OS_NAME" == "linux" && ${DC} == "dmd" ]]; then bash <(curl -s https://codecov.io/bash); fi -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Oleg Butko (deviator) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SerialPort 2 | 3 | [![Build Status](https://travis-ci.org/deviator/serialport.svg?branch=master)](https://travis-ci.org/deviator/serialport) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/64m852qc8j3re1y1?svg=true)](https://ci.appveyor.com/project/deviator/serialport) 5 | [![codecov](https://codecov.io/gh/deviator/serialport/branch/master/graph/badge.svg)](https://codecov.io/gh/deviator/serialport) 6 | [![Dub](https://img.shields.io/dub/v/serialport.svg)](http://code.dlang.org/packages/serialport) 7 | [![Downloads](https://img.shields.io/dub/dt/serialport.svg)](http://code.dlang.org/packages/serialport) 8 | [![License](https://img.shields.io/dub/l/serialport.svg)](http://code.dlang.org/packages/serialport) 9 | 10 | [Docs](http://serialport.dpldocs.info/serialport.html) generated by [adrdox](https://github.com/adamdruppe/adrdox). 11 | 12 | Library provides versatile work with serial port for Linux, Windows and MacOS. 13 | 14 | ## Simple usage 15 | 16 | ```d 17 | auto com = new SerialPortBlk("/dev/ttyUSB0", 19200); 18 | 19 | // setting parameters example 20 | com.config = SPConfig(9600, DataBits.data8, Parity.none, StopBits.one) 21 | com.set(19200).set(DataBits.data8); 22 | com.stopBits = StopBits.two; 23 | // set 9600 baudrate, 8 data bits, no parity and one stop bit 24 | com.set("9600:8N1"); 25 | 26 | auto cnt = com.write(someDataArray); 27 | // cnt must be equal someDataArray.length 28 | 29 | // res1 is slice of bufferForReading with readed data 30 | auto res1 = com.read(bufferForReading); 31 | ``` 32 | 33 | See also example: [monitor](example/monitor). 34 | 35 | ## Class hierarchy 36 | 37 | ``` 38 | abstract 39 | SerialPortBase 40 | ^ ^ 41 | | | 42 | | SerialPortNonBlk 43 | | 44 | abstract 45 | SerialPort 46 | ^ ^ 47 | | | 48 | | SerialPortFR 49 | | 50 | SerialPortBlk 51 | ``` 52 | 53 | Class `SerialPortBase` provides work with settings (baudrate, stop bits etc). 54 | 55 | Class `SerialPortNonBlk` provides non-blocking `void[] read(void[] buf)` 56 | (immediatlly return data in system serial port buffer) and 57 | `size_t write(const(void[]))` (return writed bytes count at the first onset). 58 | 59 | Class `SerialPort` provides `void[] read(void[] buf, CanRead cr=CanRead.allOrNothing)`, 60 | `void write(const(void[]))` and timeouts properties. 61 | 62 | Class `SerialPortBlk` provides blocking `read` and `write`. 63 | 64 | If you want use library in fibers it provides `SerialPortFR` (Fiber Ready), 65 | where `read` and `write` is loops: call non-blocking read and write and 66 | sleep between tries. Loops algorithms use `Fiber.yield` if available, 67 | or `Thread.sleep` as failback. If you want redefine this behavior, you 68 | can set `void delegate(Duration) @nogc sleepFunc` field directly or through 69 | last parameter of ctor. 70 | 71 | `write` method of `SerialPort` can throw `TimeoutException` if it can't 72 | finish write all data to serial port during 73 | `timeout = writeTimeout + writeTimeoutMult * data.length`. 74 | 75 | `read` method of `SerialPort` also can throw `TimeoutException`, 76 | but here the behavior can be different depending on the `CanRead` flag. 77 | 78 | Receive data time schema: 79 | 80 | ``` 81 | ---|-------|--------------|-------|--> t 82 | call | | | 83 | read | | | 84 | | |<----data receive---->| 85 | | |===== ==== | ======| 86 | | | | 87 | | |<-readedData->| 88 | | | 89 | |<---readTimeoutSum--->| 90 | | return 91 | |<---read work time--->| 92 | ``` 93 | 94 | where `readTimeoutSum = readTimeout + readTimeoutMult * dataBuffer.length;` 95 | 96 | if `CanRead cr` flag is: 97 | 98 | * `CanRead.allOrNothing` 99 | 100 | ```d 101 | if (readedData.length < dataBuffer.length) 102 | throw TimeoutException(port); 103 | else return readedData; 104 | ``` 105 | 106 | * `CanRead.anyNonZero` 107 | 108 | ```d 109 | if (readedData.length == 0) 110 | throw TimeoutException(port); 111 | else return readedData; 112 | ``` 113 | 114 | * `CanRead.zero` 115 | 116 | ```d 117 | return readedData; 118 | ``` 119 | 120 | ## `SerialPortFR.readContinues` method 121 | 122 | void[] readContinues(void[] arr, Duration startTimeout=1.seconds, Duration frameGap=50.msecs, bool expectAnything=true) 123 | 124 | It reads in loop from serial port while silent time is less what `frameGap` and 125 | throws `TimeoutException` only if timeout is expires when no data was readed and 126 | `expectAnything` flag is setted. 127 | 128 | ``` 129 | ------|--------|-----|------------|-----|------------> t 130 | call | | | | 131 | readContinues | | | | 132 | | | | | | 133 | | |<---------data receive---------->| 134 | | |=== ===== ======| | |== =| data stream 135 | | | | | | | | 136 | |<--timeout--->| | | | | 137 | | |<-1->| |<2>| |<-3->| 138 | | | | | 139 | | |<---readedData--->| | 140 | | return 141 | |<-------readAll work time------->| 142 | 143 | (1) if readedData.length > 0 then continue reading 144 | else if expectAnything throw TimeoutException 145 | else return readedData (empty) 146 | (2) silent time, if silent < frameGap then continue reading 147 | (3) else if silent > frameGap then stop reading 148 | and return readedData 149 | ``` 150 | 151 | It's useful if you don't know how much data can come: 152 | 153 | * allocate buffer for reading (4kB for example) 154 | * call `readContinues` and get data frame 155 | 156 | ## Warning 157 | 158 | **unix systems allow only standard speeds:** 159 | 160 | **[0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400]** 161 | 162 | ## Tests 163 | 164 | ### Real hardware 165 | 166 | Two paired USB->UART (FTDI FT232RL) uses for tests on linux and windows. 167 | 168 | ### CI 169 | 170 | For linux and OSX tested (socat as tty pipe creator) 171 | 172 | * dmd (from 2.79.1 to actual and nightly) 173 | * ldc (from 1.8.0 to actual and beta) 174 | 175 | For windows tested (build only, see [note](#note)) fox x86 and x64 176 | 177 | * dmd stable and nightly 178 | * ldc stable and beta 179 | 180 | See [.travis.yml](.travis.yml) [.appveyor.yml](.appveyor.yml) 181 | 182 | ### NOTE 183 | 184 | 1. Windows not full tested by CI (no real test with virtual com ports) 185 | because I did not configure to adjust the work com0com program 186 | https://help.appveyor.com/discussions/questions/427-how-can-i-use-com0com -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "serialport" 2 | targetType "library" 3 | description "Crossplatform work with serialport" 4 | authors "Oleg Butko (deviator)" 5 | copyright "Copyright © 2017-2018" 6 | license "MIT" -------------------------------------------------------------------------------- /example/monitor/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | __test__*__ 7 | monitor 8 | -------------------------------------------------------------------------------- /example/monitor/dub.sdl: -------------------------------------------------------------------------------- 1 | name "monitor" 2 | targetType "executable" 3 | description "serial port monitor" 4 | dependency "serialport" path="../.." 5 | -------------------------------------------------------------------------------- /example/monitor/source/app.d: -------------------------------------------------------------------------------- 1 | import std.algorithm : max; 2 | import std.stdio; 3 | import std.string; 4 | import std.range; 5 | import core.thread; 6 | 7 | import serialport; 8 | 9 | int main(string[] args) 10 | { 11 | string port; 12 | size_t step = 8; 13 | 14 | void[1024*4] data = void; 15 | 16 | if (args.length < 2) 17 | { 18 | stderr.writeln("use: monitor /dev/"); 19 | return 1; 20 | } 21 | 22 | auto msg = "listen " ~ args[1]; 23 | 24 | size_t pcount=0; 25 | 26 | void dots(bool reset=false) 27 | { 28 | if (pcount > 5) reset = true; 29 | if (reset) 30 | { 31 | auto w = " ".repeat(msg.length+pcount+40).join; 32 | stdout.write("\r", w, "\r"); 33 | stdout.write(msg); 34 | pcount = 0; 35 | } 36 | else 37 | { 38 | pcount++; 39 | stdout.write("."); 40 | } 41 | stdout.flush(); 42 | } 43 | 44 | auto com = new SerialPortNonBlk(args[1], "9600:8N1"); 45 | 46 | stdout.writeln("port config: ", com.config); 47 | 48 | dots(true); 49 | stdout.flush(); 50 | 51 | while (true) 52 | { 53 | void[] tmp; 54 | while(tmp.length == 0) 55 | { 56 | tmp = com.read(data); 57 | Thread.sleep(500.msecs); 58 | dots(); 59 | } 60 | 61 | writeln(); 62 | writeln("-- text\n", cast(string)tmp); 63 | writeln("-- end\n\n-- data"); 64 | 65 | size_t i; 66 | foreach (c; (cast(ubyte[])tmp).chunks(step)) 67 | { 68 | auto hex = format("%(0x%02X %)", c); 69 | auto dec = format("%(% 4d%)", c); 70 | writefln("%05d..%05d > %s | %s", i*step, i*step + c.length, 71 | hex ~ " ".repeat(max(0,40-hex.length-1)).join, dec); 72 | i++; 73 | } 74 | writefln("-- end\n\nreceive %d bytes\n", tmp.length); 75 | 76 | dots(true); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deviator/serialport/4986eddc8bd46b9ebf87011fce1ea3ac79f158a5/icon.png -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 25 | 28 | 32 | 36 | 37 | 40 | 44 | 48 | 49 | 59 | 68 | 77 | 86 | 95 | 104 | 113 | 122 | 131 | 140 | 149 | 159 | 169 | 170 | 186 | 188 | 189 | 191 | image/svg+xml 192 | 194 | 195 | 196 | 197 | 198 | 203 | 208 | 213 | 214 | 218 | 223 | 228 | 233 | 238 | 243 | 248 | 253 | 258 | 263 | 264 | 265 | 266 | -------------------------------------------------------------------------------- /source/serialport/base.d: -------------------------------------------------------------------------------- 1 | /// 2 | module serialport.base; 3 | 4 | package import std.algorithm; 5 | package import std.array; 6 | package import std.conv : to, text; 7 | package import std.exception; 8 | package import std.experimental.logger; 9 | package import std.path; 10 | package import std.string; 11 | package import std.datetime.stopwatch : StopWatch, AutoStart; 12 | package import core.time; 13 | 14 | package import serialport.config; 15 | package import serialport.exception; 16 | package import serialport.types; 17 | package import serialport.util; 18 | 19 | /++ 20 | +/ 21 | abstract class SerialPortBase 22 | { 23 | protected: 24 | /// 25 | string port; 26 | 27 | SPHandle _handle = initHandle; 28 | 29 | public: 30 | 31 | /// 32 | alias Config = SPConfig; 33 | 34 | /++ Construct SerialPort using extend mode string. 35 | 36 | First part of extend mode string must have port name 37 | (e.g. "com1" or "/dev/ttyUSB0"), second part is equal 38 | to config mode string. Parts separates by `modeSplitChar` (`:`). 39 | 40 | Example extend mode string: "/dev/ttyUSB0:9600:8N1" 41 | 42 | Params: 43 | exmode = extend mode string 44 | 45 | See_Also: Config.parse, Config.set(string mode) 46 | 47 | Throws: 48 | ParseModeException 49 | +/ 50 | this(string exmode) 51 | { 52 | auto s = exmode.split(modeSplitChar); 53 | if (s.length == 0) throw new ParseModeException("empty config mode"); 54 | this(s[0], s.length > 1 ? Config.parse(s[1..$].join(modeSplitChar)) : Config.init); 55 | } 56 | 57 | /++ Construct SerialPort using port name and mode string. 58 | 59 | Params: 60 | port = port name 61 | mode = config mode string 62 | 63 | See_Also: Config.parse, Config.set(string mode) 64 | +/ 65 | this(string port, string mode) { this(port, Config.parse(mode)); } 66 | 67 | /++ Params: 68 | port = port name 69 | baudRate = baudrate 70 | +/ 71 | this(string port, uint baudRate) { this(port, Config(baudRate)); } 72 | 73 | /++ Params: 74 | port = port name 75 | baudRate = baudrate 76 | mode = config mode string 77 | 78 | See_Also: Config.parse, Config.set(string mode) 79 | +/ 80 | this(string port, uint baudRate, string mode) 81 | { this(port, Config(baudRate).set(mode)); } 82 | 83 | /++ Params: 84 | port = port name 85 | conf = config of serialport 86 | +/ 87 | this(string port, Config conf) { reopen(port, conf); } 88 | 89 | ~this() { close(); } 90 | 91 | /// Close handle 92 | void close() @nogc 93 | { 94 | if (closed) return; 95 | closeHandle(_handle); 96 | _handle = initHandle; 97 | } 98 | 99 | /// Port name 100 | string name() const @nogc @property { return port; } 101 | 102 | /// 103 | inout(SPHandle) handle() inout @nogc @property { return _handle; } 104 | 105 | /// 106 | void reopen(string np, Config cfg) 107 | { 108 | if (!closed) close(); 109 | port = np; 110 | setup(cfg); 111 | } 112 | 113 | /// 114 | void reopen(string np) { reopen(np, config); } 115 | /// 116 | void reopen(Config cfg) { reopen(port, cfg); } 117 | /// 118 | void reopen() { reopen(port, config); } 119 | 120 | /++ Returns extend mode string (example: "/dev/ttyUSB0:38400:8N1") 121 | +/ 122 | override string toString() const { return port ~ modeSplitChar ~ config.mode; } 123 | 124 | /++ Set config value 125 | Params: 126 | T = typeof of parameter, avalable: 127 | int -> baudrate, 128 | DataBit -> dataBits, 129 | StopBits -> stopBits, 130 | Parity -> parity 131 | val = value 132 | +/ 133 | typeof(this) set(T)(T val) @nogc if (is(typeof(Config.init.set(val)))) 134 | { 135 | Config tmp = config; 136 | tmp.set(val); 137 | config = tmp; 138 | return this; 139 | } 140 | 141 | /// Set config mode string 142 | typeof(this) set(string val) 143 | { 144 | Config tmp = config; 145 | tmp.set(val); 146 | config = tmp; 147 | return this; 148 | } 149 | 150 | /// 151 | bool closed() const @property @nogc nothrow 152 | { 153 | version (Posix) return _handle == initHandle; 154 | version (Windows) return _handle is initHandle; 155 | } 156 | 157 | /++ Get config 158 | 159 | const for disallow `com.config.set(value)` 160 | use `com.set(value)` instead 161 | +/ 162 | const(Config) config() @property const @nogc 163 | { 164 | if (closed) throwPortClosedException(port); 165 | 166 | Config ret; 167 | 168 | version (Posix) 169 | { 170 | termios opt; 171 | m_tcgetattr(&opt); 172 | 173 | ret.baudRate = getUintBaudRate(); 174 | 175 | if (opt.c_cflag.hasFlag(PARODD)) ret.parity = Parity.odd; 176 | else if (!(opt.c_cflag & PARENB)) ret.parity = Parity.none; 177 | else ret.parity = Parity.even; 178 | 179 | if (opt.c_cflag.hasFlag(CS8)) ret.dataBits = DataBits.data8; 180 | else if (opt.c_cflag.hasFlag(CS7)) ret.dataBits = DataBits.data7; 181 | else if (opt.c_cflag.hasFlag(CS6)) ret.dataBits = DataBits.data6; 182 | else if (opt.c_cflag.hasFlag(CS5)) ret.dataBits = DataBits.data5; 183 | else throwSerialPortException(port, "unknown flags for databits"); 184 | 185 | ret.stopBits = opt.c_cflag.hasFlag(CSTOPB) ? StopBits.two : StopBits.one; 186 | } 187 | version (Windows) 188 | { 189 | DCB cfg; 190 | GetCommState(cast(SPHandle)_handle, &cfg); 191 | 192 | ret.baudRate = cast(uint)cfg.BaudRate; 193 | 194 | switch (cfg.Parity) 195 | { 196 | case NOPARITY: ret.parity = Parity.none; break; 197 | case ODDPARITY: ret.parity = Parity.odd; break; 198 | case EVENPARITY: ret.parity = Parity.even; break; 199 | default: throwSerialPortException(port, "unknown parity"); break; 200 | } 201 | 202 | if (cfg.ByteSize < 5 || cfg.ByteSize > 8) 203 | throwSerialPortException(port, "unknown databist count"); 204 | ret.dataBits = cast(DataBits)cfg.ByteSize; 205 | 206 | ret.stopBits = cfg.StopBits == ONESTOPBIT ? StopBits.one : StopBits.two; 207 | } 208 | 209 | return ret; 210 | } 211 | 212 | /// Set config 213 | void config(Config c) @property @nogc 214 | { 215 | if (closed) throwPortClosedException(port); 216 | 217 | version (Posix) 218 | { 219 | setUintBaudRate(c.baudRate); 220 | 221 | termios opt; 222 | m_tcgetattr(&opt); 223 | 224 | final switch (c.parity) 225 | { 226 | case Parity.none: 227 | opt.c_cflag &= ~PARENB; 228 | break; 229 | case Parity.odd: 230 | opt.c_cflag |= (PARENB | PARODD); 231 | break; 232 | case Parity.even: 233 | opt.c_cflag &= ~PARODD; 234 | opt.c_cflag |= PARENB; 235 | break; 236 | } 237 | 238 | final switch (c.stopBits) 239 | { 240 | case StopBits.one: 241 | opt.c_cflag &= ~CSTOPB; 242 | break; 243 | case StopBits.onePointFive: 244 | case StopBits.two: 245 | opt.c_cflag |= CSTOPB; 246 | break; 247 | } 248 | 249 | opt.c_cflag &= ~CSIZE; 250 | final switch (c.dataBits) with (DataBits) 251 | { 252 | case data5: opt.c_cflag |= CS5; break; 253 | case data6: opt.c_cflag |= CS6; break; 254 | case data7: opt.c_cflag |= CS7; break; 255 | case data8: opt.c_cflag |= CS8; break; 256 | } 257 | 258 | m_tcsetattr(TCSANOW, &opt); 259 | 260 | const test = config; 261 | if (test.baudRate != c.baudRate) throwUnsupportedException(port, c.baudRate); 262 | if (test.parity != c.parity) throwUnsupportedException(port, c.parity); 263 | if (test.stopBits != c.stopBits) throwUnsupportedException(port, c.stopBits); 264 | if (test.dataBits != c.dataBits) throwUnsupportedException(port, c.dataBits); 265 | } 266 | version (Windows) 267 | { 268 | DCB cfg; 269 | GetCommState(_handle, &cfg); 270 | 271 | if (cfg.BaudRate != cast(DWORD)c.baudRate) 272 | { 273 | cfg.BaudRate = cast(DWORD)c.baudRate; 274 | if (!SetCommState(_handle, &cfg)) 275 | throwUnsupportedException(port, c.baudRate); 276 | } 277 | 278 | auto tmpParity = NOPARITY; 279 | if (c.parity == Parity.odd) tmpParity = ODDPARITY; 280 | if (c.parity == Parity.even) tmpParity = EVENPARITY; 281 | 282 | if (cfg.Parity != tmpParity) 283 | { 284 | cfg.Parity = cast(ubyte)tmpParity; 285 | if (!SetCommState(_handle, &cfg)) 286 | throwUnsupportedException(port, c.parity); 287 | } 288 | 289 | auto tmpStopBits = ONESTOPBIT; 290 | if (c.stopBits == StopBits.two) tmpStopBits = TWOSTOPBITS; 291 | 292 | if (cfg.StopBits != tmpStopBits) 293 | { 294 | cfg.StopBits = cast(ubyte)tmpStopBits; 295 | if (!SetCommState(_handle, &cfg)) 296 | throwUnsupportedException(port, c.stopBits); 297 | } 298 | 299 | if (cfg.ByteSize != cast(typeof(cfg.ByteSize))c.dataBits) 300 | { 301 | cfg.ByteSize = cast(typeof(cfg.ByteSize))c.dataBits; 302 | if (!SetCommState(_handle, &cfg)) 303 | throwUnsupportedException(port, c.dataBits); 304 | } 305 | } 306 | } 307 | 308 | @property @nogc 309 | { 310 | /// 311 | Parity parity() { return config.parity; } 312 | /// 313 | uint baudRate() { return config.baudRate; } 314 | /// 315 | DataBits dataBits() { return config.dataBits; } 316 | /// 317 | StopBits stopBits() { return config.stopBits; } 318 | 319 | /// 320 | Parity parity(Parity v) { set(v); return v; } 321 | /// 322 | uint baudRate(uint v) { set(v); return v; } 323 | /// 324 | DataBits dataBits(DataBits v) { set(v); return v; } 325 | /// 326 | StopBits stopBits(StopBits v) { set(v); return v; } 327 | } 328 | 329 | /++ List of available serial ports in system 330 | +/ 331 | static string[] listAvailable() @property 332 | { 333 | version (linux) 334 | { 335 | import std.file : exists; 336 | return dirEntries("/sys/class/tty", SpanMode.shallow) 337 | .map!(a=>"/dev/"~a.name.baseName) 338 | .filter!(a=>a.exists) 339 | .array.sort.array 340 | ~ 341 | dirEntries("/dev/pts", SpanMode.shallow) 342 | .map!(a=>a.name).array.sort.array; 343 | } 344 | version (OSX) 345 | { 346 | return dirEntries("/dev/", "{tty,cu}*", SpanMode.shallow) 347 | .map!(a=>a.name).array; 348 | } 349 | version (Windows) 350 | { 351 | import std.windows.registry : Registry; 352 | string[] arr; 353 | try foreach (v; Registry 354 | .localMachine() 355 | .getKey("HARDWARE") 356 | .getKey("DEVICEMAP") 357 | .getKey("SERIALCOMM") 358 | .values) 359 | arr ~= v.value_SZ; 360 | catch (Throwable e) .error(e.msg); 361 | return arr; 362 | } 363 | } 364 | 365 | protected: 366 | void[] m_read(void[] buf) @nogc 367 | { 368 | // non-blocking algorithm 369 | if (closed) throwPortClosedException(port); 370 | 371 | auto ptr = buf.ptr; 372 | auto len = buf.length; 373 | 374 | size_t res; 375 | 376 | version (Posix) 377 | { 378 | auto sres = posixRead(_handle, ptr, len); 379 | 380 | // no bytes for read, it's ok 381 | if (sres < 0) 382 | { 383 | if (errno == EAGAIN) sres = 0; 384 | else throwReadException(port, "posix read", errno); 385 | } 386 | res = sres; 387 | } 388 | version (Windows) 389 | { 390 | uint sres; 391 | auto rfr = ReadFile(_handle, ptr, cast(uint)len, &sres, null); 392 | if (!rfr) 393 | { 394 | auto err = GetLastError(); 395 | if (err == ERROR_IO_PENDING) { /+ buffer empty +/ } 396 | else throwReadException(port, "win read", err); 397 | } 398 | res = sres; 399 | } 400 | 401 | return buf[0..res]; 402 | } 403 | 404 | size_t m_write(const(void[]) arr) @nogc 405 | { 406 | // non-blocking algorithm 407 | if (closed) throwPortClosedException(port); 408 | 409 | auto ptr = arr.ptr; 410 | auto len = arr.length; 411 | 412 | version (Posix) 413 | { 414 | ptrdiff_t res = posixWrite(_handle, ptr, len); 415 | if (res < 0) 416 | { 417 | if (errno == EAGAIN) res = 0; // buffer filled 418 | else throwWriteException(port, "posix write", errno); 419 | } 420 | } 421 | version (Windows) 422 | { 423 | uint res; 424 | auto wfr = WriteFile(_handle, ptr, cast(uint)len, &res, null); 425 | if (!wfr) 426 | { 427 | auto err = GetLastError(); 428 | if (err == ERROR_IO_PENDING) res = 0; 429 | else throwWriteException(port, "win write", err); 430 | } 431 | } 432 | 433 | return res; 434 | } 435 | 436 | /// open handler, set new config 437 | void setup(Config conf) 438 | { 439 | if (port.length == 0) 440 | throwSerialPortException("", "zero length name"); 441 | 442 | version (Posix) posixSetup(conf); 443 | else winSetup(); 444 | 445 | config = conf; 446 | } 447 | 448 | version (Posix) 449 | { 450 | void m_tcgetattr(termios* t) const @nogc 451 | { 452 | if (tcgetattr(_handle, t) == -1) 453 | throwSysCallException(port, "tcgetattr", errno); 454 | } 455 | 456 | void m_tcsetattr(int v, const(termios*) t) inout @nogc 457 | { 458 | if (tcsetattr(_handle, v, t) == -1) 459 | throwSysCallException(port, "tcsetattr", errno); 460 | } 461 | 462 | version (usetermios2) 463 | { 464 | void m_ioctl(int v, termios2* t) inout 465 | { 466 | if (ioctl(_handle, v, t) == -1) 467 | throwSysCallException(port, "ioctl", errno); 468 | } 469 | } 470 | 471 | void posixSetup(Config conf) 472 | { 473 | openPort(); 474 | initialConfig(conf); 475 | } 476 | 477 | void openPort() 478 | { 479 | _handle = open(port.toStringz(), O_RDWR | O_NOCTTY | O_NONBLOCK); 480 | if (_handle == -1) 481 | throwSysCallException(port, "open", errno); 482 | } 483 | 484 | /// Set termios.c_cc[VMIN] and .c_cc[VMAX] 485 | void setCC(ubyte[2] val) @nogc 486 | { 487 | termios opt; 488 | m_tcgetattr(&opt); 489 | opt.c_cc[VMIN] = val[0]; 490 | opt.c_cc[VTIME] = val[1]; 491 | m_tcsetattr(TCSADRAIN, &opt); 492 | } 493 | 494 | /// Get termios.c_cc[VMIN] and .c_cc[VMAX] 495 | ubyte[2] getCC() @nogc 496 | { 497 | ubyte[2] ret; 498 | termios opt; 499 | m_tcgetattr(&opt); 500 | ret[0] = opt.c_cc[VMIN]; 501 | ret[1] = opt.c_cc[VTIME]; 502 | return ret; 503 | } 504 | 505 | void initialConfig(Config conf) 506 | { 507 | termios opt; 508 | m_tcgetattr(&opt); 509 | 510 | // make raw 511 | opt.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | 512 | INLCR | IGNCR | ICRNL | IXON); 513 | opt.c_oflag &= ~OPOST; 514 | opt.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); 515 | opt.c_cflag &= ~(CSIZE | PARENB); 516 | opt.c_cc[VMIN] = 0; 517 | opt.c_cc[VTIME] = 0; 518 | 519 | // hardware flow control 520 | version (OSX) 521 | { 522 | /+ 523 | The CCTS_OFLOW (CRTSCTS) flag is currently unused. 524 | http://www.manpages.info/macosx/termios.4.html 525 | +/ 526 | } 527 | else 528 | { 529 | if (conf.hardwareDisableFlowControl) 530 | opt.c_cflag &= ~CRTSCTS; 531 | } 532 | 533 | opt.c_cflag |= CS8; 534 | 535 | m_tcsetattr(TCSANOW, &opt); 536 | } 537 | 538 | void setUintBaudRate(uint br) @nogc 539 | { 540 | version (usetermios2) 541 | { 542 | import std.conv : octal; 543 | enum CBAUD = octal!10017; 544 | enum BOTHER = octal!10000; 545 | 546 | termios2 opt2; 547 | m_ioctl(TCGETS2, &opt2); 548 | opt2.c_cflag &= ~CBAUD; //Remove current BAUD rate 549 | opt2.c_cflag |= BOTHER; //Allow custom BAUD rate using int input 550 | opt2.c_ispeed = br; //Set the input BAUD rate 551 | opt2.c_ospeed = br; //Set the output BAUD rate 552 | m_ioctl(TCSETS2, &opt2); 553 | } 554 | else 555 | { 556 | if (unixBaudList.countA(br) == 0) 557 | throwUnsupportedException(port, br); 558 | 559 | auto baud = unixBaudList.firstA2B(br, B0); 560 | 561 | termios opt; 562 | m_tcgetattr(&opt); 563 | cfsetispeed(&opt, B0); 564 | cfsetospeed(&opt, baud); 565 | m_tcsetattr(TCSANOW, &opt); 566 | } 567 | } 568 | 569 | uint getUintBaudRate() const @nogc 570 | { 571 | version (usetermios2) 572 | { 573 | termios2 opt2; 574 | m_ioctl(TCGETS2, &opt2); 575 | return opt2.c_ospeed; 576 | } 577 | else 578 | { 579 | termios opt; 580 | m_tcgetattr(&opt); 581 | version (OSX) alias true_speed_t = uint; 582 | else alias true_speed_t = typeof(cfgetospeed(&opt)); 583 | auto b = cast(true_speed_t)cfgetospeed(&opt); 584 | return unixBaudList.firstB2A(b, 0); 585 | } 586 | } 587 | } 588 | 589 | version (Windows) 590 | { 591 | void winSetup() 592 | { 593 | auto fname = `\\.\` ~ port; 594 | _handle = CreateFileA(fname.toStringz, 595 | GENERIC_READ | GENERIC_WRITE, 0, null, 596 | OPEN_EXISTING, 0, null); 597 | 598 | if (_handle is INVALID_HANDLE_VALUE) 599 | throwSysCallException(port, "CreateFileA", GetLastError()); 600 | 601 | SetupComm(_handle, 4096, 4096); 602 | PurgeComm(_handle, PURGE_TXABORT | PURGE_TXCLEAR | 603 | PURGE_RXABORT | PURGE_RXCLEAR); 604 | 605 | updTimeouts(); 606 | } 607 | 608 | void updTimeouts() @nogc 609 | { 610 | setTimeouts(DWORD.max, 0, 0, 0, 0); 611 | } 612 | 613 | void setTimeouts(DWORD rit, DWORD rttm, DWORD rttc, DWORD wttm, DWORD wttc) @nogc 614 | { 615 | COMMTIMEOUTS tm; 616 | tm.ReadIntervalTimeout = rit; 617 | tm.ReadTotalTimeoutMultiplier = rttm; 618 | tm.ReadTotalTimeoutConstant = rttc; 619 | tm.WriteTotalTimeoutMultiplier = wttm; 620 | tm.WriteTotalTimeoutConstant = wttc; 621 | 622 | if (SetCommTimeouts(_handle, &tm) == 0) 623 | throwSysCallException(port, "SetCommTimeouts", GetLastError()); 624 | } 625 | } 626 | } 627 | 628 | /++ Timed work with serial port 629 | 630 | +/ 631 | abstract class SerialPort : SerialPortBase 632 | { 633 | protected: 634 | 635 | Duration _writeTimeout = 1.seconds, 636 | _writeTimeoutMult = Duration.zero, 637 | _readTimeout = 1.seconds, 638 | _readTimeoutMult = Duration.zero; 639 | 640 | 641 | void updateTimeouts() @nogc {} 642 | 643 | public: 644 | 645 | /++ Construct SerialPort 646 | 647 | See_Also: SerialPortBase.this 648 | +/ 649 | this(string exmode) { super(exmode); } 650 | 651 | /// ditto 652 | this(string port, string mode) { super(port, mode); } 653 | 654 | /// ditto 655 | this(string port, uint baudRate) { super(port, baudRate); } 656 | 657 | /// ditto 658 | this(string port, uint baudRate, string mode) 659 | { super(port, baudRate, mode); } 660 | 661 | /// ditto 662 | this(string port, Config conf) { super(port, conf); } 663 | 664 | /++ Read data from serial port while exists 665 | +/ 666 | void flush() 667 | { 668 | void[128] buf = void; 669 | const rt = _readTimeout; 670 | const rtm = _readTimeoutMult; 671 | 672 | _readTimeout = 10.msecs; 673 | _readTimeoutMult = Duration.zero; 674 | updateTimeouts(); 675 | 676 | version (Posix) 677 | { 678 | const last = getCC(); 679 | setCC([0,0]); 680 | } 681 | 682 | void[] tmp; 683 | do tmp = read(buf, CanRead.zero); 684 | while (tmp.length); 685 | 686 | version (Posix) 687 | { 688 | setCC(last); 689 | } 690 | 691 | _readTimeout = rt; 692 | _readTimeoutMult = rtm; 693 | updateTimeouts(); 694 | } 695 | 696 | @property @nogc 697 | { 698 | const 699 | { 700 | /// 701 | Duration readTimeout() { return _readTimeout; } 702 | /// 703 | Duration readTimeoutMult() { return _readTimeoutMult; } 704 | /// 705 | Duration writeTimeout() { return _writeTimeout; } 706 | /// 707 | Duration writeTimeoutMult() { return _writeTimeoutMult; } 708 | } 709 | 710 | /// 711 | void readTimeout(Duration tm) 712 | { 713 | _readTimeout = tm; 714 | updateTimeouts(); 715 | } 716 | 717 | /// 718 | void readTimeoutMult(Duration tm) 719 | { 720 | _readTimeoutMult = tm; 721 | updateTimeouts(); 722 | } 723 | 724 | /// 725 | void writeTimeout(Duration tm) 726 | { 727 | _writeTimeout = tm; 728 | updateTimeouts(); 729 | } 730 | 731 | /// 732 | void writeTimeoutMult(Duration tm) 733 | { 734 | _writeTimeout = tm; 735 | updateTimeouts(); 736 | } 737 | } 738 | 739 | /// 740 | enum CanRead 741 | { 742 | allOrNothing, /// 743 | anyNonZero, /// 744 | zero /// 745 | } 746 | 747 | /++ Read data from port 748 | 749 | Receive data time schema: 750 | 751 | ------ 752 | ---|-------|--------------|-------|--> t 753 | call | | | 754 | read | | | 755 | | | | | 756 | | || 757 | | |===== ==== | ======| 758 | | | | 759 | | |<-readedData->| 760 | | | 761 | |<---readTimeoutSum--->| 762 | | return 763 | |<---read work time--->| 764 | ------ 765 | 766 | where `readTimeoutSum = readTimeout + readTimeoutMult * dataBuffer.length;` 767 | 768 | if canReturn is: 769 | 770 | CanRead.allOrNothing 771 | 772 | --- 773 | if (readedData.length < dataBuffer.length) 774 | throw TimeoutException(port); 775 | else return readedData; 776 | --- 777 | 778 | CanReturn.anyNonZero 779 | 780 | --- 781 | if (readedData.length == 0) 782 | throw TimeoutException(port); 783 | else return readedData; 784 | --- 785 | 786 | CanReturn.zero 787 | 788 | --- 789 | return readedData; 790 | --- 791 | 792 | Params: 793 | buf = preallocated buffer for reading 794 | cr = flag what define behavior if readedData.length < buf.length 795 | then readTimeoutSum is expires 796 | 797 | Returns: slice of buf with readed data 798 | Throws: 799 | PortClosedException if port closed 800 | ReadException if read error occurs 801 | TimeoutException if timeout expires 802 | +/ 803 | abstract void[] read(void[] buf, CanRead cr=CanRead.allOrNothing) @nogc; 804 | 805 | /// 806 | protected void checkAbility(CanRead cr, size_t readed, size_t buffer) @nogc 807 | { 808 | bool err; 809 | 810 | final switch (cr) with(CanRead) 811 | { 812 | case allOrNothing: err = readed != buffer; break; 813 | case anyNonZero: err = readed == 0; break; 814 | case zero: /+ no errors +/ break; 815 | } 816 | 817 | if (err) throwTimeoutException(port, "read timeout"); 818 | } 819 | 820 | /++ Write data to port 821 | 822 | Params: 823 | arr = data for writing 824 | 825 | Throws: 826 | PortClosedException if port closed 827 | WriteException if read error occurs 828 | TimeoutException if timeout expires 829 | +/ 830 | abstract void write(const(void[]) buf) @nogc; 831 | } -------------------------------------------------------------------------------- /source/serialport/block.d: -------------------------------------------------------------------------------- 1 | /// 2 | module serialport.block; 3 | 4 | import serialport.base; 5 | 6 | /// Blocking work serialport 7 | class SerialPortBlk : SerialPort 8 | { 9 | public: 10 | /++ Construct SerialPortBlk 11 | 12 | See_Also: SerialPort.this 13 | +/ 14 | this(string exmode) { super(exmode); } 15 | 16 | /// ditto 17 | this(string port, string mode) { super(port, mode); } 18 | 19 | /// ditto 20 | this(string port, uint baudRate) { super(port, baudRate); } 21 | 22 | /// ditto 23 | this(string port, uint baudRate, string mode) 24 | { super(port, baudRate, mode); } 25 | 26 | /// ditto 27 | this(string port, Config conf) { super(port, conf); } 28 | 29 | override void[] read(void[] buf, CanRead cr=CanRead.allOrNothing) 30 | { 31 | if (closed) throwPortClosedException(port); 32 | 33 | version (Posix) 34 | { 35 | if (cr == CanRead.allOrNothing) 36 | setCC([cast(ubyte)min(buf.length, 255), 0]); 37 | else setCC([1, 0]); 38 | 39 | ssize_t res = 0; 40 | auto ttm = buf.length * readTimeoutMult + readTimeout; 41 | auto sw = StopWatch(AutoStart.yes); 42 | while (ttm > Duration.zero) 43 | { 44 | ttm -= sw.peek; 45 | sw.reset(); 46 | 47 | fd_set sset; 48 | FD_ZERO(&sset); 49 | FD_SET(_handle, &sset); 50 | 51 | timeval ctm; 52 | ctm.tv_sec = cast(int)(ttm.total!"seconds"); 53 | enum US_PER_MS = 1000; 54 | ctm.tv_usec = cast(int)(ttm.split().msecs * US_PER_MS); 55 | 56 | const rv = select(_handle + 1, &sset, null, null, &ctm); 57 | if (rv < 0) throwSysCallException(port, "select", errno); 58 | 59 | if (rv == 0) break; 60 | 61 | const r = posixRead(_handle, buf.ptr+res, buf.length-res); 62 | if (r < 0) throwReadException(port, "posix read", errno); 63 | res += r; 64 | if (res == buf.length) return buf; 65 | } 66 | } 67 | else 68 | { 69 | uint res; 70 | 71 | if (!ReadFile(handle, buf.ptr, cast(uint)buf.length, &res, null)) 72 | throwReadException(port, "win read", GetLastError()); 73 | } 74 | 75 | checkAbility(cr, res, buf.length); 76 | 77 | return buf[0..res]; 78 | } 79 | 80 | override void write(const(void[]) arr) 81 | { 82 | if (closed) throwPortClosedException(port); 83 | 84 | version (Posix) 85 | { 86 | size_t written; 87 | const ttm = arr.length * writeTimeoutMult + writeTimeout; 88 | const full = StopWatch(AutoStart.yes); 89 | while (written < arr.length) 90 | { 91 | if (full.peek > ttm) 92 | throwTimeoutException(port, "write timeout"); 93 | 94 | const res = posixWrite(_handle, arr[written..$].ptr, arr.length - written); 95 | 96 | if (res < 0) 97 | throwWriteException(port, "posix write", errno); 98 | 99 | written += res; 100 | } 101 | } 102 | else 103 | { 104 | uint written; 105 | 106 | if (!WriteFile(_handle, arr.ptr, cast(uint)arr.length, &written, null)) 107 | throwWriteException(port, "win write", GetLastError()); 108 | 109 | if (arr.length != written) 110 | throwTimeoutException(port, "write timeout"); 111 | } 112 | } 113 | 114 | protected: 115 | 116 | override void[] m_read(void[]) @nogc 117 | { assert(0, "disable m_read for blocking"); } 118 | override size_t m_write(const(void)[]) @nogc 119 | { assert(0, "disable m_write for blocking"); } 120 | 121 | override void updateTimeouts() @nogc { version (Windows) updTimeouts(); } 122 | 123 | version (Windows) 124 | { 125 | override void updTimeouts() @nogc 126 | { 127 | setTimeouts(0, cast(DWORD)readTimeoutMult.total!"msecs", 128 | cast(DWORD)readTimeout.total!"msecs", 129 | cast(DWORD)writeTimeoutMult.total!"msecs", 130 | cast(DWORD)writeTimeout.total!"msecs"); 131 | } 132 | } 133 | 134 | version (Posix) 135 | { 136 | override void posixSetup(Config conf) 137 | { 138 | openPort(); 139 | 140 | if (fcntl(_handle, F_SETFL, 0) == -1) // disable O_NONBLOCK 141 | throwSysCallException(port, "fcntl", errno); 142 | 143 | setCC([1,0]); 144 | 145 | initialConfig(conf); 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /source/serialport/config.d: -------------------------------------------------------------------------------- 1 | /// 2 | module serialport.config; 3 | 4 | import std.range : split; 5 | import std.string : format, toLower; 6 | import std.exception : enforce, assertThrown, assertNotThrown; 7 | import std.conv : to; 8 | 9 | import serialport.types; 10 | import serialport.exception; 11 | 12 | /// 13 | struct SPConfig 14 | { 15 | /// 16 | uint baudRate=9600; 17 | /// 18 | DataBits dataBits=DataBits.data8; 19 | /// 20 | Parity parity=Parity.none; 21 | /// 22 | StopBits stopBits=StopBits.one; 23 | 24 | /// 25 | bool hardwareDisableFlowControl = true; 26 | 27 | /++ Set parity value 28 | Returns: this 29 | +/ 30 | ref SPConfig set(Parity v) @nogc return { parity = v; return this; } 31 | 32 | /++ Set baudrate value 33 | Returns: this 34 | +/ 35 | ref SPConfig set(uint v) @nogc return { baudRate = v; return this; } 36 | 37 | /++ Set data bits value 38 | Returns: this 39 | +/ 40 | ref SPConfig set(DataBits v) @nogc return { dataBits = v; return this; } 41 | 42 | /++ Set stop bits value 43 | Returns: this 44 | +/ 45 | ref SPConfig set(StopBits v) @nogc return { stopBits = v; return this; } 46 | 47 | /++ Use mode string for setting baudrate, data bits, parity and stop bits. 48 | 49 | Format: "B:DPS" 50 | where: 51 | B is baud rate 52 | D is data bits (5, 6, 7, 8) 53 | P is parity ('N' or 'n' -- none, 54 | 'E' or 'e' -- even, 55 | 'O' or 'o' -- odd) 56 | S is stop bits ('1', '1.5', '2') 57 | 58 | You can skip baudrate. 59 | 60 | example mode strings: "9600:8N1" ":8n1" "7o1.5" "2400:6e2" 61 | 62 | Throws: 63 | ParseModeException if mode string is badly formatted or using bad values 64 | +/ 65 | ref SPConfig set(string mode) return 66 | { 67 | alias PME = ParseModeException; 68 | 69 | auto errstr = "error mode '%s'".format(mode); 70 | enforce(mode.length >= 3, new PME(errstr ~ ": too short")); 71 | 72 | auto vals = mode.split(modeSplitChar); 73 | 74 | if (vals.length == 0) return this; 75 | 76 | if (vals.length > 2) 77 | throw new PME(errstr ~ ": many parts"); 78 | 79 | if (vals.length == 2) 80 | { 81 | if (vals[0].length) 82 | { 83 | try baudRate = vals[0].to!uint; 84 | catch (Exception e) 85 | throw new PME(errstr ~ 86 | ": baud rate parse error: " ~ e.msg); 87 | } 88 | mode = vals[1]; 89 | } 90 | else mode = vals[0]; 91 | 92 | auto db = cast(int)mode[0] - cast(int)'0'; 93 | if (db >= 5 && db <= 8) dataBits = cast(DataBits)db; 94 | else throw new PME(errstr ~ ": unsupported data bits '" ~ mode[0] ~ "'"); 95 | 96 | auto p = mode[1..2].toLower; 97 | if (p == "n" || p == "o" || p == "e") 98 | { 99 | parity = ["n": Parity.none, 100 | "o": Parity.odd, 101 | "e": Parity.even][p]; 102 | } 103 | else throw new PME(errstr ~ ": unsupported parity '" ~ p ~ "'"); 104 | 105 | auto sb = mode[2..$]; 106 | if (sb == "1" || sb == "1.5" || sb == "2") 107 | { 108 | stopBits = ["1": StopBits.one, 109 | "1.5": StopBits.onePointFive, 110 | "2": StopBits.two][sb]; 111 | } 112 | else throw new PME(errstr ~ ": unsupported stop bits '" ~ sb ~ "'"); 113 | 114 | return this; 115 | } 116 | 117 | /// 118 | unittest 119 | { 120 | SPConfig c; 121 | c.set("2400:7e1.5"); 122 | assertNotThrown(c.set(c.mode)); 123 | assert(c.baudRate == 2400); 124 | assert(c.dataBits == DataBits.data7); 125 | assert(c.parity == Parity.even); 126 | assert(c.stopBits == StopBits.onePointFive); 127 | c.set("8N1"); 128 | assertNotThrown(c.set(c.mode)); 129 | assert(c.baudRate == 2400); 130 | assert(c.dataBits == DataBits.data8); 131 | assert(c.parity == Parity.none); 132 | assert(c.stopBits == StopBits.one); 133 | c.set("320:5o2"); 134 | assertNotThrown(c.set(c.mode)); 135 | assert(c.baudRate == 320); 136 | assert(c.dataBits == DataBits.data5); 137 | assert(c.parity == Parity.odd); 138 | assert(c.stopBits == StopBits.two); 139 | 140 | alias PME = ParseModeException; 141 | assertThrown!PME(c.set("4o2")); 142 | assertThrown!PME(c.set("5x2")); 143 | assertThrown!PME(c.set("8e3")); 144 | assertNotThrown!PME(c.set(":8N1")); 145 | assertNotThrown(c.set(c.mode)); 146 | } 147 | 148 | /++ Construct config, parse mode to it and return. 149 | 150 | Returns: new config 151 | 152 | See_Also: set(string mode) 153 | +/ 154 | static SPConfig parse(string mode) 155 | { 156 | SPConfig ret; 157 | ret.set(mode); 158 | return ret; 159 | } 160 | 161 | /++ Build mode string. 162 | 163 | Can be used for parsing. 164 | 165 | Returns: mode string 166 | 167 | See_Also: parse, set(string mode) 168 | +/ 169 | string mode() const @property 170 | { 171 | return "%s:%s%s%s".format( 172 | baudRate, 173 | dataBits.to!int, 174 | [Parity.none: "n", 175 | Parity.odd: "o", 176 | Parity.even: "e"][parity], 177 | [StopBits.one: "1", 178 | StopBits.onePointFive: "1.5", 179 | StopBits.two: "2" 180 | ][stopBits] 181 | ); 182 | } 183 | } 184 | 185 | unittest 186 | { 187 | SPConfig a, b; 188 | a.set("2400:7e2"); 189 | b.set(a.mode); 190 | assert(a == b); 191 | a.set(Parity.none).set(DataBits.data8).set(19200).set(StopBits.one); 192 | assert(a.parity == Parity.none); 193 | assert(a.dataBits == DataBits.data8); 194 | assert(a.baudRate == 19200); 195 | assert(a.stopBits == StopBits.one); 196 | } 197 | 198 | unittest 199 | { 200 | SPConfig a; 201 | assertThrown!ParseModeException(a.set("2400:7e2:32")); 202 | assertThrown!ParseModeException(a.set("24a0:7e2")); 203 | } 204 | -------------------------------------------------------------------------------- /source/serialport/exception.d: -------------------------------------------------------------------------------- 1 | /// 2 | module serialport.exception; 3 | 4 | import std.conv : text; 5 | 6 | import serialport.types; 7 | 8 | /// 9 | class ParseModeException : Exception 10 | { 11 | this(string msg, string file=__FILE__, size_t line=__LINE__) 12 | @safe pure nothrow @nogc 13 | { super(msg, file, line); } 14 | } 15 | 16 | /// General 17 | class SerialPortException : Exception 18 | { 19 | string port; 20 | private this() @safe pure nothrow @nogc { super(""); } 21 | } 22 | 23 | /// Unsupported config 24 | class UnsupportedException : SerialPortException 25 | { private this() @safe pure nothrow @nogc { super(); } } 26 | 27 | /// 28 | class PortClosedException : SerialPortException 29 | { private this() @safe pure nothrow @nogc { super(); } } 30 | 31 | /// 32 | class TimeoutException : SerialPortException 33 | { private this() @safe pure nothrow @nogc { super(); } } 34 | 35 | /// 36 | class SysCallException : SerialPortException 37 | { 38 | /// sys call name 39 | string fnc; 40 | /// errno or GetLastError 41 | int err; 42 | private this() @safe pure nothrow @nogc { super(); } 43 | } 44 | 45 | /// 46 | class ReadException : SysCallException 47 | { private this() @safe pure nothrow @nogc { super(); } } 48 | 49 | /// 50 | class WriteException : SysCallException 51 | { private this() @safe pure nothrow @nogc { super(); } } 52 | 53 | private E setFields(E: SerialPortException)(E e, string port, string msg, 54 | string file, size_t line) 55 | { 56 | if (e is null) // assert(0) not omit on optimize by compiler 57 | assert(0, "setField get null exception object"); 58 | e.port = port; 59 | e.msg = msg; 60 | e.file = file; 61 | e.line = line; 62 | return e; 63 | } 64 | 65 | import std.format; 66 | 67 | private enum preallocated; 68 | private enum prealloc_prefix = "prealloc"; 69 | 70 | private mixin template throwSPEMix(E, string defaultMsg="") 71 | if (is(E: SerialPortException)) 72 | { 73 | enum string name = E.stringof; 74 | mixin(` 75 | @preallocated private %1$s %2$s%1$s; 76 | void throw%1$s(string port, string msg="%3$s", 77 | string file=__FILE__, size_t line=__LINE__) @nogc 78 | { throw %2$s%1$s.setFields(port, msg, file, line); } 79 | `.format(name, prealloc_prefix, (defaultMsg.length ? defaultMsg : name)) 80 | ); 81 | } 82 | 83 | private enum fmtSPSCEMsgFmt = "call '%s' (%s) failed: error %d"; 84 | 85 | private string fmtSPSCEMsg(string port, string fnc, int err) @nogc 86 | { 87 | import core.stdc.stdio : sprintf; 88 | import std.algorithm : min; 89 | import core.stdc.string : memcpy, memset; 90 | 91 | enum SZ = 256; 92 | 93 | static char[SZ] port_buf; 94 | static char[SZ] fnc_buf; 95 | static char[SZ*3] buf; 96 | 97 | memset(port_buf.ptr, 0, SZ); 98 | memset(fnc_buf.ptr, 0, SZ); 99 | memset(buf.ptr, 0, SZ*3); 100 | memcpy(port_buf.ptr, port.ptr, min(port.length, SZ)); 101 | memcpy(fnc_buf.ptr, fnc.ptr, min(fnc.length, SZ)); 102 | auto n = sprintf(buf.ptr, fmtSPSCEMsgFmt, fnc_buf.ptr, port_buf.ptr, err); 103 | return cast(string)buf[0..n]; 104 | } 105 | 106 | unittest 107 | { 108 | import std.format : format; 109 | 110 | static auto fmtSPSCEMsgGC(string port, string fnc, int err) 111 | { return format!fmtSPSCEMsgFmt(fnc, port, err); } 112 | 113 | void test(string port, string fnc, int err) 114 | { 115 | auto trg = fmtSPSCEMsg(port, fnc, err); 116 | auto tst = fmtSPSCEMsgGC(port, fnc, err); 117 | if (trg != tst) assert(0, "not equals:\n%s\n%s".format(trg, tst)); 118 | } 119 | 120 | test("/dev/ttyUSB0", "open", 2); 121 | test("/very/very/very/very/very/very/very/very/very/very/big/path/to/com/port/device/dev", 122 | "veryVeryVeryVeryLongFunctionName12345678901234567890123456789012345678901234567890", 123 | int.max); 124 | test("", "", 0); 125 | } 126 | 127 | private mixin template throwSPSCEMix(E) 128 | if (is(E: SysCallException)) 129 | { 130 | enum name = E.stringof; 131 | mixin(` 132 | @preallocated private %1$s %2$s%1$s; 133 | void throw%1$s(string port, string fnc, int err, string msg="", 134 | string file=__FILE__, size_t line=__LINE__) @nogc 135 | { 136 | if (msg.length == 0) 137 | msg = fmtSPSCEMsg(port, fnc, err); 138 | auto e = %2$s%1$s.setFields(port, msg, file, line); 139 | e.fnc = fnc; 140 | e.err = err; 141 | throw e; 142 | } 143 | `.format(name, prealloc_prefix) 144 | ); 145 | } 146 | 147 | static this() 148 | { 149 | // can't use origin getSymbolsByUDA because 150 | // https://issues.dlang.org/show_bug.cgi?id=20054 151 | static if (__VERSION__ < 2088) 152 | { 153 | import std.traits : getSymbolsByUDA; 154 | alias plist = getSymbolsByUDA!(mixin(__MODULE__), preallocated); 155 | } 156 | else 157 | { 158 | import std.meta : AliasSeq; 159 | 160 | alias plist = AliasSeq!( 161 | preallocSerialPortException, 162 | preallocPortClosedException, 163 | preallocTimeoutException, 164 | preallocSysCallException, 165 | preallocReadException, 166 | preallocWriteException, 167 | preallocUnsupported 168 | ); 169 | } 170 | 171 | static foreach (sym; plist) sym = new typeof(sym); 172 | } 173 | 174 | mixin throwSPEMix!SerialPortException; 175 | mixin throwSPEMix!PortClosedException; 176 | mixin throwSPEMix!TimeoutException; 177 | 178 | mixin throwSPSCEMix!SysCallException; 179 | mixin throwSPSCEMix!ReadException; 180 | mixin throwSPSCEMix!WriteException; 181 | 182 | import serialport.types; 183 | import core.stdc.stdio; 184 | 185 | private char[1024] UEMPB; 186 | 187 | @preallocated 188 | private UnsupportedException preallocUnsupported; 189 | 190 | void throwUnsupportedException(string port, int baudrate, 191 | string file=__FILE__, size_t line=__LINE__) @nogc 192 | { 193 | auto ln = sprintf(UEMPB.ptr, "unsupported baudrate: %d", baudrate); 194 | throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line); 195 | } 196 | 197 | void throwUnsupportedException(string port, DataBits dbits, 198 | string file=__FILE__, size_t line=__LINE__) @nogc 199 | { 200 | auto ln = sprintf(UEMPB.ptr, "unsupported data bits: %d", cast(int)dbits); 201 | throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line); 202 | } 203 | 204 | void throwUnsupportedException(string port, StopBits sbits, 205 | string file=__FILE__, size_t line=__LINE__) @nogc 206 | { 207 | string str; 208 | final switch (sbits) with (StopBits) 209 | { 210 | case one: str = "1\0"; break; 211 | case two: str = "2\0"; break; 212 | case onePointFive: str = "1.5\0"; break; 213 | } 214 | 215 | auto ln = sprintf(UEMPB.ptr, "unsupported stop bits: %s", str.ptr); 216 | throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line); 217 | } 218 | 219 | void throwUnsupportedException(string port, Parity parity, 220 | string file=__FILE__, size_t line=__LINE__) @nogc 221 | { 222 | string str; 223 | final switch (parity) with (Parity) 224 | { 225 | case none: str = "none\0"; break; 226 | case even: str = "even\0"; break; 227 | case odd: str = "odd\0"; break; 228 | } 229 | auto ln = sprintf(UEMPB.ptr, "unsupported parity: %s", str.ptr); 230 | throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line); 231 | } -------------------------------------------------------------------------------- /source/serialport/fiberready.d: -------------------------------------------------------------------------------- 1 | /// 2 | module serialport.fiberready; 3 | 4 | import serialport.base; 5 | 6 | import std.traits : isSomeFunction, 7 | FunctionAttribute, 8 | functionAttributes; 9 | 10 | /++ Serial Port Fiber Ready 11 | +/ 12 | class SerialPortFR : SerialPort 13 | { 14 | protected: 15 | 16 | /++ Preform pause 17 | 18 | If sleepFunc isn't null call it. Else use `Thread.sleep` or 19 | `Fiber.yield` if code executes in fiber. 20 | 21 | Params: 22 | dt = sleep time 23 | +/ 24 | void sleep(Duration dt) @nogc 25 | { 26 | if (_sleepFunc is null) msleep(dt); 27 | else _sleepFunc(dt); 28 | } 29 | 30 | /++ Calc pause for sleep in read and write loops 31 | +/ 32 | Duration ioPause() @nogc 33 | { 34 | auto cfg = config; 35 | auto cnt = 1 + // start bit 36 | cast(int)cfg.dataBits + 37 | (cfg.parity == Parity.none ? 0 : 1) + 38 | (cfg.stopBits == StopBits.one ? 1 : 39 | cfg.stopBits == StopBits.onePointFive ? 1.5 : 2) + 40 | 1.5 // reserve 41 | ; 42 | return (cast(ulong)(cnt / cfg.baudRate * 1e6) + 100/+reserve+/).usecs; 43 | } 44 | 45 | void delegate(Duration) @nogc _sleepFunc; 46 | 47 | public: 48 | /// assume @nogc 49 | deprecated 50 | alias SleepFunc = void delegate(Duration); 51 | 52 | /// 53 | alias SleepFuncNoGC = void delegate(Duration) @nogc; 54 | 55 | /// extended delegate for perform sleep 56 | deprecated("sleep function must be @nogc") 57 | void sleepFunc(SleepFunc dlg) @property 58 | { _sleepFunc = cast(void delegate(Duration) @nogc)dlg; } 59 | 60 | /// 61 | deprecated("sleep function must be @nogc") 62 | void sleepFunc(void function(Duration) fnc) @property 63 | { _sleepFunc = (d){ (cast(void function(Duration) @nogc)fnc)(d); }; } 64 | 65 | /// extended delegate for perform sleep 66 | void sleepFunc(void delegate(Duration) @nogc dlg) @property 67 | { _sleepFunc = dlg; } 68 | 69 | /// ditto 70 | void sleepFunc(void function(Duration) @nogc fnc) @property 71 | { _sleepFunc = (d){ fnc(d); }; } 72 | 73 | /// ditto 74 | SleepFuncNoGC sleepFunc() @property { return _sleepFunc; } 75 | 76 | /++ Construct SerialPortFR 77 | 78 | See_Also: SerialPort.this 79 | +/ 80 | deprecated("sleep function must be @nogc") 81 | this(F=SleepFunc)(string exmode, F sf) 82 | if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc)) 83 | { sleepFunc = sf; super(exmode); } 84 | 85 | /// ditto 86 | deprecated("sleep function must be @nogc") 87 | this(F=SleepFunc)(string port, string mode, F sf) 88 | if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc)) 89 | { sleepFunc = sf; super(port, mode); } 90 | 91 | /// ditto 92 | deprecated("sleep function must be @nogc") 93 | this(F=SleepFunc)(string port, uint baudRate, F sf) 94 | if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc)) 95 | { sleepFunc = sf; super(port, baudRate); } 96 | 97 | /// ditto 98 | deprecated("sleep function must be @nogc") 99 | this(F=SleepFunc)(string port, uint baudRate, string mode, F sf) 100 | if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc)) 101 | { sleepFunc = sf; super(port, baudRate, mode); } 102 | 103 | /// ditto 104 | deprecated("sleep function must be @nogc") 105 | this(F=SleepFunc)(string port, Config conf, F sf) 106 | if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc)) 107 | { sleepFunc = sf; super(port, conf); } 108 | 109 | /// ditto 110 | this(F=SleepFuncNoGC)(string exmode, F sf=null) 111 | if (isSomeFunction!F) 112 | { sleepFunc = sf; super(exmode); } 113 | 114 | /// ditto 115 | this(F=SleepFuncNoGC)(string port, string mode, F sf=null) 116 | if (isSomeFunction!F && (functionAttributes!F & FunctionAttribute.nogc)) 117 | { sleepFunc = sf; super(port, mode); } 118 | 119 | /// ditto 120 | this(F=SleepFuncNoGC)(string port, uint baudRate, F sf=null) 121 | if (isSomeFunction!F && (functionAttributes!F & FunctionAttribute.nogc)) 122 | { sleepFunc = sf; super(port, baudRate); } 123 | 124 | /// ditto 125 | this(F=SleepFuncNoGC)(string port, uint baudRate, string mode, F sf=null) 126 | if (isSomeFunction!F && (functionAttributes!F & FunctionAttribute.nogc)) 127 | { sleepFunc = sf; super(port, baudRate, mode); } 128 | 129 | /// ditto 130 | this(F=SleepFuncNoGC)(string port, Config conf, F sf=null) 131 | if (isSomeFunction!F && (functionAttributes!F & FunctionAttribute.nogc)) 132 | { sleepFunc = sf; super(port, conf); } 133 | 134 | override void[] read(void[] buf, CanRead cr=CanRead.allOrNothing) 135 | { 136 | if (closed) throwPortClosedException(port); 137 | 138 | size_t res; 139 | const timeout = buf.length * readTimeoutMult + readTimeout; 140 | const pause = ioPause(); 141 | const sw = StopWatch(AutoStart.yes); 142 | while (sw.peek < timeout) 143 | { 144 | res += m_read(buf[res..$]).length; 145 | if (res == buf.length) return buf[]; 146 | this.sleep(pause); 147 | } 148 | 149 | checkAbility(cr, res, buf.length); 150 | 151 | return buf[0..res]; 152 | } 153 | 154 | override void write(const(void[]) arr) 155 | { 156 | if (closed) throwPortClosedException(port); 157 | 158 | size_t written; 159 | const timeout = arr.length * writeTimeoutMult + writeTimeout; 160 | const pause = ioPause(); 161 | const sw = StopWatch(AutoStart.yes); 162 | while (sw.peek < timeout) 163 | { 164 | written += m_write(arr[written..$]); 165 | if (written == arr.length) return; 166 | this.sleep(pause); 167 | } 168 | 169 | throwTimeoutException(port, "write timeout"); 170 | } 171 | 172 | /++ Read data while available by parts, sleep between checks. 173 | 174 | Sleep time calculates from baud rate and count of bits in one byte. 175 | 176 | ------- 177 | ------|--------|-----|------------|-----|------------> t 178 | call | | | | 179 | readContinues | | | | 180 | | | | | | 181 | | |<---------data receive---------->| 182 | | |=== ===== ======| | |== =| data stream 183 | | | | | | | | 184 | |<--timeout--->| | | | | 185 | | |<-1->| |<2>| |<-3->| 186 | | | | | 187 | | |<---readedData--->| | 188 | | return 189 | |<-------readAll work time------->| 190 | 191 | (1) if readedData.length > 0 then continue reading 192 | else if expectAnything throw TimeoutException 193 | else return readedData (empty) 194 | (2) silent time, if silent < frameGap then continue reading 195 | (3) else if silent > frameGap then stop reading 196 | and return readedData 197 | ------- 198 | 199 | Params: 200 | buf = buffer for reading 201 | startTimeout = timeout for first byte recive 202 | frameGap = detect new data frame by silence period 203 | expectAnything = function throw exception if no data 204 | before startTimeout 205 | 206 | Returns: slice of buf with readed data 207 | 208 | Throws: 209 | PortClosedException 210 | ReadException 211 | TimeoutException 212 | 213 | See_Also: SerialPort.read 214 | +/ 215 | void[] readContinues(void[] buf, Duration startTimeout=1.seconds, 216 | Duration frameGap=50.msecs, 217 | bool expectAnything=true) 218 | { 219 | if (closed) throwPortClosedException(port); 220 | 221 | ptrdiff_t readed; 222 | 223 | auto pause = ioPause(); 224 | 225 | StopWatch silence, full; 226 | 227 | full.start(); 228 | while (true) 229 | { 230 | const res = m_read(buf[readed..$]).length; 231 | 232 | readed += res; 233 | 234 | // buffer filled 235 | if (readed == buf.length) return buf[]; 236 | 237 | if (res == 0) 238 | { 239 | if (readed > 0 && silence.peek > frameGap) 240 | return buf[0..readed]; 241 | 242 | if (!silence.running) silence.start(); 243 | } 244 | else 245 | { 246 | silence.stop(); 247 | silence.reset(); 248 | } 249 | 250 | if (readed == 0 && full.peek > startTimeout) 251 | { 252 | if (expectAnything) 253 | throwTimeoutException(port, "read timeout"); 254 | else 255 | return buf[0..0]; 256 | } 257 | 258 | this.sleep(pause); 259 | } 260 | } 261 | } -------------------------------------------------------------------------------- /source/serialport/nonblock.d: -------------------------------------------------------------------------------- 1 | /// 2 | module serialport.nonblock; 3 | 4 | import serialport.base; 5 | 6 | /// Non-blocking work with serial port 7 | class SerialPortNonBlk : SerialPortBase 8 | { 9 | /++ Construct SerialPortNonBlk 10 | 11 | See_Also: SerialPortBase.this 12 | +/ 13 | this(string exmode) { super(exmode); } 14 | 15 | /// ditto 16 | this(string port, string mode) { super(port, mode); } 17 | 18 | /// ditto 19 | this(string port, uint baudRate) { super(port, baudRate); } 20 | 21 | /// ditto 22 | this(string port, uint baudRate, string mode) 23 | { super(port, baudRate, mode); } 24 | 25 | /// ditto 26 | this(string port, Config conf) { super(port, conf); } 27 | 28 | /++ Non-block read data from port 29 | 30 | Params: 31 | buf = preallocated buffer for reading 32 | 33 | Returns: slice of buf with readed data 34 | 35 | Throws: 36 | PortClosedException if port closed 37 | ReadException if read error occurs 38 | +/ 39 | void[] read(void[] buf) @nogc { return m_read(buf); } 40 | 41 | /++ Non-block write data to port 42 | 43 | Params: 44 | arr = data for writing 45 | 46 | Returns: count of writen bytes 47 | 48 | Throws: 49 | PortClosedException if port closed 50 | WriteException if read error occurs 51 | +/ 52 | size_t write(const(void)[] buf) @nogc { return m_write(buf); } 53 | } -------------------------------------------------------------------------------- /source/serialport/package.d: -------------------------------------------------------------------------------- 1 | /++ 2 | Crossplatform work with serialport. 3 | 4 | See also `example/monitor` 5 | +/ 6 | module serialport; 7 | 8 | version (Posix) {} else version (Windows) {} 9 | else static assert(0, "unsupported platform"); 10 | 11 | public: 12 | 13 | import serialport.base; 14 | import serialport.config; 15 | import serialport.block; 16 | import serialport.fiberready; 17 | import serialport.nonblock; 18 | import serialport.exception; 19 | import serialport.types; -------------------------------------------------------------------------------- /source/serialport/types.d: -------------------------------------------------------------------------------- 1 | /// Definitions of using types 2 | module serialport.types; 3 | 4 | /// 5 | enum Parity 6 | { 7 | none, /// 8 | odd, /// 9 | even /// 10 | } 11 | 12 | /// 13 | enum DataBits : uint 14 | { 15 | data8 = 8, /// 16 | data7 = 7, /// 17 | data6 = 6, /// 18 | data5 = 5, /// 19 | } 20 | 21 | /// 22 | enum StopBits 23 | { 24 | one, /// 25 | onePointFive, /// 26 | two /// 27 | } 28 | 29 | package 30 | { 31 | version(Posix) 32 | { 33 | import core.sys.posix.unistd; 34 | version(linux) 35 | import core.sys.linux.termios; 36 | else 37 | import core.sys.posix.termios; 38 | 39 | import core.sys.posix.fcntl; 40 | import core.sys.posix.sys.select; 41 | import core.sys.posix.sys.ioctl; 42 | import core.stdc.errno; 43 | import std.algorithm; 44 | import std.file; 45 | 46 | alias posixRead = core.sys.posix.unistd.read; 47 | alias posixWrite = core.sys.posix.unistd.write; 48 | alias posixClose = core.sys.posix.unistd.close; 49 | 50 | alias closeHandle = posixClose; 51 | /// Posix handle is int 52 | alias SPHandle = int; 53 | enum initHandle = -1; 54 | } 55 | version(Windows) 56 | { 57 | import core.sys.windows.windows; 58 | import std.bitmanip; 59 | 60 | enum NOPARITY = 0x0; 61 | enum EVENPARITY = 0x2; 62 | enum MARKPARITY = 0x3; 63 | enum ODDPARITY = 0x1; 64 | enum SPACEPARITY = 0x4; 65 | 66 | enum ONESTOPBIT = 0x0; 67 | enum ONE5STOPBITS = 0x1; 68 | enum TWOSTOPBITS = 0x2; 69 | 70 | struct DCB 71 | { 72 | DWORD DCBlength; 73 | DWORD BaudRate; 74 | 75 | mixin(bitfields!( 76 | DWORD, "fBinary", 1, 77 | DWORD, "fParity", 1, 78 | DWORD, "fOutxCtsFlow", 1, 79 | DWORD, "fOutxDsrFlow", 1, 80 | DWORD, "fDtrControl", 2, 81 | DWORD, "fDsrSensitivity", 1, 82 | DWORD, "fTXContinueOnXoff", 1, 83 | DWORD, "fOutX", 1, 84 | DWORD, "fInX", 1, 85 | DWORD, "fErrorChar", 1, 86 | DWORD, "fNull", 1, 87 | DWORD, "fRtsControl", 2, 88 | DWORD, "fAbortOnError", 1, 89 | DWORD, "fDummy2", 17)); 90 | 91 | WORD wReserved; 92 | WORD XonLim; 93 | WORD XoffLim; 94 | BYTE ByteSize; 95 | BYTE Parity; 96 | BYTE StopBits; 97 | ubyte XonChar; 98 | ubyte XoffChar; 99 | ubyte ErrorChar; 100 | ubyte EofChar; 101 | ubyte EvtChar; 102 | WORD wReserved1; 103 | } 104 | 105 | struct COMMTIMEOUTS 106 | { 107 | DWORD ReadIntervalTimeout; 108 | DWORD ReadTotalTimeoutMultiplier; 109 | DWORD ReadTotalTimeoutConstant; 110 | DWORD WriteTotalTimeoutMultiplier; 111 | DWORD WriteTotalTimeoutConstant; 112 | } 113 | 114 | extern(Windows) @nogc 115 | { 116 | bool GetCommState(HANDLE hFile, DCB* lpDCB); 117 | bool SetCommState(HANDLE hFile, DCB* lpDCB); 118 | bool SetCommTimeouts(HANDLE hFile, COMMTIMEOUTS* lpCommTimeouts); 119 | } 120 | 121 | alias closeHandle = CloseHandle; 122 | /// Windows is HANDLE 123 | alias SPHandle = HANDLE; 124 | enum initHandle = null; 125 | } 126 | } 127 | 128 | /// 129 | static immutable string modeSplitChar=":"; 130 | 131 | version (Posix) 132 | { 133 | import serialport.util; 134 | 135 | version (OSX) 136 | { 137 | /+ 138 | 38400 is max for OSX 139 | http://www.manpages.info/macosx/cfsetospeed.3.html 140 | +/ 141 | static immutable unixBaudList = pairList( 142 | pair( 0, B0), 143 | pair( 50, B50), 144 | pair( 75, B75), 145 | pair( 110, B110), 146 | pair( 134, B134), 147 | pair( 150, B150), 148 | pair( 200, B200), 149 | pair( 300, B300), 150 | pair( 600, B600), 151 | pair( 1200, B1200), 152 | pair( 1800, B1800), 153 | pair( 2400, B2400), 154 | pair( 4800, B4800), 155 | pair( 9600, B9600), 156 | pair(19200, B19200), 157 | pair(38400, B38400) 158 | ); 159 | } 160 | else 161 | { 162 | static immutable unixBaudList = pairList( 163 | pair( 0, B0), 164 | pair( 50, B50), 165 | pair( 75, B75), 166 | pair( 110, B110), 167 | pair( 134, B134), 168 | pair( 150, B150), 169 | pair( 200, B200), 170 | pair( 300, B300), 171 | pair( 600, B600), 172 | pair( 1200, B1200), 173 | pair( 1800, B1800), 174 | pair( 2400, B2400), 175 | pair( 4800, B4800), 176 | pair( 9600, B9600), 177 | pair( 19200, B19200), 178 | pair( 38400, B38400), 179 | pair( 57600, B57600), 180 | pair(115200, B115200), 181 | pair(230400, B230400) 182 | ); 183 | } 184 | 185 | static assert(unixBaudList.isUniqA, "not uniq A vals in unix baud list"); 186 | static assert(unixBaudList.isUniqB, "not uniq B vals in unix baud list"); 187 | } -------------------------------------------------------------------------------- /source/serialport/ut.d: -------------------------------------------------------------------------------- 1 | module serialport.ut; 2 | 3 | version (unittest): private: 4 | 5 | import serialport; 6 | 7 | import std.range; 8 | import std.concurrency; 9 | import std.exception; 10 | import std.datetime; 11 | import std.conv; 12 | import std.string; 13 | import std.stdio; 14 | import std.random; 15 | import std.process; 16 | import core.thread; 17 | 18 | enum BUFFER_SIZE = 1024; 19 | 20 | interface ComPipe 21 | { 22 | void open(); 23 | void close(); 24 | string command() const @property; 25 | string[2] ports() const @property; 26 | } 27 | 28 | class SocatPipe : ComPipe 29 | { 30 | int bufferSize; 31 | ProcessPipes pipe; 32 | string[2] _ports; 33 | string _command; 34 | 35 | this(int bs) 36 | { 37 | bufferSize = bs; 38 | _command = ("socat -d -d -b%d pty,raw,"~ 39 | "echo=0 pty,raw,echo=0").format(bufferSize); 40 | } 41 | 42 | static string parsePort(string ln) 43 | { 44 | auto ret = ln.split[$-1]; 45 | enforce(ret.startsWith("/dev/"), 46 | "unexpected last word in output line '%s'".format(ln)); 47 | return ret; 48 | } 49 | 50 | override void close() 51 | { 52 | if (pipe.pid is null) return; 53 | kill(pipe.pid); 54 | } 55 | 56 | override void open() 57 | { 58 | pipe = pipeShell(_command); 59 | _ports[0] = parsePort(pipe.stderr.readln.strip); 60 | _ports[1] = parsePort(pipe.stderr.readln.strip); 61 | } 62 | 63 | override const @property 64 | { 65 | string command() { return _command; } 66 | string[2] ports() { return _ports; } 67 | } 68 | } 69 | 70 | unittest 71 | { 72 | enum socat_out_ln = "2018/03/08 02:56:58 socat[30331] N PTY is /dev/pts/1"; 73 | assert(SocatPipe.parsePort(socat_out_ln) == "/dev/pts/1"); 74 | assertThrown(SocatPipe.parsePort("some string")); 75 | } 76 | 77 | class DefinedPorts : ComPipe 78 | { 79 | string[2] env; 80 | string[2] _ports; 81 | 82 | this(string[2] envNames = ["SERIALPORT_TEST_PORT1", "SERIALPORT_TEST_PORT2"]) 83 | { env = envNames; } 84 | 85 | override: 86 | 87 | void open() 88 | { 89 | import std.process : environment; 90 | import std.range : lockstep; 91 | import std.algorithm : canFind; 92 | 93 | auto lst = SerialPort.listAvailable; 94 | 95 | foreach (ref e, ref p; lockstep(env[], _ports[])) 96 | { 97 | p = environment[e]; 98 | enforce(lst.canFind(p), new Exception("unknown port '%s' in env var '%s'".format(p, e))); 99 | } 100 | } 101 | 102 | void close() { } 103 | 104 | string command() const @property 105 | { 106 | return "env: %s=%s, %s=%s".format( 107 | env[0], _ports[0], 108 | env[1], _ports[1] 109 | ); 110 | } 111 | 112 | string[2] ports() const @property { return _ports; } 113 | } 114 | 115 | ComPipe getPlatformComPipe(int bufsz) 116 | { 117 | stderr.writeln("available ports count: ", SerialPort.listAvailable.length); 118 | 119 | try 120 | { 121 | auto ret = new DefinedPorts; 122 | ret.open(); 123 | return ret; 124 | } 125 | catch (Exception e) 126 | { 127 | stderr.writeln(); 128 | stderr.writeln("error while open predefined ports: ", e.msg); 129 | 130 | version (Posix) return new SocatPipe(bufsz); 131 | else return null; 132 | } 133 | } 134 | 135 | // real test main 136 | //version (realtest) 137 | unittest 138 | { 139 | stderr.writeln("=== start real test ===\n"); 140 | scope (success) stderr.writeln("=== finish real test ==="); 141 | scope (failure) stderr.writeln("!!! fail real test !!!"); 142 | auto cp = getPlatformComPipe(BUFFER_SIZE); 143 | if (cp is null) 144 | { 145 | stderr.writeln("platform doesn't support real test"); 146 | return; 147 | } 148 | 149 | stderr.writefln("port source `%s`\n", cp.command); 150 | 151 | void reopen() 152 | { 153 | cp.close(); 154 | Thread.sleep(30.msecs); 155 | cp.open(); 156 | stderr.writefln("pipe ports: %s <=> %s", cp.ports[0], cp.ports[1]); 157 | } 158 | 159 | reopen(); 160 | 161 | utCall!(threadTest!SerialPortFR)("thread test for fiber ready", cp.ports); 162 | utCall!(threadTest!SerialPortBlk)("thread test for block", cp.ports); 163 | utCall!testNonBlock("test non block", cp.ports); 164 | utCall!fiberTest("fiber test", cp.ports); 165 | utCall!fiberTest2("fiber test 2", cp.ports); 166 | utCall!readTimeoutTest("read timeout test", cp.ports); 167 | alias rttc = readTimeoutTestConfig; 168 | alias rttc2 = readTimeoutTestConfig2; 169 | utCall!(rttc!SerialPortFR)( "read timeout test for FR cr=zero", cp.ports, SerialPort.CanRead.zero); 170 | utCall!(rttc!SerialPortBlk)("read timeout test for Blk cr=zero", cp.ports, SerialPort.CanRead.zero); 171 | utCall!(rttc!SerialPortFR)( "read timeout test for FR cr=anyNonZero", cp.ports, SerialPort.CanRead.anyNonZero); 172 | utCall!(rttc!SerialPortBlk)("read timeout test for Blk cr=anyNonZero", cp.ports, SerialPort.CanRead.anyNonZero); 173 | utCall!(rttc!SerialPortFR)( "read timeout test for FR cr=allOrNothing", cp.ports, SerialPort.CanRead.allOrNothing); 174 | utCall!(rttc!SerialPortBlk)("read timeout test for Blk cr=allOrNothing", cp.ports, SerialPort.CanRead.allOrNothing); 175 | utCall!(rttc2!SerialPortFR)( "read timeout test 2 for FR cr=zero", cp.ports, SerialPort.CanRead.zero); 176 | utCall!(rttc2!SerialPortBlk)("read timeout test 2 for Blk cr=zero", cp.ports, SerialPort.CanRead.zero); 177 | utCall!(rttc2!SerialPortFR)( "read timeout test 2 for FR cr=anyNonZero", cp.ports, SerialPort.CanRead.anyNonZero); 178 | utCall!(rttc2!SerialPortBlk)("read timeout test 2 for Blk cr=anyNonZero", cp.ports, SerialPort.CanRead.anyNonZero); 179 | utCall!(rttc2!SerialPortFR)( "read timeout test 2 for FR cr=allOrNothing", cp.ports, SerialPort.CanRead.allOrNothing); 180 | utCall!(rttc2!SerialPortBlk)("read timeout test 2 for Blk cr=allOrNothing", cp.ports, SerialPort.CanRead.allOrNothing); 181 | utCall!(fiberSleepFuncTest)("fiber sleep func test", cp.ports); 182 | } 183 | 184 | unittest 185 | { 186 | enum name = "/some/path/to/notexisting/device"; 187 | auto e = enforce(collectException(new SerialPortBlk(name, 19200)), "exception not thrown"); 188 | auto sce = cast(SysCallException)e; 189 | assert (sce !is null); 190 | assert (sce.port == name, "wrong name"); 191 | version (Posix) 192 | { 193 | assert(sce.fnc == "open", "'" ~ sce.fnc ~ "' is not 'open'"); 194 | assert(sce.err == 2, "unexpectable errno %d".format(sce.err)); 195 | } 196 | auto exp = format!"call '%s' (%s) failed: error %d"(sce.fnc, name, sce.err); 197 | if (!e.msg.startsWith(exp)) 198 | { 199 | import std.stdio : stderr; 200 | stderr.writeln("exp: ", exp); 201 | stderr.writeln("msg: ", e.msg); 202 | assert(0, "wrong msg"); 203 | } 204 | } 205 | 206 | void testPrint(Args...)(Args args) { stderr.write(" "); stderr.writeln(args); } 207 | void testPrintf(Args...)(Args args) { stderr.write(" "); stderr.writefln(args); } 208 | 209 | auto utCall(alias fnc, Args...)(string name, Args args) 210 | { 211 | stderr.writefln(">>> run %s", name); 212 | scope (success) stderr.writefln("<<< success %s\n", name); 213 | scope (failure) stderr.writefln("!!! failure %s\n", name); 214 | return fnc(args); 215 | } 216 | 217 | void threadTest(SPT)(string[2] ports) 218 | { 219 | assert(SerialPort.listAvailable.length != 0); 220 | 221 | static struct ExcStruct { string msg, type; } 222 | 223 | static void echoThread(string port) 224 | { 225 | void[BUFFER_SIZE] buffer = void; 226 | auto com = new SPT(port, "2400:8N1"); 227 | scope (exit) com.close(); 228 | com.flush(); 229 | 230 | com.set(1200); 231 | assert(com.config.baudRate == 1200); 232 | 233 | com.baudRate = 38_400; 234 | assert(com.config.baudRate == 38_400); 235 | 236 | bool work = true; 237 | com.readTimeout = 1000.msecs; 238 | 239 | bool needRead; 240 | 241 | while (work) 242 | { 243 | try 244 | { 245 | if (needRead) 246 | { 247 | Thread.sleep(500.msecs); 248 | auto data = com.read(buffer, com.CanRead.zero); 249 | 250 | if (data.length) 251 | { 252 | testPrint("child readed: ", cast(string)(data.idup)); 253 | send(ownerTid, cast(string)(data.idup)); 254 | } 255 | } 256 | 257 | receiveTimeout(500.msecs, 258 | (SPConfig cfg) 259 | { 260 | com.config = cfg; 261 | testPrint("child get cfg: ", cfg.mode); 262 | }, 263 | (bool nr) 264 | { 265 | if (nr) needRead = true; 266 | else 267 | { 268 | work = false; 269 | needRead = false; 270 | } 271 | testPrint("get needRead ", nr); 272 | }, 273 | (OwnerTerminated e) { work = false; } 274 | ); 275 | } 276 | catch (Throwable e) 277 | { 278 | work = false; 279 | testPrint("exception in child: ", e); 280 | send(ownerTid, ExcStruct(e.msg, e.classinfo.stringof)); 281 | } 282 | } 283 | } 284 | 285 | auto t = spawnLinked(&echoThread, ports[1]); 286 | 287 | auto com = new SPT(ports[0], 19_200); 288 | com.flush(); 289 | 290 | assert(com.baudRate == 19_200); 291 | assert(com.dataBits == DataBits.data8); 292 | assert(com.parity == Parity.none); 293 | assert(com.stopBits == StopBits.one); 294 | 295 | assert(com.config.baudRate == 19_200); 296 | assert(com.config.dataBits == DataBits.data8); 297 | assert(com.config.parity == Parity.none); 298 | assert(com.config.stopBits == StopBits.one); 299 | 300 | scope (exit) com.close(); 301 | 302 | string[] list; 303 | 304 | const sets = [ 305 | SPConfig(38_400), 306 | SPConfig(2400), 307 | SPConfig.parse("19200:8N2"), 308 | ]; 309 | 310 | auto cfg = SPConfig(38_400); 311 | com.config = cfg; 312 | send(t, cfg); 313 | 314 | Thread.sleep(1000.msecs); 315 | 316 | string msg = sets.front.mode; 317 | com.write(msg); 318 | 319 | bool work = true; 320 | send(t, true); 321 | while (work) 322 | { 323 | receive( 324 | (string rec) 325 | { 326 | enforce(rec == msg, "break message: '%s' != '%s'".format(msg, rec)); 327 | 328 | if (list.empty) 329 | { 330 | testPrint("owner send data finish"); 331 | send(t, false); 332 | } 333 | else 334 | { 335 | msg = list.front; 336 | list.popFront(); 337 | } 338 | 339 | com.write(msg); 340 | testPrint("owner write msg to com: ", msg); 341 | }, 342 | (ExcStruct e) { throw new Exception("%s:%s".format(e.type, e.msg)); }, 343 | (LinkTerminated e) 344 | { 345 | work = false; 346 | testPrintf("link terminated for %s, child tid %s", e.tid, t); 347 | //assert(e.tid == t); 348 | } 349 | ); 350 | } 351 | } 352 | 353 | void testNonBlock(string[2] ports) 354 | { 355 | import std.datetime.stopwatch : StopWatch, AutoStart; 356 | enum mode = "38400:8N1"; 357 | 358 | const data = "1234567890987654321qazxswedcvfrtgbnhyujm,ki"; 359 | 360 | static void thfunc(string port) 361 | { 362 | auto com = new SerialPortNonBlk(port, mode); 363 | scope (exit) com.close(); 364 | 365 | void[1024] buffer = void; 366 | size_t readed; 367 | 368 | const sw = StopWatch(AutoStart.yes); 369 | 370 | // flush 371 | while (sw.peek < 10.msecs) 372 | { 373 | com.read(buffer); 374 | Thread.sleep(1.msecs); 375 | } 376 | 377 | while (sw.peek < 1.seconds) 378 | readed += com.read(buffer[readed..$]).length; 379 | 380 | send(ownerTid, buffer[0..readed].idup); 381 | 382 | Thread.sleep(200.msecs); 383 | } 384 | 385 | auto com = new SerialPortNonBlk(ports[0], 38_400, "8N1"); 386 | scope (exit) com.close(); 387 | 388 | spawnLinked(&thfunc, ports[1]); 389 | 390 | Thread.sleep(100.msecs); 391 | 392 | size_t written; 393 | while (written < data.length) 394 | written += com.write(data[written..$]); 395 | 396 | receive((immutable(void)[] readed) 397 | { 398 | testPrint("readed: ", cast(string)readed); 399 | testPrint(" data: ", data); 400 | assert(cast(string)readed == data); 401 | }); 402 | 403 | receive((LinkTerminated e) { }); 404 | } 405 | 406 | class CF : Fiber 407 | { 408 | void[] data; 409 | 410 | SerialPortFR com; 411 | 412 | this(SerialPortFR com, size_t bufsize) 413 | { 414 | this.com = com; 415 | this.com.flush(); 416 | this.data = new void[bufsize]; 417 | foreach (ref v; cast(ubyte[])data) 418 | v = cast(ubyte)uniform(0, 128); 419 | super(&run); 420 | } 421 | 422 | abstract void run(); 423 | } 424 | 425 | class CFSlave : CF 426 | { 427 | void[] result; 428 | 429 | Duration readTimeout = 40.msecs; 430 | Duration readGapTimeout = 100.msecs; 431 | 432 | this(SerialPortFR com, size_t bufsize) 433 | { super(com, bufsize); } 434 | 435 | override void run() 436 | { 437 | testPrint("start read loop"); 438 | result = com.readContinues(data, readTimeout, readGapTimeout); 439 | testPrint("finish read loop ("~result.length.to!string~")"); 440 | } 441 | } 442 | 443 | class CFMaster : CF 444 | { 445 | CFSlave slave; 446 | 447 | Duration writeTimeout = 20.msecs; 448 | 449 | this(SerialPortFR com, size_t bufsize) 450 | { super(com, bufsize); } 451 | 452 | override void run() 453 | { 454 | testPrint("start write loop ("~data.length.to!string~")"); 455 | com.writeTimeout = writeTimeout; 456 | com.write(data); 457 | testPrint("finish write loop"); 458 | } 459 | } 460 | 461 | void fiberTest(string[2] ports) 462 | { 463 | auto slave = new CFSlave(new SerialPortFR(ports[0]), BUFFER_SIZE); 464 | scope (exit) slave.com.close(); 465 | auto master = new CFMaster(new SerialPortFR(ports[1]), BUFFER_SIZE); 466 | scope (exit) master.com.close(); 467 | 468 | bool work = true; 469 | int step; 470 | while (work) 471 | { 472 | alias TERM = Fiber.State.TERM; 473 | if (master.state != TERM) master.call; 474 | if (slave.state != TERM) slave.call; 475 | 476 | step++; 477 | Thread.sleep(30.msecs); 478 | if (master.state == TERM && slave.state == TERM) 479 | { 480 | if (slave.result.length == master.data.length) 481 | { 482 | import std.algorithm : equal; 483 | enforce(equal(cast(ubyte[])slave.result, cast(ubyte[])master.data)); 484 | work = false; 485 | testPrint("basic loop steps: ", step); 486 | } 487 | else throw new Exception(text(slave.result, " != ", master.data)); 488 | } 489 | } 490 | } 491 | 492 | void fiberTest2(string[2] ports) 493 | { 494 | string mode = "9600:8N1"; 495 | 496 | auto scom = new SerialPortFR(ports[0], 9600, "8N1"); 497 | auto mcom = new SerialPortFR(ports[1], "19200:8N1"); 498 | scope (exit) scom.close(); 499 | scope (exit) mcom.close(); 500 | 501 | version (Posix) 502 | assertThrown!UnsupportedException(scom.baudRate = 9200); 503 | 504 | scom.reopen(ports[0], SPConfig.parse(mode)); 505 | mcom.reopen(ports[1], SPConfig.parse(mode)); 506 | scom.flush(); 507 | mcom.flush(); 508 | 509 | scom.config = mcom.config; 510 | 511 | scom.readTimeout = 1000.msecs; 512 | mcom.writeTimeout = 100.msecs; 513 | 514 | version (OSX) enum BS = BUFFER_SIZE / 2; 515 | else enum BS = BUFFER_SIZE * 4; 516 | 517 | auto slave = new CFSlave(scom, BS); 518 | auto master = new CFMaster(mcom, BS); 519 | 520 | void run() 521 | { 522 | bool work = true; 523 | int step; 524 | alias TERM = Fiber.State.TERM; 525 | while (work) 526 | { 527 | if (master.state != TERM) master.call; 528 | Thread.sleep(5.msecs); 529 | if (slave.state != TERM) slave.call; 530 | 531 | step++; 532 | if (master.state == TERM && slave.state == TERM) 533 | { 534 | assert(slave.result.length == master.data.length); 535 | import std.algorithm : equal; 536 | enforce(equal(cast(ubyte[])slave.result, cast(ubyte[])master.data)); 537 | work = false; 538 | testPrint("basic loop steps: ", step); 539 | } 540 | } 541 | } 542 | 543 | run(); 544 | } 545 | 546 | void readTimeoutTest(string[2] ports) 547 | { 548 | void[1024] buffer = void; 549 | 550 | auto comA = new SerialPortFR(ports[0], 19_200); 551 | scope (exit) comA.close(); 552 | comA.flush(); 553 | assertThrown!TimeoutException(comA.readContinues(buffer[], 1.msecs, 1.msecs)); 554 | assertNotThrown!TimeoutException(comA.readContinues(buffer[], 1.msecs, 1.msecs, false)); 555 | assertThrown!TimeoutException(comA.read(buffer[])); 556 | assertThrown!TimeoutException(comA.read(buffer[], comA.CanRead.anyNonZero)); 557 | 558 | auto comB = new SerialPortBlk(ports[1], 19_200, "8N1"); 559 | scope (exit) comB.close(); 560 | comB.flush(); 561 | comB.readTimeout = 1.msecs; 562 | assertThrown!TimeoutException(comB.read(buffer[])); 563 | assertThrown!TimeoutException(comB.read(buffer[], comB.CanRead.anyNonZero)); 564 | } 565 | 566 | void readTimeoutTestConfig(SP : SerialPort)(string[2] ports, SerialPort.CanRead cr) 567 | { 568 | enum mode = "38400:8N1"; 569 | 570 | enum FULL = 100; 571 | enum SEND = "helloworld"; 572 | 573 | static void thfunc(string port) 574 | { 575 | auto com = new SP(port, mode); 576 | com.flush(); 577 | scope (exit) com.close(); 578 | com.write(SEND); 579 | } 580 | 581 | auto com = new SP(ports[0], mode); 582 | scope (exit) com.close(); 583 | auto rt = 300.msecs; 584 | com.readTimeout = rt; 585 | com.flush(); 586 | assert(com.readTimeout == rt); 587 | 588 | void[FULL] buffer = void; 589 | void[] data; 590 | 591 | spawnLinked(&thfunc, ports[1]); 592 | 593 | Thread.sleep(rt); 594 | 595 | if (cr == SerialPort.CanRead.anyNonZero) 596 | { 597 | assertNotThrown(data = com.read(buffer, cr)); 598 | assert(cast(string)data == SEND); 599 | assertThrown!TimeoutException(data = com.read(buffer, cr)); 600 | } 601 | else if (cr == SerialPort.CanRead.allOrNothing) 602 | assertThrown!TimeoutException(data = com.read(buffer)); 603 | else if (cr == SerialPort.CanRead.zero) 604 | { 605 | assertNotThrown(data = com.read(buffer, cr)); 606 | assertNotThrown(data = com.read(buffer, cr)); 607 | assertNotThrown(data = com.read(buffer, cr)); 608 | } 609 | else assert(0, "not tested variant of CanRead"); 610 | 611 | receive((LinkTerminated e) { }); 612 | } 613 | 614 | void readTimeoutTestConfig2(SP : SerialPort)(string[2] ports, SerialPort.CanRead cr) 615 | { 616 | enum mode = "38400:8N1"; 617 | 618 | static void thfunc(string port) 619 | { 620 | auto com = new SP(port, mode); 621 | scope (exit) com.close(); 622 | com.flush(); 623 | Thread.sleep(200.msecs); 624 | com.write("one"); 625 | Thread.sleep(200.msecs); 626 | com.write("two"); 627 | } 628 | 629 | auto com = new SP(ports[0], mode); 630 | scope (exit) com.close(); 631 | com.readTimeout = cr == SerialPort.CanRead.zero ? 10.msecs : 300.msecs; 632 | com.flush(); 633 | 634 | void[6] buffer = void; 635 | void[] data; 636 | 637 | spawnLinked(&thfunc, ports[1]); 638 | 639 | if (cr == SerialPort.CanRead.anyNonZero) 640 | { 641 | assertNotThrown(data = com.read(buffer, cr)); 642 | assert(cast(string)data == "one"); 643 | assertNotThrown(data = com.read(buffer, cr)); 644 | assert(cast(string)data == "two"); 645 | } 646 | else if (cr == SerialPort.CanRead.allOrNothing) 647 | assertThrown!TimeoutException(data = com.read(buffer)); 648 | else if (cr == SerialPort.CanRead.zero) 649 | { 650 | assertNotThrown(data = com.read(buffer, cr)); 651 | assert(cast(string)data == ""); 652 | Thread.sleep(300.msecs); 653 | assertNotThrown(data = com.read(buffer, cr)); 654 | assert(cast(string)data == "one"); 655 | assertNotThrown(data = com.read(buffer, cr)); 656 | assert(cast(string)data == ""); 657 | Thread.sleep(200.msecs); 658 | assertNotThrown(data = com.read(buffer, cr)); 659 | assert(cast(string)data == "two"); 660 | assertNotThrown(data = com.read(buffer, cr)); 661 | assert(cast(string)data == ""); 662 | } 663 | else assert(0, "not tested variant of CanRead"); 664 | 665 | receive((LinkTerminated e) { }); 666 | } 667 | 668 | void fiberSleepFuncTest(string[2] ports) 669 | { 670 | import std.datetime.stopwatch : StopWatch, AutoStart; 671 | 672 | static void sf(Duration d) @nogc 673 | { 674 | const sw = StopWatch(AutoStart.yes); 675 | if (auto f = Fiber.getThis) 676 | while (sw.peek < d) f.yield(); 677 | else Thread.sleep(d); 678 | } 679 | 680 | CFMaster master; 681 | 682 | size_t sf2_cnt; 683 | void sf2(Duration d) @nogc 684 | { 685 | const sw = StopWatch(AutoStart.yes); 686 | if (auto f = Fiber.getThis) 687 | while (sw.peek < d) 688 | { 689 | master.yield(); 690 | sf2_cnt++; 691 | } 692 | else Thread.sleep(d); 693 | } 694 | 695 | auto slave = new CFSlave(new SerialPortFR(ports[0], &sf), BUFFER_SIZE); 696 | scope (exit) slave.com.close(); 697 | master = new CFMaster(new SerialPortFR(ports[1], &sf2), BUFFER_SIZE); 698 | scope (exit) master.com.close(); 699 | 700 | bool work = true; 701 | int step; 702 | while (work) 703 | { 704 | alias TERM = Fiber.State.TERM; 705 | if (master.state != TERM) master.call; 706 | if (slave.state != TERM) slave.call; 707 | 708 | step++; 709 | Thread.sleep(30.msecs); 710 | if (master.state == TERM && slave.state == TERM) 711 | { 712 | if (slave.result.length == master.data.length) 713 | { 714 | import std.algorithm : equal; 715 | enforce(equal(cast(ubyte[])slave.result, cast(ubyte[])master.data)); 716 | work = false; 717 | testPrint("basic loop steps: ", step); 718 | } 719 | else throw new Exception(text(slave.result, " != ", master.data)); 720 | } 721 | } 722 | } -------------------------------------------------------------------------------- /source/serialport/util.d: -------------------------------------------------------------------------------- 1 | module serialport.util; 2 | 3 | import std.range; 4 | import std.algorithm; 5 | 6 | import std.datetime.stopwatch; 7 | 8 | void msleep(Duration dt) @nogc 9 | { 10 | import core.thread : Fiber, Thread; 11 | if (Fiber.getThis is null) Thread.sleep(dt); 12 | else 13 | { 14 | const tm = StopWatch(AutoStart.yes); 15 | do Fiber.yield(); while (tm.peek < dt); 16 | } 17 | } 18 | 19 | package bool hasFlag(A,B)(A a, B b) @property { return (a & b) == b; } 20 | 21 | struct Pair(A,B) { A a; B b; } 22 | auto pair(A,B)(A a, B b) { return Pair!(A,B)(a, b); } 23 | 24 | struct PairList(A,B) 25 | { 26 | Pair!(A,B)[] list; 27 | this(Pair!(A,B)[] list) { this.list = list; } 28 | 29 | @safe pure @nogc nothrow const 30 | { 31 | size_t countA(A a) { return list.map!(a=>a.a).count(a); } 32 | size_t countB(B b) { return list.map!(a=>a.b).count(b); } 33 | 34 | bool isUniqA() @property { return list.all!(v=>countA(v.a) == 1); } 35 | bool isUniqB() @property { return list.all!(v=>countB(v.b) == 1); } 36 | 37 | B firstA2B(A a, B defalutValue) 38 | { 39 | auto f = list.find!(v=>v.a == a); 40 | if (f.empty) return defalutValue; 41 | else return f.front.b; 42 | } 43 | 44 | A firstB2A(B b, A defalutValue) 45 | { 46 | auto f = list.find!(v=>v.b == b); 47 | if (f.empty) return defalutValue; 48 | else return f.front.a; 49 | } 50 | } 51 | 52 | @safe pure nothrow const 53 | { 54 | auto allA2B(A a) { return list.filter!(v=>v.a == a).map!(v=>v.b); } 55 | auto allB2A(B b) { return list.filter!(v=>v.b == b).map!(v=>v.a); } 56 | } 57 | } 58 | 59 | auto pairList(A,B)(Pair!(A,B)[] list...) { return PairList!(A,B)(list); } 60 | 61 | unittest 62 | { 63 | auto pl = pairList( 64 | pair(1, "hello"), 65 | pair(1, "ololo"), 66 | pair(2, "world"), 67 | pair(3, "okda") 68 | ); 69 | 70 | assert(pl.countA(1) == 2); 71 | assert(pl.firstA2B(1, "ok") == "hello"); 72 | assert(pl.countB("ok") == 0); 73 | assert(pl.countB("okda") == 1); 74 | assert(pl.firstB2A("okda", 0) == 3); 75 | assert(pl.isUniqA == false); 76 | assert(pl.isUniqB == true); 77 | } 78 | 79 | unittest 80 | { 81 | static immutable pl = pairList( 82 | pair(1, "hello"), 83 | pair(2, "world"), 84 | pair(3, "okda") 85 | ); 86 | 87 | static assert(pl.firstA2B(2, "ok") == "world"); 88 | } 89 | 90 | unittest 91 | { 92 | import std.algorithm : sum; 93 | import std.string : join; 94 | 95 | immutable pl = pairList( 96 | pair(1, "hello"), 97 | pair(2, "okda"), 98 | pair(1, "world"), 99 | pair(3, "okda") 100 | ); 101 | 102 | assert(pl.allA2B(1).join(" ") == "hello world"); 103 | assert(pl.allB2A("okda").sum == 5); 104 | } --------------------------------------------------------------------------------