1 /// modbus with back end
2 module modbus.facade;
3 
4 import modbus.backend;
5 import modbus.protocol;
6 
7 version(Have_serialport)
8 {
9     public import std.datetime : Duration, dur, hnsecs, nsecs, msecs, seconds;
10     public import serialport;
11 
12     ///
13     class SerialPortConnection : Connection
14     {
15         ///
16         SerialPort sp;
17         ///
18         this(SerialPort sp) { this.sp = sp; }
19     override:
20         ///
21         size_t write(const(void)[] msg) { return sp.write(msg); }
22         ///
23         void[] read(void[] buffer) { return sp.read(buffer); }
24     }
25 
26     /// ModbusMaster with RTU backend
27     class ModbusRTUMaster : ModbusMaster
28     {
29     protected:
30         ///
31         SerialPortConnection spcom;
32 
33         override @property
34         {
35             Duration writeStepPause() { return readStepPause; }
36             Duration readStepPause()
37             { return (cast(ulong)(1e8 / com.baudRate)).hnsecs; }
38         }
39 
40     public:
41 
42         ///
43         this(string port, uint baudrate, string mode="8N1")
44         { this(port, SerialPort.Config(baudrate).set(mode)); }
45 
46         ///
47         this(string port, string mode)
48         { this(port, SerialPort.Config.parse(mode)); }
49 
50         ///
51         this(string port, uint baudrate, void delegate(Duration) sf,
52                 SpecRules sr=null)
53         { this(port, SerialPort.Config(baudrate), sf, sr); }
54 
55         ///
56         this(string port, SerialPort.Config cfg,
57                 void delegate(Duration) sf=null, SpecRules sr=null)
58         {
59             this(new SerialPort(port, cfg), sf, sr);
60             _manageSerialPort = true;
61         }
62 
63         private bool _manageSerialPort;
64         bool manageSerialPort() const @property { return _manageSerialPort; }
65 
66         ///
67         this(SerialPort sp, void delegate(Duration) sf=null,
68                 SpecRules sr=null)
69         {
70             spcom = new SerialPortConnection(sp);
71             super(new RTU(spcom, sr), sf);
72         }
73 
74         ///
75         void flush()
76         {
77             void[240] buf = void;
78             auto res = com.read(buf);
79             version (modbus_verbose)
80                 .info("flush ", cast(ubyte[])(res));
81         }
82 
83         ///
84         inout(SerialPort) com() inout @property { return spcom.sp; }
85     }
86     
87     /// ModbusSlave with RTU backend
88     class ModbusRTUSlave : ModbusSlave
89     {
90     protected:
91         ///
92         SerialPortConnection spcom;
93 
94         override Duration writeStepPause() @property
95         { return (cast(ulong)(1e8 / com.baudRate)).hnsecs; }
96 
97     public:
98 
99         ///
100         this(ulong dev, string port, uint baudrate, string mode="8N1")
101         { this(dev, port, SerialPort.Config(baudrate).set(mode)); }
102 
103         ///
104         this(ulong dev, string port, string mode)
105         { this(dev, port, SerialPort.Config.parse(mode)); }
106 
107         ///
108         this(ulong dev, string port, uint baudrate,
109                 void delegate(Duration) sf, SpecRules sr=null)
110         { this(dev, port, SerialPort.Config(baudrate), sf, sr); }
111 
112         ///
113         this(ulong dev, string port, SerialPort.Config cfg,
114                 void delegate(Duration) sf=null, SpecRules sr=null)
115         {
116             this(dev, new SerialPort(port, cfg), sf, sr);
117             _manageSerialPort = true;
118         }
119 
120         private bool _manageSerialPort;
121         bool manageSerialPort() const @property { return _manageSerialPort; }
122 
123         ///
124         this(ulong dev, SerialPort sp, void delegate(Duration) sf=null,
125                 SpecRules sr=null)
126         {
127             spcom = new SerialPortConnection(sp);
128             super(dev, new RTU(spcom, sr));
129         }
130 
131         ///
132         inout(SerialPort) com() inout @property { return spcom.sp; }
133     }
134 }
135 
136 import modbus.connection.tcp;
137 import std.socket : TcpSocket;
138 
139 /// Modbus with TCP backend based on TcpSocket from std.socket
140 class ModbusTCPMaster : ModbusMaster
141 {
142 protected:
143     MasterTcpConnection mtc;
144 public:
145     ///
146     this(Address addr, SpecRules sr=null)
147     {
148         mtc = new MasterTcpConnection(addr);
149         super(new TCP(mtc, sr));
150     }
151 
152     ///
153     inout(TcpSocket) socket() inout @property { return mtc.socket; }
154 
155     ~this() { mtc.socket.close(); }
156 }
157 
158 version (unittest)
159 {
160     //version = modbus_verbose;
161 
162     import std.stdio;
163     import std.datetime.stopwatch;
164     import std.range;
165 
166     class ConT1 : Connection
167     {
168         string name;
169         ubyte[]* wbuf;
170         ubyte[]* rbuf;
171         this(string name, ref ubyte[] wbuf, ref ubyte[] rbuf)
172         {
173             this.name = name;
174             this.wbuf = &wbuf;
175             this.rbuf = &rbuf;
176         }
177     override:
178         size_t write(const(void)[] msg)
179         {
180             (*wbuf) = cast(ubyte[])(msg.dup);
181             version (modbus_verbose)
182                 stderr.writefln("%s write %s", name, (*wbuf));
183             return msg.length;
184         }
185 
186         void[] read(void[] buffer)
187         {
188             auto ub = cast(ubyte[])buffer;
189             size_t i;
190             version (modbus_verbose)
191                 stderr.writefln("%s read %s", name, (*rbuf));
192             for (i=0; i < ub.length; i++)
193             {
194                 if ((*rbuf).empty)
195                     return buffer[0..i];
196                 ub[i] = (*rbuf).front;
197                 (*rbuf).popFront;
198             }
199             return buffer[0..i];
200         }
201     }
202 
203     class ConT2 : ConT1
204     {
205         import std.random;
206 
207         this(string name, ref ubyte[] wbuf, ref ubyte[] rbuf)
208         { super(name, wbuf, rbuf); }
209 
210         void slp(Duration d)
211         {
212             import core.thread;
213             auto dt = StopWatch(AutoStart.yes);
214             import std.conv : to;
215             while (dt.peek.to!Duration < d) Fiber.yield();
216         }
217 
218     override:
219         size_t write(const(void)[] msg)
220         {
221             auto l = uniform!"[]"(0, msg.length);
222             (*wbuf) ~= cast(ubyte[])(msg[0..l].dup);
223             slp(uniform(1, 5).usecs);
224             version (modbus_verbose)
225                 stderr.writefln("%s write %s", name, (*wbuf));
226             return l;
227         }
228 
229         void[] read(void[] buffer)
230         {
231             auto l = uniform!"[]"(0, (*rbuf).length);
232             auto ub = cast(ubyte[])buffer;
233             size_t i;
234             version (modbus_verbose)
235                 stderr.writefln("%s read %s", name, (*rbuf));
236             for (i=0; i < ub.length; i++)
237             {
238                 if (i > l) return buffer[0..i];
239                 slp(uniform(1, 5).msecs);
240                 if ((*rbuf).empty)
241                     return buffer[0..i];
242                 ub[i] = (*rbuf).front;
243                 (*rbuf).popFront;
244             }
245             return buffer[0..i];
246         }
247     }
248 
249     void testFunc(CT)()
250     {
251         ubyte[] chA, chB;
252 
253         auto conA = new CT("A", chA, chB);
254         auto conB = new CT("B", chB, chA);
255 
256         auto sr = new BasicSpecRules;
257         auto mm = new ModbusMaster(new RTU(conA, sr));
258         mm.writeTimeout = 100.msecs;
259         mm.readTimeout = 200.msecs;
260 
261         auto ms = new class ModbusSlave
262         {
263             ushort[] table;
264             this()
265             {
266                 super(1, new RTU(conB, sr));
267                 writeTimeout = 100.msecs;
268                 table = [123, 234, 345, 456, 567, 678, 789, 890, 901];
269                 func[FuncCode.readHoldingRegisters] = (Message m)
270                 {
271                     enum us = ushort.sizeof;
272                     auto start = be.unpackT!ushort(m.data[0..us]);
273                     auto count = be.unpackT!ushort(m.data[us..us*2]);
274                     version (modbus_verbose)
275                     {
276                         import std.stdio;
277                         stderr.writeln("count check fails: ", count == 0 || count > 125);
278                         stderr.writeln("start check fails: ", start >= table.length);
279                     }
280                     if (count == 0 || count > 125) return illegalDataValue;
281                     if (start >= table.length) return illegalDataAddress;
282                     if (start+count >= table.length) return illegalDataAddress;
283 
284                     return packResult(cast(ubyte)(count*2),
285                         table[start..start+count]);
286                 };
287             }
288         };
289 
290         import core.thread;
291 
292         auto f1 = new Fiber(
293         {
294             bool thrown;
295             try mm.readHoldingRegisters(1, 3, 100);
296             catch (FunctionErrorException e)
297             {
298                 thrown = true;
299                 assert(e.code == FunctionErrorCode.ILLEGAL_DATA_ADDRESS);
300             }
301             assert (thrown);
302 
303             thrown = false;
304             try mm.readHoldingRegisters(1, 200, 2);
305             catch (FunctionErrorException e)
306             {
307                 thrown = true;
308                 assert(e.code == FunctionErrorCode.ILLEGAL_DATA_ADDRESS);
309             }
310             assert (thrown);
311 
312             thrown = false;
313             try mm.readInputRegisters(1, 200, 2);
314             catch (FunctionErrorException e)
315             {
316                 thrown = true;
317                 assert(e.code == FunctionErrorCode.ILLEGAL_FUNCTION);
318             }
319             assert (thrown);
320 
321             auto data = mm.readHoldingRegisters(1, 2, 3);
322             assert (data == [345, 456, 567]);
323         });
324 
325         auto f2 = new Fiber({
326             while (true)
327             {
328                 ms.iterate();
329                 import std.conv;
330                 auto dt = StopWatch(AutoStart.yes);
331                 while (dt.peek.to!Duration < 1.msecs)
332                     Fiber.yield();
333             }
334         });
335         while (true)
336         {
337             if (f1.state == f1.state.TERM) break;
338             f1.call();
339             f2.call();
340         }
341     }
342 }
343 
344 unittest
345 {
346     testFunc!ConT1();
347     testFunc!ConT2();
348 }