1 ///
2 module modbus.protocol.slave;
3 
4 import modbus.protocol.base;
5 public import modbus.types;
6 
7 /++ Base class for modbus slave devices
8 
9     Iteration and message parsing process
10 
11     Define types
12 
13     need override `checkDeviceNumber` and `onMessage`
14  +/
15 class ModbusSlaveBase : Modbus
16 {
17 protected:
18     size_t readed;
19 
20     StopWatch dt;
21 
22     /+ need separate with Modbus.buffer because
23        otherwise in Modbus.write Modbus.buffer
24        start pack with device number and function
25        and can override answer data
26      +/
27     void[MAX_BUFFER] mBuffer;
28 
29     ///
30     enum Reaction
31     {
32         none, ///
33         onlyProcessMessage, ///
34         processAndAnswer ///
35     }
36 
37     ///
38     abstract Reaction checkDeviceNumber(ulong dev);
39 
40     /++
41         Example:
42         ---
43         if (msg.fnc == FuncCode.readInputRegisters)
44             return packResult(/* return data */);
45         return illegalFunction;
46         ---
47      +/
48     abstract MsgProcRes onMessage(ref const Message msg);
49 
50     static MsgProcRes failMsgProcRes(T)(T val)
51     { return MsgProcRes(cast(void[])[val], 1); }
52 
53     /// process message and send result if needed
54     void processMessage(ref const Message msg)
55     {
56         MsgProcRes res;
57         try
58         {
59             auto pm = checkDeviceNumber(msg.dev);
60             if (pm == Reaction.none) return;
61             res = onMessage(msg);
62             if (pm == Reaction.processAndAnswer)
63                 this.write(msg.dev, msg.fnc | (res.error ? 0x80 : 0), res.data);
64         }
65         catch (Throwable e)
66         {
67             import std.experimental.logger;
68             errorf("%s", e);
69             this.write(msg.dev, msg.fnc | 0x80,
70                     FunctionErrorCode.SLAVE_DEVICE_FAILURE);
71         }
72     }
73 
74 public:
75     ///
76     this(Backend be, void delegate(Duration) sf=null)
77     {
78         super(be, sf);
79         // approx 10 bytes (10 bits) on 9600 speed
80         readTimeout = (cast(ulong)(1e7/96.0)).hnsecs;
81     }
82 
83     ///
84     struct MsgProcRes
85     {
86         ///
87         void[] data;
88         ///
89         uint error;
90     }
91 
92     ///
93     enum illegalFunction = failMsgProcRes(FunctionErrorCode.ILLEGAL_FUNCTION);
94     ///
95     enum illegalDataValue = failMsgProcRes(FunctionErrorCode.ILLEGAL_DATA_VALUE);
96     ///
97     enum illegalDataAddress = failMsgProcRes(FunctionErrorCode.ILLEGAL_DATA_ADDRESS);
98 
99     ///
100     alias Function = MsgProcRes delegate(Message);
101 
102     /// Functions
103     enum FuncCode : ubyte
104     {
105         readCoils             = 0x1,  /// 0x1 (1)
106         readDiscreteInputs    = 0x2,  /// 0x2 (2)
107         readHoldingRegisters  = 0x3,  /// 0x3 (3)
108         readInputRegisters    = 0x4,  /// 0x4 (4)
109         writeSingleCoil       = 0x5,  /// 0x5 (5)
110         writeSingleRegister   = 0x6,  /// 0x6 (6)
111         writeMultipleRegister = 0x10, /// 0x10 (16)
112     }
113 
114     ///
115     MsgProcRes packResult(Args...)(Args args)
116     {
117         static void appendData(T)(void[] buf, T data, Backend backend, ref size_t start)
118         {
119             import std.traits : isArray;
120             import std.range : isInputRange;
121             static if (isArray!T || isInputRange!T)
122             {
123                 foreach (e; data)
124                     appendData(buf, e, backend, start);
125             }
126             else
127             {
128                 auto p = backend.packT(data);
129                 auto end = start + p.length;
130                 if (end > buf.length)
131                     throw modbusException("fill message buffer: to many args for pack data");
132                 buf[start..end] = p;
133                 start = end;
134             }
135         }
136 
137         size_t filled;
138 
139         foreach (arg; args)
140             appendData(mBuffer[], arg, be, filled);
141 
142         return MsgProcRes(mBuffer[0..filled]);
143     }
144 
145     ///
146     void iterate()
147     {
148         Message msg;
149 
150         if (dt.peek.to!Duration > readTimeout)
151         {
152             dt.stop();
153             readed = 0;
154         }
155 
156         size_t now_readed;
157 
158         do
159         {
160             now_readed = be.connection.read(buffer[readed..$]).length;
161             readed += now_readed;
162             version (modbus_verbose) if (now_readed)
163             {
164                 import std.stdio;
165                 stderr.writeln(" now readed: ", now_readed);
166                 stderr.writeln("full readed: ", readed);
167             }
168         }
169         while (now_readed);
170 
171         if (!readed) return;
172         if (!dt.running) dt.start();
173 
174         auto res = be.parseMessage(buffer[0..readed], msg);
175 
176         with (Backend.ParseResult) final switch(res)
177         {
178             case success:
179                 processMessage(msg);
180                 readed = 0;
181                 break;
182             case errorMsg: /+ master send error? WTF? +/ break;
183             case uncomplete: break;
184             case checkFail: break;
185         }
186     }
187 
188     /// aux function
189     ushort[2] parseMessageFirstTwoUshorts(ref const Message m)
190     {
191         return [be.unpackT!ushort(m.data[0..2]),
192                 be.unpackT!ushort(m.data[2..4])];
193     }
194 }
195 
196 /++ One device modbus slave
197 
198     Usage:
199         set device, backend and sleep function (optional) in ctor
200         add process funcs (use modbus methods for parsing packs)
201         profit
202  +/
203 class ModbusSlave : ModbusSlaveBase
204 {
205 protected:
206     ///
207     ulong dev;
208     ///
209     bool broadcastAnswer;
210     ///
211     ulong broadcastDevId = 0;
212 
213     override Reaction checkDeviceNumber(ulong dn)
214     {
215         if (dn == dev) return Reaction.processAndAnswer;
216         else if (dn == broadcastDevId)
217         {
218             if (broadcastAnswer)
219                 return Reaction.processAndAnswer;
220             else
221                 return Reaction.onlyProcessMessage;
222         }
223         else return Reaction.none;
224     }
225 
226 public:
227     ///
228     this(ulong dev, Backend be, void delegate(Duration) sf=null)
229     {
230         super(be, sf);
231         this.dev = dev;
232         broadcastAnswer = false;
233     }
234 
235     /++ Example:
236         ---
237         slave.func[FuncCode.readInputRegisters] = (m)
238         {
239             auto origStart = slave.backend.unpackT!ushort(m.data[0..2]);
240             auto count = slave.backend.unpackT!ushort(m.data[2..4]);
241 
242             if (count == 0 || count > 125) return slave.illegalDataValue;
243             if (count > dataTable.length) return slave.illegalDataValue;
244             ptrdiff_t start = origStart - START_DATA_REG;
245             if (start >=  table.length || start < 0)
246                 return slave.illegalDataAddress;
247 
248             return slave.packResult(cast(ubyte)(count*2), dataTable[start..start+count]);
249         };
250         ---
251      +/
252     Function[ubyte] func;
253 
254     override MsgProcRes onMessage(ref const Message m)
255     {
256         if (m.fnc in func) return func[m.fnc](m);
257         return illegalFunction;
258     }
259 }
260 
261 unittest
262 {
263     auto mb = new ModbusSlave(1, new RTU(nullConnection));
264     import std.range;
265     import std.algorithm;
266     mb.packResult(iota(cast(ubyte)10));
267     
268     assert(equal((cast(ubyte[])mb.mBuffer)[0..10], iota(10)));
269 }