1 ///
2 module modbus.protocol.master;
3 
4 import modbus.protocol.base;
5 
6 ///
7 class ModbusMaster : Modbus
8 {
9     /// approx 1 byte (10 bits) on 9600 speed
10     protected Duration readStepPause() @property
11     { return (cast(ulong)(1e6/96.0)).hnsecs; }
12 
13     ///
14     this(Backend be, void delegate(Duration) sf=null) { super(be, sf); }
15 
16     /++ Read from connection
17 
18         Params:
19             dev = modbus device address (number)
20             fnc = function number
21             bytes = expected response data length in bytes
22         Returns:
23             result in big endian
24      +/
25     const(void)[] read(ulong dev, ubyte fnc, size_t bytes)
26     {
27         size_t mustRead = be.aduLength(bytes);
28         size_t cnt = 0;
29         Message msg;
30         try
31         {
32             auto dt = StopWatch(AutoStart.yes);
33             RL: while (cnt < mustRead)
34             {
35                 auto tmp = be.connection.read(buffer[cnt..mustRead]);
36                 if (tmp.length)
37                 {
38                     cnt += tmp.length;
39                     auto res = be.parseMessage(buffer[0..cnt], msg);
40                     FS: final switch(res) with (Backend.ParseResult)
41                     {
42                         case success:
43                             if (cnt == mustRead) break RL;
44                             else throw readDataLengthException(dev,
45                                                     fnc, mustRead, cnt);
46                         case errorMsg: break RL;
47                         case uncomplete: break FS;
48                         case checkFail:
49                             if (cnt == mustRead)
50                                 throw checkFailException(dev, fnc);
51                             else break FS;
52                     }
53                 }
54                 if (dt.peek.to!Duration > readTimeout)
55                     throw modbusTimeoutException("read", dev, fnc,
56                                                     readTimeout);
57                 this.sleep(readStepPause);
58             }
59 
60             version (modbus_verbose)
61                 if (msg.dev != dev)
62                     .warningf("receive from unexpected device "~
63                                 "%d (expect %d)", msg.dev, dev);
64             
65             if (msg.fnc != fnc)
66                 throw functionErrorException(dev, fnc, msg.fnc, 
67                                         (cast(ubyte[])msg.data)[0]);
68 
69             if (msg.data.length != bytes)
70                 throw readDataLengthException(dev, fnc, bytes,
71                                                 msg.data.length);
72 
73             return msg.data;
74         }
75         catch (ModbusDevException e)
76         {
77             e.readed = buffer[0..cnt];
78             throw e;
79         }
80     }
81 
82     /++ Write and read to modbus
83 
84         Params:
85             dev = slave device number
86             fnc = called function number
87             bytes = expected response data bytes
88             args = sending data
89         Returns:
90             result in big endian
91      +/
92     const(void)[] request(Args...)(ulong dev, ubyte fnc,
93                                    size_t bytes, Args args)
94     {
95         auto tmp = write(dev, fnc, args);
96 
97         try return read(dev, fnc, bytes);
98         catch (ModbusDevException e)
99         {
100             e.writed = tmp[];
101             throw e;
102         }
103     }
104 
105     /// 01 (0x01) Read Coils
106     const(BitArray) readCoils(ulong dev, ushort start, ushort cnt)
107     {
108         if (cnt >= 2000) throw modbusException("very big count");
109         return const(BitArray)(cast(void[])request(
110                 dev, 1, 1+(cnt+7)/8, start, cnt)[1..$], cnt);
111     }
112 
113     /// 02 (0x02) Read Discrete Inputs
114     const(BitArray) readDiscreteInputs(ulong dev, ushort start, ushort cnt)
115     {
116         if (cnt >= 2000) throw modbusException("very big count");
117         return const(BitArray)(cast(void[])request(
118                 dev, 2, 1+(cnt+7)/8, start, cnt)[1..$], cnt);
119     }
120 
121     private alias be2na = bigEndianToNativeArr;
122 
123     /++ 03 (0x03) Read Holding Registers
124         Returns: data in native endian
125      +/ 
126     const(ushort)[] readHoldingRegisters(ulong dev, ushort start, ushort cnt)
127     {
128         if (cnt >= 125) throw modbusException("very big count");
129         return be2na(cast(ushort[])request(
130                 dev, 3, 1+cnt*2, start, cnt)[1..$]);
131     }
132 
133     /++ 04 (0x04) Read Input Registers
134         Returns: data in native endian
135      +/ 
136     const(ushort)[] readInputRegisters(ulong dev, ushort start, ushort cnt)
137     {
138         if (cnt >= 125) throw modbusException("very big count");
139         return be2na(cast(ushort[])request(
140                 dev, 4, 1+cnt*2, start, cnt)[1..$]);
141     }
142 
143     /// 05 (0x05) Write Single Coil
144     void writeSingleCoil(ulong dev, ushort addr, bool val)
145     { request(dev, 5, 4, addr, cast(ushort)(val ? 0xff00 : 0x0000)); }
146 
147     /// 06 (0x06) Write Single Register
148     void writeSingleRegister(ulong dev, ushort addr, ushort value)
149     { request(dev, 6, 4, addr, value); }
150 
151     /// 16 (0x10) Write Multiple Registers
152     void writeMultipleRegisters(ulong dev, ushort addr, const(ushort)[] values)
153     {
154         if (values.length >= 125) throw modbusException("very big count");
155         request(dev, 16, 4, addr, cast(ushort)values.length,
156                     cast(byte)(values.length*2), values);
157     }
158 }
159 
160 unittest
161 {
162     static import std.bitmanip;
163     alias bwrite = std.bitmanip.write;
164     alias bread = std.bitmanip.read;
165 
166     static class ModbusEmulator
167     {
168         align(1)
169         static struct DeviceData
170         {
171             align(1):
172             ushort[4] simpleRegister; // 0..3
173             int intValue; // 4
174             float floatValue; // 6
175         }
176 
177         SpecRules sr;
178         DeviceData[ulong] regs;
179         ubyte[256] res;
180         size_t idx;
181 
182         this(SpecRules sr)
183         {
184             regs[70] = DeviceData([1234, 10405, 12, 42], 3^^12, 3.14);
185             regs[1] = DeviceData([2345, 50080, 34, 42], 7^^9, 2.71);
186             this.sr = sr;
187         }
188 
189         size_t write(const(void)[] msg)
190         {
191             idx = 0;
192             auto ubmsg = cast(const(ubyte)[])msg;
193             ulong dev;
194             ubyte fnc;
195             sr.peekDF(ubmsg, dev, fnc);
196             ubmsg = ubmsg[sr.deviceTypeSize+1..$];
197 
198             if (dev !in regs) return msg.length;
199 
200             res[idx..idx+sr.deviceTypeSize] = cast(ubyte[])sr.packDF(dev, fnc)[0..sr.deviceTypeSize];
201             idx += sr.deviceTypeSize;
202 
203             import std.stdio;
204             if (!checkCRC(msg))
205                 storeFail(fnc, FunctionErrorCode.ILLEGAL_DATA_VALUE);
206             else
207             {
208                 bwrite(res[], fnc, &idx);
209 
210                 switch (fnc)
211                 {
212                     case 4:
213                         auto d = (cast(ushort*)(dev in regs))[0..DeviceData.sizeof/2];
214                         auto st = bread!ushort(ubmsg);
215                         auto cnt = cast(ubyte)bread!ushort(ubmsg);
216                         bwrite(res[], cnt, &idx);
217                         foreach (i; 0 .. cnt)
218                             bwrite(res[], d[st+i], &idx);
219                         break;
220                     default:
221                         storeFail(fnc, FunctionErrorCode.ILLEGAL_DATA_VALUE);
222                         break;
223                 }
224             }
225             storeCRC();
226             readResult = res[0..idx];
227             return msg.length;
228         }
229 
230         ubyte[] readResult;
231         void[] read(void[] buffer)
232         {
233             import std.range;
234             auto ubbuf = cast(ubyte[])buffer;
235             foreach (i; 0 .. buffer.length)
236             {
237                 if (readResult.empty)
238                     return buffer[0..i];
239                 ubbuf[i] = readResult.front;
240                 readResult.popFront();
241             }
242             return buffer;
243         }
244 
245         void storeFail(ubyte fnc, FunctionErrorCode c)
246         {
247             bwrite(res[], cast(ubyte)(fnc|0x80), &idx);
248             bwrite(res[], cast(ubyte)c, &idx);
249         }
250 
251         void storeCRC()
252         {
253             auto crc = crc16(res[0..idx]);
254             bwrite(res[], crc[0], &idx);
255             bwrite(res[], crc[1], &idx);
256         }
257     }
258 
259     BasicSpecRules sr = new BasicSpecRules;
260 
261     auto com = new ModbusEmulator(sr);
262 
263     auto mbus = new ModbusMaster(new RTU(new class Connection{
264         override:
265             size_t write(const(void)[] msg) { return com.write(msg); }
266             void[] read(void[] buffer) { return com.read(buffer); }
267         }, sr));
268 
269     assert(mbus.readInputRegisters(70, 0, 1)[0] == 1234);
270     import std.algorithm : equal;
271     assert(equal(mbus.readInputRegisters(1, 0, 4), [2345, 50080, 34, 42]));
272 }