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) _sleepFunc(dt);
27         else
28         {
29             import core.thread : Fiber, Thread;
30             if (Fiber.getThis is null) Thread.sleep(dt);
31             else
32             {
33                 const tm = StopWatch(AutoStart.yes);
34                 do Fiber.yield(); while (tm.peek < dt);
35             }
36         }
37     }
38 
39     /++ Calc pause for sleep in read and write loops
40      +/
41     Duration ioPause() @nogc
42     {
43         auto cfg = config;
44         auto cnt = 1 + // start bit
45                    cast(int)cfg.dataBits +
46                    (cfg.parity == Parity.none ? 0 : 1) +
47                    (cfg.stopBits == StopBits.one ? 1 :
48                     cfg.stopBits == StopBits.onePointFive ? 1.5 : 2) +
49                     1.5 // reserve
50                     ;
51         return (cast(ulong)(cnt / cfg.baudRate * 1e6) + 100/+reserve+/).usecs;
52     }
53 
54     void delegate(Duration) @nogc _sleepFunc;
55 
56 public:
57     /// assume @nogc
58     deprecated
59     alias SleepFunc = void delegate(Duration);
60 
61     ///
62     alias SleepFuncNoGC = void delegate(Duration) @nogc;
63 
64     /// extended delegate for perform sleep
65     deprecated("sleep function must be @nogc")
66     void sleepFunc(SleepFunc dlg) @property
67     { _sleepFunc = cast(void delegate(Duration) @nogc)dlg; }
68 
69     ///
70     deprecated("sleep function must be @nogc")
71     void sleepFunc(void function(Duration) fnc) @property
72     { _sleepFunc = (d){ (cast(void function(Duration) @nogc)fnc)(d); }; }
73 
74     /// extended delegate for perform sleep
75     void sleepFunc(void delegate(Duration) @nogc dlg) @property
76     { _sleepFunc = dlg; }
77 
78     /// ditto
79     void sleepFunc(void function(Duration) @nogc fnc) @property
80     { _sleepFunc = (d){ fnc(d); }; }
81 
82     /// ditto
83     SleepFuncNoGC sleepFunc() @property { return _sleepFunc; }
84 
85     /++ Construct SerialPortFR
86 
87         See_Also: SerialPort.this
88      +/
89     deprecated("sleep function must be @nogc")
90     this(F=SleepFunc)(string exmode, F sf)
91         if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc))
92     { sleepFunc = sf; super(exmode); }
93 
94     /// ditto
95     deprecated("sleep function must be @nogc")
96     this(F=SleepFunc)(string port, string mode, F sf)
97         if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc))
98     { sleepFunc = sf; super(port, mode); }
99 
100     /// ditto
101     deprecated("sleep function must be @nogc")
102     this(F=SleepFunc)(string port, uint baudRate, F sf)
103         if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc))
104     { sleepFunc = sf; super(port, baudRate); }
105 
106     /// ditto
107     deprecated("sleep function must be @nogc")
108     this(F=SleepFunc)(string port, uint baudRate, string mode, F sf)
109         if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc))
110     { sleepFunc = sf; super(port, baudRate, mode); }
111 
112     /// ditto
113     deprecated("sleep function must be @nogc")
114     this(F=SleepFunc)(string port, Config conf, F sf)
115         if (isSomeFunction!F && !(functionAttributes!F & FunctionAttribute.nogc))
116     { sleepFunc = sf; super(port, conf); }
117 
118     /// ditto
119     this(F=SleepFuncNoGC)(string exmode, F sf=null)
120         if (isSomeFunction!F)
121     { sleepFunc = sf; super(exmode); }
122 
123     /// ditto
124     this(F=SleepFuncNoGC)(string port, string mode, F sf=null)
125         if (isSomeFunction!F && (functionAttributes!F & FunctionAttribute.nogc))
126     { sleepFunc = sf; super(port, mode); }
127 
128     /// ditto
129     this(F=SleepFuncNoGC)(string port, uint baudRate, F sf=null)
130         if (isSomeFunction!F && (functionAttributes!F & FunctionAttribute.nogc))
131     { sleepFunc = sf; super(port, baudRate); }
132 
133     /// ditto
134     this(F=SleepFuncNoGC)(string port, uint baudRate, string mode, F sf=null)
135         if (isSomeFunction!F && (functionAttributes!F & FunctionAttribute.nogc))
136     { sleepFunc = sf; super(port, baudRate, mode); }
137 
138     /// ditto
139     this(F=SleepFuncNoGC)(string port, Config conf, F sf=null)
140         if (isSomeFunction!F && (functionAttributes!F & FunctionAttribute.nogc))
141     { sleepFunc = sf; super(port, conf); }
142 
143     override void[] read(void[] buf, CanRead cr=CanRead.allOrNothing)
144     {
145         if (closed) throwPortClosedException(port);
146 
147         size_t res;
148         const timeout = buf.length * readTimeoutMult + readTimeout;
149         const pause = ioPause();
150         const sw = StopWatch(AutoStart.yes);
151         while (sw.peek < timeout)
152         {
153             res += m_read(buf[res..$]).length;
154             if (res == buf.length) return buf[];
155             this.sleep(pause);
156         }
157 
158         checkAbility(cr, res, buf.length);
159 
160         return buf[0..res];
161     }
162 
163     override void write(const(void[]) arr)
164     {
165         if (closed) throwPortClosedException(port);
166 
167         size_t written;
168         const timeout = arr.length * writeTimeoutMult + writeTimeout;
169         const pause = ioPause();
170         const sw = StopWatch(AutoStart.yes);
171         while (sw.peek < timeout)
172         {
173             written += m_write(arr[written..$]);
174             if (written == arr.length) return;
175             this.sleep(pause);
176         }
177 
178         throwTimeoutException(port, "write timeout");
179     }
180 
181     /++ Read data while available by parts, sleep between checks.
182 
183         Sleep time calculates from baud rate and count of bits in one byte.
184 
185         -------
186         ---|-----|-----|------------|-----|------------> t
187          call    |     |            |     |
188         readAll  |     |            |     |
189            |     |     |            |     |
190            |     |<---------data receive---------->|
191            |     |=== =====   ======|     |   |== =|
192            |     |     |  |   |     |     |
193            |<-timeout->|  |   |     |     |
194            |     |<-1->|  |<2>|     |<-3->|
195            |     |                  |     |
196            |     |<---readedData--->|     |
197            |                            return
198            |<------readAll work time----->|
199         
200         (1) if readedData.length > 0 then continue reading
201             else throw TimeoutException
202         (2) silent time, if silent < frameGap then continue reading
203         (3) else if silent > frameGap then stop reading
204             and return readedData
205         -------
206 
207         Params:
208             buf = buffer for reading
209             startTimeout = timeout for first byte recive
210             frameGap = detect new data frame by silence period
211 
212         Returns: slice of buf with readed data
213 
214         Throws:
215             PortClosedException
216             ReadException
217             TimeoutException
218 
219         See_Also: SerialPort.read
220      +/
221     void[] readContinues(void[] buf, Duration startTimeout=1.seconds,
222                                      Duration frameGap=50.msecs)
223     {
224         if (closed) throwPortClosedException(port);
225 
226         ptrdiff_t readed;
227 
228         auto pause = ioPause();
229 
230         StopWatch silence, full;
231 
232         full.start();
233         while (true)
234         {
235             const res = m_read(buf[readed..$]).length;
236 
237             readed += res;
238 
239             // buffer filled
240             if (readed == buf.length) return buf[];
241 
242             if (res == 0)
243             {
244                 if (readed > 0 && silence.peek > frameGap)
245                     return buf[0..readed];
246 
247                 if (!silence.running) silence.start();
248             }
249             else
250             {
251                 silence.stop();
252                 silence.reset();
253             }
254 
255             if (readed == 0 && full.peek > startTimeout)
256                 throwTimeoutException(port, "read timeout");
257 
258             this.sleep(pause);
259         }
260     }
261 }