1 /// 2 module modbus.backend.base; 3 4 version (modbus_verbose) 5 public import std.experimental.logger; 6 7 import std.traits; 8 9 import modbus.types; 10 public import modbus.exception; 11 public import modbus.connection; 12 public import modbus.backend.specrules; 13 14 /// Message builder and parser 15 interface Backend 16 { 17 /// Returns abstract connection 18 Connection connection() @property; 19 20 /++ Full build message for sending 21 22 work on preallocated buffer 23 24 Returns: 25 slice of preallocated buffer with message 26 +/ 27 final void[] buildMessage(Args...)(void[] buffer, ulong dev, ubyte fnc, Args args) 28 { 29 size_t idx = 0; 30 startMessage(buffer, idx, dev, fnc); 31 void _append(T)(T val) 32 { 33 static if (isArray!T) 34 { 35 import std.range : ElementType; 36 static if (is(Unqual!(ElementType!T) == void)) 37 appendBytes(buffer, idx, val); 38 else foreach (e; val) _append(e); 39 } 40 else 41 { 42 static if (is(T == struct)) 43 foreach (v; val.tupleof) _append(v); 44 else static if (isNumeric!T) append(buffer, idx, val); 45 else static assert(0, "unsupported type " ~ T.stringof); 46 } 47 } 48 foreach (arg; args) _append(arg); 49 completeMessage(buffer, idx); 50 return buffer[0..idx]; 51 } 52 53 /// 54 enum ParseResult 55 { 56 success, /// 57 errorMsg, /// error message (fnc >= 0x80) 58 uncomplete, /// 59 checkFail /// for RTU check CRC fail 60 } 61 62 /++ Read data to temp message buffer 63 Params: 64 data = parsing data buffer, CRC and etc 65 result = reference to result message 66 Retruns: 67 parse result 68 +/ 69 ParseResult parseMessage(const(void)[] data, ref Message result); 70 71 size_t aduLength(size_t dataBytes); 72 73 final 74 { 75 size_t minMsgLength() @property { return aduLength(1); } 76 77 const(void)[] packT(T)(T value) { return sr.packT(value); } 78 T unpackT(T)(const(void)[] data) { return sr.unpackT!T(data); } 79 T unpackTT(T)(T value) { return sr.unpackT!T(cast(void[])[value]); } 80 } 81 82 protected: 83 84 SpecRules sr() @property; 85 86 /// start building message 87 void startMessage(void[] buf, ref size_t idx, ulong dev, ubyte func); 88 89 /// append data to message buffer 90 final void append(T)(void[] buf, ref size_t idx, T val) 91 if (isNumeric!T && !is(T == real)) 92 { 93 union cst { T value; void[T.sizeof] data; } 94 appendBytes(buf, idx, sr.pack(cst(val).data[])); 95 } 96 /// ditto 97 void appendBytes(void[] buf, ref size_t idx, const(void)[]); 98 /// finalize message 99 void completeMessage(void[] buf, ref size_t idx); 100 } 101 102 /++ Basic functionality of Backend 103 +/ 104 abstract class BaseBackend : Backend 105 { 106 protected: 107 enum functionTypeSize = 1; 108 SpecRules specRules; 109 110 immutable size_t devOffset; 111 immutable size_t serviceData; 112 113 override SpecRules sr() @property { return specRules; } 114 115 Connection con; 116 117 public: 118 119 /++ 120 Params: 121 s = rules for pack N-byte data to sending package 122 serviceData = size of CRC for RTU, protocol id for TCP etc 123 deviceOffset = offset of device number (address) in message 124 +/ 125 this(Connection con, SpecRules s, size_t serviceData, size_t deviceOffset) 126 { 127 import std.exception : enforce; 128 this.con = enforce(con, "connection is null"); 129 this.specRules = s !is null ? s : new BasicSpecRules; 130 this.serviceData = serviceData; 131 this.devOffset = deviceOffset; 132 } 133 134 override 135 { 136 Connection connection() @property { return con; } 137 138 ParseResult parseMessage(const(void)[] data, ref Message msg) 139 { 140 if (data.length < startDataSplit+1+endDataSplit) 141 return ParseResult.uncomplete; 142 if (auto err = sr.peekDF(data[devOffset..$], msg.dev, msg.fnc)) 143 return ParseResult.uncomplete; 144 auto ret = ParseResult.success; 145 if (msg.fnc >= 0x80) 146 { 147 data = data[0..startDataSplit+1+endDataSplit]; 148 ret = ParseResult.errorMsg; 149 } 150 msg.data = data[startDataSplit..$-endDataSplit]; 151 if (!check(data)) return ParseResult.checkFail; 152 return ret; 153 } 154 155 size_t aduLength(size_t dataBytes) 156 { 157 return serviceData + 158 sr.deviceTypeSize + 159 functionTypeSize + 160 dataBytes; 161 } 162 } 163 164 protected: 165 166 override 167 { 168 void appendBytes(void[] buf, ref size_t idx, const(void)[] v) 169 { 170 auto inc = v.length; 171 if (idx + inc + serviceData >= buf.length) 172 throw modbusException("many args"); 173 buf[idx..idx+inc] = v[]; 174 idx += inc; 175 version (modbus_verbose) 176 .trace("append msg buffer data: ", buf[0..idx]); 177 } 178 } 179 180 abstract 181 { 182 void startMessage(void[] buf, ref size_t idx, ulong dev, ubyte func); 183 void completeMessage(void[] buf, ref size_t idx); 184 185 bool check(const(void)[] data); 186 size_t endDataSplit() @property; 187 } 188 189 size_t startDataSplit() @property 190 { return devOffset + sr.deviceTypeSize + functionTypeSize; } 191 192 void appendDF(void[] buf, ref size_t idx, ulong dev, ubyte fnc) 193 { appendBytes(buf, idx, sr.packDF(dev, fnc)); } 194 }