1 /**
2  * Utility functions to convert PLC words
3  *
4  *	Copyright: © 2016-2026 Orfeo Da Vià.
5  *	License: Boost Software License - Version 1.0 - August 17th, 2003
6  *	Authors: Orfeo Da Vià
7  */
8 module dfins.util;
9 
10 import std.range;
11 import std.system : Endian;
12 
13 /**
14  * Converts ushort value into BDC format
15  *
16  * Params:
17  *  dec = ushort in decimal format
18  *
19  * Returns: BCD value
20  */
21 ushort toBCD(ushort dec) {
22    enum ushort MAX_VALUE = 9999;
23    enum ushort MIN_VALUE = 0;
24    if ((dec > MAX_VALUE) || (dec < MIN_VALUE)) {
25       throw new Exception("Decimal out of range (should be 0..9999)");
26    } else {
27       ushort bcd;
28       enum ushort NUM_BASE = 10;
29       ushort i;
30       for (; dec > 0; dec /= NUM_BASE) {
31          ushort rem = cast(ushort)(dec % NUM_BASE);
32          bcd += cast(ushort)(rem << 4 * i++);
33       }
34       return bcd;
35    }
36 }
37 
38 ///
39 unittest {
40    assert(0.toBCD() == 0);
41    assert(10.toBCD() == 0x10);
42    assert(34.toBCD() == 52);
43    assert(127.toBCD() == 0x127);
44    assert(110.toBCD() == 0x110);
45    assert(9999.toBCD() == 0x9999);
46    assert(9999.toBCD() == 39_321);
47 }
48 
49 /**
50  * Converts BCD value into decimal format
51  *
52  * Params:
53  *  bcd = ushort in BCD format
54  *
55  * Returns: decimal value
56  */
57 ushort fromBCD(ushort bcd) {
58    enum int NO_OF_DIGITS = 8;
59    enum ushort MAX_VALUE = 0x9999;
60    enum ushort MIN_VALUE = 0;
61    if ((bcd > MAX_VALUE) || (bcd < MIN_VALUE)) {
62       throw new Exception("BCD out of range (should be 0..39321)");
63    } else {
64       ushort dec;
65       ushort weight = 1;
66       foreach (j; 0 .. NO_OF_DIGITS) {
67          dec += cast(ushort)((bcd & 0x0F) * weight);
68          bcd = cast(ushort)(bcd >> 4);
69          weight *= 10;
70       }
71       return dec;
72    }
73 }
74 ///
75 unittest {
76    assert(0.fromBCD() == 0);
77    assert((0x22).fromBCD() == 22);
78    assert((34).fromBCD() == 22);
79    // 17bcd
80    assert((0b0001_0111).fromBCD() == 17);
81    assert(295.fromBCD() == 127);
82    assert(39_321.fromBCD() == 9_999);
83    assert((0x9999).fromBCD() == 9_999);
84 }
85 
86 /**
87  * Takes an input range of ubyte and converts the first $(D T.sizeof)
88  * words to $(D T).
89  * The array is consumed.
90  *
91  * Params:
92  *  T = The type to convert the first `T.sizeof` bytes
93  *  input = The input range of ubyte to convert
94  */
95 T readFins(T, R)(ref R input) if ((isInputRange!R) && is(ElementType!R : const ubyte)) {
96    import std.bitmanip : read, bigEndianToNative;
97    import std.algorithm.mutation : swapAt;
98 
99    static if (is(T == int) || is(T == uint) || is(T == float)) {
100       ubyte[T.sizeof] bytes;
101       foreach (ref e; bytes) {
102          e = input.front;
103          input.popFront();
104       }
105       bytes.swapAt(0, 2);
106       bytes.swapAt(1, 3);
107       return bigEndianToNative!T(bytes);
108    } else static if (is(T == double)) {
109       static assert(false, "Unsupported type " ~ T.stringof);
110    } else {
111       return input.read!(T);
112    }
113 }
114 
115 unittest {
116    import std.bitmanip : read;
117    import std.math : approxEqual;
118 
119    // dfmt off
120    ubyte[] buf = [
121       0x0, 0x10,
122       0xF5, 0xC3, 0x40, 0x48, // 3.14
123       0x1E, 0xB8, 0x41, 0x9D, // 19.64
124       0xF5, 0xC3, 0x40, 0x48, // 1_078_523_331
125       0xF5, 0xC3, 0x40, 0x48, // 1_078_523_331
126       0x0A, 0x3D, 0xBF, 0xB7,
127       0x0C, 0x0D, 0x0A, 0xB // 0x0a0b0c0d
128    ];
129    // dfmt on
130    assert(buf.readFins!ushort == 0x10);
131    assert(approxEqual(buf.readFins!float, 3.14));
132    assert(approxEqual(buf.readFins!float, 19.64));
133    assert(buf.readFins!uint == 1_078_523_331);
134    assert(buf.readFins!int == 1_078_523_331);
135    assert(buf.readFins!int == -1_078_523_331);
136    assert(buf.readFins!uint == 0x0a0b0c0d);
137 }
138 
139 /**
140  * Converts the given value from the native endianness to Fins format and
141  * returns it as a `ubyte[n]` where `n` is the size of the given type.
142  */
143 ubyte[] nativeToFins(T)(T val) pure nothrow {
144    import std.bitmanip : nativeToBigEndian;
145    import std.algorithm.mutation : swapAt;
146 
147    static if (is(T == int) || is(T == uint) || is(T == float)) {
148       ubyte[] bytes = nativeToBigEndian!T(val).dup;
149       bytes.swapAt(0, 2);
150       bytes.swapAt(1, 3);
151       return bytes;
152    } else static if (is(T == string)) {
153       ubyte[] blob = cast(ubyte[])val;
154       if (blob.length & 1) {
155          blob ~= 0x0;
156       }
157       return blob.swapByteOrder!2;
158    } else static if (is(T == double)) {
159       static assert(false, "Unsupported type " ~ T.stringof);
160    } else {
161       return nativeToBigEndian!T(val);
162    }
163 }
164 
165 unittest {
166    import std.algorithm.comparison : equal;
167 
168    ubyte[] abcFins = [0x42, 0x41, 0x44, 0x43, 0x46, 0x45, 0x48, 0x47, 0x0, 0x49];
169    ubyte[] abc = nativeToFins!string("ABCDEFGHI");
170    assert(equal(abc, abcFins));
171    assert(equal(nativeToFins!float(3.14), [0xF5, 0xC3, 0x40, 0x48]));
172    //0x0a0b0c0d = 168_496_141
173    assert(equal(nativeToFins!uint(0x0a0b0c0d), [0x0C, 0x0D, 0x0A, 0xB]));
174 }
175 
176 @("PC2PLC")
177 unittest {
178    import std.bitmanip : nativeToBigEndian;
179 
180    assert(nativeToBigEndian!uint(0x8034) == [0, 0, 0x80, 0x34]);
181    assert(nativeToBigEndian!uint(0x010464) == [0x0, 0x01, 0x04, 0x64]);
182 
183    //0x4048F5C3
184    assert(nativeToBigEndian!float(3.14) == [0x40, 0x48, 0xF5, 0xC3]);
185    //0x419D1EB8
186    assert(nativeToBigEndian!float(19.64) == [0x41, 0x9D, 0x1E, 0xB8]);
187 }
188 
189 @("PLC2PC")
190 unittest {
191    import std.bitmanip : read;
192    import std.math : approxEqual;
193 
194    // dfmt off
195    ubyte[] buf = [
196       0x0, 0x10,
197       0x40, 0x48, 0xF5, 0xC3, // 3.14
198       0x41, 0x9D, 0x1E, 0xB8, // 19.64
199       0xF5, 0xC3, 0x40, 0x48,
200    ];
201    // dfmt on
202    //assert(buf.peek!ushort == 0x10);
203    assert(buf.read!ushort == 0x10);
204    assert(approxEqual(buf.read!float, 3.14));
205    assert(approxEqual(buf.read!float, 19.64));
206    //assert(approxEqual(buf.read!float, 3.14));
207 }
208 
209 /**
210  * Swap byte order of items in an array.
211  *
212  * Params:
213  *  L = Lenght
214  *  array = Buffer with values to fix byte order of.
215  */
216 ubyte[] swapByteOrder(int L = 4)(ubyte[] data) @trusted pure nothrow if (L == 2 || L == 4 || L == 0) {
217    import std.algorithm.mutation : swapAt;
218 
219    ubyte[] array = data.dup;
220 
221    size_t ptr;
222    static if (L == 2) {
223       while (ptr < array.length - 1) {
224          array.swapAt(ptr, ptr + 1);
225          ptr += 2;
226       }
227    } else static if (L == 4) {
228       while (ptr < array.length - 3) {
229          array.swapAt(ptr + 0, ptr + 2);
230          array.swapAt(ptr + 1, ptr + 3);
231          ptr += 4;
232       }
233    }
234    return array;
235 }
236 
237 unittest {
238    import std.algorithm.comparison : equal;
239 
240    ubyte[] a = [0xF5, 0xC3, 0x40, 0x48, 0xFF];
241 
242    assert(a.swapByteOrder.equal([0x40, 0x48, 0xF5, 0xc3, 0xFF]));
243    assert(a.swapByteOrder!(2).equal([0xc3, 0xF5, 0x48, 0x40, 0xFF]));
244 }