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 }