1 module modbus.protocol;
2 
3 import std.exception : enforce;
4 import std.string;
5 import std.bitmanip;
6 import std.traits : isArray, Unqual, isNumeric;
7 import std.range : ElementType;
8 import std.experimental.logger;
9 
10 public import modbus.exception;
11 
12 import modbus.func;
13 
14 class Modbus
15 {
16 protected:
17     Backend be;
18 
19 public:
20 
21     static interface Backend
22     {
23         void start(ubyte dev, ubyte func);
24 
25         void append(byte);
26         void append(ubyte);
27         void append(short);
28         void append(ushort);
29         void append(int);
30         void append(uint);
31         void append(long);
32         void append(ulong);
33         void append(float);
34         void append(double);
35         void append(const(void)[]);
36 
37         bool messageComplite() const @property;
38         const(void)[] tempBuffer() const @property;
39 
40         void send();
41 
42         static struct Response
43         {
44             ubyte dev, fnc;
45             const(void)[] data;
46         }
47 
48         Response read(size_t expectedBytes);
49     }
50 
51     invariant
52     {
53         if (be !is null)
54             assert(be.messageComplite);
55     }
56 
57     this(Backend be) { this.be = enforce(be, "backend is null"); }
58 
59     bool needCheckCRC = true;
60 
61     void write(Args...)(ubyte dev, ubyte func, Args args)
62     {
63         be.start(dev, func);
64 
65         void _append(T)(T val)
66         {
67             static if (isArray!T)
68             {
69                 static if (is(Unqual!(ElementType!T) == void))
70                     be.append(val);
71                 else foreach (e; val) _append(e);
72             }
73             else
74             {
75                 static if (is(T == struct))
76                     foreach (name; __traits(allMembers, T))
77                         _append(__traits(getMember, val, name));
78                 else static if (isNumeric!T) be.append(val);
79                 else static assert(0, "unsupported type " ~ T.stringof);
80             }
81         }
82 
83         foreach (arg; args) _append(arg);
84 
85         be.send();
86     }
87 
88     // result in big endian
89     const(void)[] read(size_t bytes, ubyte dev, ubyte fnc)
90     {
91         auto res = be.read(bytes);
92 
93         if (res.dev != dev)
94             .warningf("receive from unexpected device %d (expect %d)",
95                              res.dev, dev);
96 
97         enforce(res.fnc == fnc,
98             new FunctionErrorException(dev, fnc, res.fnc, (cast(ubyte[])res.data)[0]));
99 
100         enforce(res.data.length == bytes,
101             new ReadDataLengthException(dev, fnc, bytes, res.data.length));
102 
103         return res.data;
104     }
105 
106     const(BitArray) readCoils(ubyte dev, ushort start, ushort cnt)
107     {
108         enforce(cnt <= 2000, "very big count");
109         this.write(dev, 1, start, cnt);
110 
111         return const(BitArray)(cast(void[])this.read(1+(cnt+7)/8, dev, 1)[1..$], cnt);
112     }
113 
114     const(BitArray) readDiscreteInputs(ubyte dev, ushort start, ushort cnt)
115     {
116         enforce(cnt <= 2000, "very big count");
117         this.write(dev, 2, start, cnt);
118         return const(BitArray)(cast(void[])this.read(1+(cnt+7)/8, dev, 2)[1..$], cnt);
119     }
120 
121     ushort[] readHoldingRegisters(ubyte dev, ushort start, ushort cnt)
122     {
123         enforce(cnt <= 125, "very big count");
124         this.write(dev, 3, start, cnt);
125         auto res = this.read(1+cnt*2, dev, 3);
126         return bigEndianToNativeArr(cast(ushort[])res[1..$]);
127     }
128 
129     ushort[] readInputRegisters(ubyte dev, ushort start, ushort cnt)
130     {
131         enforce(cnt <= 125, "very big count");
132         this.write(dev, 4, start, cnt);
133         auto res = this.read(1+cnt*2, dev, 4);
134         return bigEndianToNativeArr(cast(ushort[])res[1..$]);
135     }
136 
137     void writeSingleCoil(ubyte dev, ushort addr, bool val)
138     {
139         this.write(dev, 5, addr, cast(ushort)(val ? 0xff00: 0x0000));
140         this.read(4, dev, 5);
141     }
142 
143     void writeSingleRegister(ubyte dev, ushort addr, ushort value)
144     {
145         this.write(dev, 6, addr, value);
146         this.read(4, dev, 6);
147     }
148 
149     void writeMultipleRegisters(ubyte dev, ushort addr, ushort[] values)
150     {
151         enforce(values.length <= 125, "very big count");
152         this.write(dev, 16, addr, cast(ushort)values.length,
153                     cast(byte)(values.length*2), values);
154         this.read(4, dev, 16);
155     }
156 }