1 /**
2  * Defines channel objects layer.
3  *
4  * A channel is a message delivery mechanism that forwards a message from a
5  * sender to one receiver.
6  */
7 module dolina.channel;
8 
9 import std.exception;
10 import serial.device;
11 
12 /**
13  * Defines a basic HostLink channel
14  */
15 interface IHostLinkChannel {
16    /**
17     * Reads a message from channel
18     *
19     * Returns: message read
20     */
21    string read();
22 
23    /**
24     * Writes a message on channel
25     *
26     * Params:  message = The message being sent on the channel.
27     */
28    void write(string message);
29 }
30 
31 /**
32  * Channel based on serial RS232 communication
33  */
34 class HostLinkChannel : IHostLinkChannel {
35    private SerialPort serialPort;
36    this(SerialPort serialPort) {
37       enforce(serialPort !is null);
38       this.serialPort = serialPort;
39    }
40 
41    string read() {
42       enum START = 0x40; // @
43       enum END = 0x0D; // CR
44       bool inside;
45 
46       ubyte[1] buffer;
47       ubyte[] reply;
48       ubyte b;
49       // dfmt off
50       do {
51          immutable(size_t) length = serialPort.read(buffer);
52          if (length > 0) {
53             b = buffer[0];
54             if (b == START) {
55                inside = true;
56             }
57             if (inside) {
58                reply ~= b;
59             }
60          }
61 
62       } while (b != END);
63       // dfmt on
64       return cast(string)(reply).idup;
65    }
66 
67    void write(string message) {
68       serialPort.write(cast(void[])message);
69    }
70 }
71 
72 // https://forum.dlang.org/thread/kpvypzrhwbeizzkkamkc@forum.dlang.org
73 //if ( __traits(hasMember, S, "read"))
74 class HLChannel(S = SerialPort) : IHostLinkChannel {
75    static assert(__traits(hasMember, S, "read"));
76    static assert(__traits(hasMember, S, "write"));
77 
78    private S serialPort;
79    this(S serialPort) {
80       enforce(serialPort !is null);
81       this.serialPort = serialPort;
82    }
83 
84    string read() {
85       enum START = 0x40; // @
86       enum END = 0x0D; // CR
87       bool inside;
88 
89       ubyte[1] buffer;
90       ubyte[] reply;
91       ubyte b;
92       // dfmt off
93       do {
94          immutable(size_t) length = serialPort.read(buffer);
95          if (length > 0) {
96             b = buffer[0];
97             if (b == START) {
98                inside = true;
99             }
100             if (inside) {
101                reply ~= b;
102             }
103          }
104       } while (b != END);
105       // dfmt on
106       return cast(string)(reply).idup;
107    }
108 
109    void write(string message) {
110       serialPort.write(cast(void[])message);
111    }
112 }
113 
114 unittest {
115    class SerialMockW {
116       bool writeDone;
117       void write(const(void[]) arr) {
118          writeDone = true;
119       }
120 
121       size_t read(void[] arr) {
122          return 3;
123       }
124    }
125 
126    auto serial = new SerialMockW();
127 
128    IHostLinkChannel chan = new HLChannel!SerialMockW(serial);
129    assert(!serial.writeDone);
130    chan.write("a");
131    assert(serial.writeDone);
132 }
133 
134 unittest {
135    class SerialMockR {
136       void write(const(void[]) arr) {
137       }
138 
139       private ubyte[] buf = [0x39, 0x40, 0x41, 0x0D, 0x43, 0x44];
140       private size_t ptr;
141       size_t read(void[] arr) {
142          ubyte[] b = cast(ubyte[])arr;
143          b[0] = buf[ptr++];
144          return 1;
145       }
146    }
147 
148    auto serial = new SerialMockR();
149 
150    IHostLinkChannel chan = new HLChannel!SerialMockR(serial);
151    string msg = chan.read();
152    assert(msg.length == 3);
153    assert(msg == "@A" ~ '\r');
154 }