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