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 }