diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8c52ff9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fcadb2c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..552f221 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +*.log diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..68846b6 --- /dev/null +++ b/.npmignore @@ -0,0 +1,7 @@ +examples/ +test/ +.* +AUTHORS.md +CHANGES.md +CONTRIBUTING.md +README.md diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000..3849918 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,3 @@ +# Authors ordered by first contribution + +* Alasdair Mercer diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bfb44ca --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing + +If you have any questions about [Blinkt](https://github.com/NotNinja/node-blinkt) please feel free to +[raise an issue](https://github.com/NotNinja/node-blinkt/issues/new). + +Please [search existing issues](https://github.com/NotNinja/node-blinkt/issues) for the same feature and/or issue before +raising a new issue. Commenting on an existing issue is usually preferred over raising duplicate issues. + +You must have [Node.js](https://nodejs.org) installed. + +All pull requests should be made to the `develop` branch. + +Don't forget to add your details to the list of +[AUTHORS.md](https://github.com/NotNinja/node-blinkt/blob/master/AUTHORS.md) if you want your contribution to be +recognized by others. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..6cc536a --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (C) 2017 Alasdair Mercer, !ninja + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b6b7cd7 --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ + 888888b. 888 d8b 888 888 888 + 888 "88b 888 Y8P 888 888 888 + 888 .88P 888 888 888 888 + 8888888K. 888 888 88888b. 888 888 888888 888 + 888 "Y88b 888 888 888 "88b 888 .88P 888 888 + 888 888 888 888 888 888 888888K 888 Y8P + 888 d88P 888 888 888 888 888 "88b Y88b. " + 8888888P" 888 888 888 888 888 888 "Y888 888 + +[Blinkt](https://github.com/NotNinja/node-blinkt) is Node.js library that allows you to interact with your Blinkt +hardware: + +https://shop.pimoroni.com/products/blinkt + +[![Dependency Status](https://img.shields.io/david/NotNinja/node-blinkt.svg?style=flat-square)](https://david-dm.org/NotNinja/node-blinkt) +[![License](https://img.shields.io/npm/l/blinkt.svg?style=flat-square)](https://github.com/NotNinja/blinkt/blob/master/LICENSE.md) +[![Release](https://img.shields.io/npm/v/blinkt.svg?style=flat-square)](https://www.npmjs.com/package/blinkt) + +* [Install](#install) +* [API](#api) +* [Bugs](#bugs) +* [Contributors](#contributors) +* [License](#license) + +## Install + +Install using `npm`: + +``` bash +$ npm install --save blinkt +``` + +## API + +### Set A Single Pixel + +The bread and butter of Blintk! is setting pixels. You can set any of the 8 pixels on your Blinkt! to one of around 16 +million colors! + +The `brightness` argument is completely optional. Omit it to keep the last brightness value set for that particular +pixel. + +``` javascript +blinkt.setPixel(index, red, green, blue, brightness) +``` + +| Parameter | Description | Required | +| ---------- | ------------------------------------------------------------------ | -------- | +| index | The horizontal position of the pixel (between 0 and 7 - inclusive) | Yes | +| red | The amount of red to be set (between 0 and 255 - inclusive) | Yes | +| green | The amount of green to be set (between 0 and 255 - inclusive) | Yes | +| blue | The amount of blue to be set (between 0 and 255 - inclusive) | Yes | +| brightness | The brightness to be set (between 0 and 1 - inclusive) | No | + +### Set All Pixels + +Sometimes you need to set all the pixels to the same color. This convenience method does just that! + +The `brightness` argument is completely optional. Omit it to keep the last brightness values set for each pixel. + +``` javascript +blinkt.setPixels(red, green, blue, brightness) +``` + +| Parameter | Description | Required | +| ---------- | ------------------------------------------------------------- | -------- | +| red | The amount of red to be set (between 0 and 255 - inclusive) | Yes | +| green | The amount of green to be set (between 0 and 255 - inclusive) | Yes | +| blue | The amount of blue to be set (between 0 and 255 - inclusive) | Yes | +| brightness | The brightness to be set (between 0 and 1 - inclusive) | No | + +### Show + +None of your pixels will appear on Blinkt! until you `show()` them. This method writes all the pixel data out to your +device. + +``` javascript +blinkt.show() +``` + +### Clear + +Exactly the same as calling `setAll(0,0,0)`, clear sets all the pixels to black. + +You must also call `show()` if you want to turn Blinkt! off. + +``` javascript +blinkt.clear() +``` + +### Enable/Disable Clear On Exit + +Sometimes you want a script that runs and quits, leaving a pattern up on Blinkt! + +``` javascript +blinkt.setClearOnExit(value) +``` + +| Parameter | Description | Required | +| --------- | ------------------------------------------------- | -------- | +| value | `true` to clear pixels on exit; otherwise `false` | No | + +### Get A Single Pixel + +Returns the colors and brightness for a particular pixel. + +``` javascript +blinkt.getPixel(index) +``` + +| Parameter | Description | Required | +| --------- | ------------------------------------------------------------------ | -------- | +| index | The horizontal position of the pixel (between 0 and 7 - inclusive) | Yes | + +### Constants + +Blinkt! has 8 pixels. Simple. Use the constant `NUM_PIXELS` when you’re iterating over pixels, so you can avoid a *magic +number* in your code. + +``` javascript +blinkt.NUM_PIXELS +``` + +## Bugs + +If you have any problems with using this library or would like to see changes currently in development you can do so +[here](https://github.com/NotNinja/node-blinkt/issues). + +If you believe that you are experiencing issues with your Blinkt hardware, then you +[get help](http://forums.pimoroni.com/c/support). + +## Contributors + +If you want to contribute, you're a legend! Information on how you can do so can be found in +[CONTRIBUTING.md](https://github.com/NotNinja/node-blinkt/blob/master/CONTRIBUTING.md). We want your suggestions and +pull requests! + +A list of Blinkt contributors can be found in +[AUTHORS.md](https://github.com/NotNinja/node-blinkt/blob/master/AUTHORS.md). + +## License + +See [LICENSE.md](https://github.com/NotNinja/node-blinkt/raw/master/LICENSE.md) for more information on our MIT license. + +[![Copyright !ninja](https://cdn.rawgit.com/NotNinja/branding/master/assets/copyright/base/not-ninja-copyright-186x25.png)](https://not.ninja) diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json new file mode 100644 index 0000000..7b5ebdb --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "blinkt", + "version": "0.1.0", + "description": "Module for interacting with the Raspberry Pi Blinkt! addon", + "homepage": "https://github.com/NotNinja/node-blinkt", + "bugs": { + "url": "https://github.com/NotNinja/node-blinkt/issues" + }, + "author": { + "name": "Alasdair Mercer", + "email": "mercer.alasdair@gmail.com", + "url": "https://not.ninja" + }, + "license": "MIT", + "keywords": [ + "raspberry", + "pi", + "gpio", + "blinkt", + "led", + "rgb" + ], + "repository": { + "type": "git", + "url": "https://github.com/NotNinja/node-blinkt.git" + }, + "dependencies": { + "rpio": "^0.9.16" + }, + "main": "src/blinkt.js" +} diff --git a/src/blinkt.js b/src/blinkt.js new file mode 100644 index 0000000..dce1aec --- /dev/null +++ b/src/blinkt.js @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2017 Alasdair Mercer, !ninja + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +'use strict'; + +var rpio = require('rpio'); + +var DAT = 23; +var CLK = 24; +var NUM_PIXELS = 8; +var BRIGHTNESS = 7; + +var pixels = (function() { + var results = []; + + for (var i = 0; i < NUM_PIXELS; i++) { + resultss.push([ 0, 0, 0, BRIGHTNESS ]); + } + + return results; +}()); + +var clearOnExit = true; +var gpioSetup = false; + +/** + * Clears the pixel buffer. + * + * @return {void} + * @public + */ +exports.clear = function clear() { + for (var i = 0; i < NUM_PIXELS; i++) { + pixels[i].splice(0, 3, 0, 0, 0); + } +}; + +/** + * Gets the RGB and brightness values of the pixel at the index + * provided. + * + * @param {number} index - the horizontal position of the pixel (between 0 and 7 - inclusive) + * @return {number[]} An array containing the pixel information (red, green, blue, brightness). + * @throws {RangeError} If index is less than 0 or greater than 7. + * @throws {TypeError} If index is null or not a number. + * @public + */ +exports.getPixel = function getPixel(index) { + validateIndex(index); + + var pixel = pixels[index].slice(); + pixel[3] = (pixel[3] / 31).toPrecision(3); + + return pixel; +}; + +/** + * Sets the specified RGB values, and optionally brightness, of all pixels. + * + * If you don't supply a brightness value, the last value set for each pixel be kept. + * + * @param {number} red - the amount of red to be set (between 0 and 255 - inclusive) + * @param {number} green - the amount of green to be set (between 0 and 255 - inclusive) + * @param {number} blue - the amount of blue to be set (between 0 and 255 - inclusive) + * @param {number} [brightness] - the brightness to be set (between 0 and 1 - inclusive) + * @return {void} + * @throws {RangeError} If brightness is less than 0 or greater than 1 or any color is less than 0 or + * greater than 255. + * @throws {TypeError} If any color is null or not a number, or brightness is not a number. + * @public + */ +exports.setAll = function setAll(red, green, blue, brightness) { + validateRGB(red, green, blue); + validateBrightness(brightness, true); + + for (var i = 0; i < NUM_PIXELS; i++) { + setPixelInternal(i, red, green, blue, brightness); + } +}; + +/** + * Sets the brightness of all pixels to brightness. + * + * @param {number} brightness - the brightness to be set (between 0 and 1 - inclusive) + * @return {void} + * @throws {RangeError} If brightness is less than 0 or greater than 1. + * @throws {TypeError} If brightness is null or not a number. + * @public + */ +exports.setBrightness = function setBrightness(brightness) { + validateBrightness(brightness, false); + + for (var i = 0; i < NUM_PIXELS; i++) { + pixels[i][3] = toBrightnessValue(brightness); + } +}; + +/** + * Sets whether Blinkt! should be cleared upon exit. + * + * By default, Blinkt! will turn off all of the pixels on exit, but calling blinkt.setClearOnExit(false) + * will ensure that it does not. + * + * @param {boolean} [value=true] - true to clear pixels on exit; otherwise false + * @return {void} + * @public + */ +exports.setClearOnExit = function setClearOnExit(value) { + clearOnExit = value == null || value === true; +}; + +/** + * Sets the specified RGB values, and optionally brightness, of the pixel at the index + * provided. + * + * If you don't supply a brightness value, the last value set will be kept. + * + * @param {number} index - the horizontal position of the pixel (between 0 and 7 - inclusive) + * @param {number} red - the amount of red to be set (between 0 and 255 - inclusive) + * @param {number} green - the amount of green to be set (between 0 and 255 - inclusive) + * @param {number} blue - the amount of blue to be set (between 0 and 255 - inclusive) + * @param {number} [brightness] - the brightness to be set (between 0 and 1 - inclusive) + * @return {void} + * @throws {RangeError} If index is less than 0 or greather than 7, brightness is less than 0 + * or greater than 1, or any color is less than 0 or greater than 255. + * @throws {TypeError} If index or any color are null or not a number, or + * brightness is not a number. + * @public + */ +exports.setPixel = function setPixel(index, red, green, blue, brightness) { + validateIndex(index); + validateRGB(red, green, blue); + validateBrightness(brightness, true); + + setPixelInternal(index, red, green, blue, brightness); +}; + +/** + * Outputs the buffer to Blinkt! + * + * @return {void} + * @public + */ +exports.show = function show() { + if (!gpioSetup) { + rpio.init(); + rpio.open(DAT, rpio.OUTPUT); + rpio.open(CLK, rpio.OUTPUT); + + gpioSetup = true; + } + + sof(); + + var pixel; + + for (var i = 0, length = pixels.length; i < length; i++) { + pixel = pixels[i]; + + writeByte(0xe0 | pixel[3]); + writeByte(pixel[2]); + writeByte(pixel[1]); + writeByte(pixel[0]); + } + + eof(); +}; + +function cleanup() { + rpio.close(DAT); + rpio.close(CLK); +} + +function eof() { + /* + * Emit exactly enough clock pulses to latch the small dark die APA102s which are weird for some reason it takes 36 + * clocks, the other IC takes just 4 (number of pixels/2). + */ + rpio.write(DAT, 0); + + for (var i = 0; i < 36; i++) { + rpio.write(CLK, 1); + rpio.write(CLK, 0); + } +} + +function exit() { + if (clearOnExit) { + clear(); + show(); + } + + cleanup(); +} + +function setPixelInternal(index, red, green, blue, brightness) { + pixels[index] = [ + toColorValue(red), + toColorValue(green), + toColorValue(blue), + brightness != null ? toBrightnessValue(brightness) : pixels[index][3] + ]; +} + +function sof() { + var pulses = NUM_PIXELS * NUM_PIXELS / 2; + + rpio.write(DAT, 0); + + for (var i = 0; i < 32; i++) { + rpio.write(CLK, 1); + rpio.write(CLK, 0); + } +} + +function toBrightnessValue(brightness) { + return Math.floor(31 * brightness) & 0x1f; +} + +function toColorValue(color) { + return Math.floor(color) & 0xff; +} + +function validateBrightness(brightness, nullable) { + validateInput('brightness', brightness, 0, 1, nullable); +} + +function validateIndex(index) { + validateInput('index', index, 0, NUM_PIXELS - 1, false); +} + +function validateInput(name, value, min, max, nullable) { + if (value == null) { + if (!nullable) { + throw new TypeError(name + ' must not be null'); + } + } else if (typeof value !== 'number' || value !== +value) { + throw new TypeError(name + ' must be a number'); + } else if (brightness < 0 || brightness > 1) { + throw new RangeError(name + ' must be between ' + min + ' and ' + max + ' (inclusive)'); + } +} + +function validateRGB(red, green, blue) { + validateInput('red', red, 0, 255, false); + validateInput('green', green, 0, 255, false); + validateInput('blue', blue, 0, 255, false); +} + +function writeByte(byte) { + for (var i = 0; i < NUM_PIXELS; i++) { + rpio.write(DAT, byte & 0x80); + rpio.write(CLK, 1); + byte <<= 1; + rpio.write(CLK, 0); + } +} + +process.on('exit', exit);