1 ///
2 module serialport.config;
3 
4 import std.range : split;
5 import std.string : format, toLower;
6 import std.exception : enforce, assertThrown, assertNotThrown;
7 import std.conv : to;
8 
9 import serialport.types;
10 import serialport.exception;
11 
12 ///
13 struct SPConfig
14 {
15     ///
16     uint baudRate=9600;
17     ///
18     DataBits dataBits=DataBits.data8;
19     ///
20     Parity parity=Parity.none;
21     ///
22     StopBits stopBits=StopBits.one;
23 
24     ///
25     bool hardwareDisableFlowControl = true;
26 
27     /++ Set parity value
28         Returns: this
29         +/
30     ref SPConfig set(Parity v) @nogc { parity = v; return this; }
31 
32     /++ Set baudrate value
33         Returns: this
34         +/
35     ref SPConfig set(uint v) @nogc { baudRate = v; return this; }
36 
37     /++ Set data bits value
38         Returns: this
39         +/
40     ref SPConfig set(DataBits v) @nogc { dataBits = v; return this; }
41 
42     /++ Set stop bits value
43         Returns: this
44         +/
45     ref SPConfig set(StopBits v) @nogc { stopBits = v; return this; }
46 
47     /++ Use mode string for setting baudrate, data bits, parity and stop bits.
48 
49         Format: "B:DPS"
50         where:
51             B is baud rate
52             D is data bits (5, 6, 7, 8)
53             P is parity ('N' or 'n' -- none,
54                             'E' or 'e' -- even,
55                             'O' or 'o' -- odd)
56             S is stop bits ('1', '1.5', '2')
57 
58         You can skip baudrate.
59 
60         example mode strings: "9600:8N1" ":8n1" "7o1.5" "2400:6e2"
61 
62         Throws:
63             ParseModeException if mode string is badly formatted or using bad values
64         +/
65     ref SPConfig set(string mode)
66     {
67         alias PME = ParseModeException;
68 
69         auto errstr = "error mode '%s'".format(mode);
70         enforce(mode.length >= 3, new PME(errstr ~ ": too short"));
71 
72         auto vals = mode.split(modeSplitChar);
73 
74         if (vals.length == 0) return this;
75 
76         if (vals.length > 2)
77             throw new PME(errstr ~ ": many parts");
78 
79         if (vals.length == 2)
80         {
81             if (vals[0].length)
82             {
83                 try baudRate = vals[0].to!uint;
84                 catch (Exception e)
85                     throw new PME(errstr ~
86                             ": baud rate parse error: " ~ e.msg);
87             }
88             mode = vals[1];
89         }
90         else mode = vals[0];
91 
92         auto db = cast(int)mode[0] - cast(int)'0';
93         if (db >= 5 && db <= 8) dataBits = cast(DataBits)db;
94         else throw new PME(errstr ~ ": unsupported data bits '" ~ mode[0] ~ "'");
95 
96         auto p = mode[1..2].toLower;
97         if (p == "n" || p == "o" || p == "e")
98         {
99             parity = ["n": Parity.none,
100                         "o": Parity.odd,
101                         "e": Parity.even][p];
102         }
103         else throw new PME(errstr ~ ": unsupported parity '" ~ p ~ "'");
104 
105         auto sb = mode[2..$];
106         if (sb == "1" || sb == "1.5" || sb == "2")
107         {
108             stopBits = ["1": StopBits.one,
109                         "1.5": StopBits.onePointFive,
110                         "2": StopBits.two][sb];
111         }
112         else throw new PME(errstr ~ ": unsupported stop bits '" ~ sb ~ "'");
113 
114         return this;
115     }
116 
117     ///
118     unittest
119     {
120         SPConfig c;
121         c.set("2400:7e1.5");
122         assertNotThrown(c.set(c.mode));
123         assert(c.baudRate == 2400);
124         assert(c.dataBits == DataBits.data7);
125         assert(c.parity == Parity.even);
126         assert(c.stopBits == StopBits.onePointFive);
127         c.set("8N1");
128         assertNotThrown(c.set(c.mode));
129         assert(c.baudRate == 2400);
130         assert(c.dataBits == DataBits.data8);
131         assert(c.parity == Parity.none);
132         assert(c.stopBits == StopBits.one);
133         c.set("320:5o2");
134         assertNotThrown(c.set(c.mode));
135         assert(c.baudRate == 320);
136         assert(c.dataBits == DataBits.data5);
137         assert(c.parity == Parity.odd);
138         assert(c.stopBits == StopBits.two);
139 
140         alias PME = ParseModeException;
141         assertThrown!PME(c.set("4o2"));
142         assertThrown!PME(c.set("5x2"));
143         assertThrown!PME(c.set("8e3"));
144         assertNotThrown!PME(c.set(":8N1"));
145         assertNotThrown(c.set(c.mode));
146     }
147 
148     /++ Construct config, parse mode to it and return.
149 
150         Returns: new config
151 
152         See_Also: set(string mode)
153         +/
154     static SPConfig parse(string mode)
155     {
156         SPConfig ret;
157         ret.set(mode);
158         return ret;
159     }
160 
161     /++ Build mode string.
162 
163         Can be used for parsing.
164 
165         Returns: mode string
166 
167         See_Also: parse, set(string mode)
168         +/
169     string mode() const @property
170     {
171         return "%s:%s%s%s".format(
172             baudRate,
173             dataBits.to!int,
174             [Parity.none: "n",
175              Parity.odd:  "o",
176              Parity.even: "e"][parity],
177             [StopBits.one: "1",
178              StopBits.onePointFive: "1.5",
179              StopBits.two: "2"
180             ][stopBits]
181         );
182     }
183 }
184 
185 unittest
186 {
187     SPConfig a, b;
188     a.set("2400:7e2");
189     b.set(a.mode);
190     assert(a == b);
191     a.set(Parity.none).set(DataBits.data8).set(19200).set(StopBits.one);
192     assert(a.parity == Parity.none);
193     assert(a.dataBits == DataBits.data8);
194     assert(a.baudRate == 19200);
195     assert(a.stopBits == StopBits.one);
196 }
197 
198 unittest
199 {
200     SPConfig a;
201     assertThrown!ParseModeException(a.set("2400:7e2:32"));
202     assertThrown!ParseModeException(a.set("24a0:7e2"));
203 }