1 module modbus.backend.rtu;
2 
3 import std.experimental.logger;
4 import std.exception : enforce;
5 import std.bitmanip;
6 
7 import modbus.exception;
8 import modbus.iface;
9 import modbus.protocol;
10 
11 version (unittest) static this() { sharedLog = new NullLogger; }
12 
13 class RTU : Modbus.Backend
14 {
15     SerialPortIface com;
16     ubyte[256] buffer;
17     size_t idx;
18 
19     enum minimumMsgLength = 5;
20 
21 public:
22 
23     this(SerialPortIface spi)
24     {
25         this.com = enforce(spi, "serial port is null");
26     }
27 
28     override
29     {
30         void start(ubyte dev, ubyte func)
31         {
32             this.write(dev);
33             this.write(func);
34         }
35 
36         void append(byte v) { this.write(v); }
37         void append(ubyte v) { this.write(v); }
38         void append(short v) { this.write(v); }
39         void append(ushort v) { this.write(v); }
40         void append(int v) { this.write(v); }
41         void append(uint v) { this.write(v); };
42         void append(long v) { this.write(v); };
43         void append(ulong v) { this.write(v); };
44         void append(float v) { this.write(v); };
45         void append(double v) { this.write(v); };
46 
47         void append(const(void)[] v)
48         {
49             auto inc = v.length;
50             //                 CRC
51             enforce(inc + idx + 2 < buffer.length, "many args");
52             buffer[idx..idx+inc] = cast(ubyte[])v;
53             idx += inc;
54         };
55 
56         bool messageComplite() const @property { return idx == 0; }
57         const(void)[] tempBuffer() const @property { return buffer[0..idx]; }
58 
59         void send()
60         {
61             scope (exit) idx = 0;
62             append(cast(const(void)[])(crc16(buffer[0..idx])[]));
63             com.write(buffer[0..idx]);
64             .trace("write bytes: ", buffer[0..idx]);
65         }
66 
67         Response read(size_t expectedBytes)
68         {
69             //              dev fnc CRC
70             expectedBytes += 1 + 1 + 2;
71 
72             Response res;
73             auto tmp = com.read(buffer[]);
74 
75             .trace(" read bytes: ", cast(ubyte[])tmp);
76 
77             enforce(tmp.length >= minimumMsgLength,
78                 new ReadDataLengthException(tmp.length < 1 ? 0 : (cast(ubyte[])tmp)[0],
79                                             tmp.length < 2 ? 0 : (cast(ubyte[])tmp)[1],
80                                             expectedBytes, tmp.length));
81 
82             if (tmp.length > expectedBytes)
83             {
84                 .warningf("receive more bytes what expected (%d): %(0x%02x %)",
85                             expectedBytes, cast(ubyte[])tmp[expectedBytes..$]);
86 
87                 tmp = tmp[0..expectedBytes];
88             }
89 
90             res.dev = (cast(ubyte[])tmp)[0];
91             res.fnc = (cast(ubyte[])tmp)[1];
92 
93             enforce(checkCRC(tmp), new CheckCRCException(res.dev, res.fnc));
94 
95             res.data = tmp[2..$-2];
96 
97             return res;
98         }
99     }
100 
101 protected:
102 
103     void write(T)(T v)
104     {
105         scope (failure) idx = 0;
106         //                      CRC
107         enforce(T.sizeof + idx + 2 < buffer.length, "many args");
108         buffer[].write(v, &idx);
109     }
110 }
111 
112 unittest
113 {
114     import std.algorithm;
115 
116     void[] buf;
117 
118     auto rtu = new RTU(new class SerialPortIface
119             { override:
120                 void write(const(void)[] t) { buf = t.dup; }
121                 void[] read(void[] buffer)
122                 {
123                     assert(buffer.length <= buf.length);
124                     buffer[0..buf.length] = buf[];
125                     return buffer[0..buf.length];
126                 }
127             });
128 
129     enum C1 = ushort(10100);
130     enum C2 = ushort(12345);
131     rtu.start(1, 6);
132     rtu.append(C1);
133     rtu.append(C2);
134     assert(!rtu.messageComplite);
135     assert(rtu.tempBuffer.length == 2 + 2 + 2);
136     rtu.send();
137     assert(rtu.messageComplite);
138     assert(rtu.tempBuffer.length == 0);
139     assert(equal(cast(ubyte[])buf[0..$-2],
140                 cast(ubyte[])[1, 6] ~ nativeToBigEndian(C1) ~ nativeToBigEndian(C2)));
141 
142     auto crc = cast(ubyte[])crc16(buf[0..$-2]);
143     assert(crc[0] == (cast(ubyte[])buf)[$-2]);
144     assert(crc[1] == (cast(ubyte[])buf)[$-1]);
145 }
146 
147 bool checkCRC(const(void)[] data)
148 {
149     auto msg = cast(const(ubyte[]))data;
150     auto a = msg[$-2..$];
151     auto b = crc16(msg[0..$-2]);
152     return a[0] == b[0] && a[1] == b[1];
153 }
154 
155 unittest
156 {
157     immutable ubyte[] data = [0x02, 0x03, 0x00, 0x00, 0x00, 0x05, 0x85, 0xFA];
158     assert(checkCRC(data));
159 }
160 
161 ubyte[2] crc16(const(void)[] data) pure nothrow @trusted
162 {
163 
164     ubyte hi = 0xFF;
165     ubyte lo = 0xFF;
166 
167     size_t idx;
168 
169     foreach (val; cast(ubyte[])data)
170     {
171         idx = lo ^ val;
172         lo = hi ^ tblHi[idx];
173         hi = tblLo[idx];
174     }
175 
176     return [lo, hi];
177 }
178 
179 unittest
180 {
181     immutable ubyte[] data = [0x02, 0x03, 0x00, 0x00, 0x00, 0x05];
182     assert(crc16(data)[1] == 0xFA);
183     assert(crc16(data)[0] == 0x85);
184 }
185 
186 private:
187 enum ubyte[] tblHi = [
188     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
189     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
190     0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
191     0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
192     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
193     0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
194     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
195     0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
196     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
197     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
198     0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
199     0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
200     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
201     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
202     0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
203     0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
204     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
205     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
206     0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
207     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
208     0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
209     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
210     0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
211     0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
212     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
213     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40];
214 
215 enum ubyte[] tblLo = [
216     0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
217     0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
218     0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
219     0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
220     0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
221     0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
222     0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
223     0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
224     0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
225     0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
226     0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
227     0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
228     0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
229     0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
230     0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
231     0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
232     0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
233     0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
234     0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
235     0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
236     0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
237     0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
238     0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
239     0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
240     0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
241     0x43, 0x83, 0x41, 0x81, 0x80, 0x40];