1 ///
2 module modbus.backend.rtu;
3 
4 import modbus.backend.base;
5 
6 ///
7 class RTU : BaseBackend!256
8 {
9 protected:
10     enum lengthOfCRC = 2;
11 
12 public:
13     ///
14     this(Connection c, SpecRules s=null) { super(c, s, lengthOfCRC, 0); }
15 
16 override:
17     ///
18     void start(ulong dev, ubyte func) { appendDF(dev, func); }
19 
20     ///
21     void send()
22     {
23         scope (exit) idx = 0;
24         append(cast(const(void)[])(crc16(buffer[0..idx])[]));
25         conn.write(buffer[0..idx]);
26         version (modbusverbose)
27             .trace("write bytes: ", buffer[0..idx]);
28     }
29 
30     ///
31     Response read(size_t expectedBytes)
32     {
33         auto res = baseRead(expectedBytes);
34         auto spack = devOffset+sr.deviceTypeSize+functionTypeSize;
35         // errors can have noise before crc
36         if ((cast(ubyte[])res.data)[devOffset+sr.deviceTypeSize] >= 0x80)
37             res.data = res.data[0..spack+ubyte.sizeof+lengthOfCRC];
38 
39         if (!checkCRC(res.data)) throw checkCRCException(res.dev, res.fnc);
40         res.data = res.data[spack..$-lengthOfCRC];
41         return res;
42     }
43 }
44 
45 unittest
46 {
47     import std.algorithm;
48     import std.bitmanip;
49 
50     void[] buf;
51 
52     auto rtu = new RTU(new class Connection
53     { override:
54         void write(const(void)[] t) { buf = t.dup; }
55         void[] read(void[] buffer)
56         {
57             assert(buffer.length <= buf.length);
58             buffer[0..buf.length] = buf[];
59             return buffer[0..buf.length];
60         }
61     });
62 
63     enum C1 = ushort(10100);
64     enum C2 = ushort(12345);
65     rtu.start(1, 6);
66     rtu.append(C1);
67     rtu.append(C2);
68     assert(!rtu.messageComplite);
69     assert(rtu.tempBuffer.length == 2 + 2 + 2);
70     rtu.send();
71     assert(rtu.messageComplite);
72     assert(rtu.tempBuffer.length == 0);
73     assert(equal(cast(ubyte[])buf[0..$-2],
74                 cast(ubyte[])[1, 6] ~ nativeToBigEndian(C1) ~ nativeToBigEndian(C2)));
75 
76     auto crc = cast(ubyte[])crc16(buf[0..$-2]);
77     assert(crc[0] == (cast(ubyte[])buf)[$-2]);
78     assert(crc[1] == (cast(ubyte[])buf)[$-1]);
79 }
80 
81 /++ Check CRC16 of data
82     Params:
83     data = last two bytes used as CRC16
84  +/
85 bool checkCRC(const(void)[] data) pure nothrow @trusted @nogc
86 {
87     auto msg = cast(const(ubyte[]))data;
88     auto a = msg[$-2..$];
89     auto b = crc16(msg[0..$-2]);
90     return a[0] == b[0] && a[1] == b[1];
91 }
92 
93 @safe unittest
94 {
95     immutable ubyte[] d1 = [0x02, 0x03, 0x00, 0x00, 0x00, 0x05, 0x85, 0xFA];
96     immutable ubyte[] d2 = [0x01, 0x04, 0x02, 0xFF, 0xFF, 0xB8, 0x80];
97     immutable ubyte[] d3 = [0x01, 0x04, 0x02, 0xFF, 0xFF, 0xB8, 0x00]; // invalid frame
98 
99     assert(d1.checkCRC);
100     assert(d2.checkCRC);
101     assert(!d3.checkCRC);
102 }
103 
104 /++ Calculate CRC16
105     Params:
106     data = input data for calculation CRC16
107  +/
108 ubyte[2] crc16(const(void)[] data) pure nothrow @trusted @nogc
109 {
110 
111     ubyte hi = 0xFF;
112     ubyte lo = 0xFF;
113 
114     size_t idx;
115 
116     foreach (val; cast(ubyte[])data)
117     {
118         idx = lo ^ val;
119         lo = hi ^ tblHi[idx];
120         hi = tblLo[idx];
121     }
122 
123     return [lo, hi];
124 }
125 
126 @safe unittest
127 {
128     immutable ubyte[] data = [0x02, 0x03, 0x00, 0x00, 0x00, 0x05];
129     assert(crc16(data)[1] == 0xFA);
130     assert(crc16(data)[0] == 0x85);
131 }
132 
133 private:
134 enum ubyte[256] tblHi = [
135     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
136     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
137     0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
138     0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
139     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
140     0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
141     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
142     0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
143     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
144     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
145     0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
146     0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
147     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
148     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
149     0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
150     0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
151     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
152     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
153     0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
154     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
155     0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
156     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
157     0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
158     0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
159     0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
160     0x80, 0x41, 0x00, 0xC1, 0x81, 0x40];
161 
162 enum ubyte[256] tblLo = [
163     0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
164     0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
165     0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
166     0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
167     0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
168     0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
169     0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
170     0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
171     0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
172     0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
173     0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
174     0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
175     0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
176     0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
177     0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
178     0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
179     0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
180     0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
181     0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
182     0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
183     0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
184     0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
185     0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
186     0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
187     0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
188     0x43, 0x83, 0x41, 0x81, 0x80, 0x40];