TRAVIS_OS_NAME=linux <<<<<< ENV LICENSE dub.sdl example/monitor/dub.sdl example/monitor/source/app.d icon.svg source/serialport/base.d source/serialport/block.d source/serialport/config.d source/serialport/exception.d source/serialport/fiberready.d source/serialport/nonblock.d source/serialport/package.d source/serialport/types.d source/serialport/ut.d source/serialport/util.d <<<<<< network # path=source-serialport-block.lst |/// |module serialport.block; | |import serialport.base; | |/// Blocking work serialport |class SerialPortBlk : SerialPort |{ |public: | /++ Construct SerialPortBlk | | See_Also: SerialPort.this | +/ 0000000| this(string exmode) { super(exmode); } | | /// ditto 26| this(string port, string mode) { super(port, mode); } | | /// ditto 3| this(string port, uint baudRate) { super(port, baudRate); } | | /// ditto 1| this(string port, uint baudRate, string mode) 1| { super(port, baudRate, mode); } | | /// ditto 0000000| this(string port, Config conf) { super(port, conf); } | | override void[] read(void[] buf, CanRead cr=CanRead.allOrNothing) | { 34| if (closed) throwPortClosedException(port); | | version (Posix) | { 34| if (cr == CanRead.allOrNothing) 3| setCC([cast(ubyte)min(buf.length, 255), 0]); 31| else setCC([1, 0]); | 34| ssize_t res = 0; 34| auto ttm = buf.length * readTimeoutMult + readTimeout; 34| auto sw = StopWatch(AutoStart.yes); 43| while (ttm > Duration.zero) | { 43| ttm -= sw.peek; 43| sw.reset(); | 43| fd_set sset; 43| FD_ZERO(&sset); 43| FD_SET(_handle, &sset); | 43| timeval ctm; 43| ctm.tv_sec = cast(int)(ttm.total!"seconds"); 43| enum US_PER_MS = 1000; 43| ctm.tv_usec = cast(int)(ttm.split().msecs * US_PER_MS); | 43| const rv = select(_handle + 1, &sset, null, null, &ctm); 43| if (rv < 0) throwSysCallException(port, "select", errno); | 77| if (rv == 0) break; | 9| const r = posixRead(_handle, buf.ptr+res, buf.length-res); 9| if (r < 0) throwReadException(port, "posix read", errno); 9| res += r; 9| if (res == buf.length) return buf; | } | } | else | { | uint res; | | if (!ReadFile(handle, buf.ptr, cast(uint)buf.length, &res, null)) | throwReadException(port, "win read", GetLastError()); | } | 34| checkAbility(cr, res, buf.length); | 29| return buf[0..res]; | } | | override void write(const(void[]) arr) | { 11| if (closed) throwPortClosedException(port); | | version (Posix) | { 11| size_t written; 11| const ttm = arr.length * writeTimeoutMult + writeTimeout; 11| const full = StopWatch(AutoStart.yes); 22| while (written < arr.length) | { 11| if (full.peek > ttm) 0000000| throwTimeoutException(port, "write timeout"); | 11| const res = posixWrite(_handle, arr[written..$].ptr, arr.length - written); | 11| if (res < 0) 0000000| throwWriteException(port, "posix write", errno); | 11| written += res; | } | } | else | { | uint written; | | if (!WriteFile(_handle, arr.ptr, cast(uint)arr.length, &written, null)) | throwWriteException(port, "win write", GetLastError()); | | if (arr.length != written) | throwTimeoutException(port, "write timeout"); | } | } | |protected: | | override void[] m_read(void[]) @nogc 0000000| { assert(0, "disable m_read for blocking"); } | override size_t m_write(const(void)[]) @nogc 0000000| { assert(0, "disable m_write for blocking"); } | | override void updateTimeouts() @nogc { version (Windows) updTimeouts(); } | | version (Windows) | { | override void updTimeouts() @nogc | { | setTimeouts(0, cast(DWORD)readTimeoutMult.total!"msecs", | cast(DWORD)readTimeout.total!"msecs", | cast(DWORD)writeTimeoutMult.total!"msecs", | cast(DWORD)writeTimeout.total!"msecs"); | } | } | | version (Posix) | { | override void posixSetup(Config conf) | { 16| openPort(); | 15| if (fcntl(_handle, F_SETFL, 0) == -1) // disable O_NONBLOCK 0000000| throwSysCallException(port, "fcntl", errno); | 15| setCC([1,0]); | 15| initialConfig(conf); | } | } |} source/serialport/block.d is 86% covered <<<<<< EOF # path=source-serialport-nonblock.lst |/// |module serialport.nonblock; | |import serialport.base; | |/// Non-blocking work with serial port |class SerialPortNonBlk : SerialPortBase |{ | /++ Construct SerialPortNonBlk | | See_Also: SerialPortBase.this | +/ 0000000| this(string exmode) { super(exmode); } | | /// ditto 2| this(string port, string mode) { super(port, mode); } | | /// ditto 0000000| this(string port, uint baudRate) { super(port, baudRate); } | | /// ditto 1| this(string port, uint baudRate, string mode) 1| { super(port, baudRate, mode); } | | /// ditto 0000000| this(string port, Config conf) { super(port, conf); } | | /++ Non-block read data from port | | Params: | buf = preallocated buffer for reading | | Returns: slice of buf with readed data | | Throws: | PortClosedException if port closed | ReadException if read error occurs | +/ 2070333| void[] read(void[] buf) @nogc { return m_read(buf); } | | /++ Non-block write data to port | | Params: | arr = data for writing | | Returns: count of writen bytes | | Throws: | PortClosedException if port closed | WriteException if read error occurs | +/ 1| size_t write(const(void)[] buf) @nogc { return m_write(buf); } |} source/serialport/nonblock.d is 62% covered <<<<<< EOF # path=source-serialport-types.lst |/// Definitions of using types |module serialport.types; | |/// |enum Parity |{ | none, /// | odd, /// | even /// |} | |/// |enum DataBits : uint |{ | data8 = 8, /// | data7 = 7, /// | data6 = 6, /// | data5 = 5, /// |} | |/// |enum StopBits |{ | one, /// | onePointFive, /// | two /// |} | |package |{ | version(Posix) | { | import core.sys.posix.unistd; | version(linux) | import core.sys.linux.termios; | else | import core.sys.posix.termios; | | import core.sys.posix.fcntl; | import core.sys.posix.sys.select; | import core.sys.posix.sys.ioctl; | import core.stdc.errno; | import std.algorithm; | import std.file; | | alias posixRead = core.sys.posix.unistd.read; | alias posixWrite = core.sys.posix.unistd.write; | alias posixClose = core.sys.posix.unistd.close; | | alias closeHandle = posixClose; | /// Posix handle is int | alias SPHandle = int; | enum initHandle = -1; | } | version(Windows) | { | import core.sys.windows.windows; | import std.bitmanip; | | enum NOPARITY = 0x0; | enum EVENPARITY = 0x2; | enum MARKPARITY = 0x3; | enum ODDPARITY = 0x1; | enum SPACEPARITY = 0x4; | | enum ONESTOPBIT = 0x0; | enum ONE5STOPBITS = 0x1; | enum TWOSTOPBITS = 0x2; | | struct DCB | { | DWORD DCBlength; | DWORD BaudRate; | | mixin(bitfields!( | DWORD, "fBinary", 1, | DWORD, "fParity", 1, | DWORD, "fOutxCtsFlow", 1, | DWORD, "fOutxDsrFlow", 1, | DWORD, "fDtrControl", 2, | DWORD, "fDsrSensitivity", 1, | DWORD, "fTXContinueOnXoff", 1, | DWORD, "fOutX", 1, | DWORD, "fInX", 1, | DWORD, "fErrorChar", 1, | DWORD, "fNull", 1, | DWORD, "fRtsControl", 2, | DWORD, "fAbortOnError", 1, | DWORD, "fDummy2", 17)); | | WORD wReserved; | WORD XonLim; | WORD XoffLim; | BYTE ByteSize; | BYTE Parity; | BYTE StopBits; | ubyte XonChar; | ubyte XoffChar; | ubyte ErrorChar; | ubyte EofChar; | ubyte EvtChar; | WORD wReserved1; | } | | struct COMMTIMEOUTS | { | DWORD ReadIntervalTimeout; | DWORD ReadTotalTimeoutMultiplier; | DWORD ReadTotalTimeoutConstant; | DWORD WriteTotalTimeoutMultiplier; | DWORD WriteTotalTimeoutConstant; | } | | extern(Windows) @nogc | { | bool GetCommState(HANDLE hFile, DCB* lpDCB); | bool SetCommState(HANDLE hFile, DCB* lpDCB); | bool SetCommTimeouts(HANDLE hFile, COMMTIMEOUTS* lpCommTimeouts); | } | | alias closeHandle = CloseHandle; | /// Windows is HANDLE | alias SPHandle = HANDLE; | enum initHandle = null; | } |} | |/// |static immutable string modeSplitChar=":"; | |version (Posix) |{ | import serialport.util; | | version (OSX) | { | /+ | 38400 is max for OSX | http://www.manpages.info/macosx/cfsetospeed.3.html | +/ | static immutable unixBaudList = pairList( | pair( 0, B0), | pair( 50, B50), | pair( 75, B75), | pair( 110, B110), | pair( 134, B134), | pair( 150, B150), | pair( 200, B200), | pair( 300, B300), | pair( 600, B600), | pair( 1200, B1200), | pair( 1800, B1800), | pair( 2400, B2400), | pair( 4800, B4800), | pair( 9600, B9600), | pair(19200, B19200), | pair(38400, B38400) | ); | } | else | { | static immutable unixBaudList = pairList( | pair( 0, B0), | pair( 50, B50), | pair( 75, B75), | pair( 110, B110), | pair( 134, B134), | pair( 150, B150), | pair( 200, B200), | pair( 300, B300), | pair( 600, B600), | pair( 1200, B1200), | pair( 1800, B1800), | pair( 2400, B2400), | pair( 4800, B4800), | pair( 9600, B9600), | pair( 19200, B19200), | pair( 38400, B38400), | pair( 57600, B57600), | pair(115200, B115200), | pair(230400, B230400) | ); | } | | static assert(unixBaudList.isUniqA, "not uniq A vals in unix baud list"); | static assert(unixBaudList.isUniqB, "not uniq B vals in unix baud list"); |} source/serialport/types.d has no code <<<<<< EOF # path=source-serialport-util.lst |module serialport.util; | |import std.range; |import std.algorithm; | |import std.datetime.stopwatch; | |void msleep(Duration dt) @nogc |{ | import core.thread : Fiber, Thread; 21628| if (Fiber.getThis is null) Thread.sleep(dt); | else | { 2| const tm = StopWatch(AutoStart.yes); 4| do Fiber.yield(); while (tm.peek < dt); | } |} | 408|package bool hasFlag(A,B)(A a, B b) @property { return (a & b) == b; } | |struct Pair(A,B) { A a; B b; } 8|auto pair(A,B)(A a, B b) { return Pair!(A,B)(a, b); } | |struct PairList(A,B) |{ | Pair!(A,B)[] list; 4| this(Pair!(A,B)[] list) { this.list = list; } | | @safe pure @nogc nothrow const | { 1010| size_t countA(A a) { return list.map!(a=>a.a).count(a); } 30| size_t countB(B b) { return list.map!(a=>a.b).count(b); } | 2| bool isUniqA() @property { return list.all!(v=>countA(v.a) == 1); } 5| bool isUniqB() @property { return list.all!(v=>countB(v.b) == 1); } | | B firstA2B(A a, B defalutValue) | { 794| auto f = list.find!(v=>v.a == a); 50| if (f.empty) return defalutValue; 50| else return f.front.b; | } | | A firstB2A(B b, A defalutValue) | { 2184| auto f = list.find!(v=>v.b == b); 137| if (f.empty) return defalutValue; 137| else return f.front.a; | } | } | | @safe pure nothrow const | { 7| auto allA2B(A a) { return list.filter!(v=>v.a == a).map!(v=>v.b); } 7| auto allB2A(B b) { return list.filter!(v=>v.b == b).map!(v=>v.a); } | } |} | 2|auto pairList(A,B)(Pair!(A,B)[] list...) { return PairList!(A,B)(list); } | |unittest |{ 1| auto pl = pairList( | pair(1, "hello"), | pair(1, "ololo"), | pair(2, "world"), | pair(3, "okda") | ); | 1| assert(pl.countA(1) == 2); 1| assert(pl.firstA2B(1, "ok") == "hello"); 1| assert(pl.countB("ok") == 0); 1| assert(pl.countB("okda") == 1); 1| assert(pl.firstB2A("okda", 0) == 3); 1| assert(pl.isUniqA == false); 1| assert(pl.isUniqB == true); |} | |unittest |{ 1| static immutable pl = pairList( | pair(1, "hello"), | pair(2, "world"), | pair(3, "okda") | ); | | static assert(pl.firstA2B(2, "ok") == "world"); |} | |unittest |{ | import std.algorithm : sum; | import std.string : join; | 1| immutable pl = pairList( | pair(1, "hello"), | pair(2, "okda"), | pair(1, "world"), | pair(3, "okda") | ); | 1| assert(pl.allA2B(1).join(" ") == "hello world"); 1| assert(pl.allB2A("okda").sum == 5); |} source/serialport/util.d is 100% covered <<<<<< EOF # path=source-serialport-fiberready.lst |/// |module serialport.fiberready; | |import serialport.base; | |import std.traits : isSomeFunction, | FunctionAttribute, | functionAttributes; | |/++ Serial Port Fiber Ready | +/ |class SerialPortFR : SerialPort |{ |protected: | | /++ Preform pause | | If sleepFunc isn't null call it. Else use `Thread.sleep` or | `Fiber.yield` if code executes in fiber. | | Params: | dt = sleep time | +/ | void sleep(Duration dt) @nogc | { 21654| if (_sleepFunc is null) msleep(dt); 25| else _sleepFunc(dt); | } | | /++ Calc pause for sleep in read and write loops | +/ | Duration ioPause() @nogc | { 61| auto cfg = config; 61| auto cnt = 1 + // start bit | cast(int)cfg.dataBits + 61| (cfg.parity == Parity.none ? 0 : 1) + 61| (cfg.stopBits == StopBits.one ? 1 : 0000000| cfg.stopBits == StopBits.onePointFive ? 1.5 : 2) + | 1.5 // reserve | ; 61| return (cast(ulong)(cnt / cfg.baudRate * 1e6) + 100/+reserve+/).usecs; | } | | void delegate(Duration) @nogc _sleepFunc; | |public: | /// assume @nogc | deprecated | alias SleepFunc = void delegate(Duration); | | /// | alias SleepFuncNoGC = void delegate(Duration) @nogc; | | /// extended delegate for perform sleep | deprecated("sleep function must be @nogc") | void sleepFunc(SleepFunc dlg) @property 0000000| { _sleepFunc = cast(void delegate(Duration) @nogc)dlg; } | | /// | deprecated("sleep function must be @nogc") | void sleepFunc(void function(Duration) fnc) @property 0000000| { _sleepFunc = (d){ (cast(void function(Duration) @nogc)fnc)(d); }; } | | /// extended delegate for perform sleep | void sleepFunc(void delegate(Duration) @nogc dlg) @property 20| { _sleepFunc = dlg; } | | /// ditto | void sleepFunc(void function(Duration) @nogc fnc) @property 18| { _sleepFunc = (d){ fnc(d); }; } | | /// ditto 0000000| SleepFuncNoGC sleepFunc() @property { return _sleepFunc; } | | /++ Construct SerialPortFR | | See_Also: SerialPort.this | +/ | deprecated("sleep function must be @nogc") | this(F=SleepFunc)(string exmode, F sf) | if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc)) | { sleepFunc = sf; super(exmode); } | | /// ditto | deprecated("sleep function must be @nogc") | this(F=SleepFunc)(string port, string mode, F sf) | if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc)) | { sleepFunc = sf; super(port, mode); } | | /// ditto | deprecated("sleep function must be @nogc") | this(F=SleepFunc)(string port, uint baudRate, F sf) | if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc)) | { sleepFunc = sf; super(port, baudRate); } | | /// ditto | deprecated("sleep function must be @nogc") | this(F=SleepFunc)(string port, uint baudRate, string mode, F sf) | if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc)) | { sleepFunc = sf; super(port, baudRate, mode); } | | /// ditto | deprecated("sleep function must be @nogc") | this(F=SleepFunc)(string port, Config conf, F sf) | if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc)) | { sleepFunc = sf; super(port, conf); } | | /// ditto 4| this(F=SleepFuncNoGC)(string exmode, F sf=null) | if (isSomeFunction!F) 8| { sleepFunc = sf; super(exmode); } | | /// ditto 14| this(F=SleepFuncNoGC)(string port, string mode, F sf=null) | if (isSomeFunction!F && (functionAttributes!F & FunctionAttribute.nogc)) 28| { sleepFunc = sf; super(port, mode); } | | /// ditto 2| this(F=SleepFuncNoGC)(string port, uint baudRate, F sf=null) | if (isSomeFunction!F && (functionAttributes!F & FunctionAttribute.nogc)) 4| { sleepFunc = sf; super(port, baudRate); } | | /// ditto 1| this(F=SleepFuncNoGC)(string port, uint baudRate, string mode, F sf=null) | if (isSomeFunction!F && (functionAttributes!F & FunctionAttribute.nogc)) 2| { sleepFunc = sf; super(port, baudRate, mode); } | | /// ditto | this(F=SleepFuncNoGC)(string port, Config conf, F sf=null) | if (isSomeFunction!F && (functionAttributes!F & FunctionAttribute.nogc)) | { sleepFunc = sf; super(port, conf); } | | override void[] read(void[] buf, CanRead cr=CanRead.allOrNothing) | { 42| if (closed) throwPortClosedException(port); | 42| size_t res; 42| const timeout = buf.length * readTimeoutMult + readTimeout; 42| const pause = ioPause(); 42| const sw = StopWatch(AutoStart.yes); 10877| while (sw.peek < timeout) | { 10837| res += m_read(buf[res..$]).length; 10836| if (res == buf.length) return buf[]; 10836| this.sleep(pause); | } | 42| checkAbility(cr, res, buf.length); | 37| return buf[0..res]; | } | | override void write(const(void[]) arr) | { 14| if (closed) throwPortClosedException(port); | 14| size_t written; 14| const timeout = arr.length * writeTimeoutMult + writeTimeout; 14| const pause = ioPause(); 14| const sw = StopWatch(AutoStart.yes); 14| while (sw.peek < timeout) | { 14| written += m_write(arr[written..$]); 28| if (written == arr.length) return; 0000000| this.sleep(pause); | } | 0000000| throwTimeoutException(port, "write timeout"); | } | | /++ Read data while available by parts, sleep between checks. | | Sleep time calculates from baud rate and count of bits in one byte. | | ------- | ------|--------|-----|------------|-----|------------> t | call | | | | | readContinues | | | | | | | | | | | | |<---------data receive---------->| | | |=== ===== ======| | |== =| data stream | | | | | | | | | |<--timeout--->| | | | | | | |<-1->| |<2>| |<-3->| | | | | | | | |<---readedData--->| | | | return | |<-------readAll work time------->| | | (1) if readedData.length > 0 then continue reading | else if expectAnything throw TimeoutException | else return readedData (empty) | (2) silent time, if silent < frameGap then continue reading | (3) else if silent > frameGap then stop reading | and return readedData | ------- | | Params: | buf = buffer for reading | startTimeout = timeout for first byte recive | frameGap = detect new data frame by silence period | expectAnything = function throw exception if no data | before startTimeout | | Returns: slice of buf with readed data | | Throws: | PortClosedException | ReadException | TimeoutException | | See_Also: SerialPort.read | +/ | void[] readContinues(void[] buf, Duration startTimeout=1.seconds, | Duration frameGap=50.msecs, | bool expectAnything=true) | { 5| if (closed) throwPortClosedException(port); | 5| ptrdiff_t readed; | 5| auto pause = ioPause(); | 10| StopWatch silence, full; | 5| full.start(); 12| while (true) | { 12| const res = m_read(buf[readed..$]).length; | 12| readed += res; | | // buffer filled 15| if (readed == buf.length) return buf[]; | 9| if (res == 0) | { 8| if (readed > 0 && silence.peek > frameGap) 0000000| return buf[0..readed]; | 12| if (!silence.running) silence.start(); | } | else | { 1| silence.stop(); 1| silence.reset(); | } | 17| if (readed == 0 && full.peek > startTimeout) | { 2| if (expectAnything) 1| throwTimeoutException(port, "read timeout"); | else 1| return buf[0..0]; | } | 7| this.sleep(pause); | } | } |} source/serialport/fiberready.d is 88% covered <<<<<< EOF # path=source-serialport-config.lst |/// |module serialport.config; | |import std.range : split; |import std.string : format, toLower; |import std.exception : enforce, assertThrown, assertNotThrown; |import std.conv : to; | |import serialport.types; |import serialport.exception; | |/// |struct SPConfig |{ | /// | uint baudRate=9600; | /// | DataBits dataBits=DataBits.data8; | /// | Parity parity=Parity.none; | /// | StopBits stopBits=StopBits.one; | | /// | bool hardwareDisableFlowControl = true; | | /++ Set parity value | Returns: this | +/ 2| ref SPConfig set(Parity v) @nogc { parity = v; return this; } | | /++ Set baudrate value | Returns: this | +/ 12| ref SPConfig set(uint v) @nogc { baudRate = v; return this; } | | /++ Set data bits value | Returns: this | +/ 2| ref SPConfig set(DataBits v) @nogc { dataBits = v; return this; } | | /++ Set stop bits value | Returns: this | +/ 2| ref SPConfig set(StopBits v) @nogc { stopBits = v; return this; } | | /++ Use mode string for setting baudrate, data bits, parity and stop bits. | | Format: "B:DPS" | where: | B is baud rate | D is data bits (5, 6, 7, 8) | P is parity ('N' or 'n' -- none, | 'E' or 'e' -- even, | 'O' or 'o' -- odd) | S is stop bits ('1', '1.5', '2') | | You can skip baudrate. | | example mode strings: "9600:8N1" ":8n1" "7o1.5" "2400:6e2" | | Throws: | ParseModeException if mode string is badly formatted or using bad values | +/ | ref SPConfig set(string mode) | { 50| alias PME = ParseModeException; | 50| auto errstr = "error mode '%s'".format(mode); 50| enforce(mode.length >= 3, new PME(errstr ~ ": too short")); | 50| auto vals = mode.split(modeSplitChar); | 50| if (vals.length == 0) return this; | 50| if (vals.length > 2) 1| throw new PME(errstr ~ ": many parts"); | 49| if (vals.length == 2) | { 42| if (vals[0].length) | { 41| try baudRate = vals[0].to!uint; | catch (Exception e) 1| throw new PME(errstr ~ | ": baud rate parse error: " ~ e.msg); | } 41| mode = vals[1]; | } 7| else mode = vals[0]; | 48| auto db = cast(int)mode[0] - cast(int)'0'; 142| if (db >= 5 && db <= 8) dataBits = cast(DataBits)db; 1| else throw new PME(errstr ~ ": unsupported data bits '" ~ mode[0] ~ "'"); | 47| auto p = mode[1..2].toLower; 61| if (p == "n" || p == "o" || p == "e") | { 46| parity = ["n": Parity.none, | "o": Parity.odd, | "e": Parity.even][p]; | } 1| else throw new PME(errstr ~ ": unsupported parity '" ~ p ~ "'"); | 46| auto sb = mode[2..$]; 62| if (sb == "1" || sb == "1.5" || sb == "2") | { 45| stopBits = ["1": StopBits.one, | "1.5": StopBits.onePointFive, | "2": StopBits.two][sb]; | } 1| else throw new PME(errstr ~ ": unsupported stop bits '" ~ sb ~ "'"); | 45| return this; | } | | /// | unittest | { 1| SPConfig c; 1| c.set("2400:7e1.5"); 2| assertNotThrown(c.set(c.mode)); 1| assert(c.baudRate == 2400); 1| assert(c.dataBits == DataBits.data7); 1| assert(c.parity == Parity.even); 1| assert(c.stopBits == StopBits.onePointFive); 1| c.set("8N1"); 2| assertNotThrown(c.set(c.mode)); 1| assert(c.baudRate == 2400); 1| assert(c.dataBits == DataBits.data8); 1| assert(c.parity == Parity.none); 1| assert(c.stopBits == StopBits.one); 1| c.set("320:5o2"); 2| assertNotThrown(c.set(c.mode)); 1| assert(c.baudRate == 320); 1| assert(c.dataBits == DataBits.data5); 1| assert(c.parity == Parity.odd); 1| assert(c.stopBits == StopBits.two); | 1| alias PME = ParseModeException; 2| assertThrown!PME(c.set("4o2")); 2| assertThrown!PME(c.set("5x2")); 2| assertThrown!PME(c.set("8e3")); 2| assertNotThrown!PME(c.set(":8N1")); 2| assertNotThrown(c.set(c.mode)); | } | | /++ Construct config, parse mode to it and return. | | Returns: new config | | See_Also: set(string mode) | +/ | static SPConfig parse(string mode) | { 32| SPConfig ret; 32| ret.set(mode); 32| return ret; | } | | /++ Build mode string. | | Can be used for parsing. | | Returns: mode string | | See_Also: parse, set(string mode) | +/ | string mode() const @property | { 9| return "%s:%s%s%s".format( | baudRate, | dataBits.to!int, | [Parity.none: "n", | Parity.odd: "o", | Parity.even: "e"][parity], | [StopBits.one: "1", | StopBits.onePointFive: "1.5", | StopBits.two: "2" | ][stopBits] | ); | } |} | |unittest |{ 2| SPConfig a, b; 1| a.set("2400:7e2"); 1| b.set(a.mode); 1| assert(a == b); 1| a.set(Parity.none).set(DataBits.data8).set(19200).set(StopBits.one); 1| assert(a.parity == Parity.none); 1| assert(a.dataBits == DataBits.data8); 1| assert(a.baudRate == 19200); 1| assert(a.stopBits == StopBits.one); |} | |unittest |{ 1| SPConfig a; 2| assertThrown!ParseModeException(a.set("2400:7e2:32")); 2| assertThrown!ParseModeException(a.set("24a0:7e2")); |} source/serialport/config.d is 100% covered <<<<<< EOF # path=source-serialport-exception.lst |/// |module serialport.exception; | |import std.conv : text; | |import serialport.types; | |/// |class ParseModeException : Exception |{ 5| this(string msg, string file=__FILE__, size_t line=__LINE__) | @safe pure nothrow @nogc 5| { super(msg, file, line); } |} | |/// General |class SerialPortException : Exception |{ | string port; 224| private this() @safe pure nothrow @nogc { super(""); } |} | |/// Unsupported config |class UnsupportedException : SerialPortException 32|{ private this() @safe pure nothrow @nogc { super(); } } | |/// |class PortClosedException : SerialPortException 32|{ private this() @safe pure nothrow @nogc { super(); } } | |/// |class TimeoutException : SerialPortException 32|{ private this() @safe pure nothrow @nogc { super(); } } | |/// |class SysCallException : SerialPortException |{ | /// sys call name | string fnc; | /// errno or GetLastError | int err; 96| private this() @safe pure nothrow @nogc { super(); } |} | |/// |class ReadException : SysCallException 32|{ private this() @safe pure nothrow @nogc { super(); } } | |/// |class WriteException : SysCallException 32|{ private this() @safe pure nothrow @nogc { super(); } } | |private E setFields(E: SerialPortException)(E e, string port, string msg, | string file, size_t line) |{ 13| if (e is null) // assert(0) not omit on optimize by compiler 0000000| assert(0, "setField get null exception object"); 13| e.port = port; 13| e.msg = msg; 13| e.file = file; 13| e.line = line; 13| return e; |} | |import std.format; | |private enum preallocated; |private enum prealloc_prefix = "prealloc"; | |private mixin template throwSPEMix(E, string defaultMsg="") | if (is(E: SerialPortException)) |{ | enum string name = E.stringof; | mixin(` | @preallocated private %1$s %2$s%1$s; | void throw%1$s(string port, string msg="%3$s", | string file=__FILE__, size_t line=__LINE__) @nogc | { throw %2$s%1$s.setFields(port, msg, file, line); } | `.format(name, prealloc_prefix, (defaultMsg.length ? defaultMsg : name)) | ); |} | |private enum fmtSPSCEMsgFmt = "call '%s' (%s) failed: error %d"; | |private string fmtSPSCEMsg(string port, string fnc, int err) @nogc |{ | import core.stdc.stdio : sprintf; | import std.algorithm : min; | import core.stdc.string : memcpy, memset; | 4| enum SZ = 256; | 4| static char[SZ] port_buf; 4| static char[SZ] fnc_buf; 4| static char[SZ*3] buf; | 4| memset(port_buf.ptr, 0, SZ); 4| memset(fnc_buf.ptr, 0, SZ); 4| memset(buf.ptr, 0, SZ*3); 4| memcpy(port_buf.ptr, port.ptr, min(port.length, SZ)); 4| memcpy(fnc_buf.ptr, fnc.ptr, min(fnc.length, SZ)); 4| auto n = sprintf(buf.ptr, fmtSPSCEMsgFmt, fnc_buf.ptr, port_buf.ptr, err); 4| return cast(string)buf[0..n]; |} | |unittest |{ | import std.format : format; | 1| static auto fmtSPSCEMsgGC(string port, string fnc, int err) 3| { return format!fmtSPSCEMsgFmt(fnc, port, err); } | 1| void test(string port, string fnc, int err) | { 3| auto trg = fmtSPSCEMsg(port, fnc, err); 3| auto tst = fmtSPSCEMsgGC(port, fnc, err); 3| if (trg != tst) assert(0, "not equals:\n%s\n%s".format(trg, tst)); | } | 1| test("/dev/ttyUSB0", "open", 2); 1| test("/very/very/very/very/very/very/very/very/very/very/big/path/to/com/port/device/dev", | "veryVeryVeryVeryLongFunctionName12345678901234567890123456789012345678901234567890", | int.max); 1| test("", "", 0); |} | |private mixin template throwSPSCEMix(E) | if (is(E: SysCallException)) |{ | enum name = E.stringof; | mixin(` | @preallocated private %1$s %2$s%1$s; | void throw%1$s(string port, string fnc, int err, string msg="", | string file=__FILE__, size_t line=__LINE__) @nogc | { | if (msg.length == 0) | msg = fmtSPSCEMsg(port, fnc, err); | auto e = %2$s%1$s.setFields(port, msg, file, line); | e.fnc = fnc; | e.err = err; | throw e; | } | `.format(name, prealloc_prefix) | ); |} | |static this() |{ | // can't use origin getSymbolsByUDA because | // https://issues.dlang.org/show_bug.cgi?id=20054 | static if (__VERSION__ < 2088) | { | import std.traits : getSymbolsByUDA; 16| alias plist = getSymbolsByUDA!(mixin(__MODULE__), preallocated); | } | else | { | import std.meta : AliasSeq; | | alias plist = AliasSeq!( | preallocSerialPortException, | preallocPortClosedException, | preallocTimeoutException, | preallocSysCallException, | preallocReadException, | preallocWriteException, | preallocUnsupported | ); | } | 224| static foreach (sym; plist) sym = new typeof(sym); |} | |mixin throwSPEMix!SerialPortException; |mixin throwSPEMix!PortClosedException; |mixin throwSPEMix!TimeoutException; | |mixin throwSPSCEMix!SysCallException; |mixin throwSPSCEMix!ReadException; |mixin throwSPSCEMix!WriteException; | |import serialport.types; |import core.stdc.stdio; | |private char[1024] UEMPB; | |@preallocated |private UnsupportedException preallocUnsupported; | |void throwUnsupportedException(string port, int baudrate, | string file=__FILE__, size_t line=__LINE__) @nogc |{ 1| auto ln = sprintf(UEMPB.ptr, "unsupported baudrate: %d", baudrate); 1| throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line); |} | |void throwUnsupportedException(string port, DataBits dbits, | string file=__FILE__, size_t line=__LINE__) @nogc |{ 0000000| auto ln = sprintf(UEMPB.ptr, "unsupported data bits: %d", cast(int)dbits); 0000000| throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line); |} | |void throwUnsupportedException(string port, StopBits sbits, | string file=__FILE__, size_t line=__LINE__) @nogc |{ 0000000| string str; 0000000| final switch (sbits) with (StopBits) | { 0000000| case one: str = "1\0"; break; 0000000| case two: str = "2\0"; break; 0000000| case onePointFive: str = "1.5\0"; break; | } | 0000000| auto ln = sprintf(UEMPB.ptr, "unsupported stop bits: %s", str.ptr); 0000000| throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line); |} | |void throwUnsupportedException(string port, Parity parity, | string file=__FILE__, size_t line=__LINE__) @nogc |{ 0000000| string str; 0000000| final switch (parity) with (Parity) | { 0000000| case none: str = "none\0"; break; 0000000| case even: str = "even\0"; break; 0000000| case odd: str = "odd\0"; break; | } 0000000| auto ln = sprintf(UEMPB.ptr, "unsupported parity: %s", str.ptr); 0000000| throw preallocUnsupported.setFields(port, cast(immutable)UEMPB[0..ln], file, line); |} source/serialport/exception.d is 69% covered <<<<<< EOF # path=source-serialport-base.lst |/// |module serialport.base; | |package import std.algorithm; |package import std.array; |package import std.conv : to, text; |package import std.exception; |package import std.experimental.logger; |package import std.path; |package import std.string; |package import std.datetime.stopwatch : StopWatch, AutoStart; |package import core.time; | |package import serialport.config; |package import serialport.exception; |package import serialport.types; |package import serialport.util; | |/++ | +/ |abstract class SerialPortBase |{ |protected: | /// | string port; | | SPHandle _handle = initHandle; | |public: | | /// | alias Config = SPConfig; | | /++ Construct SerialPort using extend mode string. | | First part of extend mode string must have port name | (e.g. "com1" or "/dev/ttyUSB0"), second part is equal | to config mode string. Parts separates by `modeSplitChar` (`:`). | | Example extend mode string: "/dev/ttyUSB0:9600:8N1" | | Params: | exmode = extend mode string | | See_Also: Config.parse, Config.set(string mode) | | Throws: | ParseModeException | +/ 4| this(string exmode) | { 4| auto s = exmode.split(modeSplitChar); 4| if (s.length == 0) throw new ParseModeException("empty config mode"); 8| this(s[0], s.length > 1 ? Config.parse(s[1..$].join(modeSplitChar)) : Config.init); | } | | /++ Construct SerialPort using port name and mode string. | | Params: | port = port name | mode = config mode string | | See_Also: Config.parse, Config.set(string mode) | +/ 56| this(string port, string mode) { this(port, Config.parse(mode)); } | | /++ Params: | port = port name | baudRate = baudrate | +/ 7| this(string port, uint baudRate) { this(port, Config(baudRate)); } | | /++ Params: | port = port name | baudRate = baudrate | mode = config mode string | | See_Also: Config.parse, Config.set(string mode) | +/ 3| this(string port, uint baudRate, string mode) 3| { this(port, Config(baudRate).set(mode)); } | | /++ Params: | port = port name | conf = config of serialport | +/ 77| this(string port, Config conf) { reopen(port, conf); } | 8| ~this() { close(); } | | /// Close handle | void close() @nogc | { 56| if (closed) return; 40| closeHandle(_handle); 40| _handle = initHandle; | } | | /// Port name 0000000| string name() const @nogc @property { return port; } | | /// 0000000| inout(SPHandle) handle() inout @nogc @property { return _handle; } | | /// | void reopen(string np, Config cfg) | { 43| if (!closed) close(); 41| port = np; 41| setup(cfg); | } | | /// 0000000| void reopen(string np) { reopen(np, config); } | /// 0000000| void reopen(Config cfg) { reopen(port, cfg); } | /// 0000000| void reopen() { reopen(port, config); } | | /++ Returns extend mode string (example: "/dev/ttyUSB0:38400:8N1") | +/ 0000000| override string toString() const { return port ~ modeSplitChar ~ config.mode; } | | /++ Set config value | Params: | T = typeof of parameter, avalable: | int -> baudrate, | DataBit -> dataBits, | StopBits -> stopBits, | Parity -> parity | val = value | +/ | typeof(this) set(T)(T val) @nogc if (is(typeof(Config.init.set(val)))) | { 5| Config tmp = config; 5| tmp.set(val); 5| config = tmp; 4| return this; | } | | /// Set config mode string | typeof(this) set(string val) | { 0000000| Config tmp = config; 0000000| tmp.set(val); 0000000| config = tmp; 0000000| return this; | } | | /// | bool closed() const @property @nogc nothrow | { 2081575| version (Posix) return _handle == initHandle; | version (Windows) return _handle is initHandle; | } | | /++ Get config | | const for disallow `com.config.set(value)` | use `com.set(value)` instead | +/ | const(Config) config() @property const @nogc | { 136| if (closed) throwPortClosedException(port); | 136| Config ret; | | version (Posix) | { 136| termios opt; 136| m_tcgetattr(&opt); | 136| ret.baudRate = getUintBaudRate(); | 136| if (opt.c_cflag.hasFlag(PARODD)) ret.parity = Parity.odd; 272| else if (!(opt.c_cflag & PARENB)) ret.parity = Parity.none; 0000000| else ret.parity = Parity.even; | 272| if (opt.c_cflag.hasFlag(CS8)) ret.dataBits = DataBits.data8; 0000000| else if (opt.c_cflag.hasFlag(CS7)) ret.dataBits = DataBits.data7; 0000000| else if (opt.c_cflag.hasFlag(CS6)) ret.dataBits = DataBits.data6; 0000000| else if (opt.c_cflag.hasFlag(CS5)) ret.dataBits = DataBits.data5; 0000000| else throwSerialPortException(port, "unknown flags for databits"); | 272| ret.stopBits = opt.c_cflag.hasFlag(CSTOPB) ? StopBits.two : StopBits.one; | } | version (Windows) | { | DCB cfg; | GetCommState(cast(SPHandle)_handle, &cfg); | | ret.baudRate = cast(uint)cfg.BaudRate; | | switch (cfg.Parity) | { | case NOPARITY: ret.parity = Parity.none; break; | case ODDPARITY: ret.parity = Parity.odd; break; | case EVENPARITY: ret.parity = Parity.even; break; | default: throwSerialPortException(port, "unknown parity"); break; | } | | if (cfg.ByteSize < 5 || cfg.ByteSize > 8) | throwSerialPortException(port, "unknown databist count"); | ret.dataBits = cast(DataBits)cfg.ByteSize; | | ret.stopBits = cfg.StopBits == ONESTOPBIT ? StopBits.one : StopBits.two; | } | 136| return ret; | } | | /// Set config | void config(Config c) @property @nogc | { 50| if (closed) throwPortClosedException(port); | | version (Posix) | { 50| setUintBaudRate(c.baudRate); | 49| termios opt; 49| m_tcgetattr(&opt); | 49| final switch (c.parity) | { 49| case Parity.none: 49| opt.c_cflag &= ~PARENB; 49| break; 0000000| case Parity.odd: 0000000| opt.c_cflag |= (PARENB | PARODD); 0000000| break; 0000000| case Parity.even: 0000000| opt.c_cflag &= ~PARODD; 0000000| opt.c_cflag |= PARENB; 0000000| break; | } | 49| final switch (c.stopBits) | { 49| case StopBits.one: 49| opt.c_cflag &= ~CSTOPB; 49| break; 0000000| case StopBits.onePointFive: 0000000| case StopBits.two: 0000000| opt.c_cflag |= CSTOPB; 0000000| break; | } | 49| opt.c_cflag &= ~CSIZE; 49| final switch (c.dataBits) with (DataBits) | { 0000000| case data5: opt.c_cflag |= CS5; break; 0000000| case data6: opt.c_cflag |= CS6; break; 0000000| case data7: opt.c_cflag |= CS7; break; 147| case data8: opt.c_cflag |= CS8; break; | } | 49| m_tcsetattr(TCSANOW, &opt); | 49| const test = config; 49| if (test.baudRate != c.baudRate) throwUnsupportedException(port, c.baudRate); 49| if (test.parity != c.parity) throwUnsupportedException(port, c.parity); 49| if (test.stopBits != c.stopBits) throwUnsupportedException(port, c.stopBits); 49| if (test.dataBits != c.dataBits) throwUnsupportedException(port, c.dataBits); | } | version (Windows) | { | DCB cfg; | GetCommState(_handle, &cfg); | | if (cfg.BaudRate != cast(DWORD)c.baudRate) | { | cfg.BaudRate = cast(DWORD)c.baudRate; | if (!SetCommState(_handle, &cfg)) | throwUnsupportedException(port, c.baudRate); | } | | auto tmpParity = NOPARITY; | if (c.parity == Parity.odd) tmpParity = ODDPARITY; | if (c.parity == Parity.even) tmpParity = EVENPARITY; | | if (cfg.Parity != tmpParity) | { | cfg.Parity = cast(ubyte)tmpParity; | if (!SetCommState(_handle, &cfg)) | throwUnsupportedException(port, c.parity); | } | | auto tmpStopBits = ONESTOPBIT; | if (c.stopBits == StopBits.two) tmpStopBits = TWOSTOPBITS; | | if (cfg.StopBits != tmpStopBits) | { | cfg.StopBits = cast(ubyte)tmpStopBits; | if (!SetCommState(_handle, &cfg)) | throwUnsupportedException(port, c.stopBits); | } | | if (cfg.ByteSize != cast(typeof(cfg.ByteSize))c.dataBits) | { | cfg.ByteSize = cast(typeof(cfg.ByteSize))c.dataBits; | if (!SetCommState(_handle, &cfg)) | throwUnsupportedException(port, c.dataBits); | } | } | } | | @property @nogc | { | /// 2| Parity parity() { return config.parity; } | /// 2| uint baudRate() { return config.baudRate; } | /// 2| DataBits dataBits() { return config.dataBits; } | /// 2| StopBits stopBits() { return config.stopBits; } | | /// 0000000| Parity parity(Parity v) { set(v); return v; } | /// 5| uint baudRate(uint v) { set(v); return v; } | /// 0000000| DataBits dataBits(DataBits v) { set(v); return v; } | /// 0000000| StopBits stopBits(StopBits v) { set(v); return v; } | } | | /++ List of available serial ports in system | +/ | static string[] listAvailable() @property | { | version (linux) | { | import std.file : exists; 4| return dirEntries("/sys/class/tty", SpanMode.shallow) 800| .map!(a=>"/dev/"~a.name.baseName) 400| .filter!(a=>a.exists) | .array.sort.array | ~ | dirEntries("/dev/pts", SpanMode.shallow) 32| .map!(a=>a.name).array.sort.array; | } | version (OSX) | { | return dirEntries("/dev/", "{tty,cu}*", SpanMode.shallow) | .map!(a=>a.name).array; | } | version (Windows) | { | import std.windows.registry : Registry; | string[] arr; | try foreach (v; Registry | .localMachine() | .getKey("HARDWARE") | .getKey("DEVICEMAP") | .getKey("SERIALCOMM") | .values) | arr ~= v.value_SZ; | catch (Throwable e) .error(e.msg); | return arr; | } | } | |protected: | void[] m_read(void[] buf) @nogc | { | // non-blocking algorithm 2081180| if (closed) throwPortClosedException(port); | 2081180| auto ptr = buf.ptr; 2081180| auto len = buf.length; | 2081181| size_t res; | | version (Posix) | { 2081182| auto sres = posixRead(_handle, ptr, len); | | // no bytes for read, it's ok 2081185| if (sres < 0) | { 0000000| if (errno == EAGAIN) sres = 0; 0000000| else throwReadException(port, "posix read", errno); | } 2081184| res = sres; | } | version (Windows) | { | uint sres; | auto rfr = ReadFile(_handle, ptr, cast(uint)len, &sres, null); | if (!rfr) | { | auto err = GetLastError(); | if (err == ERROR_IO_PENDING) { /+ buffer empty +/ } | else throwReadException(port, "win read", err); | } | res = sres; | } | 2081180| return buf[0..res]; | } | | size_t m_write(const(void[]) arr) @nogc | { | // non-blocking algorithm 15| if (closed) throwPortClosedException(port); | 15| auto ptr = arr.ptr; 15| auto len = arr.length; | | version (Posix) | { 15| ptrdiff_t res = posixWrite(_handle, ptr, len); 15| if (res < 0) | { 0000000| if (errno == EAGAIN) res = 0; // buffer filled 0000000| else throwWriteException(port, "posix write", errno); | } | } | version (Windows) | { | uint res; | auto wfr = WriteFile(_handle, ptr, cast(uint)len, &res, null); | if (!wfr) | { | auto err = GetLastError(); | if (err == ERROR_IO_PENDING) res = 0; | else throwWriteException(port, "win write", err); | } | } | 15| return res; | } | | /// open handler, set new config | void setup(Config conf) | { 41| if (port.length == 0) 0000000| throwSerialPortException("", "zero length name"); | 41| version (Posix) posixSetup(conf); | else winSetup(); | 40| config = conf; | } | | version (Posix) | { | void m_tcgetattr(termios* t) const @nogc | { 573| if (tcgetattr(_handle, t) == -1) 0000000| throwSysCallException(port, "tcgetattr", errno); | } | | void m_tcsetattr(int v, const(termios*) t) inout @nogc | { 263| if (tcsetattr(_handle, v, t) == -1) 0000000| throwSysCallException(port, "tcsetattr", errno); | } | | version (usetermios2) | { | void m_ioctl(int v, termios2* t) inout | { | if (ioctl(_handle, v, t) == -1) | throwSysCallException(port, "ioctl", errno); | } | } | | void posixSetup(Config conf) | { 25| openPort(); 25| initialConfig(conf); | } | | void openPort() | { 41| _handle = open(port.toStringz(), O_RDWR | O_NOCTTY | O_NONBLOCK); 41| if (_handle == -1) 1| throwSysCallException(port, "open", errno); | } | | /// Set termios.c_cc[VMIN] and .c_cc[VMAX] | void setCC(ubyte[2] val) @nogc | { 125| termios opt; 125| m_tcgetattr(&opt); 125| opt.c_cc[VMIN] = val[0]; 125| opt.c_cc[VTIME] = val[1]; 125| m_tcsetattr(TCSADRAIN, &opt); | } | | /// Get termios.c_cc[VMIN] and .c_cc[VMAX] | ubyte[2] getCC() @nogc | { 38| ubyte[2] ret; 38| termios opt; 38| m_tcgetattr(&opt); 38| ret[0] = opt.c_cc[VMIN]; 38| ret[1] = opt.c_cc[VTIME]; 38| return ret; | } | | void initialConfig(Config conf) | { 40| termios opt; 40| m_tcgetattr(&opt); | | // make raw 40| opt.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | | INLCR | IGNCR | ICRNL | IXON); 40| opt.c_oflag &= ~OPOST; 40| opt.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); 40| opt.c_cflag &= ~(CSIZE | PARENB); 40| opt.c_cc[VMIN] = 0; 40| opt.c_cc[VTIME] = 0; | | // hardware flow control | version (OSX) | { | /+ | The CCTS_OFLOW (CRTSCTS) flag is currently unused. | http://www.manpages.info/macosx/termios.4.html | +/ | } | else | { 40| if (conf.hardwareDisableFlowControl) 40| opt.c_cflag &= ~CRTSCTS; | } | 40| opt.c_cflag |= CS8; | 40| m_tcsetattr(TCSANOW, &opt); | } | | void setUintBaudRate(uint br) @nogc | { | version (usetermios2) | { | import std.conv : octal; | enum CBAUD = octal!10017; | enum BOTHER = octal!10000; | | termios2 opt2; | m_ioctl(TCGETS2, &opt2); | opt2.c_cflag &= ~CBAUD; //Remove current BAUD rate | opt2.c_cflag |= BOTHER; //Allow custom BAUD rate using int input | opt2.c_ispeed = br; //Set the input BAUD rate | opt2.c_ospeed = br; //Set the output BAUD rate | m_ioctl(TCSETS2, &opt2); | } | else | { 50| if (unixBaudList.countA(br) == 0) 1| throwUnsupportedException(port, br); | 49| auto baud = unixBaudList.firstA2B(br, B0); | 49| termios opt; 49| m_tcgetattr(&opt); 49| cfsetispeed(&opt, B0); 49| cfsetospeed(&opt, baud); 49| m_tcsetattr(TCSANOW, &opt); | } | } | | uint getUintBaudRate() const @nogc | { | version (usetermios2) | { | termios2 opt2; | m_ioctl(TCGETS2, &opt2); | return opt2.c_ospeed; | } | else | { 136| termios opt; 136| m_tcgetattr(&opt); | version (OSX) alias true_speed_t = uint; 136| else alias true_speed_t = typeof(cfgetospeed(&opt)); 136| auto b = cast(true_speed_t)cfgetospeed(&opt); 136| return unixBaudList.firstB2A(b, 0); | } | } | } | | version (Windows) | { | void winSetup() | { | auto fname = `\\.\` ~ port; | _handle = CreateFileA(fname.toStringz, | GENERIC_READ | GENERIC_WRITE, 0, null, | OPEN_EXISTING, 0, null); | | if (_handle is INVALID_HANDLE_VALUE) | throwSysCallException(port, "CreateFileA", GetLastError()); | | SetupComm(_handle, 4096, 4096); | PurgeComm(_handle, PURGE_TXABORT | PURGE_TXCLEAR | | PURGE_RXABORT | PURGE_RXCLEAR); | | updTimeouts(); | } | | void updTimeouts() @nogc | { | setTimeouts(DWORD.max, 0, 0, 0, 0); | } | | void setTimeouts(DWORD rit, DWORD rttm, DWORD rttc, DWORD wttm, DWORD wttc) @nogc | { | COMMTIMEOUTS tm; | tm.ReadIntervalTimeout = rit; | tm.ReadTotalTimeoutMultiplier = rttm; | tm.ReadTotalTimeoutConstant = rttc; | tm.WriteTotalTimeoutMultiplier = wttm; | tm.WriteTotalTimeoutConstant = wttc; | | if (SetCommTimeouts(_handle, &tm) == 0) | throwSysCallException(port, "SetCommTimeouts", GetLastError()); | } | } |} | |/++ Timed work with serial port | | +/ |abstract class SerialPort : SerialPortBase |{ |protected: | | Duration _writeTimeout = 1.seconds, | _writeTimeoutMult = Duration.zero, | _readTimeout = 1.seconds, | _readTimeoutMult = Duration.zero; | | | void updateTimeouts() @nogc {} | |public: | | /++ Construct SerialPort | | See_Also: SerialPortBase.this | +/ 8| this(string exmode) { super(exmode); } | | /// ditto 54| this(string port, string mode) { super(port, mode); } | | /// ditto 7| this(string port, uint baudRate) { super(port, baudRate); } | | /// ditto 2| this(string port, uint baudRate, string mode) 2| { super(port, baudRate, mode); } | | /// ditto 0000000| this(string port, Config conf) { super(port, conf); } | | /++ Read data from serial port while exists | +/ | void flush() | { 38| void[128] buf = void; 38| const rt = _readTimeout; 38| const rtm = _readTimeoutMult; | 38| _readTimeout = 10.msecs; 38| _readTimeoutMult = Duration.zero; 38| updateTimeouts(); | | version (Posix) | { 38| const last = getCC(); 38| setCC([0,0]); | } | 38| void[] tmp; 42| do tmp = read(buf, CanRead.zero); 42| while (tmp.length); | | version (Posix) | { 38| setCC(last); | } | 38| _readTimeout = rt; 38| _readTimeoutMult = rtm; 38| updateTimeouts(); | } | | @property @nogc | { | const | { | /// 82| Duration readTimeout() { return _readTimeout; } | /// 76| Duration readTimeoutMult() { return _readTimeoutMult; } | /// 25| Duration writeTimeout() { return _writeTimeout; } | /// 25| Duration writeTimeoutMult() { return _writeTimeoutMult; } | } | | /// | void readTimeout(Duration tm) | { 16| _readTimeout = tm; 16| updateTimeouts(); | } | | /// | void readTimeoutMult(Duration tm) | { 0000000| _readTimeoutMult = tm; 0000000| updateTimeouts(); | } | | /// | void writeTimeout(Duration tm) | { 4| _writeTimeout = tm; 4| updateTimeouts(); | } | | /// | void writeTimeoutMult(Duration tm) | { 0000000| _writeTimeout = tm; 0000000| updateTimeouts(); | } | } | | /// | enum CanRead | { | allOrNothing, /// | anyNonZero, /// | zero /// | } | | /++ Read data from port | | Receive data time schema: | | ------ | ---|-------|--------------|-------|--> t | call | | | | read | | | | | | | | | | || | | |===== ==== | ======| | | | | | | |<-readedData->| | | | | |<---readTimeoutSum--->| | | return | |<---read work time--->| | ------ | | where `readTimeoutSum = readTimeout + readTimeoutMult * dataBuffer.length;` | | if canReturn is: | | CanRead.allOrNothing | | --- | if (readedData.length < dataBuffer.length) | throw TimeoutException(port); | else return readedData; | --- | | CanReturn.anyNonZero | | --- | if (readedData.length == 0) | throw TimeoutException(port); | else return readedData; | --- | | CanReturn.zero | | --- | return readedData; | --- | | Params: | buf = preallocated buffer for reading | cr = flag what define behavior if readedData.length < buf.length | then readTimeoutSum is expires | | Returns: slice of buf with readed data | Throws: | PortClosedException if port closed | ReadException if read error occurs | TimeoutException if timeout expires | +/ | abstract void[] read(void[] buf, CanRead cr=CanRead.allOrNothing) @nogc; | | /// | protected void checkAbility(CanRead cr, size_t readed, size_t buffer) @nogc | { 76| bool err; | 76| final switch (cr) with(CanRead) | { 18| case allOrNothing: err = readed != buffer; break; 30| case anyNonZero: err = readed == 0; break; 120| case zero: /+ no errors +/ break; | } | 86| if (err) throwTimeoutException(port, "read timeout"); | } | | /++ Write data to port | | Params: | arr = data for writing | | Throws: | PortClosedException if port closed | WriteException if read error occurs | TimeoutException if timeout expires | +/ | abstract void write(const(void[]) buf) @nogc; |} source/serialport/base.d is 77% covered <<<<<< EOF # path=-tmp-dub_test_root-ac27b3eb-4226-4b04-b418-4c8c6554bf54.lst |module dub_test_root; |import std.typetuple; |static import serialport.base; |static import serialport.block; |static import serialport.config; |static import serialport.exception; |static import serialport.fiberready; |static import serialport.nonblock; |static import serialport.types; |static import serialport.ut; |static import serialport.util; |alias allModules = TypeTuple!(serialport.base, serialport.block, serialport.config, serialport.exception, serialport.fiberready, serialport.nonblock, serialport.types, serialport.ut, serialport.util); | | import std.stdio; | import core.runtime; | 1| void main() { writeln("All unit tests have been run successfully."); } | shared static this() { | version (Have_tested) { | import tested; | import core.runtime; | import std.exception; | Runtime.moduleUnitTester = () => true; | //runUnitTests!app(new JsonTestResultWriter("results.json")); | enforce(runUnitTests!allModules(new ConsoleTestResultWriter), "Unit tests failed."); | } | } | /tmp/dub_test_root-ac27b3eb-4226-4b04-b418-4c8c6554bf54.d is 100% covered <<<<<< EOF # path=source-serialport-ut.lst |module serialport.ut; | |version (unittest): private: | |import serialport; | |import std.range; |import std.concurrency; |import std.exception; |import std.datetime; |import std.conv; |import std.string; |import std.stdio; |import std.random; |import std.process; |import core.thread; | |enum BUFFER_SIZE = 1024; | |interface ComPipe |{ | void open(); | void close(); | string command() const @property; | string[2] ports() const @property; |} | |class SocatPipe : ComPipe |{ | int bufferSize; | ProcessPipes pipe; | string[2] _ports; | string _command; | 1| this(int bs) | { 1| bufferSize = bs; 1| _command = ("socat -d -d -b%d pty,raw,"~ | "echo=0 pty,raw,echo=0").format(bufferSize); | } | | static string parsePort(string ln) | { 4| auto ret = ln.split[$-1]; 4| enforce(ret.startsWith("/dev/"), 1| "unexpected last word in output line '%s'".format(ln)); 3| return ret; | } | | override void close() | { 2| if (pipe.pid is null) return; 0000000| kill(pipe.pid); | } | | override void open() | { 1| pipe = pipeShell(_command); 1| _ports[0] = parsePort(pipe.stderr.readln.strip); 1| _ports[1] = parsePort(pipe.stderr.readln.strip); | } | | override const @property | { 1| string command() { return _command; } 21| string[2] ports() { return _ports; } | } |} | |unittest |{ 1| enum socat_out_ln = "2018/03/08 02:56:58 socat[30331] N PTY is /dev/pts/1"; 1| assert(SocatPipe.parsePort(socat_out_ln) == "/dev/pts/1"); 2| assertThrown(SocatPipe.parsePort("some string")); |} | |class DefinedPorts : ComPipe |{ | string[2] env; | string[2] _ports; | 1| this(string[2] envNames = ["SERIALPORT_TEST_PORT1", "SERIALPORT_TEST_PORT2"]) 1| { env = envNames; } | |override: | | void open() | { | import std.process : environment; | import std.range : lockstep; | import std.algorithm : canFind; | 1| auto lst = SerialPort.listAvailable; | 1| foreach (ref e, ref p; lockstep(env[], _ports[])) | { 1| p = environment[e]; 0000000| enforce(lst.canFind(p), new Exception("unknown port '%s' in env var '%s'".format(p, e))); | } | } | | void close() { } | | string command() const @property | { 0000000| return "env: %s=%s, %s=%s".format( | env[0], _ports[0], | env[1], _ports[1] | ); | } | 0000000| string[2] ports() const @property { return _ports; } |} | |ComPipe getPlatformComPipe(int bufsz) |{ 1| stderr.writeln("available ports count: ", SerialPort.listAvailable.length); | | try | { 1| auto ret = new DefinedPorts; 1| ret.open(); 0000000| return ret; | } | catch (Exception e) | { 1| stderr.writeln(); 1| stderr.writeln("error while open predefined ports: ", e.msg); | 1| version (Posix) return new SocatPipe(bufsz); | else return null; | } |} | |// real test main |//version (realtest) |unittest |{ 1| stderr.writeln("=== start real test ===\n"); 2| scope (success) stderr.writeln("=== finish real test ==="); 0000000| scope (failure) stderr.writeln("!!! fail real test !!!"); 1| auto cp = getPlatformComPipe(BUFFER_SIZE); 1| if (cp is null) | { 0000000| stderr.writeln("platform doesn't support real test"); 0000000| return; | } | 1| stderr.writefln("port source `%s`\n", cp.command); | 1| void reopen() | { 1| cp.close(); 1| Thread.sleep(30.msecs); 1| cp.open(); 1| stderr.writefln("pipe ports: %s <=> %s", cp.ports[0], cp.ports[1]); | } | 1| reopen(); | 1| utCall!(threadTest!SerialPortFR)("thread test for fiber ready", cp.ports); 1| utCall!(threadTest!SerialPortBlk)("thread test for block", cp.ports); 1| utCall!testNonBlock("test non block", cp.ports); 1| utCall!fiberTest("fiber test", cp.ports); 1| utCall!fiberTest2("fiber test 2", cp.ports); 1| utCall!readTimeoutTest("read timeout test", cp.ports); 1| alias rttc = readTimeoutTestConfig; 1| alias rttc2 = readTimeoutTestConfig2; 1| utCall!(rttc!SerialPortFR)( "read timeout test for FR cr=zero", cp.ports, SerialPort.CanRead.zero); 1| utCall!(rttc!SerialPortBlk)("read timeout test for Blk cr=zero", cp.ports, SerialPort.CanRead.zero); 1| utCall!(rttc!SerialPortFR)( "read timeout test for FR cr=anyNonZero", cp.ports, SerialPort.CanRead.anyNonZero); 1| utCall!(rttc!SerialPortBlk)("read timeout test for Blk cr=anyNonZero", cp.ports, SerialPort.CanRead.anyNonZero); 1| utCall!(rttc!SerialPortFR)( "read timeout test for FR cr=allOrNothing", cp.ports, SerialPort.CanRead.allOrNothing); 1| utCall!(rttc!SerialPortBlk)("read timeout test for Blk cr=allOrNothing", cp.ports, SerialPort.CanRead.allOrNothing); 1| utCall!(rttc2!SerialPortFR)( "read timeout test 2 for FR cr=zero", cp.ports, SerialPort.CanRead.zero); 1| utCall!(rttc2!SerialPortBlk)("read timeout test 2 for Blk cr=zero", cp.ports, SerialPort.CanRead.zero); 1| utCall!(rttc2!SerialPortFR)( "read timeout test 2 for FR cr=anyNonZero", cp.ports, SerialPort.CanRead.anyNonZero); 1| utCall!(rttc2!SerialPortBlk)("read timeout test 2 for Blk cr=anyNonZero", cp.ports, SerialPort.CanRead.anyNonZero); 1| utCall!(rttc2!SerialPortFR)( "read timeout test 2 for FR cr=allOrNothing", cp.ports, SerialPort.CanRead.allOrNothing); 1| utCall!(rttc2!SerialPortBlk)("read timeout test 2 for Blk cr=allOrNothing", cp.ports, SerialPort.CanRead.allOrNothing); 1| utCall!(fiberSleepFuncTest)("fiber sleep func test", cp.ports); |} | |unittest |{ 1| enum name = "/some/path/to/notexisting/device"; 2| auto e = enforce(collectException(new SerialPortBlk(name, 19200)), "exception not thrown"); 1| auto sce = cast(SysCallException)e; 1| assert (sce !is null); 1| assert (sce.port == name, "wrong name"); | version (Posix) | { 1| assert(sce.fnc == "open", "'" ~ sce.fnc ~ "' is not 'open'"); 1| assert(sce.err == 2, "unexpectable errno %d".format(sce.err)); | } 1| auto exp = format!"call '%s' (%s) failed: error %d"(sce.fnc, name, sce.err); 1| if (!e.msg.startsWith(exp)) | { | import std.stdio : stderr; 0000000| stderr.writeln("exp: ", exp); 0000000| stderr.writeln("msg: ", e.msg); 0000000| assert(0, "wrong msg"); | } |} | 58|void testPrint(Args...)(Args args) { stderr.write(" "); stderr.writeln(args); } 4|void testPrintf(Args...)(Args args) { stderr.write(" "); stderr.writefln(args); } | |auto utCall(alias fnc, Args...)(string name, Args args) |{ 19| stderr.writefln(">>> run %s", name); 38| scope (success) stderr.writefln("<<< success %s\n", name); 0000000| scope (failure) stderr.writefln("!!! failure %s\n", name); 38| return fnc(args); |} | |void threadTest(SPT)(string[2] ports) |{ 2| assert(SerialPort.listAvailable.length != 0); | 2| static struct ExcStruct { string msg, type; } | 2| static void echoThread(string port) | { 2| void[BUFFER_SIZE] buffer = void; 2| auto com = new SPT(port, "2400:8N1"); 2| scope (exit) com.close(); 2| com.flush(); | 2| com.set(1200); 2| assert(com.config.baudRate == 1200); | 2| com.baudRate = 38_400; 2| assert(com.config.baudRate == 38_400); | 2| bool work = true; 2| com.readTimeout = 1000.msecs; | 2| bool needRead; | 10| while (work) | { | try | { 8| if (needRead) | { 2| Thread.sleep(500.msecs); 2| auto data = com.read(buffer, com.CanRead.zero); | 2| if (data.length) | { 2| testPrint("child readed: ", cast(string)(data.idup)); 2| send(ownerTid, cast(string)(data.idup)); | } | } | 8| receiveTimeout(500.msecs, | (SPConfig cfg) | { 2| com.config = cfg; 2| testPrint("child get cfg: ", cfg.mode); | }, | (bool nr) | { 6| if (nr) needRead = true; | else | { 2| work = false; 2| needRead = false; | } 4| testPrint("get needRead ", nr); | }, 0000000| (OwnerTerminated e) { work = false; } | ); | } | catch (Throwable e) | { 0000000| work = false; 0000000| testPrint("exception in child: ", e); 0000000| send(ownerTid, ExcStruct(e.msg, e.classinfo.stringof)); | } | } | } | 2| auto t = spawnLinked(&echoThread, ports[1]); | 2| auto com = new SPT(ports[0], 19_200); 2| com.flush(); | 2| assert(com.baudRate == 19_200); 2| assert(com.dataBits == DataBits.data8); 2| assert(com.parity == Parity.none); 2| assert(com.stopBits == StopBits.one); | 2| assert(com.config.baudRate == 19_200); 2| assert(com.config.dataBits == DataBits.data8); 2| assert(com.config.parity == Parity.none); 2| assert(com.config.stopBits == StopBits.one); | 2| scope (exit) com.close(); | 2| string[] list; | 2| const sets = [ | SPConfig(38_400), | SPConfig(2400), | SPConfig.parse("19200:8N2"), | ]; | 2| auto cfg = SPConfig(38_400); 2| com.config = cfg; 2| send(t, cfg); | 2| Thread.sleep(1000.msecs); | 2| string msg = sets.front.mode; 2| com.write(msg); | 2| bool work = true; 2| send(t, true); 6| while (work) | { 4| receive( | (string rec) | { 2| enforce(rec == msg, "break message: '%s' != '%s'".format(msg, rec)); | 2| if (list.empty) | { 2| testPrint("owner send data finish"); 2| send(t, false); | } | else | { 0000000| msg = list.front; 0000000| list.popFront(); | } | 2| com.write(msg); 2| testPrint("owner write msg to com: ", msg); | }, 0000000| (ExcStruct e) { throw new Exception("%s:%s".format(e.type, e.msg)); }, | (LinkTerminated e) | { 2| work = false; 2| testPrintf("link terminated for %s, child tid %s", e.tid, t); | //assert(e.tid == t); | } | ); | } |} | |void testNonBlock(string[2] ports) |{ | import std.datetime.stopwatch : StopWatch, AutoStart; 1| enum mode = "38400:8N1"; | 1| const data = "1234567890987654321qazxswedcvfrtgbnhyujm,ki"; | 1| static void thfunc(string port) | { 1| auto com = new SerialPortNonBlk(port, mode); 1| scope (exit) com.close(); | 1| void[1024] buffer = void; 1| size_t readed; | 1| const sw = StopWatch(AutoStart.yes); | | // flush 11| while (sw.peek < 10.msecs) | { 10| com.read(buffer); 10| Thread.sleep(1.msecs); | } | 2070324| while (sw.peek < 1.seconds) 2070323| readed += com.read(buffer[readed..$]).length; | 1| send(ownerTid, buffer[0..readed].idup); | 1| Thread.sleep(200.msecs); | } | 1| auto com = new SerialPortNonBlk(ports[0], 38_400, "8N1"); 1| scope (exit) com.close(); | 1| spawnLinked(&thfunc, ports[1]); | 1| Thread.sleep(100.msecs); | 1| size_t written; 2| while (written < data.length) 1| written += com.write(data[written..$]); | 1| receive((immutable(void)[] readed) | { 1| testPrint("readed: ", cast(string)readed); 1| testPrint(" data: ", data); 1| assert(cast(string)readed == data); | }); | 1| receive((LinkTerminated e) { }); |} | |class CF : Fiber |{ | void[] data; | | SerialPortFR com; | 6| this(SerialPortFR com, size_t bufsize) | { 6| this.com = com; 6| this.com.flush(); 6| this.data = new void[bufsize]; 36882| foreach (ref v; cast(ubyte[])data) 12288| v = cast(ubyte)uniform(0, 128); 6| super(&run); | } | | abstract void run(); |} | |class CFSlave : CF |{ | void[] result; | | Duration readTimeout = 40.msecs; | Duration readGapTimeout = 100.msecs; | 3| this(SerialPortFR com, size_t bufsize) 3| { super(com, bufsize); } | | override void run() | { 3| testPrint("start read loop"); 3| result = com.readContinues(data, readTimeout, readGapTimeout); 3| testPrint("finish read loop ("~result.length.to!string~")"); | } |} | |class CFMaster : CF |{ | CFSlave slave; | | Duration writeTimeout = 20.msecs; | 3| this(SerialPortFR com, size_t bufsize) 3| { super(com, bufsize); } | | override void run() | { 3| testPrint("start write loop ("~data.length.to!string~")"); 3| com.writeTimeout = writeTimeout; 3| com.write(data); 3| testPrint("finish write loop"); | } |} | |void fiberTest(string[2] ports) |{ 1| auto slave = new CFSlave(new SerialPortFR(ports[0]), BUFFER_SIZE); 1| scope (exit) slave.com.close(); 1| auto master = new CFMaster(new SerialPortFR(ports[1]), BUFFER_SIZE); 1| scope (exit) master.com.close(); | 1| bool work = true; 1| int step; 3| while (work) | { 2| alias TERM = Fiber.State.TERM; 3| if (master.state != TERM) master.call; 4| if (slave.state != TERM) slave.call; | 2| step++; 2| Thread.sleep(30.msecs); 4| if (master.state == TERM && slave.state == TERM) | { 1| if (slave.result.length == master.data.length) | { | import std.algorithm : equal; 1| enforce(equal(cast(ubyte[])slave.result, cast(ubyte[])master.data)); 1| work = false; 1| testPrint("basic loop steps: ", step); | } 0000000| else throw new Exception(text(slave.result, " != ", master.data)); | } | } |} | |void fiberTest2(string[2] ports) |{ 1| string mode = "9600:8N1"; | 1| auto scom = new SerialPortFR(ports[0], 9600, "8N1"); 1| auto mcom = new SerialPortFR(ports[1], "19200:8N1"); 1| scope (exit) scom.close(); 1| scope (exit) mcom.close(); | | version (Posix) 2| assertThrown!UnsupportedException(scom.baudRate = 9200); | 1| scom.reopen(ports[0], SPConfig.parse(mode)); 1| mcom.reopen(ports[1], SPConfig.parse(mode)); 1| scom.flush(); 1| mcom.flush(); | 1| scom.config = mcom.config; | 1| scom.readTimeout = 1000.msecs; 1| mcom.writeTimeout = 100.msecs; | | version (OSX) enum BS = BUFFER_SIZE / 2; 1| else enum BS = BUFFER_SIZE * 4; | 1| auto slave = new CFSlave(scom, BS); 1| auto master = new CFMaster(mcom, BS); | 1| void run() | { 1| bool work = true; 1| int step; 1| alias TERM = Fiber.State.TERM; 3| while (work) | { 3| if (master.state != TERM) master.call; 2| Thread.sleep(5.msecs); 4| if (slave.state != TERM) slave.call; | 2| step++; 4| if (master.state == TERM && slave.state == TERM) | { 1| assert(slave.result.length == master.data.length); | import std.algorithm : equal; 1| enforce(equal(cast(ubyte[])slave.result, cast(ubyte[])master.data)); 1| work = false; 1| testPrint("basic loop steps: ", step); | } | } | } | 1| run(); |} | |void readTimeoutTest(string[2] ports) |{ 1| void[1024] buffer = void; | 1| auto comA = new SerialPortFR(ports[0], 19_200); 1| scope (exit) comA.close(); 1| comA.flush(); 2| assertThrown!TimeoutException(comA.readContinues(buffer[], 1.msecs, 1.msecs)); 2| assertNotThrown!TimeoutException(comA.readContinues(buffer[], 1.msecs, 1.msecs, false)); 2| assertThrown!TimeoutException(comA.read(buffer[])); 2| assertThrown!TimeoutException(comA.read(buffer[], comA.CanRead.anyNonZero)); | 1| auto comB = new SerialPortBlk(ports[1], 19_200, "8N1"); 1| scope (exit) comB.close(); 1| comB.flush(); 1| comB.readTimeout = 1.msecs; 2| assertThrown!TimeoutException(comB.read(buffer[])); 2| assertThrown!TimeoutException(comB.read(buffer[], comB.CanRead.anyNonZero)); |} | |void readTimeoutTestConfig(SP : SerialPort)(string[2] ports, SerialPort.CanRead cr) |{ 6| enum mode = "38400:8N1"; | 6| enum FULL = 100; 6| enum SEND = "helloworld"; | 6| static void thfunc(string port) | { 6| auto com = new SP(port, mode); 6| com.flush(); 6| scope (exit) com.close(); 6| com.write(SEND); | } | 6| auto com = new SP(ports[0], mode); 6| scope (exit) com.close(); 6| auto rt = 300.msecs; 6| com.readTimeout = rt; 6| com.flush(); 6| assert(com.readTimeout == rt); | 6| void[FULL] buffer = void; 6| void[] data; | 6| spawnLinked(&thfunc, ports[1]); | 6| Thread.sleep(rt); | 6| if (cr == SerialPort.CanRead.anyNonZero) | { 4| assertNotThrown(data = com.read(buffer, cr)); 2| assert(cast(string)data == SEND); 4| assertThrown!TimeoutException(data = com.read(buffer, cr)); | } 4| else if (cr == SerialPort.CanRead.allOrNothing) 4| assertThrown!TimeoutException(data = com.read(buffer)); 2| else if (cr == SerialPort.CanRead.zero) | { 4| assertNotThrown(data = com.read(buffer, cr)); 4| assertNotThrown(data = com.read(buffer, cr)); 4| assertNotThrown(data = com.read(buffer, cr)); | } 0000000| else assert(0, "not tested variant of CanRead"); | 6| receive((LinkTerminated e) { }); |} | |void readTimeoutTestConfig2(SP : SerialPort)(string[2] ports, SerialPort.CanRead cr) |{ 6| enum mode = "38400:8N1"; | 6| static void thfunc(string port) | { 6| auto com = new SP(port, mode); 6| scope (exit) com.close(); 6| com.flush(); 6| Thread.sleep(200.msecs); 6| com.write("one"); 6| Thread.sleep(200.msecs); 6| com.write("two"); | } | 6| auto com = new SP(ports[0], mode); 6| scope (exit) com.close(); 12| com.readTimeout = cr == SerialPort.CanRead.zero ? 10.msecs : 300.msecs; 6| com.flush(); | 6| void[6] buffer = void; 6| void[] data; | 6| spawnLinked(&thfunc, ports[1]); | 6| if (cr == SerialPort.CanRead.anyNonZero) | { 4| assertNotThrown(data = com.read(buffer, cr)); 2| assert(cast(string)data == "one"); 4| assertNotThrown(data = com.read(buffer, cr)); 2| assert(cast(string)data == "two"); | } 4| else if (cr == SerialPort.CanRead.allOrNothing) 4| assertThrown!TimeoutException(data = com.read(buffer)); 2| else if (cr == SerialPort.CanRead.zero) | { 4| assertNotThrown(data = com.read(buffer, cr)); 2| assert(cast(string)data == ""); 2| Thread.sleep(300.msecs); 4| assertNotThrown(data = com.read(buffer, cr)); 2| assert(cast(string)data == "one"); 4| assertNotThrown(data = com.read(buffer, cr)); 2| assert(cast(string)data == ""); 2| Thread.sleep(200.msecs); 4| assertNotThrown(data = com.read(buffer, cr)); 2| assert(cast(string)data == "two"); 4| assertNotThrown(data = com.read(buffer, cr)); 2| assert(cast(string)data == ""); | } 0000000| else assert(0, "not tested variant of CanRead"); | 6| receive((LinkTerminated e) { }); |} | |void fiberSleepFuncTest(string[2] ports) |{ | import std.datetime.stopwatch : StopWatch, AutoStart; | 1| static void sf(Duration d) @nogc | { 17| const sw = StopWatch(AutoStart.yes); 17| if (auto f = Fiber.getThis) 3| while (sw.peek < d) f.yield(); 16| else Thread.sleep(d); | } | 1| CFMaster master; | 1| size_t sf2_cnt; 1| void sf2(Duration d) @nogc | { 8| const sw = StopWatch(AutoStart.yes); 8| if (auto f = Fiber.getThis) 0000000| while (sw.peek < d) | { 0000000| master.yield(); 0000000| sf2_cnt++; | } 8| else Thread.sleep(d); | } | 1| auto slave = new CFSlave(new SerialPortFR(ports[0], &sf), BUFFER_SIZE); 1| scope (exit) slave.com.close(); 1| master = new CFMaster(new SerialPortFR(ports[1], &sf2), BUFFER_SIZE); 1| scope (exit) master.com.close(); | 1| bool work = true; 1| int step; 3| while (work) | { 2| alias TERM = Fiber.State.TERM; 3| if (master.state != TERM) master.call; 4| if (slave.state != TERM) slave.call; | 2| step++; 2| Thread.sleep(30.msecs); 4| if (master.state == TERM && slave.state == TERM) | { 1| if (slave.result.length == master.data.length) | { | import std.algorithm : equal; 1| enforce(equal(cast(ubyte[])slave.result, cast(ubyte[])master.data)); 1| work = false; 1| testPrint("basic loop steps: ", step); | } 0000000| else throw new Exception(text(slave.result, " != ", master.data)); | } | } |} source/serialport/ut.d is 92% covered <<<<<< EOF # path=source-serialport-package.lst |/++ | Crossplatform work with serialport. | | See also `example/monitor` | +/ |module serialport; | |version (Posix) {} else version (Windows) {} |else static assert(0, "unsupported platform"); | |public: | |import serialport.base; |import serialport.config; |import serialport.block; |import serialport.fiberready; |import serialport.nonblock; |import serialport.exception; |import serialport.types; source/serialport/package.d has no code <<<<<< EOF