1 /// 2 module modbus.protocol; 3 4 import std.bitmanip : BitArray; 5 version (modbus_verbose) 6 import std.experimental.logger; 7 8 public import modbus.exception; 9 10 import modbus.func; 11 12 /// 13 class Modbus 14 { 15 protected: 16 Backend be; 17 18 int fiber_mutex; 19 static struct FSync 20 { 21 int* mutex; 22 this(Modbus m) 23 { 24 mutex = &(m.fiber_mutex); 25 if (*mutex != 0) m.yield(); 26 *mutex = 1; 27 } 28 29 ~this() { *mutex = 0; } 30 } 31 32 void yield() 33 { 34 import core.thread; 35 if (yieldFunc != null) yieldFunc(); 36 else if (Fiber.getThis !is null) 37 Fiber.yield(); 38 else 39 { 40 // unspecific state, if vars must 41 // changes it can be not changed 42 version (modbus_verbose) 43 .warning("Thread.yield can block execution"); 44 Thread.yield(); 45 } 46 } 47 48 void delegate() yieldFunc; 49 50 public: 51 52 /// 53 static interface Backend 54 { 55 /// start building message 56 void start(ulong dev, ubyte func); 57 58 /// append data to message buffer 59 void append(byte); 60 /// ditto 61 void append(short); 62 /// ditto 63 void append(int); 64 /// ditto 65 void append(long); 66 /// ditto 67 void append(float); 68 /// ditto 69 void append(double); 70 /// ditto 71 void append(const(void)[]); 72 73 /// 74 bool messageComplite() const @property; 75 76 /// temp message buffer 77 const(void)[] tempBuffer() const @property; 78 79 /// send and clear temp message 80 void send(); 81 82 /// Readed modbus response 83 static struct Response 84 { 85 /// device number 86 ulong dev; 87 /// function number 88 ubyte fnc; 89 90 /// data without any changes 91 const(void)[] data; 92 } 93 94 /++ Read data to temp message buffer 95 Params: 96 expectedBytes = count of bytes in data section of message, 97 exclude device address, function number, CRC and etc 98 +/ 99 Response read(size_t expectedBytes); 100 } 101 102 /++ 103 Params: 104 be = Backend 105 yieldFunc = needs if used in fiber-based code (vibe for example) 106 +/ 107 this(Backend be, void delegate() yieldFunc=null) 108 { 109 if (be is null) 110 throw modbusException("backend is null"); 111 this.be = be; 112 this.yieldFunc = yieldFunc; 113 } 114 115 // fiber unsafe write 116 private void fusWrite(Args...)(ulong dev, ubyte fnc, Args args) 117 { 118 import std.range : ElementType; 119 import std.traits : isArray, isNumeric, Unqual; 120 import std.exception : enforce; 121 122 enforce(be.messageComplite, "uncomplite meassage"); 123 124 be.start(dev, fnc); 125 126 void _append(T)(T val) 127 { 128 static if (isArray!T) 129 { 130 static if (is(Unqual!(ElementType!T) == void)) 131 be.append(val); 132 else foreach (e; val) _append(e); 133 } 134 else 135 { 136 static if (is(T == struct)) 137 foreach (name; __traits(allMembers, T)) 138 _append(__traits(getMember, val, name)); 139 else static if (isNumeric!T) be.append(val); 140 else static assert(0, "unsupported type " ~ T.stringof); 141 } 142 } 143 144 foreach (arg; args) _append(arg); 145 146 be.send(); 147 } 148 149 /++ Write to serial port 150 151 fiber-safe 152 153 Params: 154 dev = modbus device address (number) 155 fnc = function number 156 args = writed data in native endian 157 +/ 158 void write(Args...)(ulong dev, ubyte fnc, Args args) 159 { 160 auto fsync = FSync(this); 161 fusWrite(dev, fnc, args); 162 } 163 164 void flush() 165 { 166 import serialport; 167 try 168 { 169 auto res = be.read(240); 170 version (modbus_verbose) 171 .info("flush ", cast(ubyte[])(res.data)); 172 } 173 catch (TimeoutException e) 174 version (modbus_verbose) 175 .trace("flust timeout"); 176 } 177 178 // fiber unsafe read 179 private const(void)[] fusRead(ulong dev, ubyte fnc, size_t bytes) 180 { 181 import std.exception : enforce; 182 enforce(be.messageComplite, "uncomplite meassage"); 183 184 auto res = be.read(bytes); 185 186 version (modbus_verbose) 187 if (res.dev != dev) 188 .warningf("receive from unexpected device %d (expect %d)", 189 res.dev, dev); 190 191 if (res.fnc != fnc) 192 throw functionErrorException(dev, fnc, res.fnc, (cast(ubyte[])res.data)[0]); 193 194 if (res.data.length != bytes) 195 throw readDataLengthException(dev, fnc, bytes, res.data.length); 196 197 return res.data; 198 } 199 200 /++ Read from serial port 201 202 fiber-safe 203 204 Params: 205 dev = modbus device address (number) 206 fnc = function number 207 bytes = expected response length in bytes 208 209 Returns: 210 result in big endian 211 +/ 212 const(void)[] read(ulong dev, ubyte fnc, size_t bytes) 213 { 214 auto fsync = FSync(this); 215 return fusRead(dev, fnc, bytes); 216 } 217 218 // fiber unsafe request 219 private const(void)[] fusRequest(Args...)(ulong dev, ubyte fnc, size_t bytes, Args args) 220 { 221 fusWrite(dev, fnc, args); 222 return fusRead(dev, fnc, bytes); 223 } 224 225 /++ Write and read to modbus 226 227 fiber-safe 228 229 Params: 230 dev = slave device number 231 fnc = called function number 232 bytes = expected bytes for reading 233 args = sending data 234 Returns: 235 result in big endian 236 +/ 237 const(void)[] request(Args...)(ulong dev, ubyte fnc, size_t bytes, Args args) 238 { 239 auto fsync = FSync(this); 240 return fusRequest(dev, fnc, bytes, args); 241 } 242 243 /// function number 0x1 (1) 244 const(BitArray) readCoils(ulong dev, ushort start, ushort cnt) 245 { 246 if (cnt >= 2000) throw modbusException("very big count"); 247 auto fsync = FSync(this); 248 return const(BitArray)(cast(void[])fusRequest(dev, 1, 1+(cnt+7)/8, start, cnt)[1..$], cnt); 249 } 250 251 /// function number 0x2 (2) 252 const(BitArray) readDiscreteInputs(ulong dev, ushort start, ushort cnt) 253 { 254 if (cnt >= 2000) throw modbusException("very big count"); 255 auto fsync = FSync(this); 256 return const(BitArray)(cast(void[])fusRequest(dev, 2, 1+(cnt+7)/8, start, cnt)[1..$], cnt); 257 } 258 259 /++ function number 0x3 (3) 260 Returns: data in native endian 261 +/ 262 const(ushort)[] readHoldingRegisters(ulong dev, ushort start, ushort cnt) 263 { 264 if (cnt >= 125) throw modbusException("very big count"); 265 auto fsync = FSync(this); 266 return bigEndianToNativeArr(cast(ushort[])fusRequest(dev, 3, 1+cnt*2, start, cnt)[1..$]); 267 } 268 269 /++ function number 0x4 (4) 270 Returns: data in native endian 271 +/ 272 const(ushort)[] readInputRegisters(ulong dev, ushort start, ushort cnt) 273 { 274 if (cnt >= 125) throw modbusException("very big count"); 275 auto fsync = FSync(this); 276 return bigEndianToNativeArr(cast(ushort[])fusRequest(dev, 4, 1+cnt*2, start, cnt)[1..$]); 277 } 278 279 /// function number 0x5 (5) 280 void writeSingleCoil(ulong dev, ushort addr, bool val) 281 { request(dev, 5, 4, addr, cast(ushort)(val ? 0xff00 : 0x0000)); } 282 283 /// function number 0x6 (6) 284 void writeSingleRegister(ulong dev, ushort addr, ushort value) 285 { request(dev, 6, 4, addr, value); } 286 287 /// function number 0x10 (16) 288 void writeMultipleRegisters(ulong dev, ushort addr, const(ushort)[] values) 289 { 290 if (values.length >= 125) throw modbusException("very big count"); 291 request(dev, 16, 4, addr, cast(ushort)values.length, 292 cast(byte)(values.length*2), values); 293 } 294 }