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     invariant
103     {
104         assert(be.messageComplite, "uncomplite message");
105     }
106 
107     /++ 
108         Params:
109             be = Backend
110             yieldFunc = needs if used in fiber-based code (vibe for example)
111      +/
112     this(Backend be, void delegate() yieldFunc=null)
113     {
114         if (be is null)
115             throw modbusException("backend is null");
116         this.be = be;
117         this.yieldFunc = yieldFunc;
118     }
119 
120     // fiber unsafe write
121     private void fusWrite(Args...)(ulong dev, ubyte fnc, Args args)
122     {
123         import std.range : ElementType;
124         import std.traits : isArray, isNumeric, Unqual;
125 
126         be.start(dev, fnc);
127 
128         void _append(T)(T val)
129         {
130             static if (isArray!T)
131             {
132                 static if (is(Unqual!(ElementType!T) == void))
133                     be.append(val);
134                 else foreach (e; val) _append(e);
135             }
136             else
137             {
138                 static if (is(T == struct))
139                     foreach (name; __traits(allMembers, T))
140                         _append(__traits(getMember, val, name));
141                 else static if (isNumeric!T) be.append(val);
142                 else static assert(0, "unsupported type " ~ T.stringof);
143             }
144         }
145 
146         foreach (arg; args) _append(arg);
147 
148         be.send();
149     }
150 
151     /++ Write to serial port
152 
153         fiber-safe
154 
155         Params:
156             dev = modbus device address (number)
157             fnc = function number
158             args = writed data in native endian
159      +/
160     void write(Args...)(ulong dev, ubyte fnc, Args args)
161     {
162         auto fsync = FSync(this);
163         fusWrite(dev, fnc, args);
164     }
165 
166     /++ Read from serial port
167 
168         not fiber-safe
169 
170         Params:
171             dev = modbus device address (number)
172             fnc = function number
173             bytes = expected response length in bytes
174 
175         Returns:
176             result in big endian
177      +/
178     const(void)[] read(ulong dev, ubyte fnc, size_t bytes)
179     {
180         auto res = be.read(bytes);
181 
182         version (modbus_verbose)
183             if (res.dev != dev)
184                 .warningf("receive from unexpected device %d (expect %d)",
185                                 res.dev, dev);
186         
187         if (res.fnc != fnc)
188             throw functionErrorException(dev, fnc, res.fnc, (cast(ubyte[])res.data)[0]);
189 
190         if (res.data.length != bytes)
191             throw readDataLengthException(dev, fnc, bytes, res.data.length);
192 
193         return res.data;
194     }
195 
196     /++ Write and read to modbus
197 
198         Params:
199             dev = slave device number
200             fnc = called function number
201             bytes = expected bytes for reading
202             args = sending data
203         Returns:
204             result in big endian
205      +/
206     const(void)[] request(Args...)(ulong dev, ubyte fnc, size_t bytes, Args args)
207     {
208         auto fsync = FSync(this);
209         this.fusWrite(dev, fnc, args);
210         return read(dev, fnc, bytes);
211     }
212 
213     /// function number 0x1 (1)
214     const(BitArray) readCoils(ulong dev, ushort start, ushort cnt)
215     {
216         if (cnt >= 2000) throw modbusException("very big count");
217         return const(BitArray)(cast(void[])request(dev, 1, 1+(cnt+7)/8, start, cnt)[1..$], cnt);
218     }
219 
220     /// function number 0x2 (2)
221     const(BitArray) readDiscreteInputs(ulong dev, ushort start, ushort cnt)
222     {
223         if (cnt >= 2000) throw modbusException("very big count");
224         return const(BitArray)(cast(void[])request(dev, 2, 1+(cnt+7)/8, start, cnt)[1..$], cnt);
225     }
226 
227     /++ function number 0x3 (3)
228         Returns: data in native endian
229      +/ 
230     const(ushort)[] readHoldingRegisters(ulong dev, ushort start, ushort cnt)
231     {
232         if (cnt >= 125) throw modbusException("very big count");
233         return bigEndianToNativeArr(cast(ushort[])request(dev, 3, 1+cnt*2, start, cnt)[1..$]);
234     }
235 
236     /++ function number 0x4 (4)
237         Returns: data in native endian
238      +/ 
239     const(ushort)[] readInputRegisters(ulong dev, ushort start, ushort cnt)
240     {
241         if (cnt >= 125) throw modbusException("very big count");
242         return bigEndianToNativeArr(cast(ushort[])request(dev, 4, 1+cnt*2, start, cnt)[1..$]);
243     }
244 
245     /// function number 0x5 (5)
246     void writeSingleCoil(ulong dev, ushort addr, bool val)
247     { request(dev, 5, 4, addr, cast(ushort)(val ? 0xff00 : 0x0000)); }
248 
249     /// function number 0x6 (6)
250     void writeSingleRegister(ulong dev, ushort addr, ushort value)
251     { request(dev, 6, 4, addr, value); }
252 
253     /// function number 0x10 (16)
254     void writeMultipleRegisters(ulong dev, ushort addr, const(ushort)[] values)
255     {
256         if (values.length >= 125) throw modbusException("very big count");
257         request(dev, 16, 4, addr, cast(ushort)values.length,
258                     cast(byte)(values.length*2), values);
259     }
260 }