1 ///
2 module modbus.protocol;
3 
4 import std.bitmanip : BitArray;
5 version (modbus_verbose)
6     import std.experimental.logger;
7 
8 public import modbus.exception;
9 
10 import modbus.func;
11 
12 ///
13 class Modbus
14 {
15 protected:
16     Backend be;
17 
18     int fiber_mutex;
19     static struct FSync
20     {
21         int* mutex;
22         this(Modbus m)
23         {
24             mutex = &(m.fiber_mutex);
25             if (*mutex != 0) m.yield();
26             *mutex = 1;
27         }
28 
29         ~this() { *mutex = 0; }
30     }
31 
32     void yield()
33     {
34         import core.thread;
35         if (yieldFunc != null) yieldFunc();
36         else if (Fiber.getThis !is null)
37             Fiber.yield();
38         else
39         {
40             // unspecific state, if vars must
41             // changes it can be not changed
42             version (modbus_verbose)
43                 .warning("Thread.yield can block execution");
44             Thread.yield();
45         }
46     }
47 
48     void delegate() yieldFunc;
49 
50 public:
51 
52     ///
53     static interface Backend
54     {
55         /// start building message
56         void start(ulong dev, ubyte func);
57 
58         /// append data to message buffer
59         void append(byte);
60         /// ditto
61         void append(short);
62         /// ditto
63         void append(int);
64         /// ditto
65         void append(long);
66         /// ditto
67         void append(float);
68         /// ditto
69         void append(double);
70         /// ditto
71         void append(const(void)[]);
72 
73         ///
74         bool messageComplite() const @property;
75 
76         /// temp message buffer
77         const(void)[] tempBuffer() const @property;
78 
79         /// send and clear temp message
80         void send();
81 
82         /// Readed modbus response
83         static struct Response
84         {
85             /// device number
86             ulong dev;
87             /// function number
88             ubyte fnc;
89 
90             /// data without any changes
91             const(void)[] data;
92         }
93 
94         /++ Read data to temp message buffer
95             Params:
96             expectedBytes = count of bytes in data section of message,
97                             exclude device address, function number, CRC and etc
98          +/
99         Response read(size_t expectedBytes);
100     }
101 
102     /++ 
103         Params:
104             be = Backend
105             yieldFunc = needs if used in fiber-based code (vibe for example)
106      +/
107     this(Backend be, void delegate() yieldFunc=null)
108     {
109         if (be is null)
110             throw modbusException("backend is null");
111         this.be = be;
112         this.yieldFunc = yieldFunc;
113     }
114 
115     // fiber unsafe write
116     private void fusWrite(Args...)(ulong dev, ubyte fnc, Args args)
117     {
118         import std.range : ElementType;
119         import std.traits : isArray, isNumeric, Unqual;
120         import std.exception : enforce;
121 
122         enforce(be.messageComplite, "uncomplite meassage");
123 
124         be.start(dev, fnc);
125 
126         void _append(T)(T val)
127         {
128             static if (isArray!T)
129             {
130                 static if (is(Unqual!(ElementType!T) == void))
131                     be.append(val);
132                 else foreach (e; val) _append(e);
133             }
134             else
135             {
136                 static if (is(T == struct))
137                     foreach (name; __traits(allMembers, T))
138                         _append(__traits(getMember, val, name));
139                 else static if (isNumeric!T) be.append(val);
140                 else static assert(0, "unsupported type " ~ T.stringof);
141             }
142         }
143 
144         foreach (arg; args) _append(arg);
145 
146         be.send();
147     }
148 
149     /++ Write to serial port
150 
151         fiber-safe
152 
153         Params:
154             dev = modbus device address (number)
155             fnc = function number
156             args = writed data in native endian
157      +/
158     void write(Args...)(ulong dev, ubyte fnc, Args args)
159     {
160         auto fsync = FSync(this);
161         fusWrite(dev, fnc, args);
162     }
163 
164     void flush()
165     {
166         import serialport;
167         try
168         {
169             auto res = be.read(240);
170             version (modbus_verbose)
171                 .info("flush ", cast(ubyte[])(res.data));
172         }
173         catch (TimeoutException e)
174             version (modbus_verbose)
175                 .trace("flust timeout");
176     }
177 
178     // fiber unsafe read
179     private const(void)[] fusRead(ulong dev, ubyte fnc, size_t bytes)
180     {
181         import std.exception : enforce;
182         enforce(be.messageComplite, "uncomplite meassage");
183 
184         auto res = be.read(bytes);
185 
186         version (modbus_verbose)
187             if (res.dev != dev)
188                 .warningf("receive from unexpected device %d (expect %d)",
189                                 res.dev, dev);
190         
191         if (res.fnc != fnc)
192             throw functionErrorException(dev, fnc, res.fnc, (cast(ubyte[])res.data)[0]);
193 
194         if (res.data.length != bytes)
195             throw readDataLengthException(dev, fnc, bytes, res.data.length);
196 
197         return res.data;
198     }
199 
200     /++ Read from serial port
201 
202         fiber-safe
203 
204         Params:
205             dev = modbus device address (number)
206             fnc = function number
207             bytes = expected response length in bytes
208 
209         Returns:
210             result in big endian
211      +/
212     const(void)[] read(ulong dev, ubyte fnc, size_t bytes)
213     {
214         auto fsync = FSync(this);
215         return fusRead(dev, fnc, bytes);
216     }
217 
218     // fiber unsafe request
219     private const(void)[] fusRequest(Args...)(ulong dev, ubyte fnc, size_t bytes, Args args)
220     {
221         fusWrite(dev, fnc, args);
222         return fusRead(dev, fnc, bytes);
223     }
224 
225     /++ Write and read to modbus
226 
227         fiber-safe
228 
229         Params:
230             dev = slave device number
231             fnc = called function number
232             bytes = expected bytes for reading
233             args = sending data
234         Returns:
235             result in big endian
236      +/
237     const(void)[] request(Args...)(ulong dev, ubyte fnc, size_t bytes, Args args)
238     {
239         auto fsync = FSync(this);
240         return fusRequest(dev, fnc, bytes, args);
241     }
242 
243     /// function number 0x1 (1)
244     const(BitArray) readCoils(ulong dev, ushort start, ushort cnt)
245     {
246         if (cnt >= 2000) throw modbusException("very big count");
247         auto fsync = FSync(this);
248         return const(BitArray)(cast(void[])fusRequest(dev, 1, 1+(cnt+7)/8, start, cnt)[1..$], cnt);
249     }
250 
251     /// function number 0x2 (2)
252     const(BitArray) readDiscreteInputs(ulong dev, ushort start, ushort cnt)
253     {
254         if (cnt >= 2000) throw modbusException("very big count");
255         auto fsync = FSync(this);
256         return const(BitArray)(cast(void[])fusRequest(dev, 2, 1+(cnt+7)/8, start, cnt)[1..$], cnt);
257     }
258 
259     /++ function number 0x3 (3)
260         Returns: data in native endian
261      +/ 
262     const(ushort)[] readHoldingRegisters(ulong dev, ushort start, ushort cnt)
263     {
264         if (cnt >= 125) throw modbusException("very big count");
265         auto fsync = FSync(this);
266         return bigEndianToNativeArr(cast(ushort[])fusRequest(dev, 3, 1+cnt*2, start, cnt)[1..$]);
267     }
268 
269     /++ function number 0x4 (4)
270         Returns: data in native endian
271      +/ 
272     const(ushort)[] readInputRegisters(ulong dev, ushort start, ushort cnt)
273     {
274         if (cnt >= 125) throw modbusException("very big count");
275         auto fsync = FSync(this);
276         return bigEndianToNativeArr(cast(ushort[])fusRequest(dev, 4, 1+cnt*2, start, cnt)[1..$]);
277     }
278 
279     /// function number 0x5 (5)
280     void writeSingleCoil(ulong dev, ushort addr, bool val)
281     { request(dev, 5, 4, addr, cast(ushort)(val ? 0xff00 : 0x0000)); }
282 
283     /// function number 0x6 (6)
284     void writeSingleRegister(ulong dev, ushort addr, ushort value)
285     { request(dev, 6, 4, addr, value); }
286 
287     /// function number 0x10 (16)
288     void writeMultipleRegisters(ulong dev, ushort addr, const(ushort)[] values)
289     {
290         if (values.length >= 125) throw modbusException("very big count");
291         request(dev, 16, 4, addr, cast(ushort)values.length,
292                     cast(byte)(values.length*2), values);
293     }
294 }