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