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