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 }