1 /** 2 * This is the module which contains the Object-Oriented D wrapper. 3 */ 4 module blink1; 5 6 import core.thread; 7 8 import std.algorithm; 9 import std.datetime; 10 import std.exception; 11 import std..string; 12 13 import blink1.clib; 14 15 /** 16 * General exception with a BLink1 device. 17 * 18 * Usually thrown when an IO error occurs with the USB device, 19 * which is usually caused by an user unplugging the blink(1) device. 20 */ 21 class Blink1Exception : Exception { 22 mixin basicExceptionCtors; 23 } 24 /** 25 * Thrown when no Blink1 could be found. 26 */ 27 class Blink1NotFoundException : Blink1Exception { 28 mixin basicExceptionCtors; 29 } 30 31 /** 32 * Thrown when a device does not support the requested method. 33 * 34 * This exception is for example thrown when the [Blink1Device.readNote] and [Blink1Device.writeNote] 35 * functions are called on MK1 and MK2 devices, which do not support the note feature. 36 */ 37 class UnsupportedOperationException : Blink1Exception { 38 mixin basicExceptionCtors; 39 } 40 41 /** 42 * Enum containing the different models of the blink(1) devices out there. 43 * 44 * Simple alias for the type found within the C library. 45 * 46 * See_Also: blink1.clib.blink1Type_t 47 */ 48 alias Blink1Type = blink1Type_t; 49 50 alias RGB = rgb_t; 51 52 /** 53 * Represents the LED on the device. 54 */ 55 enum LED { 56 /// Special value: All LEDS 57 ALL = 0, 58 /// First LED 59 ONE = 1, 60 /// Second LED. 61 TWO = 2 62 } 63 64 /// Describes the boot mode of the MK2 and later devices. 65 enum BootMode { 66 /// Play the stored pattern if only power is connected, otherwise play nothing. 67 NORMAL = 0, 68 /// Play the stored pattern 69 PLAY = 1, 70 /// Do not play anything 71 OFF = 2 72 } 73 74 /** 75 * Argument for controlling play behaviour. 76 */ 77 enum PlayMode { 78 STOP = 0, /// Stop playing the pattern 79 PLAY = 1 /// Start playing the pattern. 80 } 81 82 /// Represents the boot parameters 83 struct StartupParameters { 84 BootMode bootMode; 85 /// From which index the device should start playing. 86 ubyte playStart; 87 /// Until which index the device should play. 88 ubyte playEnd; 89 /// The amount of times it should play. 0 = infinite. 90 ubyte playCount; 91 }; 92 93 /** 94 * Represents a pattern line. 95 */ 96 struct PatternLine { 97 /// The color of this pattern. 98 RGB color; 99 /// Time in to transition towards this pattern. 100 Duration duration; 101 /// The led. 102 LED led; 103 } 104 105 /// Playing state of the blink(1) LED 106 struct PlayState { 107 /// Wether the blink(1) is playing or not. 108 PlayMode playMode; 109 /// Starting position of the pattern. 110 ubyte playStart; 111 /// The pattern position to stop playing at 112 ubyte playEnd; 113 /// Amount of times the pattern should be played. 0 = infinite 114 ubyte playCount; 115 /// Current playing position 116 ubyte playPosition; 117 } 118 119 /** 120 * Represents a physical device. 121 * 122 * To get a reference to a device, one should call either [open], [openByPath], [openBySerial] or 123 * [openByIndex]. After being done with the device, please call [close] to release OS resources. 124 */ 125 class Blink1Device { 126 127 128 /** 129 * The maximum amount of notes that this device may hold. 130 */ 131 // These are not static since they may change with new hardware revisions or firmware versions. 132 public immutable maxNotes = 10; 133 public immutable maxNoteSize = blink1_note_size; 134 135 /// Our "handle" to the blink led. 136 protected blink1_device *m_device; 137 /// The index of our device within the blink1 library's cache. 138 protected int m_cachedIndex; 139 /// The device type of our device; 140 private immutable Blink1Type m_type; 141 // Keep the firmware version around 142 private int m_fwVersion; 143 private immutable ubyte m_maxPatterns; 144 private Duration m_defaultDur = dur!"msecs"(500); 145 private bool m_blocking = false; 146 147 private Duration m_serverDownTimeout; 148 private bool m_serverDownStayLit; 149 private ubyte m_serverDownStartPosition; 150 private ubyte m_serverDownEndPosition; 151 152 private this(blink1_device *device) { 153 this.m_device = device; 154 m_cachedIndex = blink1_getCacheIndexByDev(device); 155 m_type = blink1_deviceTypeById(m_cachedIndex); 156 157 if(m_type == Blink1Type.BLINK1_MK1) { 158 m_maxPatterns = 12; 159 } else if (m_type == Blink1Type.BLINK1_MK2 || m_type == Blink1Type.BLINK1_MK3) { 160 m_maxPatterns = 32; 161 } else { 162 m_maxPatterns = 0; 163 } 164 m_fwVersion = blink1_getVersion(m_device); 165 } 166 167 /** 168 * Opens the first found default Blink1Device. No guarantees are given which one is found first. 169 * 170 * Throws: a Blink1NotFoundException if no device could be found. 171 */ 172 @trusted 173 public static Blink1Device open() { 174 blink1_device *dev = blink1_open(); 175 enforce!Blink1NotFoundException(dev != null, "Could not find default Blink1Device"); 176 return new Blink1Device(dev); 177 } 178 179 /** 180 * Obtains a Blink1 device by the OS-specific path. 181 * 182 * Params: 183 * path = The OS-specific path to the USB device. 184 * 185 * Throws: a Blink1NotFoundException if no device could be found with the given path. 186 */ 187 @trusted 188 public static Blink1Device openByPath(string path) { 189 blink1_device *dev = blink1_openByPath(path.toStringz()); 190 enforce!Blink1NotFoundException(dev != null, "Could not find Blink1Device with path %s".format(path)); 191 return new Blink1Device(dev); 192 } 193 194 /** 195 * Opens the device by the given serial number. 196 * 197 * Params: 198 * serial = The serial number of the device. 199 * Throws: a Blink1NotFoundException if no device could be found with the given serial number. 200 */ 201 @trusted 202 public static Blink1Device openBySerial(string serial) { 203 blink1_device *dev = blink1_openBySerial(serial.toStringz()); 204 enforce!Blink1NotFoundException(dev != null, "Could not find Blink1Device with serial %s".format(serial)); 205 return new Blink1Device(dev); 206 } 207 208 /** 209 * Opens the device by the given index. 210 * 211 * Params: 212 * index = The index of the device. It should range from 0 to blink1_max_devices. 213 * 214 * Throws: a Blink1NotFoundException if no device could be found with the given index; 215 * 216 * See_Also: connectedDeviceCount 217 */ 218 @trusted 219 public static Blink1Device openByIndex(uint index) { 220 blink1_device *dev = blink1_openById(index); 221 enforce!Blink1NotFoundException(dev != null, "Could not find Blink1Device with serial %u".format(index)); 222 return new Blink1Device(dev); 223 } 224 225 /** 226 * Returns the amount of detected Blink1 devices. 227 */ 228 @trusted 229 public static int connectedDeviceCount() { 230 return blink1_enumerate(); 231 } 232 233 /** 234 * Closes this device. 235 */ 236 @trusted 237 public void close() { 238 blink1_close_internal(m_device); 239 } 240 241 /** 242 * Returns the firmware version integer. 243 * 244 * The hundreds represent the major version, while the units and tenths represent the minor version, 245 * e.g. "v3.3" = 303; 246 */ 247 @safe 248 public int getFwVersionInt() { 249 return m_fwVersion; 250 } 251 252 /** 253 * Returns the firmware version as a string in the form of v{major}.{minor} 254 */ 255 @safe 256 public string getFwVersion() { 257 int fwVersion = getFwVersionInt(); 258 int major = fwVersion / 100; 259 int minor = fwVersion % 100; 260 return "v%d.%d".format(major, minor); 261 } 262 263 /** 264 * Fades the color to the specified value with the given duration. 265 * 266 * Params: 267 * r = red component of color (0 <= r <= 255) 268 * g = green component of color (0 <= g <= 255) 269 * b = blue component of color (0 <= b <= 255) 270 * dur = duration of the fade animation. Maximum duration is 65,355 milliseconds. Defaults to `defaultDuration` 271 * led = which LED to fade. Defaults to all LEDs 272 * 273 * See_Also: [defaultDuration], [setRGB], [fadeToHSV] 274 * Throws: Blink1Exception on error. 275 */ 276 @trusted 277 public void fadeToRGB(ubyte r, ubyte g, ubyte b, Duration dur = seconds(-1), LED led = LED.ALL) { 278 if (dur.isNegative) dur = this.m_defaultDur; 279 throwIfFail(blink1_fadeToRGBN(m_device, cast(short) dur.total!"msecs", r, g, b, cast(ubyte) led)); 280 if (m_blocking) Thread.sleep(dur); 281 } 282 283 /** 284 * Fades the color to the specified value with the given duration. 285 * 286 * Params: 287 * h = hue component of color (0 <= h <= 255) 288 * s = saturation component of color (0 <= s <= 255) 289 * v = value component of color (0 <= v <= 255) 290 * dur = duration of the fade animation. Maximum duration is 65,355 milliseconds. Defaults to `defaultDuration` 291 * led = which LED to fade. Defaults to all LEDs 292 * 293 * See_Also: [defaultDuration], [setHSV], [fadeToRGB] 294 * Throws: Blink1Exception on error. 295 */ 296 @safe 297 public void fadeToHSV(ubyte h, ubyte s, ubyte v, Duration dur = seconds(-1), LED led = LED.ALL) { 298 RGB color = fromHSV(h, s, v); 299 fadeToRGB(color.r, color.g, color.b, dur, led); 300 } 301 302 /** 303 * Immediatelty changes the color to the given value 304 * 305 * Params: 306 * r = red component of color (0 <= r <= 255) 307 * g = green component of color (0 <= g <= 255) 308 * b = blue component of color (0 <= b <= 255) 309 * 310 * See_Also: [fadeToRGB], [setHSV] 311 * Throws: Blink1Exception on error. 312 */ 313 @trusted 314 public void setRGB(ubyte r, ubyte g, ubyte b) { 315 throwIfFail(blink1_setRGB(m_device, r, g, b)); 316 } 317 318 /** 319 * Immediatelty changes the color to the given value 320 * 321 * Params: 322 * h = hue component of color (0 <= h <= 255) 323 * s = saturation component of color (0 <= s <= 255) 324 * v = value component of color (0 <= v <= 255) 325 * 326 * See_Also: [fadeToRGB], [setRGB] 327 * Throws: Blink1Exception on error. 328 */ 329 @safe 330 public void setHSV(ubyte h, ubyte s, ubyte v) { 331 RGB color = fromHSV(h, s, v); 332 setRGB(color.r, color.g, color.b); 333 } 334 335 /** 336 * Enables the blink1's dead man's trigger. This will play the pattern on the LED when `pokeServerDown` hasn't 337 * been called after `timeout` has been passed. 338 * 339 * Params: 340 * timeout = when to start blinking. 341 * stayLit = if the LED should stay on after playing the pattern. 342 * startPosition = The starting position within the pattern. Default: 0 343 * endPostion = The ending position of the pattern. 344 */ 345 @trusted 346 public void enableServerDown(Duration timeout, bool stayLit, ubyte startPosition = 0, ubyte endPosition = 255) { 347 if (endPosition == 255) endPosition = m_maxPatterns; 348 this.m_serverDownTimeout = timeout; 349 this.m_serverDownStayLit = stayLit; 350 this.m_serverDownStartPosition = startPosition; 351 this.m_serverDownEndPosition = endPosition; 352 throwIfFail(blink1_serverdown(m_device, 1, cast(short) timeout.total!"msecs", cast(ubyte) stayLit, 353 startPosition, endPosition)); 354 } 355 356 /** 357 * Notifies the LED the "server" is still running, so it won't turn on. 358 * 359 * See_Also: enableServerDown 360 */ 361 @trusted 362 public void pokeServerDown() { 363 throwIfFail(blink1_serverdown(m_device, cast(ubyte) 1, cast(ushort) this.m_serverDownTimeout.total!"msecs", 364 cast(ubyte) this.m_serverDownStayLit, this.m_serverDownStartPosition, 365 this.m_serverDownEndPosition)); 366 } 367 368 /** 369 * Disables the serverDown mode. 370 * 371 * See_Also: enableServerDown 372 */ 373 @trusted 374 public void disableServerDown() { 375 throwIfFail(blink1_serverdown(m_device, 0, 0, 0, 0, 0)); 376 } 377 378 /** 379 * Plays a stored pattern. The end and count parameters are only supported on MK2 and later 380 * devices. 381 * 382 * Params: 383 * start = The pattern position to start playing from. 384 * end = The final pattern position to stop playing. 385 * count = The amount of times to loop this pattern. 0 = infinite. 386 * See_Also: [stop] 387 * Throws: Blink1Exception if error 388 */ 389 @trusted 390 public void play(ubyte start = 0, ubyte end = 255, ubyte count = 0) { 391 throwIfFail(blink1_playloop(m_device, cast(ubyte) PlayMode.PLAY, start, end, count)); 392 } 393 394 395 /// Ditto 396 @trusted 397 public void play(ubyte start = 0) { 398 throwIfFail(blink1_play(m_device, cast(ubyte) PlayMode.PLAY, start)); 399 } 400 401 /** 402 * Stop playing whatever pattern the device is playing. 403 * See_Also: [play] 404 */ 405 @trusted 406 public void stop() { 407 throwIfFail(blink1_play(m_device, cast(ubyte) PlayMode.STOP, 255)); 408 } 409 410 /** 411 * Read the play state. Supported on MK2 devices and later. 412 * 413 * Throws: Blink1Exception if error 414 */ 415 @trusted 416 public PlayState readPlayState() { 417 PlayState state; 418 throwIfFail(blink1_readPlayState(m_device, cast(ubyte*) &state.playMode, &state.playStart, 419 &state.playEnd, &state.playCount, &state.playPosition)); 420 return state; 421 } 422 423 /** 424 * Reads the stored pattern line at position `pos`. 425 * 426 * Params: 427 * pos = The position of the pattern stored on the device, starting from 0 428 * Returns: The stored PatternLine 429 * Throws: Blink1Exception if an error occurs. 430 */ 431 @trusted 432 public PatternLine readPatternLine(ubyte pos) { 433 PatternLine result; 434 ushort fadeMillis; 435 if (getFwVersionInt >= 204) { 436 throwIfFail(blink1_readPatternLineN(m_device, &fadeMillis, &result.color.r, 437 &result.color.g, &result.color.b, cast(ubyte*) &result.led, pos)); 438 439 } else { 440 throwIfFail(blink1_readPatternLine(m_device, &fadeMillis, &result.color.r, 441 &result.color.g, &result.color.b, pos)); 442 } 443 result.duration = msecs(fadeMillis); 444 return result; 445 } 446 447 /** 448 * Writes a pattern line to the device. 449 * 450 * On MK1 devices this will store it in nonvolatile storage. 451 * On MK2 and later devices this will store it in RAM. Call [savePattern] to store it in 452 * nonvolatile storage. 453 * 454 * Params: 455 * pos = The position to write to, starting from zero. 456 * patternLine = The patternLine to store. 457 * Throws: Blink1Exception if an error occurs. 458 */ 459 @trusted 460 public void writePatternLine(ubyte pos, PatternLine patternLine) { 461 writePatternLineRGB(pos, patternLine.color.r, patternLine.color.g, patternLine.color.b, 462 patternLine.duration, patternLine.led); 463 } 464 465 /** 466 * Writes a pattern line to the device. 467 * 468 * On MK1 devices this will store it in nonvolatile storage. 469 * On MK2 and later devices this will store it in RAM. Call [savePattern] to store it in 470 * nonvolatile storage. 471 * 472 * Params: 473 * pos = The position to write to, starting from zero. 474 * r = red component 475 * g = green component 476 * b = blue component 477 * duration = time it takes to fade 478 * led = The LED to use. 479 * Throws: Blink1Exception if an error occurs. 480 */ 481 @trusted 482 public void writePatternLineRGB(ubyte pos, ubyte r, ubyte g, ubyte b, Duration duration = seconds(01), 483 LED led = LED.ALL) { 484 if (duration.isNegative) duration = m_defaultDur; 485 486 if (getFwVersionInt >= 204) { 487 throwIfFail(blink1_setLEDN(m_device, cast(ubyte) led)); 488 } 489 throwIfFail(blink1_writePatternLine(m_device, cast(ushort) duration.total!"msecs", 490 r, g, b, pos)); 491 } 492 493 /** 494 * Writes a pattern line to the device. 495 * 496 * On MK1 devices this will store it in nonvolatile storage. 497 * On MK2 and later devices this will store it in RAM. Call [savePattern] to store it in 498 * nonvolatile storage. 499 * 500 * Params: 501 * pos = The position to write to, starting from zero. 502 * h = hue component 503 * s = saturation component 504 * v = value component 505 * duration = time it takes to fade 506 * led = The LED to use. 507 * Throws: Blink1Exception if an error occurs. 508 */ 509 @trusted 510 public void writePatternLineHSV(ubyte pos, ubyte h, ubyte s, ubyte v, Duration duration = seconds(01), 511 LED led = LED.ALL) { 512 RGB color = fromHSV(h, s, v); 513 writePatternLineRGB(pos, color.r, color.g, color.b, duration, led); 514 } 515 516 /** 517 * Saves the pattern in the device's ram to nonvolatile memory. 518 * Only works on MK2 devices with firmware 204 or later, or later devices. 519 * 520 * Throws: Blink1Exception if error 521 */ 522 @trusted 523 public void savePattern() { 524 throwIfFail(blink1_savePattern(m_device)); 525 } 526 527 /** 528 * Reads the startup parameters of the device. 529 * 530 * Throws: Blink1Exception if error 531 */ 532 @trusted 533 public StartupParameters getStartupParameters() { 534 StartupParameters params; 535 throwIfFail(blink1_getStartupParams(m_device, cast(ubyte*) ¶ms.bootMode, 536 ¶ms.playStart, ¶ms.playEnd, ¶ms.playCount)); 537 return params; 538 } 539 540 541 542 /** 543 * Sets the startup parameters 544 * 545 * Params: 546 * bootMode = the boot mode 547 * playStart = From which stored pattern line to start playing from 548 * playEnd = To which stored pattern line to play to 549 * playCount = How many times to repeat the pattern. 0 = infinite. 550 * 551 * Throws: Blink1Exception if error 552 */ 553 @trusted 554 public void setStartupParameters(BootMode bootMode, ubyte playStart, ubyte playEnd, ubyte playCount) { 555 throwIfFail(blink1_setStartupParams(m_device, cast(ubyte) bootMode, playStart, playEnd, playCount)); 556 } 557 558 /// Ditto 559 @safe 560 public void setStartupParameters(StartupParameters params) { 561 setStartupParameters(params.bootMode, params.playStart, params.playEnd, params.playCount); 562 } 563 564 /** 565 * Reads a note from the device. 566 * 567 * Params: 568 * id = The id of the note, 0 <= id < maxNotes 569 * Returns: The read note 570 * Throws: UnsupportedOperationException if id < [maxNotes] 571 */ 572 @trusted 573 public ubyte[] readNote(ubyte id) { 574 enforce!UnsupportedOperationException(m_type == Blink1Type.BLINK1_MK3); 575 enforce!Exception(id < maxNotes, "Device supports up to %d notes, tried to read %d".format(maxNotes, id)); 576 ubyte[] noteBuffer = new ubyte[maxNoteSize]; 577 ubyte * noteBufferPtr = noteBuffer.ptr; 578 blink1_readNote(m_device, id, ¬eBufferPtr); 579 580 return noteBuffer; 581 } 582 583 /** 584 * Writes a note with the given id. 585 * 586 * Params: 587 * id = The id of the note to write to, 588 * note = The note to write. Maximum length is maxNoteSize. Longer notes will be silently 589 * truncated. 590 */ 591 @trusted 592 public void writeNote(ubyte id, immutable ubyte[] note) { 593 // blink1_writeNote will happilly write garbage, try to sanitize it 594 // by setting remaining data to zero 595 enforce!UnsupportedOperationException(m_type == Blink1Type.BLINK1_MK3); 596 enforce!Exception(id < maxNotes, "Device supports up to %d notes, tried to write %d".format(maxNotes, id)); 597 ulong length = min(maxNoteSize, note.length); 598 ubyte[maxNoteSize] realNote; 599 realNote[0..length] = note[0..length]; 600 blink1_writeNote(m_device, id, realNote.ptr); 601 } 602 603 /** 604 * Sets the default duration for fade animations if no duration parameter is given. 605 */ 606 @safe 607 @property void defaultDuration(Duration dur) { 608 this.m_defaultDur = dur; 609 } 610 611 /** 612 * Gets the default duration for fade animations if no duration parameter is given. 613 */ 614 @safe 615 @property Duration defaultDuration() { 616 return this.m_defaultDur; 617 } 618 619 /** 620 * Enables/disables blocking mode 621 * 622 * If blocking mode is enabled, calls to `fadeToRGB` will sleep the thread until the 623 * animation is finished. If blocking mode is disabled, `fadeToRGB` will immediatly return. 624 */ 625 @safe 626 @property void blocking(bool blocking) { 627 this.m_blocking = blocking; 628 } 629 630 /** 631 * Returns if blocking mode is enabled. 632 * 633 * See_Also: blocking(bool) 634 */ 635 @safe 636 @property bool blocking() { 637 return this.m_blocking; 638 } 639 640 /** 641 * Gets the device type of this device. 642 */ 643 @safe 644 @property Blink1Type type() { 645 return this.m_type; 646 } 647 648 /** 649 * The serial number of this device. 650 */ 651 @trusted 652 @property string serial() { 653 import std.conv; 654 char* str = blink1_getCachedSerial(this.m_cachedIndex); 655 return fromStringz(str).to!string; 656 } 657 658 /** 659 * The path of this device 660 */ 661 @trusted 662 @property string path() { 663 import std.conv; 664 char* str = blink1_getCachedPath(this.m_cachedIndex); 665 return fromStringz(str).to!string; 666 } 667 668 /** 669 * Returns the maximum amount of patterns this device may hold. 670 */ 671 @safe 672 @property int maxPatterns() { 673 return m_maxPatterns; 674 } 675 676 /* 677 * Private methods 678 */ 679 680 /** 681 * Throws an exception if `result < 0`. 682 * Params: 683 * result = The result code of the operation. 684 * Throws: E 685 */ 686 @trusted 687 private void throwIfFail(E = Blink1Exception)(int result) { 688 import std.conv; 689 enforce!E(result >= 0, fromStringz(blink1_error_msg(result)).to!string()); 690 } 691 692 /** 693 * Converts HSV to RGB 694 */ 695 @trusted 696 private static RGB fromHSV(ubyte h, ubyte s, ubyte v) { 697 RGB color; 698 ubyte[] tmp = [h, s, v]; 699 hsbtorgb(&color, tmp.ptr); 700 return color; 701 } 702 };