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 invariant 103 { 104 assert(be.messageComplite, "uncomplite message"); 105 } 106 107 /++ 108 Params: 109 be = Backend 110 yieldFunc = needs if used in fiber-based code (vibe for example) 111 +/ 112 this(Backend be, void delegate() yieldFunc=null) 113 { 114 if (be is null) 115 throw modbusException("backend is null"); 116 this.be = be; 117 this.yieldFunc = yieldFunc; 118 } 119 120 // fiber unsafe write 121 private void fusWrite(Args...)(ulong dev, ubyte fnc, Args args) 122 { 123 import std.range : ElementType; 124 import std.traits : isArray, isNumeric, Unqual; 125 126 be.start(dev, fnc); 127 128 void _append(T)(T val) 129 { 130 static if (isArray!T) 131 { 132 static if (is(Unqual!(ElementType!T) == void)) 133 be.append(val); 134 else foreach (e; val) _append(e); 135 } 136 else 137 { 138 static if (is(T == struct)) 139 foreach (name; __traits(allMembers, T)) 140 _append(__traits(getMember, val, name)); 141 else static if (isNumeric!T) be.append(val); 142 else static assert(0, "unsupported type " ~ T.stringof); 143 } 144 } 145 146 foreach (arg; args) _append(arg); 147 148 be.send(); 149 } 150 151 /++ Write to serial port 152 153 fiber-safe 154 155 Params: 156 dev = modbus device address (number) 157 fnc = function number 158 args = writed data in native endian 159 +/ 160 void write(Args...)(ulong dev, ubyte fnc, Args args) 161 { 162 auto fsync = FSync(this); 163 fusWrite(dev, fnc, args); 164 } 165 166 /++ Read from serial port 167 168 not fiber-safe 169 170 Params: 171 dev = modbus device address (number) 172 fnc = function number 173 bytes = expected response length in bytes 174 175 Returns: 176 result in big endian 177 +/ 178 const(void)[] read(ulong dev, ubyte fnc, size_t bytes) 179 { 180 auto res = be.read(bytes); 181 182 version (modbus_verbose) 183 if (res.dev != dev) 184 .warningf("receive from unexpected device %d (expect %d)", 185 res.dev, dev); 186 187 if (res.fnc != fnc) 188 throw functionErrorException(dev, fnc, res.fnc, (cast(ubyte[])res.data)[0]); 189 190 if (res.data.length != bytes) 191 throw readDataLengthException(dev, fnc, bytes, res.data.length); 192 193 return res.data; 194 } 195 196 /++ Write and read to modbus 197 198 Params: 199 dev = slave device number 200 fnc = called function number 201 bytes = expected bytes for reading 202 args = sending data 203 Returns: 204 result in big endian 205 +/ 206 const(void)[] request(Args...)(ulong dev, ubyte fnc, size_t bytes, Args args) 207 { 208 auto fsync = FSync(this); 209 this.fusWrite(dev, fnc, args); 210 return read(dev, fnc, bytes); 211 } 212 213 /// function number 0x1 (1) 214 const(BitArray) readCoils(ulong dev, ushort start, ushort cnt) 215 { 216 if (cnt >= 2000) throw modbusException("very big count"); 217 return const(BitArray)(cast(void[])request(dev, 1, 1+(cnt+7)/8, start, cnt)[1..$], cnt); 218 } 219 220 /// function number 0x2 (2) 221 const(BitArray) readDiscreteInputs(ulong dev, ushort start, ushort cnt) 222 { 223 if (cnt >= 2000) throw modbusException("very big count"); 224 return const(BitArray)(cast(void[])request(dev, 2, 1+(cnt+7)/8, start, cnt)[1..$], cnt); 225 } 226 227 /++ function number 0x3 (3) 228 Returns: data in native endian 229 +/ 230 const(ushort)[] readHoldingRegisters(ulong dev, ushort start, ushort cnt) 231 { 232 if (cnt >= 125) throw modbusException("very big count"); 233 return bigEndianToNativeArr(cast(ushort[])request(dev, 3, 1+cnt*2, start, cnt)[1..$]); 234 } 235 236 /++ function number 0x4 (4) 237 Returns: data in native endian 238 +/ 239 const(ushort)[] readInputRegisters(ulong dev, ushort start, ushort cnt) 240 { 241 if (cnt >= 125) throw modbusException("very big count"); 242 return bigEndianToNativeArr(cast(ushort[])request(dev, 4, 1+cnt*2, start, cnt)[1..$]); 243 } 244 245 /// function number 0x5 (5) 246 void writeSingleCoil(ulong dev, ushort addr, bool val) 247 { request(dev, 5, 4, addr, cast(ushort)(val ? 0xff00 : 0x0000)); } 248 249 /// function number 0x6 (6) 250 void writeSingleRegister(ulong dev, ushort addr, ushort value) 251 { request(dev, 6, 4, addr, value); } 252 253 /// function number 0x10 (16) 254 void writeMultipleRegisters(ulong dev, ushort addr, const(ushort)[] values) 255 { 256 if (values.length >= 125) throw modbusException("very big count"); 257 request(dev, 16, 4, addr, cast(ushort)values.length, 258 cast(byte)(values.length*2), values); 259 } 260 }