1 /// modbus with back end 2 module modbus.facade; 3 4 import modbus.backend; 5 import modbus.protocol; 6 7 version(Have_serialport) 8 { 9 public import std.datetime : Duration, dur, hnsecs, nsecs, msecs, seconds; 10 public import serialport; 11 12 /// 13 class SerialPortConnection : Connection 14 { 15 /// 16 SerialPort sp; 17 /// 18 this(SerialPort sp) { this.sp = sp; } 19 override: 20 /// 21 size_t write(const(void)[] msg) { return sp.write(msg); } 22 /// 23 void[] read(void[] buffer) { return sp.read(buffer); } 24 } 25 26 /// ModbusMaster with RTU backend 27 class ModbusRTUMaster : ModbusMaster 28 { 29 protected: 30 /// 31 SerialPortConnection spcom; 32 33 override @property 34 { 35 Duration writeStepPause() { return readStepPause; } 36 Duration readStepPause() 37 { return (cast(ulong)(1e8 / com.baudRate)).hnsecs; } 38 } 39 40 public: 41 42 /// 43 this(string port, uint baudrate, string mode="8N1") 44 { this(port, SerialPort.Config(baudrate).set(mode)); } 45 46 /// 47 this(string port, string mode) 48 { this(port, SerialPort.Config.parse(mode)); } 49 50 /// 51 this(string port, uint baudrate, void delegate(Duration) sf, 52 SpecRules sr=null) 53 { this(port, SerialPort.Config(baudrate), sf, sr); } 54 55 /// 56 this(string port, SerialPort.Config cfg, 57 void delegate(Duration) sf=null, SpecRules sr=null) 58 { 59 this(new SerialPort(port, cfg), sf, sr); 60 _manageSerialPort = true; 61 } 62 63 private bool _manageSerialPort; 64 bool manageSerialPort() const @property { return _manageSerialPort; } 65 66 /// 67 this(SerialPort sp, void delegate(Duration) sf=null, 68 SpecRules sr=null) 69 { 70 spcom = new SerialPortConnection(sp); 71 super(new RTU(spcom, sr), sf); 72 } 73 74 /// 75 void flush() 76 { 77 void[240] buf = void; 78 auto res = com.read(buf); 79 version (modbus_verbose) 80 .info("flush ", cast(ubyte[])(res)); 81 } 82 83 /// 84 inout(SerialPort) com() inout @property { return spcom.sp; } 85 } 86 87 /// ModbusSlave with RTU backend 88 class ModbusRTUSlave : ModbusSlave 89 { 90 protected: 91 /// 92 SerialPortConnection spcom; 93 94 override Duration writeStepPause() @property 95 { return (cast(ulong)(1e8 / com.baudRate)).hnsecs; } 96 97 public: 98 99 /// 100 this(ulong dev, string port, uint baudrate, string mode="8N1") 101 { this(dev, port, SerialPort.Config(baudrate).set(mode)); } 102 103 /// 104 this(ulong dev, string port, string mode) 105 { this(dev, port, SerialPort.Config.parse(mode)); } 106 107 /// 108 this(ulong dev, string port, uint baudrate, 109 void delegate(Duration) sf, SpecRules sr=null) 110 { this(dev, port, SerialPort.Config(baudrate), sf, sr); } 111 112 /// 113 this(ulong dev, string port, SerialPort.Config cfg, 114 void delegate(Duration) sf=null, SpecRules sr=null) 115 { 116 this(dev, new SerialPort(port, cfg), sf, sr); 117 _manageSerialPort = true; 118 } 119 120 private bool _manageSerialPort; 121 bool manageSerialPort() const @property { return _manageSerialPort; } 122 123 /// 124 this(ulong dev, SerialPort sp, void delegate(Duration) sf=null, 125 SpecRules sr=null) 126 { 127 spcom = new SerialPortConnection(sp); 128 super(dev, new RTU(spcom, sr)); 129 } 130 131 /// 132 inout(SerialPort) com() inout @property { return spcom.sp; } 133 } 134 } 135 136 import modbus.connection.tcp; 137 import std.socket : TcpSocket; 138 139 /// Modbus with TCP backend based on TcpSocket from std.socket 140 class ModbusTCPMaster : ModbusMaster 141 { 142 protected: 143 MasterTcpConnection mtc; 144 public: 145 /// 146 this(Address addr, SpecRules sr=null) 147 { 148 mtc = new MasterTcpConnection(addr); 149 super(new TCP(mtc, sr)); 150 } 151 152 /// 153 inout(TcpSocket) socket() inout @property { return mtc.socket; } 154 155 ~this() { mtc.socket.close(); } 156 } 157 158 version (unittest) 159 { 160 //version = modbus_verbose; 161 162 import std.stdio; 163 import std.datetime.stopwatch; 164 import std.range; 165 166 class ConT1 : Connection 167 { 168 string name; 169 ubyte[]* wbuf; 170 ubyte[]* rbuf; 171 this(string name, ref ubyte[] wbuf, ref ubyte[] rbuf) 172 { 173 this.name = name; 174 this.wbuf = &wbuf; 175 this.rbuf = &rbuf; 176 } 177 override: 178 size_t write(const(void)[] msg) 179 { 180 (*wbuf) = cast(ubyte[])(msg.dup); 181 version (modbus_verbose) 182 stderr.writefln("%s write %s", name, (*wbuf)); 183 return msg.length; 184 } 185 186 void[] read(void[] buffer) 187 { 188 auto ub = cast(ubyte[])buffer; 189 size_t i; 190 version (modbus_verbose) 191 stderr.writefln("%s read %s", name, (*rbuf)); 192 for (i=0; i < ub.length; i++) 193 { 194 if ((*rbuf).empty) 195 return buffer[0..i]; 196 ub[i] = (*rbuf).front; 197 (*rbuf).popFront; 198 } 199 return buffer[0..i]; 200 } 201 } 202 203 class ConT2 : ConT1 204 { 205 import std.random; 206 207 this(string name, ref ubyte[] wbuf, ref ubyte[] rbuf) 208 { super(name, wbuf, rbuf); } 209 210 void slp(Duration d) 211 { 212 import core.thread; 213 auto dt = StopWatch(AutoStart.yes); 214 import std.conv : to; 215 while (dt.peek.to!Duration < d) Fiber.yield(); 216 } 217 218 override: 219 size_t write(const(void)[] msg) 220 { 221 auto l = uniform!"[]"(0, msg.length); 222 (*wbuf) ~= cast(ubyte[])(msg[0..l].dup); 223 slp(uniform(1, 5).usecs); 224 version (modbus_verbose) 225 stderr.writefln("%s write %s", name, (*wbuf)); 226 return l; 227 } 228 229 void[] read(void[] buffer) 230 { 231 auto l = uniform!"[]"(0, (*rbuf).length); 232 auto ub = cast(ubyte[])buffer; 233 size_t i; 234 version (modbus_verbose) 235 stderr.writefln("%s read %s", name, (*rbuf)); 236 for (i=0; i < ub.length; i++) 237 { 238 if (i > l) return buffer[0..i]; 239 slp(uniform(1, 5).msecs); 240 if ((*rbuf).empty) 241 return buffer[0..i]; 242 ub[i] = (*rbuf).front; 243 (*rbuf).popFront; 244 } 245 return buffer[0..i]; 246 } 247 } 248 249 void testFunc(CT)() 250 { 251 ubyte[] chA, chB; 252 253 auto conA = new CT("A", chA, chB); 254 auto conB = new CT("B", chB, chA); 255 256 auto sr = new BasicSpecRules; 257 auto mm = new ModbusMaster(new RTU(conA, sr)); 258 mm.writeTimeout = 100.msecs; 259 mm.readTimeout = 200.msecs; 260 261 auto ms = new class ModbusSlave 262 { 263 ushort[] table; 264 this() 265 { 266 super(1, new RTU(conB, sr)); 267 writeTimeout = 100.msecs; 268 table = [123, 234, 345, 456, 567, 678, 789, 890, 901]; 269 func[FuncCode.readHoldingRegisters] = (Message m) 270 { 271 enum us = ushort.sizeof; 272 auto start = be.unpackT!ushort(m.data[0..us]); 273 auto count = be.unpackT!ushort(m.data[us..us*2]); 274 version (modbus_verbose) 275 { 276 import std.stdio; 277 stderr.writeln("count check fails: ", count == 0 || count > 125); 278 stderr.writeln("start check fails: ", start >= table.length); 279 } 280 if (count == 0 || count > 125) return illegalDataValue; 281 if (start >= table.length) return illegalDataAddress; 282 if (start+count >= table.length) return illegalDataAddress; 283 284 return packResult(cast(ubyte)(count*2), 285 table[start..start+count]); 286 }; 287 } 288 }; 289 290 import core.thread; 291 292 auto f1 = new Fiber( 293 { 294 bool thrown; 295 try mm.readHoldingRegisters(1, 3, 100); 296 catch (FunctionErrorException e) 297 { 298 thrown = true; 299 assert(e.code == FunctionErrorCode.ILLEGAL_DATA_ADDRESS); 300 } 301 assert (thrown); 302 303 thrown = false; 304 try mm.readHoldingRegisters(1, 200, 2); 305 catch (FunctionErrorException e) 306 { 307 thrown = true; 308 assert(e.code == FunctionErrorCode.ILLEGAL_DATA_ADDRESS); 309 } 310 assert (thrown); 311 312 thrown = false; 313 try mm.readInputRegisters(1, 200, 2); 314 catch (FunctionErrorException e) 315 { 316 thrown = true; 317 assert(e.code == FunctionErrorCode.ILLEGAL_FUNCTION); 318 } 319 assert (thrown); 320 321 auto data = mm.readHoldingRegisters(1, 2, 3); 322 assert (data == [345, 456, 567]); 323 }); 324 325 auto f2 = new Fiber({ 326 while (true) 327 { 328 ms.iterate(); 329 import std.conv; 330 auto dt = StopWatch(AutoStart.yes); 331 while (dt.peek.to!Duration < 1.msecs) 332 Fiber.yield(); 333 } 334 }); 335 while (true) 336 { 337 if (f1.state == f1.state.TERM) break; 338 f1.call(); 339 f2.call(); 340 } 341 } 342 } 343 344 unittest 345 { 346 testFunc!ConT1(); 347 testFunc!ConT2(); 348 }