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