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 }