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 }