1 /**
2  * Utility functions to convert DM
3  */
4 module dolina.util;
5 version (unittest) {
6    import unit_threaded;
7 }
8 
9 import std.bitmanip : read;
10 import std.system : Endian; // for Endian
11 import std.range;
12 
13 /**
14  * Bytes per each DM.
15  *
16  * A DM is a $(Dushort), so each DM has 2 bytes.
17  */
18 enum BYTES_PER_DM = 2;
19 
20 /**
21  * Converts an array of type T into an ubyte array.
22  *
23  * Omron stores data in LittleEndian format.
24  *
25  * Params:  input = array of type T to convert
26  *
27  * Returns: An array of ubyte
28  */
29 ubyte[] toBytes(T)(T[] input) {
30    import std.array : appender;
31    import std.bitmanip : append;
32 
33    auto buffer = appender!(const(ubyte)[])();
34    foreach (dm; input) {
35       buffer.append!(T, Endian.littleEndian)(dm);
36    }
37    return buffer.data.dup;
38 }
39 ///
40 unittest {
41    [0x8034].toBytes!ushort().shouldEqual([0x34, 0x80]);
42 
43    ushort[] buf = [0x8034, 0x2010];
44    buf.toBytes!ushort().shouldEqual([0x34, 0x80, 0x10, 0x20]);
45    buf.length.shouldEqual(2);
46 
47    [0x8034].toBytes!uint().shouldEqual([0x34, 0x80, 0, 0]);
48    [0x010464].toBytes!uint().shouldEqual([0x64, 0x04, 1, 0]);
49 }
50 
51 /**
52  * Converts an array of bytes into DM (ushort) array.
53  *
54  * Params:  bytes = array to convert
55  *
56  * Returns: DM rapresentation
57  */
58 ushort[] toDM(ubyte[] bytes) {
59    ushort[] dm;
60    while (bytes.length >= BYTES_PER_DM) {
61       dm ~= bytes.read!(ushort, Endian.littleEndian);
62    }
63    if (bytes.length > 0) {
64       dm ~= bytes[0];
65    }
66    return dm;
67 }
68 
69 ///
70 unittest {
71    [0x10].toDM().shouldEqual([0x10]);
72    [0, 0xAB].toDM().shouldEqual([0xAB00]);
73    [0x20, 0x0].toDM().shouldEqual([0x20]);
74    [0x10, 0x20, 0x30, 0x40, 0x50].toDM().shouldEqual([0x2010, 0x4030, 0x50]);
75 }
76 
77 /**
78  * Takes an array of DM $(D ushort) and converts the first $(D T.sizeof / 2)
79  * DM to $(D T).
80  * The array is **not** consumed.
81  *
82  * Params:
83  *  T = The integral type to convert the first `T.sizeof / 2` words to.
84  *  words = The array of DM to convert
85  */
86 T peekDM(T)(ushort[] words) {
87    return peekDM!T(words, 0);
88 }
89 ///
90 unittest {
91    ushort[] words = [0x645A, 0x3ffb];
92    words.peekDM!float.shouldEqual(1.964F);
93    words.length.shouldEqual(2);
94 
95    ushort[] odd = [0x645A, 0x3ffb, 0xffaa];
96    odd.peekDM!float.shouldEqual(1.964F);
97    odd.length.shouldEqual(3);
98 }
99 
100 /**
101  * Takes an array of DM ($(D ushort)) and converts the first $(D T.sizeof / 2)
102  * DM to $(D T) starting from index *index*.
103  *
104  * The array is **not** consumed.
105  *
106  * Params:
107  * T = The integral type to convert the first `T.sizeof / 2` DM to.
108  * words = The array of DM to convert
109  * index = The index to start reading from (instead of starting at the front).
110  */
111 T peekDM(T)(ushort[] words, size_t index) {
112    import std.bitmanip : peek;
113 
114    ubyte[] buffer = toBytes(words);
115    return buffer.peek!(T, Endian.littleEndian)(index * BYTES_PER_DM);
116 }
117 ///
118 unittest {
119    [0x645A, 0x3ffb].peekDM!float(0).shouldEqual(1.964F);
120    [0, 0, 0x645A, 0x3ffb].peekDM!float(2).shouldEqual(1.964F);
121    [0, 0, 0x645A, 0x3ffb].peekDM!float(0).shouldEqual(0);
122    [0x80, 0, 0].peekDM!ushort(0).shouldEqual(128);
123    [0xFFFF].peekDM!short(0).shouldEqual(-1);
124    [0xFFFF].peekDM!ushort(0).shouldEqual(65_535);
125    [0xFFF7].peekDM!ushort(0).shouldEqual(65_527);
126    [0xFFF7].peekDM!short(0).shouldEqual(-9);
127    [0xFFFB].peekDM!short(0).shouldEqual(-5);
128    [0xFFFB].peekDM!ushort(0).shouldEqual(65_531);
129    [0x8000].peekDM!short(0).shouldEqual(-32_768);
130 }
131 
132 /**
133  * Converts ushort value into BDC format
134  *
135  * Params:
136  *  dec = ushort in decimal format
137  *
138  * Returns: BCD value
139  */
140 ushort toBCD(ushort dec) {
141    enum ushort MAX_VALUE = 9999;
142    enum ushort MIN_VALUE = 0;
143    if ((dec > MAX_VALUE) || (dec < MIN_VALUE)) {
144       throw new Exception("Decimal out of range (should be 0..9999)");
145    } else {
146       ushort bcd;
147       enum ushort NUM_BASE = 10;
148       ushort i;
149       for (; dec > 0; dec /= NUM_BASE) {
150          ushort rem = cast(ushort)(dec % NUM_BASE);
151          bcd += cast(ushort)(rem << 4 * i++);
152       }
153       return bcd;
154    }
155 }
156 ///
157 unittest {
158    0.toBCD().shouldEqual(0);
159    10.toBCD().shouldEqual(0x10);
160    34.toBCD().shouldEqual(52);
161    127.toBCD().shouldEqual(0x127);
162    110.toBCD().shouldEqual(0x110);
163    9999.toBCD().shouldEqual(0x9999);
164    9999.toBCD().shouldEqual(39_321);
165 }
166 
167 /**
168  * Converts BCD value into decimal format
169  *
170  * Params:
171  *  dec = ushort in BCD format
172  *
173  * Returns: decimal value
174  */
175 ushort fromBCD(ushort bcd) {
176    enum int NO_OF_DIGITS = 8;
177    enum ushort MAX_VALUE = 0x9999;
178    enum ushort MIN_VALUE = 0;
179    if ((bcd > MAX_VALUE) || (bcd < MIN_VALUE)) {
180       throw new Exception("BCD out of range (should be 0..39321)");
181    } else {
182       ushort dec;
183       ushort weight = 1;
184       foreach (j; 0 .. NO_OF_DIGITS) {
185          dec += cast(ushort)((bcd & 0x0F) * weight);
186          bcd = cast(ushort)(bcd >> 4);
187          weight *= 10;
188       }
189       return dec;
190    }
191 }
192 ///
193 unittest {
194    0.fromBCD().shouldEqual(0);
195 
196    (0x22).fromBCD().shouldEqual(22);
197    (34).fromBCD().shouldEqual(22);
198    // 17bcd
199    (0b0001_0111).fromBCD().shouldEqual(17);
200    295.fromBCD().shouldEqual(127);
201    39_321.fromBCD().shouldEqual(9_999);
202    (0x9999).fromBCD().shouldEqual(9_999);
203 }
204 
205 /**
206  * Takes an input range of DM ($(D ushort)) and converts the first $(D T.sizeof / 2)
207  * DM's to $(D T).
208  * The array is consumed.
209  *
210  * Params:
211  * T = The integral type to convert the first `T.sizeof / 2` DM to.
212  * input = The input range of DM to convert
213  */
214 T pop(T, R)(ref R input) if ((isInputRange!R) && is(ElementType!R : const ushort)) {
215    import std.traits : isIntegral, isSigned;
216 
217    static if (isIntegral!T) {
218       return popInteger!(R, T.sizeof / 2, isSigned!T)(input);
219    } else static if (is(T == float)) {
220       return uint2float(popInteger!(R, 2, false)(input));
221    } else static if (is(T == double)) {
222       return ulong2double(popInteger!(R, 4, false)(input));
223    } else {
224       static assert(false, "Unsupported type " ~ T.stringof);
225    }
226 }
227 
228 /**
229  * $(D pop!float) example
230  */
231 unittest {
232    ushort[] asPeek = [0x645A, 0x3ffb];
233    asPeek.pop!float.shouldEqual(1.964F);
234    asPeek.length.shouldEqual(0);
235 
236    // float.sizeOf is 4bytes => 2 DM
237    ushort[] input = [0x1eb8, 0xc19d];
238    input.length.shouldEqual(2);
239    pop!float(input).shouldEqual(-19.64F);
240    input.length.shouldEqual(0);
241 
242    input = [0x0, 0xBF00, 0x0, 0x3F00];
243    input.pop!float.shouldEqual(-0.5F);
244    pop!float(input).shouldEqual(0.5F);
245 }
246 
247 /**
248  * $(D pop!double) examples.
249  *
250  * A double has size 8 bytes => 4DM
251  */
252 unittest {
253    ushort[] input = [0x0, 0x0, 0x0, 0x3FE0];
254    input.length.shouldEqual(4);
255 
256    pop!double(input).shouldEqual(0.5);
257    input.length.shouldEqual(0);
258 
259    input = [0x0, 0x0, 0x0, 0xBFE0];
260    pop!double(input).shouldEqual(-0.5);
261 
262    input = [0x00, 0x01, 0x02, 0x03];
263    input.length.shouldEqual(4);
264    pop!int(input).shouldEqual(0x10000);
265    input.length.shouldEqual(2);
266    pop!int(input).shouldEqual(0x30002);
267    input.length.shouldEqual(0);
268 }
269 
270 /**
271  * pop!ushort and short examples
272  */
273 unittest {
274    ushort[] input = [0xFFFF, 0xFFFF, 0xFFFB, 0xFFFB];
275    pop!ushort(input).shouldEqual(0xFFFF);
276    pop!short(input).shouldEqual(-1);
277    pop!ushort(input).shouldEqual(0xFFFB);
278    pop!short(input).shouldEqual(-5);
279 }
280 
281 unittest {
282    ushort[] input = [
283       0x1eb8, 0xc19d, 0x0, 0xBF00, 0x0, 0x3F00, 0x0, 0x0, 0x0, 0x3FE0, 0x0, 0x0, 0x0, 0xBFE0, 0x00, 0x01, 0x02, 0x03,
284       0xFFFF, 0xFFFF, 0xFFFB, 0xFFFB
285    ];
286 
287    pop!float(input).shouldEqual(-19.64F);
288    pop!float(input).shouldEqual(-0.5F);
289    pop!float(input).shouldEqual(0.5F);
290 
291    pop!double(input).shouldEqual(0.5);
292    pop!double(input).shouldEqual(-0.5);
293 
294    pop!int(input).shouldEqual(0x10000);
295    pop!int(input).shouldEqual(0x30002);
296 
297    pop!ushort(input).shouldEqual(0xFFFF);
298    pop!short(input).shouldEqual(-1);
299    pop!ushort(input).shouldEqual(0xFFFB);
300    pop!short(input).shouldEqual(-5);
301 }
302 
303 unittest {
304    ushort[] shortBuffer = [0x645A];
305    shortBuffer.length.shouldEqual(1);
306    shortBuffer.pop!float().shouldThrow!Exception;
307 
308    ushort[] bBuffer = [0x0001, 0x0002, 0x0003];
309    bBuffer.length.shouldEqual(3);
310 
311    bBuffer.pop!ushort.shouldEqual(1);
312    bBuffer.length.shouldEqual(2);
313 
314    bBuffer.pop!ushort.shouldEqual(2);
315    bBuffer.length.shouldEqual(1);
316 
317    bBuffer.pop!ushort.shouldEqual(3);
318    bBuffer.length.shouldEqual(0);
319 }
320 
321 /**
322  * Takes an input range of DM ($(D ushort)) and converts the first $(D numDM)
323  * DM's to $(D T).
324  * The array is consumed.
325  *
326  * Params:
327  *  R = The integral type of innput range
328  *  numDM = Number of DM to convert
329  *  wantSigned = Get signed value
330  *  input = The input range of DM to convert
331  */
332 private auto popInteger(R, int numDM, bool wantSigned)(ref R input) if ((isInputRange!R) && is(ElementType!R : const ushort)) {
333    import std.traits : Signed;
334 
335    alias T = IntegerLargerThan!(numDM);
336    T result = 0;
337 
338    foreach (i; 0 .. numDM) {
339       result |= (cast(T)(popDM(input)) << (16 * i));
340    }
341 
342    static if (wantSigned) {
343       return cast(Signed!T)result;
344    } else {
345       return result;
346    }
347 }
348 
349 unittest {
350    ushort[] input = [0x00, 0x01, 0x02, 0x03];
351    popInteger!(ushort[], 2, false)(input).shouldEqual(0x10000);
352    popInteger!(ushort[], 2, false)(input).shouldEqual(0x30002);
353    input.length.shouldEqual(0);
354    input = [0x01, 0x02, 0x03, 0x04];
355    popInteger!(ushort[], 3, false)(input).shouldEqual(0x300020001);
356 
357    input = [0x01, 0x02];
358    popInteger!(ushort[], 3, false)(input).shouldThrow!Exception;
359 
360    input = [0x00, 0x8000];
361    popInteger!(ushort[], 2, false)(input).shouldEqual(0x8000_0000);
362 
363    input = [0xFFFF, 0xFFFF];
364    popInteger!(ushort[], 2, true)(input).shouldEqual(-1);
365    input = [0xFFFF, 0xFFFF, 0xFFFB, 0xFFFB];
366    popInteger!(ushort[], 1, false)(input).shouldEqual(0xFFFF);
367    popInteger!(ushort[], 1, true)(input).shouldEqual(-1);
368    popInteger!(ushort[], 1, false)(input).shouldEqual(0xFFFB);
369    popInteger!(ushort[], 1, true)(input).shouldEqual(-5);
370 }
371 
372 private template IntegerLargerThan(int numDM) if (numDM > 0 && numDM <= 4) {
373    static if (numDM == 1) {
374       alias IntegerLargerThan = ushort;
375    } else static if (numDM == 2) {
376       alias IntegerLargerThan = uint;
377    } else {
378       alias IntegerLargerThan = ulong;
379    }
380 }
381 
382 private ushort popDM(R)(ref R input) if ((isInputRange!R) && is(ElementType!R : const ushort)) {
383    if (input.empty) {
384       throw new Exception("Expected a ushort, but found end of input");
385    }
386 
387    const(ushort) d = input.front;
388    input.popFront();
389    return d;
390 }
391 
392 /**
393  * Writes numeric type *T* into a output range of *ushort*.
394  *
395  * Params:
396  * n = The numeric type to write into output range
397  * output = The output range of DM to convert
398  *
399  * Examples:
400  * --------------------
401  * ushort[] arr;
402  * auto app = appender(arr);
403  * write!float(app, 1.0f);
404  *
405  *
406  * auto app = appender!(const(ushort)[]);
407  * app.write!ushort(5);
408  * app.data.shouldEqual([5]);
409  * --------------------
410  */
411 void write(T, R)(ref R output, T n) if (isOutputRange!(R, ushort)) {
412    import std.traits : isIntegral;
413 
414    static if (isIntegral!T) {
415       writeInteger!(R, T.sizeof / 2)(output, n);
416    } else static if (is(T == float)) {
417       writeInteger!(R, 2)(output, float2uint(n));
418    } else static if (is(T == double)) {
419       writeInteger!(R, 4)(output, double2ulong(n));
420    } else {
421       static assert(false, "Unsupported type " ~ T.stringof);
422    }
423 }
424 /**
425  * Write float and double
426  */
427 unittest {
428    ushort[] arr;
429    auto app = appender(arr);
430    write!float(app, 1.0f);
431 
432    app.write!double(2.0);
433 
434    ushort[] expected = [0, 0x3f80, 0, 0, 0, 0x4000];
435    app.data.shouldEqual(expected);
436 }
437 
438 /**
439  * Write ushort and int
440  */
441 unittest {
442    import std.array : appender;
443 
444    auto app = appender!(const(ushort)[]);
445    app.write!ushort(5);
446    app.data.shouldEqual([5]);
447 
448    app.write!float(1.964F);
449    app.data.shouldEqual([5, 0x645A, 0x3ffb]);
450 
451    app.write!uint(0x1720_8034);
452    app.data.shouldEqual([5, 0x645A, 0x3ffb, 0x8034, 0x1720]);
453 }
454 
455 private void writeInteger(R, int numDM)(ref R output, IntegerLargerThan!numDM n) if (isOutputRange!(R, ushort)) {
456    import std.traits : Unsigned;
457 
458    alias T = IntegerLargerThan!numDM;
459    auto u = cast(Unsigned!T)n;
460    foreach (i; 0 .. numDM) {
461       immutable(ushort) b = (u >> (i * 16)) & 0xFFFF;
462       output.put(b);
463    }
464 }
465 
466 private float uint2float(uint x) pure nothrow {
467    float_uint fi;
468    fi.i = x;
469    return fi.f;
470 }
471 
472 unittest {
473    // see http://gregstoll.dyndns.org/~gregstoll/floattohex/
474    uint2float(0x24369620).shouldEqual(3.959212E-17F);
475    uint2float(0x3F000000).shouldEqual(0.5F);
476    uint2float(0xBF000000).shouldEqual(-0.5F);
477    uint2float(0x0).shouldEqual(0);
478    uint2float(0x419D1EB8).shouldEqual(19.64F);
479    uint2float(0xC19D1EB8).shouldEqual(-19.64F);
480    uint2float(0x358637bd).shouldEqual(0.000001F);
481    uint2float(0xb58637bd).shouldEqual(-0.000001F);
482 }
483 
484 private uint float2uint(float x) pure nothrow {
485    float_uint fi;
486    fi.f = x;
487    return fi.i;
488 }
489 
490 unittest {
491    // see http://gregstoll.dyndns.org/~gregstoll/floattohex/
492    float2uint(3.959212E-17F).shouldEqual(0x24369620);
493    float2uint(.5F).shouldEqual(0x3F000000);
494    float2uint(-.5F).shouldEqual(0xBF000000);
495    float2uint(0x0).shouldEqual(0);
496    float2uint(19.64F).shouldEqual(0x419D1EB8);
497    float2uint(-19.64F).shouldEqual(0xC19D1EB8);
498    float2uint(0.000001F).shouldEqual(0x358637bd);
499    float2uint(-0.000001F).shouldEqual(0xb58637bd);
500 }
501 
502 // read/write 64-bits float
503 private union float_uint {
504    float f;
505    uint i;
506 }
507 
508 double ulong2double(ulong x) pure nothrow {
509    double_ulong fi;
510    fi.i = x;
511    return fi.f;
512 }
513 
514 unittest {
515    // see http://gregstoll.dyndns.org/~gregstoll/floattohex/
516    ulong2double(0x0).shouldEqual(0);
517    ulong2double(0x3fe0000000000000).shouldEqual(0.5);
518    ulong2double(0xbfe0000000000000).shouldEqual(-0.5);
519 }
520 
521 private ulong double2ulong(double x) pure nothrow {
522    double_ulong fi;
523    fi.f = x;
524    return fi.i;
525 }
526 
527 unittest {
528    // see http://gregstoll.dyndns.org/~gregstoll/floattohex/
529    double2ulong(0).shouldEqual(0);
530    double2ulong(0.5).shouldEqual(0x3fe0000000000000);
531    double2ulong(-0.5).shouldEqual(0xbfe0000000000000);
532 }
533 
534 private union double_ulong {
535    double f;
536    ulong i;
537 }