├── .gitignore ├── .eslintignore ├── docs ├── breadboard │ ├── panel.fzz │ ├── panel_bb.png │ ├── custom_firmata.fzz │ ├── i2c_backpack.fzz │ ├── arduino_multipin.fzz │ ├── i2c_backpack_bb.png │ ├── arduino_multipin_bb.png │ ├── custom_firmata_bb.png │ ├── i2c_backpack_arduino.fzz │ ├── i2c_backpack_multipin.fzz │ ├── i2c_backpack_arduino_bb.png │ ├── i2c_backpack_multipin_bb.png │ ├── i2c_backpack_arduino_multipin.fzz │ └── i2c_backpack_arduino_multipin_bb.png ├── panel.md ├── firmata.md ├── johnnyfive.md ├── multipin.md ├── johnnyfive-i2c.md ├── rainbow-static.md ├── rainbow-dynamic.md ├── rainbow-dynamic-multipin.md ├── multipin-i2c.md └── installation.md ├── .huskyrc ├── .gitmodules ├── firmware ├── build │ ├── backpack │ │ ├── includes.h │ │ ├── backpack.ino │ │ ├── lw_ws2812.h │ │ ├── ws2812.h │ │ ├── lw_ws2812.cpp │ │ ├── light_ws2812.cpp │ │ └── ws2812.cpp │ └── node_pixel_firmata │ │ ├── lw_ws2812.h │ │ ├── ws2812.h │ │ ├── lw_ws2812.cpp │ │ ├── FirmataMarshaller.h │ │ ├── light_ws2812.cpp │ │ ├── FirmataParser.h │ │ ├── FirmataConstants.h │ │ ├── Firmata.h │ │ └── FirmataDefines.h ├── src │ ├── controller_src │ │ └── backpack │ │ │ ├── includes.h │ │ │ └── backpack.ino │ ├── libs │ │ ├── lightws2812 │ │ │ ├── lw_ws2812.h │ │ │ ├── lw_ws2812.cpp │ │ │ └── light_ws2812.cpp │ │ ├── ws2812 │ │ │ ├── ws2812.h │ │ │ └── ws2812.cpp │ │ └── protocol.md │ └── README.md └── boards.js ├── .github ├── dependabot.yml └── workflows │ ├── run_tests.yml │ └── firmware_build.yml ├── utils └── env_template.sh ├── manifest.json ├── lib ├── index.js ├── utils.js ├── constants.js ├── controllers │ ├── backpack.js │ └── firmata.js ├── strip.js └── pixel.js ├── .versionrc ├── examples ├── repl.js ├── panel.js ├── firmata.js ├── shift.js ├── johnnyfive.js ├── multipin-i2c.js ├── johnnyfive-i2c.js ├── multipin.js ├── rainbow-static.js ├── mega-multipin.js ├── rainbow-dynamic-multipin.js ├── rainbow-dynamic.js └── channel_fade.js ├── todo.md ├── LICENSE ├── .eslintrc ├── package.json ├── Makefile └── test ├── i2cbackpack.js ├── firmata.js └── core.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/* 2 | firmware/src/libs/firmata/* 3 | -------------------------------------------------------------------------------- /docs/breadboard/panel.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/panel.fzz -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "make lint", 4 | "pre-push": "make test" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/breadboard/panel_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/panel_bb.png -------------------------------------------------------------------------------- /docs/breadboard/custom_firmata.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/custom_firmata.fzz -------------------------------------------------------------------------------- /docs/breadboard/i2c_backpack.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/i2c_backpack.fzz -------------------------------------------------------------------------------- /docs/breadboard/arduino_multipin.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/arduino_multipin.fzz -------------------------------------------------------------------------------- /docs/breadboard/i2c_backpack_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/i2c_backpack_bb.png -------------------------------------------------------------------------------- /docs/breadboard/arduino_multipin_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/arduino_multipin_bb.png -------------------------------------------------------------------------------- /docs/breadboard/custom_firmata_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/custom_firmata_bb.png -------------------------------------------------------------------------------- /docs/breadboard/i2c_backpack_arduino.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/i2c_backpack_arduino.fzz -------------------------------------------------------------------------------- /docs/breadboard/i2c_backpack_multipin.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/i2c_backpack_multipin.fzz -------------------------------------------------------------------------------- /docs/breadboard/i2c_backpack_arduino_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/i2c_backpack_arduino_bb.png -------------------------------------------------------------------------------- /docs/breadboard/i2c_backpack_multipin_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/i2c_backpack_multipin_bb.png -------------------------------------------------------------------------------- /docs/breadboard/i2c_backpack_arduino_multipin.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/i2c_backpack_arduino_multipin.fzz -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "firmware/src/libs/firmata/arduino"] 2 | path = firmware/src/libs/firmata/arduino 3 | url = git@github.com:firmata/arduino.git 4 | -------------------------------------------------------------------------------- /docs/breadboard/i2c_backpack_arduino_multipin_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajfisher/node-pixel/HEAD/docs/breadboard/i2c_backpack_arduino_multipin_bb.png -------------------------------------------------------------------------------- /firmware/build/backpack/includes.h: -------------------------------------------------------------------------------- 1 | // Base include for all the main things we need to set up. 2 | #include 3 | 4 | typedef int receiveint; 5 | 6 | #define DEBUG false 7 | #define DEBUG_I2C false 8 | -------------------------------------------------------------------------------- /firmware/src/controller_src/backpack/includes.h: -------------------------------------------------------------------------------- 1 | // Base include for all the main things we need to set up. 2 | #include 3 | 4 | typedef int receiveint; 5 | 6 | #define DEBUG false 7 | #define DEBUG_I2C false 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "19:00" 8 | open-pull-requests-limit: 10 9 | reviewers: 10 | - ajfisher 11 | assignees: 12 | - ajfisher 13 | -------------------------------------------------------------------------------- /utils/env_template.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # this is a template for setting the environment variables you need for things 4 | # like building etc so grunt can do what it needs to do if needed. 5 | 6 | export ARDUINO_PATH=~/Downloads/arduino.app/Contents/MacOS/Arduino 7 | 8 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "backpack": { 3 | "bins": "/firmware/bin/backpack/", 4 | "hexPath": "/backpack.ino.hex" 5 | }, 6 | "firmata": { 7 | "bins": "/firmware/bin/firmata/", 8 | "hexPath": "/node_pixel_firmata.ino.hex" 9 | }, 10 | "version": "0.11.0" 11 | } 12 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {Strip} = require('./strip'); 4 | const { 5 | COLOR_ORDER, 6 | SHIFT_FORWARD, 7 | SHIFT_BACKWARD 8 | } = require('./constants'); 9 | 10 | module.exports = { 11 | Strip, 12 | COLOR_ORDER, 13 | FORWARD: SHIFT_FORWARD, 14 | BACKWARD: SHIFT_BACKWARD 15 | }; 16 | -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | {"type": "feat", "section": "Features"}, 4 | {"type": "fix", "section": "Bug Fixes"}, 5 | {"type": "test", "section": "Tests", "hidden": false}, 6 | {"type": "build", "section": "Build System", "hidden": false}, 7 | {"type": "ci", "section": "Build System", "hidden": false}, 8 | {"type": "chore", "section": "Chores", "hidden": false}, 9 | {"type": "docs", "section": "Documentation", "hidden": false}, 10 | {"type": "refactors", "section": "Refactoring", "hidden": false}, 11 | {"type": "sec", "section": "Security fixes", "hidden": false} 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const { 2 | GAMMA_DEFAULT 3 | } = require('./constants'); 4 | 5 | // helper function for building gamma values 6 | function create_gamma_table(steps, gamma, warning) { 7 | // used to build a gamma table for a particular value 8 | 9 | if (! warning && gamma == GAMMA_DEFAULT && ! global.IS_TEST_MODE) { 10 | console.info('INFO: Default gamma behaviour is changing'); 11 | console.info('0.9 - gamma=1.0 - consistent with pre-gamma values'); 12 | console.info('0.10 - gamma=2.8 - default fix for WS2812 LEDs'); 13 | warning = true; 14 | } 15 | 16 | const g_table = new Array(steps); 17 | for (let i = 0; i < steps; i++) { 18 | g_table[i] = Math.floor(Math.pow((i / 255.0), gamma) * 255 + 0.5); 19 | } 20 | 21 | return g_table; 22 | } 23 | 24 | module.exports = { 25 | create_gamma_table 26 | } 27 | -------------------------------------------------------------------------------- /firmware/boards.js: -------------------------------------------------------------------------------- 1 | // complete board list along with cpu and package type definition 2 | // any board added here will become a target for compilation however note that 3 | // the names of each board should map to avrgirl or it won't be able to be 4 | // flashed. 5 | 6 | module.exports = { 7 | 'uno' :{ 8 | package: 'arduino:avr:uno' 9 | }, 10 | 'nano': { 11 | cpu: 'atmega328', 12 | package: 'arduino:avr:nano:cpu=atmega328' 13 | }, 14 | 'pro-mini': { 15 | cpu: '16MHzatmega328', 16 | package: 'arduino:avr:pro:cpu=16MHzatmega328' 17 | }, 18 | 'mega': { 19 | package: 'arduino:avr:mega:cpu=atmega2560' 20 | }, 21 | 'diecimila': { 22 | package: 'arduino:avr:diecimila:cpu=atmega328' 23 | }, 24 | 'leonardo': { 25 | package: 'arduino:avr:leonardo' 26 | }, 27 | 'micro': { 28 | package: 'arduino:avr:micro' 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /examples/repl.js: -------------------------------------------------------------------------------- 1 | // This example shows how to use node-pixel using Johnny Five as the 2 | // hook for the board. 3 | const five = require('johnny-five'); 4 | const pixel = require('node-pixel'); 5 | 6 | const opts = {}; 7 | opts.port = process.argv[2] || ''; 8 | 9 | const board = new five.Board(opts); 10 | let strip = null; 11 | 12 | board.on('ready', function() { 13 | console.log('Board ready, lets add light'); 14 | 15 | strip = new pixel.Strip({ 16 | data: 6, 17 | length: 8, 18 | color_order: pixel.COLOR_ORDER.GRB, 19 | board: this, 20 | controller: 'FIRMATA' 21 | // strips: [8], 22 | }); 23 | 24 | strip.on('ready', function() { 25 | console.log("Strip ready, let's go"); 26 | console.log('You can now interact with the strip using the repl using the `strip` object'); 27 | 28 | strip.color('#000'); 29 | strip.pixel(1).color('red'); 30 | strip.show(); 31 | board.repl.inject({ 32 | strip: this 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | # Current tasks 2 | 3 | **Version:** `0.4.1` 4 | 5 | > Library for controlling addressable LEDs (such as NeoPixels) from firmata or Johnny Five. 6 | 7 | * * * 8 | 9 | ## TODO 10 | 11 | ## firmware/src/README.md 12 | 13 | - **TODO** `(line 56)` 14 | 15 | ## firmware/src/controller_src/firmata/node_pixel_firmata.ino 16 | 17 | - **TODO** `(line 29)` : use Program Control to load stored profiles from EEPROM 18 | - **TODO** `(line 302)` : put error msgs in EEPROM 19 | - **TODO** `(line 304)` : save status to EEPROM here, if changed 20 | - **TODO** `(line 368)` : save status to EEPROM here, if changed 21 | - **TODO** `(line 630)` : option to load config from EEPROM instead of default 22 | - **TODO** `(line 663)` : this can never execute, since no pins default to digital input 23 | 24 | ## firmware/src/libs/lightws2812/lw_ws2812.h 25 | 26 | - **TODO** `(line 34)` get this to work in setup 27 | 28 | ## lib/pixel.js 29 | 30 | - **TODO** `(line 3)` : 31 | 32 | 33 | * * * 34 | 35 | Last generated: Wed Dec 09 2015 00:18:01 by [grunt-todo](https://github.com/leny/grunt-todo). 36 | -------------------------------------------------------------------------------- /examples/panel.js: -------------------------------------------------------------------------------- 1 | 2 | // This example shows how to use node-pixel using Johnny Five as the 3 | // hook for the board. 4 | 5 | const five = require('johnny-five'); 6 | const pixel = require('node-pixel'); 7 | 8 | const opts = {}; 9 | opts.port = process.argv[2] || ''; 10 | 11 | const board = new five.Board(opts); 12 | let strip = null; 13 | 14 | const fps = 1; // how many frames per second do you want to try? 15 | 16 | board.on('ready', function() { 17 | console.log('Board ready, lets add light'); 18 | 19 | strip = new pixel.Strip({ 20 | board: this, 21 | controller: 'FIRMATA', 22 | data: 6, 23 | length: 64 24 | }); 25 | 26 | strip.on('ready', function() { 27 | console.log("Strip ready, let's go"); 28 | 29 | const colors = ['red', 'green', 'blue', 'yellow', 'cyan', 'magenta', 'white']; 30 | let current_colors = 0; 31 | const blinker = setInterval(function() { 32 | if (++current_colors >= colors.length) current_colors = 0; 33 | strip.color(colors[current_colors]); // blanks it out 34 | strip.show(); 35 | }, 1000/fps); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /examples/firmata.js: -------------------------------------------------------------------------------- 1 | // This example shows how to use node-pixel using firmata as the 2 | // hook for the board. 3 | 4 | const firmata = require('firmata'); 5 | const pixel = require('node-pixel'); 6 | 7 | const opts = {}; 8 | if (process.argv[2] == undefined) { 9 | console.log('Please supply a device port to connect to'); 10 | process.exit(); 11 | } 12 | 13 | opts.port = process.argv[2]; 14 | 15 | let strip = null; 16 | 17 | const board = new firmata.Board(opts.port, function() { 18 | console.log('Firmata ready, lets add light'); 19 | 20 | strip = new pixel.Strip({ 21 | data: 6, 22 | length: 4, 23 | firmata: board 24 | }); 25 | 26 | let pos = 0; 27 | const colors = ['red', 'green', 'blue', 'yellow', 'cyan', 'magenta', 'white']; 28 | let current_color = 0; 29 | 30 | const blinker = setInterval(function() { 31 | strip.color('#000'); // blanks it out 32 | 33 | if (++pos >= strip.length) { 34 | pos = 0; 35 | if (++current_color>= colors.length) current_color = 0; 36 | } 37 | strip.pixel(pos).color(colors[current_color]); 38 | 39 | strip.show(); 40 | }, 1000/2); 41 | }); 42 | -------------------------------------------------------------------------------- /examples/shift.js: -------------------------------------------------------------------------------- 1 | // This example shows how to use node-pixel using Johnny Five as the 2 | // hook for the board. 3 | const five = require('johnny-five'); 4 | const pixel = require('node-pixel'); 5 | 6 | const opts = {}; 7 | opts.port = process.argv[2] || ''; 8 | 9 | const board = new five.Board(opts); 10 | let strip = null; 11 | 12 | const fps = 20; // how many frames per second do you want to try? 13 | 14 | board.on('ready', function() { 15 | console.log('Board ready, lets add light'); 16 | 17 | strip = new pixel.Strip({ 18 | data: 7, 19 | length: 64, 20 | color_order: pixel.COLOR_ORDER.GRB, 21 | board: this, 22 | controller: 'FIRMATA' 23 | }); 24 | 25 | strip.on('ready', function() { 26 | console.log("Strip ready, let's go"); 27 | 28 | strip.color('#000'); 29 | strip.pixel(0).color('#300'); 30 | strip.pixel(1).color('#300'); 31 | strip.pixel(2).color('#300'); 32 | strip.pixel(5).color('#003'); 33 | strip.pixel(6).color('#003'); 34 | strip.show(); 35 | const blinker = setInterval(function() { 36 | strip.shift(1, pixel.BACKWARD, true); 37 | 38 | strip.show(); 39 | }, 1000/fps); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, 2015 Andrew Fisher 2 | and contributors. 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /examples/johnnyfive.js: -------------------------------------------------------------------------------- 1 | // This example shows how to use node-pixel using Johnny Five as the 2 | // hook for the board. 3 | const five = require('johnny-five'); 4 | const pixel = require('node-pixel'); 5 | 6 | const opts = {}; 7 | opts.port = process.argv[2] || ''; 8 | 9 | const board = new five.Board(opts); 10 | let strip = null; 11 | 12 | const fps = 10; // how many frames per second do you want to try? 13 | 14 | board.on('ready', function() { 15 | console.log('Board ready, lets add light'); 16 | 17 | strip = new pixel.Strip({ 18 | data: 6, 19 | length: 8, 20 | color_order: pixel.COLOR_ORDER.GRB, 21 | board: this, 22 | controller: 'FIRMATA' 23 | }); 24 | 25 | strip.on('ready', function() { 26 | console.log("Strip ready, let's go"); 27 | 28 | const colors = ['red', 'green', 'blue', 'yellow', 'cyan', 'magenta', 'white']; 29 | // var current_colors = [0,1,2,3,4]; 30 | const current_pos = [0,1,2,3,4]; 31 | 32 | current_pos.forEach((pos) => { 33 | strip.pixel(pos).color(colors[pos]); 34 | }); 35 | 36 | const blinker = setInterval(function() { 37 | strip.shift(1, pixel.FORWARD, true); 38 | 39 | strip.show(); 40 | }, 1000/fps); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | const START_SYSEX = 0xF0; 2 | const STRING_DATA = 0x71; 3 | const END_SYSEX = 0xF7; 4 | const FIRMATA_7BIT_MASK = 0x7F; 5 | const PIXEL_SHIFT_WRAP = 0x40; 6 | const PIXEL_COMMAND = 0x51; 7 | const PIXEL_OFF = 0x00; 8 | const PIXEL_CONFIG = 0x01; 9 | const PIXEL_SHOW = 0x02; 10 | const PIXEL_SET_PIXEL = 0x03; 11 | const PIXEL_SET_STRIP = 0x04; 12 | const PIXEL_SHIFT = 0x05; 13 | const SHIFT_FORWARD = 0x20; 14 | const SHIFT_BACKWARD = 0x00; 15 | 16 | const MAX_STRIPS = 8; 17 | 18 | const PIN_DEFAULT = 6; // use this if not supplied 19 | 20 | const I2C_DEFAULT = 0x42; 21 | 22 | const GAMMA_DEFAULT = 1.0; // set to 1.0 in 0.9, 2.8 in 0.10 23 | 24 | const COLOR_ORDER = { 25 | GRB: 0x00, 26 | RGB: 0x01, 27 | BRG: 0x02 28 | }; 29 | 30 | module.exports = { 31 | START_SYSEX, 32 | STRING_DATA, 33 | END_SYSEX, 34 | FIRMATA_7BIT_MASK, 35 | PIXEL_SHIFT_WRAP, 36 | PIXEL_COMMAND, 37 | PIXEL_OFF, 38 | PIXEL_CONFIG, 39 | PIXEL_SHOW, 40 | PIXEL_SET_PIXEL, 41 | PIXEL_SET_STRIP, 42 | PIXEL_SHIFT, 43 | SHIFT_FORWARD, 44 | SHIFT_BACKWARD, 45 | MAX_STRIPS, 46 | PIN_DEFAULT, 47 | I2C_DEFAULT, 48 | GAMMA_DEFAULT, 49 | COLOR_ORDER 50 | }; 51 | -------------------------------------------------------------------------------- /examples/multipin-i2c.js: -------------------------------------------------------------------------------- 1 | // This example shows how to use node-pixel 2 | // to control multiple strips on the same board 3 | 4 | const five = require('johnny-five'); 5 | const pixel = require('node-pixel'); 6 | 7 | const opts = {}; 8 | opts.port = process.argv[2] || ''; 9 | 10 | const board = new five.Board(opts); 11 | let strip = null; 12 | 13 | const fps = 30; // how many frames per second do you want to try? 14 | 15 | board.on('ready', function() { 16 | console.log('Board ready, lets add light'); 17 | 18 | strip = new pixel.Strip({ 19 | board: this, 20 | controller: 'I2CBACKPACK', 21 | color_order: pixel.COLOR_ORDER.GRB, 22 | strips: [ 8, 8 ] 23 | }); 24 | 25 | strip.on('ready', function() { 26 | console.log('Strip ready'); 27 | 28 | const colors = ['red', 'green', 'blue']; 29 | const current_pos = [0,1,2]; 30 | strip.color('#000'); // blanks it out 31 | current_pos.forEach((pos) => { 32 | strip.pixel(pos).color(colors[pos]); 33 | }); 34 | strip.show(); 35 | const blinker = setInterval(function() { 36 | strip.shift(1, pixel.FORWARD, true); 37 | strip.show(); 38 | }, 1000/fps); 39 | }); 40 | 41 | strip.on('error', function(err) { 42 | console.log(err); 43 | process.exit(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /firmware/build/backpack/backpack.ino: -------------------------------------------------------------------------------- 1 | 2 | #define I2C_SENSOR_ADDRESS 0x42 3 | #define MAX_RECEIVED_BYTES 16 4 | 5 | #define serialport Serial2 6 | 7 | #include 8 | #include "./lw_ws2812.h" 9 | #include "./ws2812.h" 10 | 11 | #include "includes.h" 12 | 13 | void setup() { 14 | 15 | 16 | Wire.begin(I2C_SENSOR_ADDRESS); 17 | Wire.onReceive(receiveData); 18 | 19 | #if DEBUG 20 | serialport.begin(9600); 21 | serialport.println("NodePixel I2C"); 22 | #endif 23 | 24 | ws2812_initialise(true); 25 | } 26 | 27 | void loop() { 28 | 29 | delay(1); 30 | } 31 | 32 | void receiveData(receiveint numbytes) { 33 | 34 | #if DEBUG_I2C 35 | serialport.println("\nRD"); 36 | serialport.print("NB: "); 37 | serialport.print(numbytes); 38 | serialport.println(); 39 | #endif 40 | 41 | byte received_bytes[MAX_RECEIVED_BYTES]; 42 | 43 | // read the data off the wire and then send it to get parsed. 44 | for (uint8_t i=0; i < numbytes; i++) { 45 | if (i < MAX_RECEIVED_BYTES) { 46 | received_bytes[i] = Wire.read(); 47 | #if DEBUG_I2C 48 | serialport.print(received_bytes[i], HEX); 49 | serialport.print(" "); 50 | #endif 51 | } else { 52 | Wire.read(); 53 | } 54 | } 55 | 56 | process_command(numbytes, received_bytes); 57 | } 58 | -------------------------------------------------------------------------------- /examples/johnnyfive-i2c.js: -------------------------------------------------------------------------------- 1 | // This example shows how to use node-pixel using Johnny Five as the 2 | // hook for the board. 3 | const five = require('johnny-five'); 4 | const pixel = require('node-pixel'); 5 | 6 | const opts = {}; 7 | opts.port = process.argv[2] || ''; 8 | 9 | const board = new five.Board(opts); 10 | let strip = null; 11 | 12 | const fps = 1; // how many frames per second do you want to try? 13 | 14 | board.on('ready', function() { 15 | console.log('Board ready, lets add light'); 16 | 17 | strip = new pixel.Strip({ 18 | color_order: pixel.COLOR_ORDER.GRB, 19 | board: this, 20 | controller: 'I2CBACKPACK', 21 | strips: [8] 22 | }); 23 | 24 | strip.on('ready', function() { 25 | console.log("Strip ready, let's go"); 26 | 27 | const colors = ['red', 'green', 'blue', 'yellow', 'cyan', 'magenta', 'white']; 28 | const current_colors = [0,1,2,3,4]; 29 | const current_pos = [0,1,2,3,4]; 30 | const blinker = setInterval(function() { 31 | strip.color('#000'); // blanks it out 32 | 33 | for (let i=0; i< current_pos.length; i++) { 34 | if (++current_pos[i] >= strip.length) { 35 | current_pos[i] = 0; 36 | if (++current_colors[i] >= colors.length) current_colors[i] = 0; 37 | } 38 | strip.pixel(current_pos[i]).color(colors[current_colors[i]]); 39 | } 40 | 41 | strip.show(); 42 | }, 1000/fps); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /firmware/src/controller_src/backpack/backpack.ino: -------------------------------------------------------------------------------- 1 | 2 | #define I2C_SENSOR_ADDRESS 0x42 3 | #define MAX_RECEIVED_BYTES 16 4 | 5 | #define serialport Serial2 6 | 7 | #include 8 | #include "./lw_ws2812.h" 9 | #include "./ws2812.h" 10 | 11 | #include "includes.h" 12 | 13 | void setup() { 14 | 15 | 16 | Wire.begin(I2C_SENSOR_ADDRESS); 17 | Wire.onReceive(receiveData); 18 | 19 | #if DEBUG 20 | serialport.begin(9600); 21 | serialport.println("NodePixel I2C"); 22 | #endif 23 | 24 | ws2812_initialise(true); 25 | } 26 | 27 | void loop() { 28 | 29 | delay(1); 30 | } 31 | 32 | void receiveData(receiveint numbytes) { 33 | 34 | #if DEBUG_I2C 35 | serialport.println("\nRD"); 36 | serialport.print("NB: "); 37 | serialport.print(numbytes); 38 | serialport.println(); 39 | #endif 40 | 41 | byte received_bytes[MAX_RECEIVED_BYTES]; 42 | 43 | // read the data off the wire and then send it to get parsed. 44 | for (uint8_t i=0; i < numbytes; i++) { 45 | if (i < MAX_RECEIVED_BYTES) { 46 | received_bytes[i] = Wire.read(); 47 | #if DEBUG_I2C 48 | serialport.print(received_bytes[i], HEX); 49 | serialport.print(" "); 50 | #endif 51 | } else { 52 | Wire.read(); 53 | } 54 | } 55 | 56 | process_command(numbytes, received_bytes); 57 | } 58 | -------------------------------------------------------------------------------- /examples/multipin.js: -------------------------------------------------------------------------------- 1 | // This example shows how to use node-pixel 2 | // to control multiple strips on the same board 3 | 4 | const five = require('johnny-five'); 5 | const pixel = require('node-pixel'); 6 | 7 | const opts = {}; 8 | opts.port = process.argv[2] || ''; 9 | 10 | const board = new five.Board(opts); 11 | let strip = null; 12 | 13 | const fps = 30; // how many frames per second do you want to try? 14 | 15 | board.on('ready', function() { 16 | console.log('Board ready, lets add light'); 17 | 18 | strip = new pixel.Strip({ 19 | board: this, 20 | controller: 'FIRMATA', 21 | strips: [ {pin: 6, length: 8}, {pin: 7, length: 8}] 22 | }); 23 | 24 | strip.on('ready', function() { 25 | console.log('Strip ready'); 26 | 27 | const colors = ['red', 'green', 'blue']; 28 | const current_colors = [0,1,2]; 29 | const pixel_list = [0,1,2]; 30 | const blinker = setInterval(function() { 31 | strip.color('#000'); // blanks it out 32 | for (let i=0; i< pixel_list.length; i++) { 33 | if (++pixel_list[i] >= strip.length) { 34 | pixel_list[i] = 0; 35 | if (++current_colors[i] >= colors.length) current_colors[i] = 0; 36 | } 37 | strip.pixel(pixel_list[i]).color(colors[current_colors[i]]); 38 | } 39 | 40 | strip.show(); 41 | }, 1000/fps); 42 | }); 43 | 44 | strip.on('error', function(err) { 45 | console.log(err); 46 | process.exit(); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /docs/panel.md: -------------------------------------------------------------------------------- 1 | # Using a panel and Strip Color 2 | 3 | This example uses a 8x8 LED panel and `strip.color()` to change the entire panel. 4 | 5 | To install the custom firmata, see the [Installation Guide](installation.md). 6 | 7 | ## Wiring 8 | 9 | Wire the neopixel panel up as shown below. 10 | 11 | ![Wiring diagram](breadboard/panel_bb.png) 12 | 13 | ## Example code 14 | 15 | ```js 16 | var five = require("johnny-five"); 17 | var pixel = require("node-pixel"); 18 | 19 | var opts = {}; 20 | opts.port = process.argv[2] || ""; 21 | 22 | var board = new five.Board(opts); 23 | var strip = null; 24 | 25 | var fps = 1; // how many frames per second do you want to try? 26 | 27 | board.on("ready", function() { 28 | 29 | console.log("Board ready, lets add light"); 30 | 31 | strip = new pixel.Strip({ 32 | board: this, 33 | controller: "FIRMATA", 34 | data: 6, 35 | length: 64, 36 | }); 37 | 38 | strip.on("ready", function() { 39 | 40 | console.log("Strip ready, let's go"); 41 | 42 | var colors = ["red", "green", "blue", "yellow", "cyan", "magenta", "white"]; 43 | var current_colors = 0; 44 | var blinker = setInterval(function() { 45 | 46 | if (++current_colors >= colors.length) current_colors = 0; 47 | strip.color(colors[current_colors]); // blanks it out 48 | strip.show(); 49 | }, 1000/fps); 50 | }); 51 | }); 52 | ``` 53 | 54 | ## Running 55 | 56 | To run the example: 57 | 58 | ``` 59 | node examples/panel.js 60 | ``` 61 | 62 | You can optionally pass a port in as a parameter. 63 | -------------------------------------------------------------------------------- /docs/firmata.md: -------------------------------------------------------------------------------- 1 | # Basic Firmata example 2 | 3 | This example uses only Firmata to control a neopixel strip using a custom firmata. 4 | 5 | To install the custom firmata, see the [Installation Guide](installation.md). 6 | 7 | ## Wiring 8 | 9 | Wire the neopixel strip up as shown below. 10 | 11 | ![Wiring diagram](breadboard/custom_firmata_bb.png) 12 | 13 | ## Example code 14 | ```js 15 | var firmata = require("firmata"); 16 | var pixel = require("node-pixel"); 17 | 18 | var opts = {}; 19 | if (process.argv[2] == undefined) { 20 | console.log("Please supply a device port to connect to"); 21 | process.exit(); 22 | } 23 | 24 | opts.port = process.argv[2]; 25 | 26 | var strip = null; 27 | 28 | var board = new firmata.Board(opts.port, function() { 29 | 30 | console.log("Firmata ready, lets add light"); 31 | 32 | strip = new pixel.Strip({ 33 | data: 6, 34 | length: 4, 35 | firmata: board, 36 | }); 37 | 38 | var pos = 0; 39 | var colors = ["red", "green", "blue", "yellow", "cyan", "magenta", "white"]; 40 | var current_color = 0; 41 | 42 | var blinker = setInterval(function() { 43 | 44 | strip.color("#000"); // blanks it out 45 | 46 | if (++pos >= strip.length) { 47 | pos = 0; 48 | if (++current_color>= colors.length) current_color = 0; 49 | } 50 | strip.pixel(pos).color(colors[current_color]); 51 | 52 | strip.show(); 53 | }, 1000/2); 54 | }); 55 | ``` 56 | 57 | ## Running 58 | 59 | To run the example: 60 | 61 | ``` 62 | node examples/firmata.js 63 | ``` 64 | 65 | You can optionally pass a port in as a parameter. 66 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 2019, 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "eqeqeq": 0, 12 | "no-var": 2, 13 | "prefer-const": 2, 14 | "no-shadow": 2, 15 | "no-shadow-restricted-names": 2, 16 | "no-use-before-define": 2, 17 | "comma-dangle": [2, "never"], 18 | "no-cond-assign": [2, "always"], 19 | "no-constant-condition": 1, 20 | "no-dupe-keys": 2, 21 | "no-duplicate-case": 2, 22 | "no-ex-assign": 2, 23 | "no-extra-boolean-cast": 1, 24 | "no-extra-semi": 2, 25 | "no-func-assign": 2, 26 | "no-irregular-whitespace": 2, 27 | "default-case": 2, 28 | "guard-for-in": 2, 29 | "no-floating-decimal": 2, 30 | "no-self-compare": 2, 31 | "no-sequences": 2, 32 | "no-throw-literal": 2, 33 | "radix": 2, 34 | "quotes": [2, "single", "avoid-escape"], 35 | "indent": [2, 2, { "SwitchCase": 1 }], 36 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 37 | "comma-style": [2, "last"], 38 | "no-multiple-empty-lines": 2, 39 | "no-nested-ternary": 2, 40 | "one-var": [2, "never"], 41 | "padded-blocks": [2, "never"], 42 | "keyword-spacing": 2, 43 | "no-console": 0, 44 | "space-before-blocks": 2, 45 | "space-before-function-paren": [2, "never"], 46 | "spaced-comment": 2, 47 | "valid-jsdoc": [2, { "requireReturn": false }], 48 | "no-unreachable": 2, 49 | "no-unexpected-multiline": 2, 50 | "no-else-return": 2, 51 | "constructor-super": 2, 52 | "no-this-before-super": 2, 53 | "object-shorthand": [2, "always"], 54 | "no-loop-func": 0, 55 | "no-param-reassign": 0, 56 | "no-empty": 0 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-pixel", 3 | "version": "0.11.0", 4 | "description": "Library for controlling addressable LEDs (such as NeoPixels) from firmata or Johnny Five.", 5 | "main": "lib/index.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "scripts": { 10 | "test": "IS_TEST_MODE=true ./node_modules/.bin/nodeunit test/*", 11 | "test-cover": "IS_TEST_MODE=true istanbul cover ./node_modules/.bin/nodeunit test/*", 12 | "coveralls": "cat ./coverage/lcov.info | coveralls", 13 | "release": "standard-version", 14 | "lint": "./node_modules/.bin/eslint ." 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/ajfisher/node-pixel.git" 19 | }, 20 | "keywords": [ 21 | "nodebots", 22 | "johnny-five", 23 | "leds", 24 | "ws2812", 25 | "ws2812b", 26 | "firmata", 27 | "neopixel", 28 | "rgb led" 29 | ], 30 | "author": { 31 | "name": "Andrew Fisher (ajfisher)", 32 | "email": "ajfisher.td@gmail.com", 33 | "url": "http://twitter.com/ajfisher" 34 | }, 35 | "license": { 36 | "type": "MIT", 37 | "url": "https://github.com/ajfisher/node-pixel/blob/master/LICENSE" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/ajfisher/node-pixel/issues" 41 | }, 42 | "dependencies": { 43 | "color-string": "^1.5.3" 44 | }, 45 | "peerDependencies": { 46 | "firmata": "^0.19.1", 47 | "johnny-five": "^2.0.0" 48 | }, 49 | "devDependencies": { 50 | "board-io": "^3.0.4", 51 | "coveralls": "^3.1.0", 52 | "eslint": "^9.0.0", 53 | "husky": "^9.0.5", 54 | "istanbul": "^0.4.5", 55 | "johnny-five": "^2.0.0", 56 | "mock-firmata": "0.2.0", 57 | "nodeunit": "^0.11.3", 58 | "sinon": "^15.0.1", 59 | "standard-version": "^9.0.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/rainbow-static.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This example shows how to use node-pixel to display a set of rainbow colors 3 | * on your NeoPixel strip using Johnny-Five. 4 | * 5 | * created by @pierceray in June 2015 6 | */ 7 | const five = require('johnny-five'); 8 | const pixel = require('node-pixel'); 9 | 10 | const opts = {}; 11 | opts.port = process.argv[2] || ''; 12 | 13 | const board = new five.Board(opts); 14 | let strip = null; 15 | 16 | board.on('ready', function() { 17 | console.log('Board ready, lets add light'); 18 | 19 | // Input a value 0 to 255 to get a color value. 20 | // The colours are a transition r - g - b - back to r. 21 | function colorWheel( WheelPos ) { 22 | let r; let g; let b; 23 | WheelPos = 255 - WheelPos; 24 | 25 | if ( WheelPos < 85 ) { 26 | r = 255 - WheelPos * 3; 27 | g = 0; 28 | b = WheelPos * 3; 29 | } else if (WheelPos < 170) { 30 | WheelPos -= 85; 31 | r = 0; 32 | g = WheelPos * 3; 33 | b = 255 - WheelPos * 3; 34 | } else { 35 | WheelPos -= 170; 36 | r = WheelPos * 3; 37 | g = 255 - WheelPos * 3; 38 | b = 0; 39 | } 40 | // returns a string with the rgb value to be used as the parameter 41 | return 'rgb(' + r +',' + g + ',' + b + ')'; 42 | } 43 | 44 | function staticRainbow() { 45 | console.log('staticRainbow'); 46 | 47 | let showColor; 48 | for (let i = 0; i < strip.length; i++) { 49 | showColor = colorWheel( ( (i+10)*256 / strip.length ) & 255 ); 50 | strip.pixel(i).color( showColor); 51 | } 52 | strip.show(); 53 | } 54 | 55 | // setup the node-pixel strip. 56 | strip = new pixel.Strip({ 57 | data: 6, 58 | length: 16, // number of pixels in the strip. 59 | board: this, 60 | controller: 'FIRMATA' 61 | }); 62 | 63 | strip.on('ready', function() { 64 | console.log("Strip ready, let's go"); 65 | 66 | staticRainbow(); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /firmware/build/backpack/lw_ws2812.h: -------------------------------------------------------------------------------- 1 | /* 2 | * light weight WS2812 lib V2.1 - Arduino support 3 | * 4 | * Controls WS2811/WS2812/WS2812B RGB-LEDs 5 | * Author: Matthias Riegler 6 | * 7 | * Mar 07 2014: Added Arduino and C++ Library 8 | * 9 | * September 6, 2014 Added option to switch between most popular color orders 10 | * (RGB, GRB, and BRG) -- Windell H. Oskay 11 | * 12 | * January 24, 2015 Added option to make color orders static again 13 | * Moved cRGB to a header so it is easier to replace / expand 14 | * (added SetHSV based on code from kasperkamperman.com) 15 | * -- Freezy 16 | * 17 | * License: GNU GPL v2 (see License.txt) 18 | */ 19 | 20 | #ifndef WS2812_H_ 21 | #define WS2812_H_ 22 | 23 | #include 24 | #include 25 | #ifndef F_CPU 26 | #define F_CPU 16000000UL 27 | #endif 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | class WS2812 { 34 | public: 35 | WS2812(uint16_t num_led, uint16_t offset); 36 | WS2812(uint16_t num_led); 37 | WS2812(); 38 | ~WS2812(); 39 | 40 | void setOutput(uint8_t pin); 41 | void set_length(uint16_t num_leds); 42 | void set_offset(uint16_t offset); 43 | 44 | void sync(uint8_t *px_array, uint8_t pixel_depth); 45 | 46 | uint16_t get_length(); 47 | 48 | private: 49 | uint16_t count_led; // how many LEDs being controlled 50 | uint16_t offset; // any offsets needing to be applied 51 | 52 | 53 | void init(uint16_t num_leds); 54 | void init(uint16_t num_leds, uint16_t offset); 55 | void ws2812_sendarray_mask( 56 | uint8_t *array, uint16_t length, 57 | uint8_t pinmask,uint8_t *port, uint8_t *portreg 58 | ); 59 | 60 | const volatile uint8_t *ws2812_port; 61 | volatile uint8_t *ws2812_port_reg; 62 | uint8_t pinMask; 63 | }; 64 | 65 | #endif /* WS2812_H_ */ 66 | -------------------------------------------------------------------------------- /firmware/src/libs/lightws2812/lw_ws2812.h: -------------------------------------------------------------------------------- 1 | /* 2 | * light weight WS2812 lib V2.1 - Arduino support 3 | * 4 | * Controls WS2811/WS2812/WS2812B RGB-LEDs 5 | * Author: Matthias Riegler 6 | * 7 | * Mar 07 2014: Added Arduino and C++ Library 8 | * 9 | * September 6, 2014 Added option to switch between most popular color orders 10 | * (RGB, GRB, and BRG) -- Windell H. Oskay 11 | * 12 | * January 24, 2015 Added option to make color orders static again 13 | * Moved cRGB to a header so it is easier to replace / expand 14 | * (added SetHSV based on code from kasperkamperman.com) 15 | * -- Freezy 16 | * 17 | * License: GNU GPL v2 (see License.txt) 18 | */ 19 | 20 | #ifndef WS2812_H_ 21 | #define WS2812_H_ 22 | 23 | #include 24 | #include 25 | #ifndef F_CPU 26 | #define F_CPU 16000000UL 27 | #endif 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | class WS2812 { 34 | public: 35 | WS2812(uint16_t num_led, uint16_t offset); 36 | WS2812(uint16_t num_led); 37 | WS2812(); 38 | ~WS2812(); 39 | 40 | void setOutput(uint8_t pin); 41 | void set_length(uint16_t num_leds); 42 | void set_offset(uint16_t offset); 43 | 44 | void sync(uint8_t *px_array, uint8_t pixel_depth); 45 | 46 | uint16_t get_length(); 47 | 48 | private: 49 | uint16_t count_led; // how many LEDs being controlled 50 | uint16_t offset; // any offsets needing to be applied 51 | 52 | 53 | void init(uint16_t num_leds); 54 | void init(uint16_t num_leds, uint16_t offset); 55 | void ws2812_sendarray_mask( 56 | uint8_t *array, uint16_t length, 57 | uint8_t pinmask,uint8_t *port, uint8_t *portreg 58 | ); 59 | 60 | const volatile uint8_t *ws2812_port; 61 | volatile uint8_t *ws2812_port_reg; 62 | uint8_t pinMask; 63 | }; 64 | 65 | #endif /* WS2812_H_ */ 66 | -------------------------------------------------------------------------------- /firmware/build/node_pixel_firmata/lw_ws2812.h: -------------------------------------------------------------------------------- 1 | /* 2 | * light weight WS2812 lib V2.1 - Arduino support 3 | * 4 | * Controls WS2811/WS2812/WS2812B RGB-LEDs 5 | * Author: Matthias Riegler 6 | * 7 | * Mar 07 2014: Added Arduino and C++ Library 8 | * 9 | * September 6, 2014 Added option to switch between most popular color orders 10 | * (RGB, GRB, and BRG) -- Windell H. Oskay 11 | * 12 | * January 24, 2015 Added option to make color orders static again 13 | * Moved cRGB to a header so it is easier to replace / expand 14 | * (added SetHSV based on code from kasperkamperman.com) 15 | * -- Freezy 16 | * 17 | * License: GNU GPL v2 (see License.txt) 18 | */ 19 | 20 | #ifndef WS2812_H_ 21 | #define WS2812_H_ 22 | 23 | #include 24 | #include 25 | #ifndef F_CPU 26 | #define F_CPU 16000000UL 27 | #endif 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | class WS2812 { 34 | public: 35 | WS2812(uint16_t num_led, uint16_t offset); 36 | WS2812(uint16_t num_led); 37 | WS2812(); 38 | ~WS2812(); 39 | 40 | void setOutput(uint8_t pin); 41 | void set_length(uint16_t num_leds); 42 | void set_offset(uint16_t offset); 43 | 44 | void sync(uint8_t *px_array, uint8_t pixel_depth); 45 | 46 | uint16_t get_length(); 47 | 48 | private: 49 | uint16_t count_led; // how many LEDs being controlled 50 | uint16_t offset; // any offsets needing to be applied 51 | 52 | 53 | void init(uint16_t num_leds); 54 | void init(uint16_t num_leds, uint16_t offset); 55 | void ws2812_sendarray_mask( 56 | uint8_t *array, uint16_t length, 57 | uint8_t pinmask,uint8_t *port, uint8_t *portreg 58 | ); 59 | 60 | const volatile uint8_t *ws2812_port; 61 | volatile uint8_t *ws2812_port_reg; 62 | uint8_t pinMask; 63 | }; 64 | 65 | #endif /* WS2812_H_ */ 66 | -------------------------------------------------------------------------------- /docs/johnnyfive.md: -------------------------------------------------------------------------------- 1 | # Basic Johnny Five example 2 | 3 | This example uses johnny five to control a neopixel strip using a custom firmata. 4 | 5 | To install the custom firmata, see the [Installation Guide](installation.md). 6 | 7 | ## Wiring 8 | 9 | Wire the neopixel strip up as shown below. 10 | 11 | ![Wiring diagram](breadboard/custom_firmata_bb.png) 12 | 13 | ## Example code 14 | 15 | ```js 16 | var five = require("johnny-five"); 17 | var pixel = require("node-pixel"); 18 | 19 | var opts = {}; 20 | opts.port = process.argv[2] || ""; 21 | 22 | var board = new five.Board(opts); 23 | var strip = null; 24 | 25 | var fps = 20; // how many frames per second do you want to try? 26 | 27 | board.on("ready", function() { 28 | 29 | console.log("Board ready, lets add light"); 30 | 31 | strip = new pixel.Strip({ 32 | data: 6, 33 | length: 8, 34 | color_order: pixel.COLOR_ORDER.GRB, 35 | board: this, 36 | controller: "FIRMATA", 37 | }); 38 | 39 | strip.on("ready", function() { 40 | 41 | console.log("Strip ready, let's go"); 42 | 43 | var colors = ["red", "green", "blue", "yellow", "cyan", "magenta", "white"]; 44 | var current_colors = [0,1,2,3,4]; 45 | var current_pos = [0,1,2,3,4]; 46 | var blinker = setInterval(function() { 47 | 48 | strip.color("#000"); // blanks it out 49 | 50 | for (var i=0; i< current_pos.length; i++) { 51 | if (++current_pos[i] >= strip.length) { 52 | current_pos[i] = 0; 53 | if (++current_colors[i] >= colors.length) current_colors[i] = 0; 54 | } 55 | strip.pixel(current_pos[i]).color(colors[current_colors[i]]); 56 | } 57 | 58 | strip.show(); 59 | }, 1000/fps); 60 | }); 61 | }); 62 | ``` 63 | 64 | ## Running 65 | 66 | To run the example: 67 | 68 | ``` 69 | node examples/johnnyfive.js 70 | ``` 71 | 72 | You can optionally pass a port in as a parameter. 73 | -------------------------------------------------------------------------------- /.github/workflows/run_tests.yml: -------------------------------------------------------------------------------- 1 | name: Library Tests 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | test-matrix: 7 | strategy: 8 | matrix: 9 | node: [ '12', '14' ] 10 | name: Test Library Node v ${{ matrix.node }} 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Code 14 | uses: actions/checkout@v2 15 | - name: Setup NodeJS 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node }} 19 | - name: Cache node modules 20 | id: cache-node-modules 21 | uses: actions/cache@v1 22 | with: 23 | path: ./node_modules 24 | key: test-${{ matrix.node }}-${{hashFiles('./package-lock.json')}} 25 | restore-keys: test-${{ matrix.node }}-${{hashFiles('./package-lock.json')}} 26 | - name: Install modules 27 | if: steps.cache-node-modules.outputs.cache-hit != 'true' 28 | run: npm install 29 | - name: Run tests 30 | run: npm test 31 | 32 | test-success: 33 | runs-on: ubuntu-latest 34 | needs: test-matrix 35 | steps: 36 | - name: Tests completed 37 | run: echo Done! 38 | 39 | test-coverage: 40 | runs-on: ubuntu-latest 41 | needs: test-success 42 | steps: 43 | - uses: actions/checkout@v2 44 | - uses: actions/setup-node@v1 45 | with: 46 | node-version: 12 47 | - uses: actions/cache@v1 48 | id: cache-node-modules 49 | with: 50 | path: ./node_modules 51 | key: test-12-${{hashFiles('./package-lock.json')}} 52 | restore-keys: test-12-${{hashFiles('./package-lock.json')}} 53 | - name: Install modules 54 | if: steps.cache-node-modules.outputs.cache-hit != 'true' 55 | run: npm install 56 | - name: Test with coverage 57 | run: npm run test-cover 58 | - name: Coveralls 59 | uses: coverallsapp/github-action@master 60 | with: 61 | github-token: ${{ secrets.GITHUB_TOKEN }} 62 | -------------------------------------------------------------------------------- /firmware/build/backpack/ws2812.h: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This the ws2812 library used to manage the strip(s) 4 | * 5 | * Most of its job is to parse messages correctly and hand off to the 6 | * light library for more serious tasking. 7 | * 8 | * You'll need to call ws2812_initialise() to begin with so it sets up 9 | * the strip appropriately 10 | * 11 | */ 12 | 13 | #ifndef WS2812_h 14 | #define WS2812_h 15 | 16 | #define DEBUG false 17 | 18 | #define serialport Serial2 19 | 20 | #include "Arduino.h" 21 | #include "lw_ws2812.h" 22 | 23 | // define the firmata commands needed 24 | #define PIXEL_COMMAND 0x51 // firmata command used for a pixel 25 | 26 | // pixel command instruction set 27 | #define PIXEL_OFF 0x00 // set strip to be off 28 | #define PIXEL_CONFIG 0x01 // DEPRECATED was setting pin and length 29 | #define PIXEL_SHOW 0x02 // latch the pixels and show them 30 | #define PIXEL_SET_PIXEL 0x03 // set the color value of pixel n using 32bit packed color value 31 | #define PIXEL_SET_STRIP 0x04 // set color of whole strip 32 | #define PIXEL_SHIFT 0x05 // shift all pixels n places along the strip 33 | 34 | // define the colour element layouts 35 | #define PIXEL_COLOUR_GRB 0x0 36 | #define PIXEL_COLOUR_RGB 0x1 37 | #define PIXEL_COLOUR_BRG 0x2 38 | 39 | #define STRIP_START_PIN 0 40 | 41 | #define MAX_STRIPS 8 42 | #define LED_DEFAULT_PIN 6 43 | #define STRIP_LENGTH 64 44 | 45 | #define BUFLENGTH 64 46 | 47 | #define OFFSET_R(r) r+offsetRed 48 | #define OFFSET_G(g) g+offsetGreen 49 | #define OFFSET_B(b) b+offsetBlue 50 | 51 | void ws2812_initialise(); 52 | void ws2812_initialise(bool backpack); 53 | void process_command(byte argc, byte *argv); 54 | void initialise_pixels(uint16_t num_pixels); 55 | uint8_t set_rgb_at(uint16_t index, uint32_t px_value); 56 | 57 | void setColorOrderRGB(); 58 | void setColorOrderGRB(); 59 | void setColorOrderBRG(); 60 | 61 | #if DEBUG 62 | void print_pixels(); 63 | int freeRam(); 64 | #endif 65 | 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /firmware/src/libs/ws2812/ws2812.h: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This the ws2812 library used to manage the strip(s) 4 | * 5 | * Most of its job is to parse messages correctly and hand off to the 6 | * light library for more serious tasking. 7 | * 8 | * You'll need to call ws2812_initialise() to begin with so it sets up 9 | * the strip appropriately 10 | * 11 | */ 12 | 13 | #ifndef WS2812_h 14 | #define WS2812_h 15 | 16 | #define DEBUG false 17 | 18 | #define serialport Serial2 19 | 20 | #include "Arduino.h" 21 | #include "lw_ws2812.h" 22 | 23 | // define the firmata commands needed 24 | #define PIXEL_COMMAND 0x51 // firmata command used for a pixel 25 | 26 | // pixel command instruction set 27 | #define PIXEL_OFF 0x00 // set strip to be off 28 | #define PIXEL_CONFIG 0x01 // DEPRECATED was setting pin and length 29 | #define PIXEL_SHOW 0x02 // latch the pixels and show them 30 | #define PIXEL_SET_PIXEL 0x03 // set the color value of pixel n using 32bit packed color value 31 | #define PIXEL_SET_STRIP 0x04 // set color of whole strip 32 | #define PIXEL_SHIFT 0x05 // shift all pixels n places along the strip 33 | 34 | // define the colour element layouts 35 | #define PIXEL_COLOUR_GRB 0x0 36 | #define PIXEL_COLOUR_RGB 0x1 37 | #define PIXEL_COLOUR_BRG 0x2 38 | 39 | #define STRIP_START_PIN 0 40 | 41 | #define MAX_STRIPS 8 42 | #define LED_DEFAULT_PIN 6 43 | #define STRIP_LENGTH 64 44 | 45 | #define BUFLENGTH 64 46 | 47 | #define OFFSET_R(r) r+offsetRed 48 | #define OFFSET_G(g) g+offsetGreen 49 | #define OFFSET_B(b) b+offsetBlue 50 | 51 | void ws2812_initialise(); 52 | void ws2812_initialise(bool backpack); 53 | void process_command(byte argc, byte *argv); 54 | void initialise_pixels(uint16_t num_pixels); 55 | uint8_t set_rgb_at(uint16_t index, uint32_t px_value); 56 | 57 | void setColorOrderRGB(); 58 | void setColorOrderGRB(); 59 | void setColorOrderBRG(); 60 | 61 | #if DEBUG 62 | void print_pixels(); 63 | int freeRam(); 64 | #endif 65 | 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /firmware/build/node_pixel_firmata/ws2812.h: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This the ws2812 library used to manage the strip(s) 4 | * 5 | * Most of its job is to parse messages correctly and hand off to the 6 | * light library for more serious tasking. 7 | * 8 | * You'll need to call ws2812_initialise() to begin with so it sets up 9 | * the strip appropriately 10 | * 11 | */ 12 | 13 | #ifndef WS2812_h 14 | #define WS2812_h 15 | 16 | #define DEBUG false 17 | 18 | #define serialport Serial2 19 | 20 | #include "Arduino.h" 21 | #include "lw_ws2812.h" 22 | 23 | // define the firmata commands needed 24 | #define PIXEL_COMMAND 0x51 // firmata command used for a pixel 25 | 26 | // pixel command instruction set 27 | #define PIXEL_OFF 0x00 // set strip to be off 28 | #define PIXEL_CONFIG 0x01 // DEPRECATED was setting pin and length 29 | #define PIXEL_SHOW 0x02 // latch the pixels and show them 30 | #define PIXEL_SET_PIXEL 0x03 // set the color value of pixel n using 32bit packed color value 31 | #define PIXEL_SET_STRIP 0x04 // set color of whole strip 32 | #define PIXEL_SHIFT 0x05 // shift all pixels n places along the strip 33 | 34 | // define the colour element layouts 35 | #define PIXEL_COLOUR_GRB 0x0 36 | #define PIXEL_COLOUR_RGB 0x1 37 | #define PIXEL_COLOUR_BRG 0x2 38 | 39 | #define STRIP_START_PIN 0 40 | 41 | #define MAX_STRIPS 8 42 | #define LED_DEFAULT_PIN 6 43 | #define STRIP_LENGTH 64 44 | 45 | #define BUFLENGTH 64 46 | 47 | #define OFFSET_R(r) r+offsetRed 48 | #define OFFSET_G(g) g+offsetGreen 49 | #define OFFSET_B(b) b+offsetBlue 50 | 51 | void ws2812_initialise(); 52 | void ws2812_initialise(bool backpack); 53 | void process_command(byte argc, byte *argv); 54 | void initialise_pixels(uint16_t num_pixels); 55 | uint8_t set_rgb_at(uint16_t index, uint32_t px_value); 56 | 57 | void setColorOrderRGB(); 58 | void setColorOrderGRB(); 59 | void setColorOrderBRG(); 60 | 61 | #if DEBUG 62 | void print_pixels(); 63 | int freeRam(); 64 | #endif 65 | 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /docs/multipin.md: -------------------------------------------------------------------------------- 1 | # Multi strip example 2 | 3 | This example uses johnny five to control multiple WS2812 strips using a custom 4 | firmata. These can be defined to work on any digital pin (except 0 & 1). 5 | 6 | To install the custom firmata, see the [Installation Guide](installation.md). 7 | 8 | ## Wiring 9 | 10 | Wire the neopixel strip up as shown below. 11 | 12 | ![Wiring diagram](breadboard/arduino_multipin_bb.png) 13 | 14 | ## Example code 15 | 16 | ```js 17 | var five = require("johnny-five"); 18 | var pixel = require("node-pixel"); 19 | 20 | var opts = {}; 21 | opts.port = process.argv[2] || ""; 22 | 23 | var board = new five.Board(opts); 24 | var strip = null; 25 | 26 | var fps = 20; // how many frames per second do you want to try? 27 | 28 | board.on("ready", function() { 29 | 30 | console.log("Board ready, lets add light"); 31 | 32 | strip = new pixel.Strip({ 33 | board: this, 34 | controller: "FIRMATA", 35 | strips: [ {pin: 9, length: 8}, {pin: 2, length: 17},] 36 | }); 37 | 38 | strip.on("ready", function() { 39 | 40 | console.log("Strip ready"); 41 | 42 | var colors = ["red", "green", "blue"]; 43 | var current_colors = [0,1,2]; 44 | var current_pos = [0,1,2]; 45 | var blinker = setInterval(function() { 46 | 47 | strip.color("#000"); // blanks it out 48 | for (var i=0; i< current_pos.length; i++) { 49 | if (++current_pos[i] >= strip.length) { 50 | current_pos[i] = 0; 51 | if (++current_colors[i] >= colors.length) current_colors[i] = 0; 52 | } 53 | strip.pixel(current_pos[i]).color(colors[current_colors[i]]); 54 | } 55 | 56 | strip.show(); 57 | }, 1000/fps); 58 | }); 59 | 60 | strip.on("error", function(err) { 61 | console.log(err); 62 | process.exit(); 63 | }); 64 | }); 65 | ``` 66 | 67 | ## Running 68 | 69 | To run the example: 70 | 71 | ``` 72 | node examples/multipin.js 73 | ``` 74 | 75 | You can optionally pass a port in as a parameter. 76 | -------------------------------------------------------------------------------- /examples/mega-multipin.js: -------------------------------------------------------------------------------- 1 | // This example shows how to use node-pixel 2 | // to control multiple strips on the same board 3 | 4 | const five = require('johnny-five'); 5 | const pixel = require('node-pixel'); 6 | 7 | const opts = {}; 8 | opts.port = process.argv[2] || ''; 9 | 10 | const board = new five.Board(opts); 11 | let strip = null; 12 | 13 | const fps = 10; // how many frames per second do you want to try? 14 | 15 | board.on('ready', function() { 16 | console.log('Board ready, lets add light'); 17 | 18 | strip = new pixel.Strip({ 19 | board: this, 20 | controller: 'FIRMATA', 21 | strips: [ 22 | {pin: 2, length: 8}, {pin: 3, length: 8}, 23 | {pin: 4, length: 8}, {pin: 5, length: 8}, 24 | {pin: 6, length: 8}, {pin: 7, length: 8}, 25 | {pin: 8, length: 8}, {pin: 9, length: 8} 26 | ] 27 | }); 28 | 29 | strip.on('ready', function() { 30 | console.log('Strip ready'); 31 | 32 | const strips = 8; 33 | const lengths = 8; 34 | 35 | const current_pos = new Array(); 36 | for (let i=0; i< strips; i++) { 37 | current_pos.push(new Array()); 38 | } 39 | 40 | // periodically drop a new item onto one of the strips randomly. 41 | const dripper = setInterval(function() { 42 | const s = Math.round(Math.random()*strips); 43 | try { 44 | current_pos[s].push(0); 45 | } catch (e) { 46 | if (e instanceof TypeError) { 47 | // this usually happens if we're splicing and writing at the 48 | // same time. 49 | return; 50 | } 51 | } 52 | }, 75); 53 | 54 | 55 | const iterator = setInterval(function() { 56 | strip.color('#000'); // blanks it out 57 | 58 | for (let i=0; i< current_pos.length; i++) { 59 | for (let j=0; j< current_pos[i].length; j++) { 60 | if (current_pos[i][j] >= 8) { 61 | current_pos[i].splice(j, 1);// remove the item 62 | } else { 63 | strip.pixel(i * lengths + current_pos[i][j]).color('#440'); 64 | current_pos[i][j]++; 65 | } 66 | } 67 | } 68 | strip.show(); 69 | }, 1000/fps); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /examples/rainbow-dynamic-multipin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This example shows how to use node-pixel to cycle colors through your 3 | * NeoPixel strip using Johnny-Five. 4 | * 5 | * adapted from the examples by @pierceray 6 | */ 7 | const five = require('johnny-five'); 8 | const pixel = require('node-pixel'); 9 | 10 | const opts = {}; 11 | opts.port = process.argv[2] || ''; 12 | 13 | const board = new five.Board(opts); 14 | let strip = null; 15 | 16 | /** 17 | * how many frames per second do you want to try? 18 | */ 19 | const fps = 30; 20 | 21 | board.on('ready', function() { 22 | console.log('Board ready, lets add light'); 23 | 24 | // Input a value 0 to 255 to get a color value. 25 | // The colors are a transition r - g - b - back to r. 26 | function colorWheel( WheelPos ) { 27 | let r; let g; let b; 28 | WheelPos = 255 - WheelPos; 29 | 30 | if ( WheelPos < 85 ) { 31 | r = 255 - WheelPos * 3; 32 | g = 0; 33 | b = WheelPos * 3; 34 | } else if (WheelPos < 170) { 35 | WheelPos -= 85; 36 | r = 0; 37 | g = WheelPos * 3; 38 | b = 255 - WheelPos * 3; 39 | } else { 40 | WheelPos -= 170; 41 | r = WheelPos * 3; 42 | g = 255 - WheelPos * 3; 43 | b = 0; 44 | } 45 | // returns a string with the rgb value to be used as the parameter 46 | return 'rgb(' + r +',' + g + ',' + b + ')'; 47 | } 48 | 49 | function dynamicRainbow( delay ) { 50 | console.log( 'dynamicRainbow' ); 51 | 52 | let showColor; 53 | let cwi = 0; // colour wheel index (current position on colour wheel) 54 | const foo = setInterval(function() { 55 | if (++cwi > 255) { 56 | cwi = 0; 57 | } 58 | 59 | for (let i = 0; i < strip.length; i++) { 60 | showColor = colorWheel( ( cwi+i ) & 255 ); 61 | strip.pixel( i ).color( showColor ); 62 | } 63 | strip.show(); 64 | }, 1000/delay); 65 | } 66 | 67 | // setup the node-pixel strip. 68 | strip = new pixel.Strip({ 69 | board: this, 70 | controller: 'FIRMATA', 71 | strips: [ {pin: 6, length: 8}, {pin: 7, length: 8}] 72 | }); 73 | 74 | strip.on('ready', function() { 75 | console.log("Strip ready, let's go"); 76 | dynamicRainbow(fps); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /examples/rainbow-dynamic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This example shows how to use node-pixel to cycle colors through your 3 | * NeoPixel strip using Johnny-Five. 4 | * https://vimeo.com/131618207 5 | * 6 | * created by @pierceray in June 2015 7 | */ 8 | const five = require('johnny-five'); 9 | const pixel = require('node-pixel'); 10 | 11 | const opts = {}; 12 | opts.port = process.argv[2] || ''; 13 | 14 | const board = new five.Board(opts); 15 | let strip = null; 16 | 17 | /** 18 | * how many frames per second do you want to try? 19 | */ 20 | const fps = 20; 21 | 22 | board.on('ready', function() { 23 | console.log('Board ready, lets add light'); 24 | 25 | // Input a value 0 to 255 to get a color value. 26 | // The colors are a transition r - g - b - back to r. 27 | function colorWheel( WheelPos ) { 28 | let r; let g; let b; 29 | WheelPos = 255 - WheelPos; 30 | 31 | if ( WheelPos < 85 ) { 32 | r = 255 - WheelPos * 3; 33 | g = 0; 34 | b = WheelPos * 3; 35 | } else if (WheelPos < 170) { 36 | WheelPos -= 85; 37 | r = 0; 38 | g = WheelPos * 3; 39 | b = 255 - WheelPos * 3; 40 | } else { 41 | WheelPos -= 170; 42 | r = WheelPos * 3; 43 | g = 255 - WheelPos * 3; 44 | b = 0; 45 | } 46 | // returns a string with the rgb value to be used as the parameter 47 | return 'rgb(' + r +',' + g + ',' + b + ')'; 48 | } 49 | 50 | function dynamicRainbow( delay ) { 51 | console.log( 'dynamicRainbow' ); 52 | 53 | let showColor; 54 | let cwi = 0; // colour wheel index (current position on colour wheel) 55 | const foo = setInterval(function() { 56 | if (++cwi > 255) { 57 | cwi = 0; 58 | } 59 | 60 | for (let i = 0; i < strip.length; i++) { 61 | showColor = colorWheel( ( cwi+i ) & 255 ); 62 | strip.pixel( i ).color( showColor ); 63 | } 64 | strip.show(); 65 | }, 1000/delay); 66 | } 67 | 68 | // setup the node-pixel strip. 69 | strip = new pixel.Strip({ 70 | data: 6, 71 | length: 17, // number of pixels in the strip. 72 | board: this, 73 | controller: 'FIRMATA' 74 | }); 75 | 76 | strip.on('ready', function() { 77 | console.log("Strip ready, let's go"); 78 | dynamicRainbow(fps); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /firmware/build/backpack/lw_ws2812.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * light weight WS2812 lib V2.1 - Arduino support 3 | * 4 | * Controls WS2811/WS2812/WS2812B RGB-LEDs 5 | * Author: Matthias Riegler 6 | * 7 | * Mar 07 2014: Added Arduino and C++ Library 8 | * 9 | * September 6, 2014: Added option to switch between most popular color orders 10 | * (RGB, GRB, and BRG) -- Windell H. Oskay 11 | * 12 | * License: GNU GPL v2 (see License.txt) 13 | */ 14 | 15 | #include "lw_ws2812.h" 16 | #include 17 | 18 | WS2812::WS2812(uint16_t num_leds, uint16_t offset) { 19 | // used if we know the length of the strip and 20 | // the offset from the start of the master array 21 | // up front 22 | init(num_leds, offset); 23 | } 24 | 25 | WS2812::WS2812(uint16_t num_leds) { 26 | // used when we know the lengths up front. 27 | init(num_leds); 28 | } 29 | 30 | WS2812::WS2812() { 31 | // used as an empty constructor for setup. 32 | init(0); 33 | } 34 | 35 | void WS2812::init(uint16_t num_leds, uint16_t offset) { 36 | 37 | set_length(num_leds); 38 | set_offset(offset); 39 | } 40 | 41 | void WS2812::init(uint16_t num_leds) { 42 | // assume offset is zeron in this case 43 | init(num_leds, 0); 44 | } 45 | 46 | void WS2812::set_offset(uint16_t master_offset) { 47 | offset = master_offset; 48 | } 49 | 50 | void WS2812::set_length(uint16_t num_leds) { 51 | count_led = num_leds; 52 | } 53 | 54 | uint16_t WS2812::get_length() { 55 | return count_led; 56 | } 57 | 58 | // TODO: Remove magic threes 59 | void WS2812::sync(uint8_t *px_array, uint8_t pixel_depth) { 60 | *ws2812_port_reg |= pinMask; // Enable DDR 61 | ws2812_sendarray_mask(px_array+(offset*pixel_depth), 62 | count_led * pixel_depth, 63 | pinMask,(uint8_t*) ws2812_port,(uint8_t*) ws2812_port_reg 64 | ); 65 | } 66 | 67 | WS2812::~WS2812() { 68 | } 69 | 70 | 71 | #ifndef ARDUINO 72 | void WS2812::setOutput(const volatile uint8_t* port, volatile uint8_t* reg, uint8_t pin) { 73 | pinMask = (1< 17 | 18 | WS2812::WS2812(uint16_t num_leds, uint16_t offset) { 19 | // used if we know the length of the strip and 20 | // the offset from the start of the master array 21 | // up front 22 | init(num_leds, offset); 23 | } 24 | 25 | WS2812::WS2812(uint16_t num_leds) { 26 | // used when we know the lengths up front. 27 | init(num_leds); 28 | } 29 | 30 | WS2812::WS2812() { 31 | // used as an empty constructor for setup. 32 | init(0); 33 | } 34 | 35 | void WS2812::init(uint16_t num_leds, uint16_t offset) { 36 | 37 | set_length(num_leds); 38 | set_offset(offset); 39 | } 40 | 41 | void WS2812::init(uint16_t num_leds) { 42 | // assume offset is zeron in this case 43 | init(num_leds, 0); 44 | } 45 | 46 | void WS2812::set_offset(uint16_t master_offset) { 47 | offset = master_offset; 48 | } 49 | 50 | void WS2812::set_length(uint16_t num_leds) { 51 | count_led = num_leds; 52 | } 53 | 54 | uint16_t WS2812::get_length() { 55 | return count_led; 56 | } 57 | 58 | // TODO: Remove magic threes 59 | void WS2812::sync(uint8_t *px_array, uint8_t pixel_depth) { 60 | *ws2812_port_reg |= pinMask; // Enable DDR 61 | ws2812_sendarray_mask(px_array+(offset*pixel_depth), 62 | count_led * pixel_depth, 63 | pinMask,(uint8_t*) ws2812_port,(uint8_t*) ws2812_port_reg 64 | ); 65 | } 66 | 67 | WS2812::~WS2812() { 68 | } 69 | 70 | 71 | #ifndef ARDUINO 72 | void WS2812::setOutput(const volatile uint8_t* port, volatile uint8_t* reg, uint8_t pin) { 73 | pinMask = (1< 17 | 18 | WS2812::WS2812(uint16_t num_leds, uint16_t offset) { 19 | // used if we know the length of the strip and 20 | // the offset from the start of the master array 21 | // up front 22 | init(num_leds, offset); 23 | } 24 | 25 | WS2812::WS2812(uint16_t num_leds) { 26 | // used when we know the lengths up front. 27 | init(num_leds); 28 | } 29 | 30 | WS2812::WS2812() { 31 | // used as an empty constructor for setup. 32 | init(0); 33 | } 34 | 35 | void WS2812::init(uint16_t num_leds, uint16_t offset) { 36 | 37 | set_length(num_leds); 38 | set_offset(offset); 39 | } 40 | 41 | void WS2812::init(uint16_t num_leds) { 42 | // assume offset is zeron in this case 43 | init(num_leds, 0); 44 | } 45 | 46 | void WS2812::set_offset(uint16_t master_offset) { 47 | offset = master_offset; 48 | } 49 | 50 | void WS2812::set_length(uint16_t num_leds) { 51 | count_led = num_leds; 52 | } 53 | 54 | uint16_t WS2812::get_length() { 55 | return count_led; 56 | } 57 | 58 | // TODO: Remove magic threes 59 | void WS2812::sync(uint8_t *px_array, uint8_t pixel_depth) { 60 | *ws2812_port_reg |= pinMask; // Enable DDR 61 | ws2812_sendarray_mask(px_array+(offset*pixel_depth), 62 | count_led * pixel_depth, 63 | pinMask,(uint8_t*) ws2812_port,(uint8_t*) ws2812_port_reg 64 | ); 65 | } 66 | 67 | WS2812::~WS2812() { 68 | } 69 | 70 | 71 | #ifndef ARDUINO 72 | void WS2812::setOutput(const volatile uint8_t* port, volatile uint8_t* reg, uint8_t pin) { 73 | pinMask = (1<= strip.length) { 61 | current_pos[i] = 0; 62 | if (++current_colors[i] >= colors.length) current_colors[i] = 0; 63 | } 64 | strip.pixel(current_pos[i]).color(colors[current_colors[i]]); 65 | } 66 | 67 | strip.show(); 68 | }, 1000/fps); 69 | }); 70 | }); 71 | ``` 72 | 73 | ## Running 74 | 75 | To run the example: 76 | 77 | ``` 78 | node examples/johnnyfive-i2c.js 79 | ``` 80 | 81 | You can optionally pass a port in as a parameter. 82 | -------------------------------------------------------------------------------- /docs/rainbow-static.md: -------------------------------------------------------------------------------- 1 | # Creating a rainbow effect 2 | 3 | This example uses a 16 LED strip and a custom rainbow calculator to create a 4 | nice rainbow over a static area. This example uses a custom firmata. 5 | 6 | To install the custom firmata, see the [Installation Guide](installation.md). 7 | 8 | ## Wiring 9 | 10 | Wire the neopixel panel up as shown below. 11 | 12 | ![Wiring diagram](breadboard/custom_firmata_bb.png) 13 | 14 | ## Example code 15 | 16 | ```js 17 | var five = require("johnny-five"); 18 | var pixel = require("node-pixel"); 19 | 20 | var opts = {}; 21 | opts.port = process.argv[2] || ""; 22 | 23 | var board = new five.Board(opts); 24 | var strip = null; 25 | 26 | board.on("ready", function() { 27 | 28 | console.log("Board ready, lets add light"); 29 | 30 | // setup the node-pixel strip. 31 | strip = new pixel.Strip({ 32 | data: 6, 33 | length: 16, // number of pixels in the strip. 34 | board: this, 35 | controller: "FIRMATA" 36 | }); 37 | 38 | strip.on("ready", function() { 39 | console.log("Strip ready, let's go"); 40 | 41 | staticRainbow(); 42 | }); 43 | 44 | function staticRainbow(){ 45 | console.log('staticRainbow'); 46 | 47 | var showColor; 48 | for(var i = 0; i < strip.length; i++) { 49 | showColor = colorWheel( ( i*256 / strip.length ) & 255 ); 50 | strip.pixel( i ).color( showColor ); 51 | } 52 | strip.show(); 53 | } 54 | 55 | // Input a value 0 to 255 to get a color value. 56 | // The colours are a transition r - g - b - back to r. 57 | function colorWheel( WheelPos ){ 58 | var r,g,b; 59 | WheelPos = 255 - WheelPos; 60 | 61 | if ( WheelPos < 85 ) { 62 | r = 255 - WheelPos * 3; 63 | g = 0; 64 | b = WheelPos * 3; 65 | } else if (WheelPos < 170) { 66 | WheelPos -= 85; 67 | r = 0; 68 | g = WheelPos * 3; 69 | b = 255 - WheelPos * 3; 70 | } else { 71 | WheelPos -= 170; 72 | r = WheelPos * 3; 73 | g = 255 - WheelPos * 3; 74 | b = 0; 75 | } 76 | // returns a string with the rgb value to be used as the parameter 77 | return "rgb(" + r +"," + g + "," + b + ")"; 78 | } 79 | 80 | }); 81 | ``` 82 | 83 | ## Running 84 | 85 | To run the example: 86 | 87 | ``` 88 | node examples/rainbow-static.js 89 | ``` 90 | 91 | You can optionally pass a port in as a parameter. 92 | -------------------------------------------------------------------------------- /firmware/build/node_pixel_firmata/FirmataMarshaller.h: -------------------------------------------------------------------------------- 1 | /* 2 | FirmataMarshaller.h 3 | Copyright (c) 2006-2008 Hans-Christoph Steiner. All rights reserved. 4 | Copyright (C) 2009-2016 Jeff Hoefs. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | See file LICENSE.txt for further informations on licensing terms. 12 | */ 13 | 14 | #ifndef FirmataMarshaller_h 15 | #define FirmataMarshaller_h 16 | 17 | #if defined(__cplusplus) && !defined(ARDUINO) 18 | #include 19 | #include 20 | #else 21 | #include 22 | #include 23 | #endif 24 | 25 | #include 26 | 27 | namespace firmata { 28 | 29 | class FirmataMarshaller 30 | { 31 | friend class FirmataClass; 32 | 33 | public: 34 | /* constructors */ 35 | FirmataMarshaller(); 36 | 37 | /* public methods */ 38 | void begin(Stream &s); 39 | void end(); 40 | 41 | /* serial send handling */ 42 | void queryFirmwareVersion(void) const; 43 | void queryVersion(void) const; 44 | void reportAnalogDisable(uint8_t pin) const; 45 | void reportAnalogEnable(uint8_t pin) const; 46 | void reportDigitalPortDisable(uint8_t portNumber) const; 47 | void reportDigitalPortEnable(uint8_t portNumber) const; 48 | void sendAnalog(uint8_t pin, uint16_t value) const; 49 | void sendAnalogMappingQuery(void) const; 50 | void sendCapabilityQuery(void) const; 51 | void sendDigital(uint8_t pin, uint8_t value) const; 52 | void sendDigitalPort(uint8_t portNumber, uint16_t portData) const; 53 | void sendFirmwareVersion(uint8_t major, uint8_t minor, size_t bytec, uint8_t *bytev) const; 54 | void sendVersion(uint8_t major, uint8_t minor) const; 55 | void sendPinMode(uint8_t pin, uint8_t config) const; 56 | void sendPinStateQuery(uint8_t pin) const; 57 | void sendString(const char *string) const; 58 | void sendSysex(uint8_t command, size_t bytec, uint8_t *bytev) const; 59 | void setSamplingInterval(uint16_t interval_ms) const; 60 | void systemReset(void) const; 61 | 62 | private: 63 | /* utility methods */ 64 | void reportAnalog(uint8_t pin, bool stream_enable) const; 65 | void reportDigitalPort(uint8_t portNumber, bool stream_enable) const; 66 | void sendExtendedAnalog(uint8_t pin, size_t bytec, uint8_t * bytev) const; 67 | void encodeByteStream (size_t bytec, uint8_t * bytev, size_t max_bytes = 0) const; 68 | 69 | Stream * FirmataStream; 70 | }; 71 | 72 | } // namespace firmata 73 | 74 | #endif /* FirmataMarshaller_h */ 75 | 76 | -------------------------------------------------------------------------------- /examples/channel_fade.js: -------------------------------------------------------------------------------- 1 | // This example does a fade through each of the 7 primary colour channels 2 | // it's primarily used to ensure we get a good transition and allows you 3 | // to tune gamma corrections if you need to. 4 | // This example shows how to use node-pixel using Johnny Five as the 5 | // hook for the board. 6 | 7 | const five = require('johnny-five'); 8 | const pixel = require('node-pixel'); 9 | 10 | const opts = {}; 11 | opts.port = process.argv[2] || ''; 12 | 13 | const board = new five.Board(opts); 14 | let strip = null; 15 | 16 | const fps = 40; // how many frames per second do you want to try? 17 | 18 | board.on('ready', function() { 19 | console.log('Board ready, lets add light'); 20 | 21 | strip = new pixel.Strip({ 22 | board: this, 23 | controller: 'FIRMATA', 24 | strips: [ {pin: 6, length: 8}, {pin: 8, length: 8}], 25 | gamma: 2.8 26 | }); 27 | 28 | strip.on('ready', function() { 29 | console.log("Strip ready, let's go"); 30 | 31 | strip.color('#000000'); 32 | strip.show(); 33 | const colors = ['red', 'green', 'blue', 'yellow', 'cyan', 'magenta']; 34 | let current_color = 0; 35 | let fade_level = 0; 36 | let fade_up = true; 37 | const fader = setInterval(function() { 38 | if (fade_up) { 39 | // fading upwards, if we hit the top then turn around 40 | // and go back down again. 41 | if (++fade_level > 255) { 42 | fade_up = false; 43 | } 44 | } else { 45 | if (--fade_level < 0) { 46 | fade_up = true; 47 | fade_level = 0; 48 | if (++current_color >= colors.length) current_color = 0; 49 | } 50 | } 51 | 52 | let hc = ''; 53 | switch (colors[current_color]) { 54 | case 'red': 55 | hc = `rgb(${fade_level}, 0, 0)`; 56 | break; 57 | case 'green': 58 | hc = `rgb(0, ${fade_level}, 0)`; 59 | break; 60 | case 'blue': 61 | hc = `rgb(0, 0, ${fade_level})`; 62 | break; 63 | case 'white': 64 | hc = `rgb(${fade_level}, ${fade_level}, ${fade_level})`; 65 | break; 66 | case 'yellow': 67 | hc = `rgb(${fade_level}, ${fade_level}, 0)`; 68 | break; 69 | case 'magenta': 70 | hc = `rgb(${fade_level}, 0, ${fade_level})`; 71 | break; 72 | case 'cyan': 73 | hc = `rgb(0, ${fade_level}, ${fade_level})`; 74 | break; 75 | default: 76 | break; 77 | } 78 | 79 | // need to do this by pixel 80 | for (let i = 0; i < strip.length; i++) { 81 | strip.pixel(i).color(hc); 82 | } 83 | // strip.color(hc); 84 | strip.show(); 85 | }, 1000/fps); 86 | }); 87 | }); 88 | 89 | -------------------------------------------------------------------------------- /docs/rainbow-dynamic.md: -------------------------------------------------------------------------------- 1 | # Creating a rainbow effect 2 | 3 | This example uses a 16 LED strip and a custom rainbow calculator to create a 4 | nice rainbow that moves dynamically. This example uses a custom firmata. 5 | 6 | To install the custom firmata, see the [Installation Guide](installation.md). 7 | 8 | ## Wiring 9 | 10 | Wire the neopixel strip up as shown below. 11 | 12 | ![Wiring diagram](breadboard/custom_firmata_bb.png) 13 | 14 | ## Example code 15 | 16 | ```js 17 | var five = require("johnny-five"); 18 | var pixel = require("node-pixel"); 19 | 20 | var opts = {}; 21 | opts.port = process.argv[2] || ""; 22 | 23 | var board = new five.Board(opts); 24 | var strip = null; 25 | 26 | /** 27 | * how many frames per second do you want to try? 28 | */ 29 | var fps = 20; 30 | 31 | board.on("ready", function() { 32 | 33 | console.log("Board ready, lets add light"); 34 | 35 | // setup the node-pixel strip. 36 | strip = new pixel.Strip({ 37 | data: 6, 38 | length: 17, // number of pixels in the strip. 39 | board: this, 40 | controller: "FIRMATA" 41 | }); 42 | 43 | strip.on("ready", function() { 44 | console.log("Strip ready, let's go"); 45 | dynamicRainbow(fps); 46 | }); 47 | 48 | function dynamicRainbow( delay ){ 49 | console.log( 'dynamicRainbow' ); 50 | 51 | var showColor; 52 | var cwi = 0; // colour wheel index (current position on colour wheel) 53 | var foo = setInterval(function(){ 54 | if (++cwi > 255) { 55 | cwi = 0; 56 | } 57 | 58 | for(var i = 0; i < strip.length; i++) { 59 | showColor = colorWheel( ( cwi+i ) & 255 ); 60 | strip.pixel( i ).color( showColor ); 61 | } 62 | strip.show(); 63 | }, 1000/delay); 64 | } 65 | 66 | // Input a value 0 to 255 to get a color value. 67 | // The colors are a transition r - g - b - back to r. 68 | function colorWheel( WheelPos ){ 69 | var r,g,b; 70 | WheelPos = 255 - WheelPos; 71 | 72 | if ( WheelPos < 85 ) { 73 | r = 255 - WheelPos * 3; 74 | g = 0; 75 | b = WheelPos * 3; 76 | } else if (WheelPos < 170) { 77 | WheelPos -= 85; 78 | r = 0; 79 | g = WheelPos * 3; 80 | b = 255 - WheelPos * 3; 81 | } else { 82 | WheelPos -= 170; 83 | r = WheelPos * 3; 84 | g = 255 - WheelPos * 3; 85 | b = 0; 86 | } 87 | // returns a string with the rgb value to be used as the parameter 88 | return "rgb(" + r +"," + g + "," + b + ")"; 89 | } 90 | 91 | }); 92 | ``` 93 | 94 | ## Running 95 | 96 | To run the example: 97 | 98 | ``` 99 | node examples/rainbow-dynamic.js 100 | ``` 101 | 102 | You can optionally pass a port in as a parameter. 103 | -------------------------------------------------------------------------------- /docs/rainbow-dynamic-multipin.md: -------------------------------------------------------------------------------- 1 | # Creating a rainbow effect with multiple strips 2 | 3 | This example uses a 16 LED strip and a custom rainbow calculator to create a 4 | nice rainbow that moves dynamically across multiple pins. This example uses a custom firmata. 5 | 6 | To install the custom firmata, see the [Installation Guide](installation.md). 7 | 8 | ## Wiring 9 | 10 | Wire the neopixel strip up as shown below. 11 | 12 | ![Wiring diagram](breadboard/arduino_multipin_bb.png) 13 | 14 | ## Example code 15 | 16 | ```js 17 | var five = require("johnny-five"); 18 | var pixel = require("node-pixel"); 19 | 20 | var opts = {}; 21 | opts.port = process.argv[2] || ""; 22 | 23 | var board = new five.Board(opts); 24 | var strip = null; 25 | 26 | /** 27 | * how many frames per second do you want to try? 28 | */ 29 | var fps = 30; 30 | 31 | board.on("ready", function() { 32 | 33 | console.log("Board ready, lets add light"); 34 | 35 | // setup the node-pixel strip. 36 | strip = new pixel.Strip({ 37 | board: this, 38 | controller: "FIRMATA", 39 | strips: [ {pin: 2, length: 8}, {pin: 9, length: 17}], 40 | }); 41 | 42 | strip.on("ready", function() { 43 | console.log("Strip ready, let's go"); 44 | dynamicRainbow(fps); 45 | }); 46 | 47 | function dynamicRainbow( delay ){ 48 | console.log( 'dynamicRainbow' ); 49 | 50 | var showColor; 51 | var cwi = 0; // colour wheel index (current position on colour wheel) 52 | var foo = setInterval(function(){ 53 | if (++cwi > 255) { 54 | cwi = 0; 55 | } 56 | 57 | for(var i = 0; i < strip.length; i++) { 58 | showColor = colorWheel( ( cwi+i ) & 255 ); 59 | strip.pixel( i ).color( showColor ); 60 | } 61 | strip.show(); 62 | }, 1000/delay); 63 | } 64 | 65 | // Input a value 0 to 255 to get a color value. 66 | // The colors are a transition r - g - b - back to r. 67 | function colorWheel( WheelPos ){ 68 | var r,g,b; 69 | WheelPos = 255 - WheelPos; 70 | 71 | if ( WheelPos < 85 ) { 72 | r = 255 - WheelPos * 3; 73 | g = 0; 74 | b = WheelPos * 3; 75 | } else if (WheelPos < 170) { 76 | WheelPos -= 85; 77 | r = 0; 78 | g = WheelPos * 3; 79 | b = 255 - WheelPos * 3; 80 | } else { 81 | WheelPos -= 170; 82 | r = WheelPos * 3; 83 | g = 255 - WheelPos * 3; 84 | b = 0; 85 | } 86 | // returns a string with the rgb value to be used as the parameter 87 | return "rgb(" + r +"," + g + "," + b + ")"; 88 | } 89 | 90 | }); 91 | ``` 92 | 93 | ## Running 94 | 95 | To run the example: 96 | 97 | ``` 98 | node examples/rainbow-dynamic-multipin.js 99 | ``` 100 | 101 | You can optionally pass a port in as a parameter. 102 | -------------------------------------------------------------------------------- /firmware/src/README.md: -------------------------------------------------------------------------------- 1 | # Firmware development guide 2 | 3 | In order to manage dependencies with minimal duplication and to build firmware 4 | for specific targets a build process is used. You will need to make sure you 5 | have npm installed the development dependencies for this to work. 6 | 7 | ## SRC structure 8 | 9 | ``` 10 | |-- src 11 | |-- controller_src 12 | |-- node_pixel_firmata 13 | |-- backpack 14 | |-- libs 15 | |-- firmata 16 | |-- neopixel 17 | |-- ws2812 18 | ``` 19 | 20 | The `controller_src` directory contains target firmwares that need to be made. 21 | These are all in one location for convenience. The `libs` directory comtains 22 | the common library dependencies for the firmware. These are pulled into the 23 | relevant locations during the build process. 24 | 25 | ## Dev environment set up 26 | 27 | This is all built using the `arduino-cli` command line tools. 28 | 29 | Firstly - install the arduino-cli 30 | [according to the directions here](https://arduino.github.io/arduino-cli/latest/installation/) 31 | on the arduino CLI site. 32 | 33 | You should also view the [getting started guide](https://arduino.github.io/arduino-cli/latest/getting-started/) 34 | because you will need to ensure that you have all of the cores etc all set up 35 | for use in your default configuration. 36 | 37 | The main cores you will need are: 38 | 39 | * arduino:avr 40 | 41 | You may also need to install some core libs as well. You can do this with the 42 | `arduino-cli lib install` command. Some that may not be included by default are 43 | listed below (note capitalisation): 44 | 45 | * Servo 46 | 47 | ## Make 48 | 49 | A `Makefile` is included that has pretty much everything you need in it. 50 | 51 | `make build` will get all the files to the right location and then you can use 52 | the arduino cli tools or the arduino IDE to compile and flash to the board. 53 | 54 | `make compile` is used to build and compile all targets and will use the arduino 55 | command like invocation. 56 | 57 | A useful trick is if you just want to build one target use `make uno` or `make nano` 58 | and this will build just the backpack and firmata for that target. 59 | 60 | ## Uploading a file for testing 61 | 62 | The easiest way to do this is to build and upload using the arduino IDE. If you 63 | don't want to do that and want to test the actual build process to make sure 64 | the hex file you're outputting makes sense then you can do it using avrgirl 65 | 66 | eg: 67 | 68 | ``` 69 | npx avrgirl-arduino flash -f firmware/bin/firmata/uno/node_pixel_firmata.ino.hex -a uno 70 | ``` 71 | 72 | Will flash the output firmata hexfile for uno to the board. 73 | 74 | ## Building for deployment 75 | 76 | If there are any mods to the src files then run `make build` to make sure 77 | everything is updated properly. 78 | 79 | 80 | ## TODO 81 | 82 | * Get issue fixed with arduino cli in order to build hex files automatically for 83 | all major boards. 84 | * Use the skt500 programmer to be able to install hex files directly without arduino 85 | 86 | -------------------------------------------------------------------------------- /docs/multipin-i2c.md: -------------------------------------------------------------------------------- 1 | # Multi strip example 2 | 3 | This example uses johnny five to control multiple WS2812 strips using an I2C 4 | backpack 5 | 6 | To install the I2C backpack, see the [Installation Guide](installation.md). 7 | 8 | ## Wiring 9 | 10 | Wire the neopixel strip up as shown below. This can be done on any I2C compatible 11 | board that Johnny Five supports. This example uses a Raspberry Pi with 2 strips 12 | attached to the backpack. For the Raspberry Pi to work with the backpack, include the [raspi-io](https://www.npmjs.com/package/raspi-io) plugin to Johnny Five when initializing the Board. 13 | 14 | ![Wiring diagram](breadboard/i2c_backpack_multipin_bb.png) 15 | 16 | The example below uses two strips attached to the backpack connected to the host 17 | Arduino Uno. 18 | 19 | ![Wiring diagram](breadboard/i2c_backpack_arduino_multipin_bb.png) 20 | 21 | ### I2C LED pins 22 | 23 | Note that you can't specify the pins to use when using I2C. As such you must 24 | start with pin 0 and work upwards from there to 8 max. 25 | 26 | ## Example code 27 | 28 | ```js 29 | var five = require("johnny-five"); 30 | var pixel = require("node-pixel"); 31 | 32 | var opts = {}; 33 | opts.port = process.argv[2] || ""; 34 | 35 | var board = new five.Board(opts); 36 | var strip = null; 37 | 38 | var fps = 20; // how many frames per second do you want to try? 39 | 40 | board.on("ready", function() { 41 | 42 | console.log("Board ready, lets add light"); 43 | 44 | strip = new pixel.Strip({ 45 | board: this, 46 | controller: "I2CBACKPACK", 47 | color_order: pixel.COLOR_ORDER.GRB, 48 | strips: [ 17,8 ] 49 | }); 50 | 51 | strip.on("ready", function() { 52 | 53 | console.log("Strip ready"); 54 | 55 | var colors = ["red", "green", "blue"]; 56 | var current_colors = [0,1,2]; 57 | var current_pos = [0,1,2]; 58 | var blinker = setInterval(function() { 59 | 60 | strip.color("#000"); // blanks it out 61 | for (var i=0; i< current_pos.length; i++) { 62 | if (++current_pos[i] >= strip.length) { 63 | current_pos[i] = 0; 64 | if (++current_colors[i] >= colors.length) current_colors[i] = 0; 65 | } 66 | strip.pixel(current_pos[i]).color(colors[current_colors[i]]); 67 | } 68 | 69 | strip.show(); 70 | }, 1000/fps); 71 | }); 72 | 73 | strip.on("error", function(err) { 74 | console.log(err); 75 | process.exit(); 76 | }); 77 | }); 78 | ``` 79 | 80 | When using a Raspberry Pi: 81 | 82 | ```js 83 | var Raspi = require("raspi-io"); 84 | var five = require("johnny-five"); 85 | 86 | const board = new five.Board({ 87 | io: new Raspi() 88 | }); 89 | 90 | // .. Rest of your code 91 | ``` 92 | 93 | 94 | ## Running 95 | 96 | To run the example: 97 | 98 | ``` 99 | node examples/multipin-i2c.js 100 | ``` 101 | 102 | You can optionally pass a port in as a parameter. 103 | -------------------------------------------------------------------------------- /firmware/src/libs/protocol.md: -------------------------------------------------------------------------------- 1 | # Protocol used to talk to the pixel strip using firmata 2 | 3 | This is the definition of the protocol used to talk to the LED strips. The 4 | protocol is largely the same whether you are using custom firmata or I2C 5 | backpack in order to keep things simple. 6 | 7 | There are some exceptions to this, notably around configuration. 8 | 9 | ## Multi strip protocol consideration. 10 | 11 | In the instance of multiple physical strips being attached to multiple pins on 12 | the board, the protocol interface remains the same however the strips are 13 | _logically_ joined end to end in the order they are allocated during the config 14 | phase. It is up to the caller to pass the appropriate pixel value in order 15 | to ensure that the strips are mapped to the correct sequence. 16 | 17 | ## Protocol Pixel Command Instructions. 18 | 19 | ``` 20 | #define PIXEL_OFF 0x00 // set strip to be off 21 | #define PIXEL_CONFIG 0x01 // configure the strip 22 | #define PIXEL_SHOW 0x02 // latch the pixels and show them 23 | #define PIXEL_SET_PIXEL 0x03 // set the color value of pixel n using 32bit packed color value 24 | #define PIXEL_SET_STRIP 0x04 // set color of whole strip 25 | #define PIXEL_SHIFT 0x05 // shift all the pixels n places along the strip 26 | // PIXEL_RESERVED 0x08-0x0F // Reserved for future instructions. 27 | ``` 28 | 29 | ### Config 30 | 31 | Sets the pins that the pixel strips use and length of the strips. This is given 32 | using 10 bits thus providing for 1023 pixels on each strip (note this is a 33 | theoretical maximum). If using firmata then a pin number will need to be supplied 34 | whereas a backpack does not. 35 | 36 | ``` 37 | 0 START_SYSEX 0xF0 38 | 1 PIXEL_COMMAND 0x51 39 | 2 PIXEL_CONFIG 0x01 40 | 3 Colour order (int value use upper 2 bits 0-3 GRB=0 default) 41 | 3 [Pin Number] OPTIONAL (int value use lower 5 bits Pin 0-31) 42 | 4 10 bit strand length LSB 43 | 5 10 bit strand length MSB (upper 4 bits future reserved) 44 | ... Repeat bytes 3..5 for up to 8 LED strips max 45 | N END_SYSEX 0xF7 46 | ``` 47 | 48 | #### Colour order definitions. 49 | 50 | Colour ordering is given by: 51 | 52 | ``` 53 | 0x00 GRB (Default) 54 | 0x01 RGB 55 | 0x02 BRG 56 | ``` 57 | 58 | ### Show 59 | 60 | Latches the frame and triggers sending the data down the wire. 61 | 62 | ``` 63 | 0 START_SYSEX 0xF0 64 | 1 PIXEL_COMMAND 0x51 65 | 2 PIXEL_SHOW 0x02 66 | 3 END_SYSEX 0xF7 67 | ``` 68 | 69 | ### Set Strip Colour 70 | 71 | Sets the whole strip to a particular color 72 | 73 | ``` 74 | 0 START_SYSEX 0xF0 75 | 1 PIXEL_COMMAND 0x51 76 | 2 PIXEL_SET_STRIP 0x04 77 | 3 24 bit packed RGB color value LSB 78 | 4 24 bit packed RGB color value lower middle bits 79 | 5 24 bit packed RGB color value upper middle bits 80 | 6 24 bit packed RGB color value MSB 81 | 7 END_SYSEX 0xF7 82 | ``` 83 | 84 | ### Set Pixel Colour 85 | 86 | Sets a given pixel to a particular color 87 | 88 | ``` 89 | 0 START_SYSEX 0xF0 90 | 1 PIXEL_COMMAND 0x51 91 | 2 PIXEL_SET_PIXEL 0x03 92 | 3 max 14 bit index of pixel in strip LSB 93 | 4 max 14 bit index of pixel in strip MSB 94 | 5 24 bit packed RGB color value LSB 95 | 6 24 bit packed RGB color value lower middle bits 96 | 7 24 bit packed RGB color value upper middle bits 97 | 8 24 bit packed RGB color value MSB 98 | 9 END_SYSEX 0xF7 99 | ``` 100 | 101 | ### Shift pixels - PROPOSAL 102 | 103 | Shifts the pixels along the strip N places. Direction flag specifies direction 104 | and wrap flag determines if pixels move off the "end" of the strip are placed 105 | back on the other end. 106 | 107 | ``` 108 | 0 START_SYSEX 0xF0 109 | 1 PIXEL_COMMAND 0x51 110 | 2 PIXEL_SHIFT 0x05 111 | 3 Shift 112 | 3 bits 0-5 provide number of LEDs to shift (range 0-31), 113 | 3 bit 6 direction 0=add to current position, 1=subtract 114 | 3 bit 7 wrap behaviour 0=no wrapping, 1= wrapping 115 | 4 END_SYSEX 0xF7 116 | ``` 117 | 118 | 119 | -------------------------------------------------------------------------------- /firmware/build/backpack/light_ws2812.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * light weight WS2812 lib V2.1 - Arduino support 3 | * 4 | * Controls WS2811/WS2812/WS2812B RGB-LEDs 5 | * Author: Tim (cpldcpu@gmail.com) 6 | * 7 | * Jan 18th, 2014 v2.0b Initial Version 8 | * March 7th, 2014 v2.1 Added option to retarget the port register during runtime 9 | * Removes inlining to allow compiling with c++ 10 | * 11 | * License: GNU GPL v2 (see License.txt) 12 | */ 13 | 14 | #include "lw_ws2812.h" 15 | 16 | /* 17 | This routine writes an array of bytes with RGB values to the Dataout pin 18 | using the fast 800kHz clockless WS2811/2812 protocol. 19 | */ 20 | 21 | // Timing in ns 22 | #define w_zeropulse 350 23 | #define w_onepulse 900 24 | #define w_totalperiod 1250 25 | 26 | // Fixed cycles used by the inner loop 27 | #define w_fixedlow 3 28 | #define w_fixedhigh 6 29 | #define w_fixedtotal 10 30 | 31 | // Insert NOPs to match the timing, if possible 32 | #define w_zerocycles (((F_CPU/1000)*w_zeropulse )/1000000) 33 | #define w_onecycles (((F_CPU/1000)*w_onepulse +500000)/1000000) 34 | #define w_totalcycles (((F_CPU/1000)*w_totalperiod +500000)/1000000) 35 | 36 | // w1 - nops between rising edge and falling edge - low 37 | #define w1 (w_zerocycles-w_fixedlow) 38 | // w2 nops between fe low and fe high 39 | #define w2 (w_onecycles-w_fixedhigh-w1) 40 | // w3 nops to complete loop 41 | #define w3 (w_totalcycles-w_fixedtotal-w1-w2) 42 | 43 | #if w1>0 44 | #define w1_nops w1 45 | #else 46 | #define w1_nops 0 47 | #endif 48 | 49 | // The only critical timing parameter is the minimum pulse length of the "0" 50 | // Warn or throw error if this timing can not be met with current F_CPU settings. 51 | #define w_lowtime ((w1_nops+w_fixedlow)*1000000)/(F_CPU/1000) 52 | #if w_lowtime>550 53 | #error "Light_ws2812: Sorry, the clock speed is too low. Did you set F_CPU correctly?" 54 | #elif w_lowtime>450 55 | #warning "Light_ws2812: The timing is critical and may only work on WS2812B, not on WS2812(S)." 56 | #warning "Please consider a higher clockspeed, if possible" 57 | #endif 58 | 59 | #if w2>0 60 | #define w2_nops w2 61 | #else 62 | #define w2_nops 0 63 | #endif 64 | 65 | #if w3>0 66 | #define w3_nops w3 67 | #else 68 | #define w3_nops 0 69 | #endif 70 | 71 | #define w_nop1 "nop \n\t" 72 | #define w_nop2 "rjmp .+0 \n\t" 73 | #define w_nop4 w_nop2 w_nop2 74 | #define w_nop8 w_nop4 w_nop4 75 | #define w_nop16 w_nop8 w_nop8 76 | 77 | void WS2812::ws2812_sendarray_mask( 78 | uint8_t *data, uint16_t datlen, 79 | uint8_t maskhi,uint8_t *port, uint8_t *portreg 80 | ) { 81 | // data is a pointer to an array of bytes 82 | // start is the offset from the start of the array 83 | // datlen is how many bytes to go write. 84 | 85 | uint8_t curbyte,ctr,masklo; 86 | uint8_t sreg_prev; 87 | 88 | masklo = ~maskhi & *port; 89 | maskhi |= *port; 90 | sreg_prev=SREG; 91 | cli(); 92 | 93 | while (datlen--) { 94 | curbyte=*data++; 95 | 96 | asm volatile( 97 | " ldi %0,8 \n\t" 98 | "loop%=: \n\t" 99 | " st X,%3 \n\t" // '1' [02] '0' [02] - re 100 | #if (w1_nops&1) 101 | w_nop1 102 | #endif 103 | #if (w1_nops&2) 104 | w_nop2 105 | #endif 106 | #if (w1_nops&4) 107 | w_nop4 108 | #endif 109 | #if (w1_nops&8) 110 | w_nop8 111 | #endif 112 | #if (w1_nops&16) 113 | w_nop16 114 | #endif 115 | " sbrs %1,7 \n\t" // '1' [04] '0' [03] 116 | " st X,%4 \n\t" // '1' [--] '0' [05] - fe-low 117 | " lsl %1 \n\t" // '1' [05] '0' [06] 118 | #if (w2_nops&1) 119 | w_nop1 120 | #endif 121 | #if (w2_nops&2) 122 | w_nop2 123 | #endif 124 | #if (w2_nops&4) 125 | w_nop4 126 | #endif 127 | #if (w2_nops&8) 128 | w_nop8 129 | #endif 130 | #if (w2_nops&16) 131 | w_nop16 132 | #endif 133 | " brcc skipone%= \n\t" // '1' [+1] '0' [+2] - 134 | " st X,%4 \n\t" // '1' [+3] '0' [--] - fe-high 135 | "skipone%=: " // '1' [+3] '0' [+2] - 136 | 137 | #if (w3_nops&1) 138 | w_nop1 139 | #endif 140 | #if (w3_nops&2) 141 | w_nop2 142 | #endif 143 | #if (w3_nops&4) 144 | w_nop4 145 | #endif 146 | #if (w3_nops&8) 147 | w_nop8 148 | #endif 149 | #if (w3_nops&16) 150 | w_nop16 151 | #endif 152 | 153 | " dec %0 \n\t" // '1' [+4] '0' [+3] 154 | " brne loop%=\n\t" // '1' [+5] '0' [+4] 155 | : "=&d" (ctr) 156 | // : "r" (curbyte), "I" (_SFR_IO_ADDR(ws2812_PORTREG)), "r" (maskhi), "r" (masklo) 157 | : "r" (curbyte), "x" (port), "r" (maskhi), "r" (masklo) 158 | ); 159 | } 160 | 161 | SREG=sreg_prev; 162 | sei(); 163 | } 164 | -------------------------------------------------------------------------------- /firmware/src/libs/lightws2812/light_ws2812.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * light weight WS2812 lib V2.1 - Arduino support 3 | * 4 | * Controls WS2811/WS2812/WS2812B RGB-LEDs 5 | * Author: Tim (cpldcpu@gmail.com) 6 | * 7 | * Jan 18th, 2014 v2.0b Initial Version 8 | * March 7th, 2014 v2.1 Added option to retarget the port register during runtime 9 | * Removes inlining to allow compiling with c++ 10 | * 11 | * License: GNU GPL v2 (see License.txt) 12 | */ 13 | 14 | #include "lw_ws2812.h" 15 | 16 | /* 17 | This routine writes an array of bytes with RGB values to the Dataout pin 18 | using the fast 800kHz clockless WS2811/2812 protocol. 19 | */ 20 | 21 | // Timing in ns 22 | #define w_zeropulse 350 23 | #define w_onepulse 900 24 | #define w_totalperiod 1250 25 | 26 | // Fixed cycles used by the inner loop 27 | #define w_fixedlow 3 28 | #define w_fixedhigh 6 29 | #define w_fixedtotal 10 30 | 31 | // Insert NOPs to match the timing, if possible 32 | #define w_zerocycles (((F_CPU/1000)*w_zeropulse )/1000000) 33 | #define w_onecycles (((F_CPU/1000)*w_onepulse +500000)/1000000) 34 | #define w_totalcycles (((F_CPU/1000)*w_totalperiod +500000)/1000000) 35 | 36 | // w1 - nops between rising edge and falling edge - low 37 | #define w1 (w_zerocycles-w_fixedlow) 38 | // w2 nops between fe low and fe high 39 | #define w2 (w_onecycles-w_fixedhigh-w1) 40 | // w3 nops to complete loop 41 | #define w3 (w_totalcycles-w_fixedtotal-w1-w2) 42 | 43 | #if w1>0 44 | #define w1_nops w1 45 | #else 46 | #define w1_nops 0 47 | #endif 48 | 49 | // The only critical timing parameter is the minimum pulse length of the "0" 50 | // Warn or throw error if this timing can not be met with current F_CPU settings. 51 | #define w_lowtime ((w1_nops+w_fixedlow)*1000000)/(F_CPU/1000) 52 | #if w_lowtime>550 53 | #error "Light_ws2812: Sorry, the clock speed is too low. Did you set F_CPU correctly?" 54 | #elif w_lowtime>450 55 | #warning "Light_ws2812: The timing is critical and may only work on WS2812B, not on WS2812(S)." 56 | #warning "Please consider a higher clockspeed, if possible" 57 | #endif 58 | 59 | #if w2>0 60 | #define w2_nops w2 61 | #else 62 | #define w2_nops 0 63 | #endif 64 | 65 | #if w3>0 66 | #define w3_nops w3 67 | #else 68 | #define w3_nops 0 69 | #endif 70 | 71 | #define w_nop1 "nop \n\t" 72 | #define w_nop2 "rjmp .+0 \n\t" 73 | #define w_nop4 w_nop2 w_nop2 74 | #define w_nop8 w_nop4 w_nop4 75 | #define w_nop16 w_nop8 w_nop8 76 | 77 | void WS2812::ws2812_sendarray_mask( 78 | uint8_t *data, uint16_t datlen, 79 | uint8_t maskhi,uint8_t *port, uint8_t *portreg 80 | ) { 81 | // data is a pointer to an array of bytes 82 | // start is the offset from the start of the array 83 | // datlen is how many bytes to go write. 84 | 85 | uint8_t curbyte,ctr,masklo; 86 | uint8_t sreg_prev; 87 | 88 | masklo = ~maskhi & *port; 89 | maskhi |= *port; 90 | sreg_prev=SREG; 91 | cli(); 92 | 93 | while (datlen--) { 94 | curbyte=*data++; 95 | 96 | asm volatile( 97 | " ldi %0,8 \n\t" 98 | "loop%=: \n\t" 99 | " st X,%3 \n\t" // '1' [02] '0' [02] - re 100 | #if (w1_nops&1) 101 | w_nop1 102 | #endif 103 | #if (w1_nops&2) 104 | w_nop2 105 | #endif 106 | #if (w1_nops&4) 107 | w_nop4 108 | #endif 109 | #if (w1_nops&8) 110 | w_nop8 111 | #endif 112 | #if (w1_nops&16) 113 | w_nop16 114 | #endif 115 | " sbrs %1,7 \n\t" // '1' [04] '0' [03] 116 | " st X,%4 \n\t" // '1' [--] '0' [05] - fe-low 117 | " lsl %1 \n\t" // '1' [05] '0' [06] 118 | #if (w2_nops&1) 119 | w_nop1 120 | #endif 121 | #if (w2_nops&2) 122 | w_nop2 123 | #endif 124 | #if (w2_nops&4) 125 | w_nop4 126 | #endif 127 | #if (w2_nops&8) 128 | w_nop8 129 | #endif 130 | #if (w2_nops&16) 131 | w_nop16 132 | #endif 133 | " brcc skipone%= \n\t" // '1' [+1] '0' [+2] - 134 | " st X,%4 \n\t" // '1' [+3] '0' [--] - fe-high 135 | "skipone%=: " // '1' [+3] '0' [+2] - 136 | 137 | #if (w3_nops&1) 138 | w_nop1 139 | #endif 140 | #if (w3_nops&2) 141 | w_nop2 142 | #endif 143 | #if (w3_nops&4) 144 | w_nop4 145 | #endif 146 | #if (w3_nops&8) 147 | w_nop8 148 | #endif 149 | #if (w3_nops&16) 150 | w_nop16 151 | #endif 152 | 153 | " dec %0 \n\t" // '1' [+4] '0' [+3] 154 | " brne loop%=\n\t" // '1' [+5] '0' [+4] 155 | : "=&d" (ctr) 156 | // : "r" (curbyte), "I" (_SFR_IO_ADDR(ws2812_PORTREG)), "r" (maskhi), "r" (masklo) 157 | : "r" (curbyte), "x" (port), "r" (maskhi), "r" (masklo) 158 | ); 159 | } 160 | 161 | SREG=sreg_prev; 162 | sei(); 163 | } 164 | -------------------------------------------------------------------------------- /firmware/build/node_pixel_firmata/light_ws2812.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * light weight WS2812 lib V2.1 - Arduino support 3 | * 4 | * Controls WS2811/WS2812/WS2812B RGB-LEDs 5 | * Author: Tim (cpldcpu@gmail.com) 6 | * 7 | * Jan 18th, 2014 v2.0b Initial Version 8 | * March 7th, 2014 v2.1 Added option to retarget the port register during runtime 9 | * Removes inlining to allow compiling with c++ 10 | * 11 | * License: GNU GPL v2 (see License.txt) 12 | */ 13 | 14 | #include "lw_ws2812.h" 15 | 16 | /* 17 | This routine writes an array of bytes with RGB values to the Dataout pin 18 | using the fast 800kHz clockless WS2811/2812 protocol. 19 | */ 20 | 21 | // Timing in ns 22 | #define w_zeropulse 350 23 | #define w_onepulse 900 24 | #define w_totalperiod 1250 25 | 26 | // Fixed cycles used by the inner loop 27 | #define w_fixedlow 3 28 | #define w_fixedhigh 6 29 | #define w_fixedtotal 10 30 | 31 | // Insert NOPs to match the timing, if possible 32 | #define w_zerocycles (((F_CPU/1000)*w_zeropulse )/1000000) 33 | #define w_onecycles (((F_CPU/1000)*w_onepulse +500000)/1000000) 34 | #define w_totalcycles (((F_CPU/1000)*w_totalperiod +500000)/1000000) 35 | 36 | // w1 - nops between rising edge and falling edge - low 37 | #define w1 (w_zerocycles-w_fixedlow) 38 | // w2 nops between fe low and fe high 39 | #define w2 (w_onecycles-w_fixedhigh-w1) 40 | // w3 nops to complete loop 41 | #define w3 (w_totalcycles-w_fixedtotal-w1-w2) 42 | 43 | #if w1>0 44 | #define w1_nops w1 45 | #else 46 | #define w1_nops 0 47 | #endif 48 | 49 | // The only critical timing parameter is the minimum pulse length of the "0" 50 | // Warn or throw error if this timing can not be met with current F_CPU settings. 51 | #define w_lowtime ((w1_nops+w_fixedlow)*1000000)/(F_CPU/1000) 52 | #if w_lowtime>550 53 | #error "Light_ws2812: Sorry, the clock speed is too low. Did you set F_CPU correctly?" 54 | #elif w_lowtime>450 55 | #warning "Light_ws2812: The timing is critical and may only work on WS2812B, not on WS2812(S)." 56 | #warning "Please consider a higher clockspeed, if possible" 57 | #endif 58 | 59 | #if w2>0 60 | #define w2_nops w2 61 | #else 62 | #define w2_nops 0 63 | #endif 64 | 65 | #if w3>0 66 | #define w3_nops w3 67 | #else 68 | #define w3_nops 0 69 | #endif 70 | 71 | #define w_nop1 "nop \n\t" 72 | #define w_nop2 "rjmp .+0 \n\t" 73 | #define w_nop4 w_nop2 w_nop2 74 | #define w_nop8 w_nop4 w_nop4 75 | #define w_nop16 w_nop8 w_nop8 76 | 77 | void WS2812::ws2812_sendarray_mask( 78 | uint8_t *data, uint16_t datlen, 79 | uint8_t maskhi,uint8_t *port, uint8_t *portreg 80 | ) { 81 | // data is a pointer to an array of bytes 82 | // start is the offset from the start of the array 83 | // datlen is how many bytes to go write. 84 | 85 | uint8_t curbyte,ctr,masklo; 86 | uint8_t sreg_prev; 87 | 88 | masklo = ~maskhi & *port; 89 | maskhi |= *port; 90 | sreg_prev=SREG; 91 | cli(); 92 | 93 | while (datlen--) { 94 | curbyte=*data++; 95 | 96 | asm volatile( 97 | " ldi %0,8 \n\t" 98 | "loop%=: \n\t" 99 | " st X,%3 \n\t" // '1' [02] '0' [02] - re 100 | #if (w1_nops&1) 101 | w_nop1 102 | #endif 103 | #if (w1_nops&2) 104 | w_nop2 105 | #endif 106 | #if (w1_nops&4) 107 | w_nop4 108 | #endif 109 | #if (w1_nops&8) 110 | w_nop8 111 | #endif 112 | #if (w1_nops&16) 113 | w_nop16 114 | #endif 115 | " sbrs %1,7 \n\t" // '1' [04] '0' [03] 116 | " st X,%4 \n\t" // '1' [--] '0' [05] - fe-low 117 | " lsl %1 \n\t" // '1' [05] '0' [06] 118 | #if (w2_nops&1) 119 | w_nop1 120 | #endif 121 | #if (w2_nops&2) 122 | w_nop2 123 | #endif 124 | #if (w2_nops&4) 125 | w_nop4 126 | #endif 127 | #if (w2_nops&8) 128 | w_nop8 129 | #endif 130 | #if (w2_nops&16) 131 | w_nop16 132 | #endif 133 | " brcc skipone%= \n\t" // '1' [+1] '0' [+2] - 134 | " st X,%4 \n\t" // '1' [+3] '0' [--] - fe-high 135 | "skipone%=: " // '1' [+3] '0' [+2] - 136 | 137 | #if (w3_nops&1) 138 | w_nop1 139 | #endif 140 | #if (w3_nops&2) 141 | w_nop2 142 | #endif 143 | #if (w3_nops&4) 144 | w_nop4 145 | #endif 146 | #if (w3_nops&8) 147 | w_nop8 148 | #endif 149 | #if (w3_nops&16) 150 | w_nop16 151 | #endif 152 | 153 | " dec %0 \n\t" // '1' [+4] '0' [+3] 154 | " brne loop%=\n\t" // '1' [+5] '0' [+4] 155 | : "=&d" (ctr) 156 | // : "r" (curbyte), "I" (_SFR_IO_ADDR(ws2812_PORTREG)), "r" (maskhi), "r" (masklo) 157 | : "r" (curbyte), "x" (port), "r" (maskhi), "r" (masklo) 158 | ); 159 | } 160 | 161 | SREG=sreg_prev; 162 | sei(); 163 | } 164 | -------------------------------------------------------------------------------- /firmware/build/node_pixel_firmata/FirmataParser.h: -------------------------------------------------------------------------------- 1 | /* 2 | FirmataParser.h 3 | Copyright (c) 2006-2008 Hans-Christoph Steiner. All rights reserved. 4 | Copyright (C) 2009-2016 Jeff Hoefs. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | See file LICENSE.txt for further informations on licensing terms. 12 | */ 13 | 14 | #ifndef FirmataParser_h 15 | #define FirmataParser_h 16 | 17 | #if defined(__cplusplus) && !defined(ARDUINO) 18 | #include 19 | #include 20 | #else 21 | #include 22 | #include 23 | #endif 24 | 25 | namespace firmata { 26 | 27 | class FirmataParser 28 | { 29 | public: 30 | /* callback function types */ 31 | typedef void (*callbackFunction)(void * context, uint8_t command, uint16_t value); 32 | typedef void (*dataBufferOverflowCallbackFunction)(void * context); 33 | typedef void (*stringCallbackFunction)(void * context, const char * c_str); 34 | typedef void (*sysexCallbackFunction)(void * context, uint8_t command, size_t argc, uint8_t * argv); 35 | typedef void (*systemCallbackFunction)(void * context); 36 | typedef void (*versionCallbackFunction)(void * context, size_t sv_major, size_t sv_minor, const char * firmware); 37 | 38 | FirmataParser(uint8_t * dataBuffer = (uint8_t *)NULL, size_t dataBufferSize = 0); 39 | 40 | /* serial receive handling */ 41 | void parse(uint8_t value); 42 | bool isParsingMessage(void) const; 43 | int setDataBufferOfSize(uint8_t * dataBuffer, size_t dataBufferSize); 44 | 45 | /* attach & detach callback functions to messages */ 46 | void attach(uint8_t command, callbackFunction newFunction, void * context = NULL); 47 | void attach(dataBufferOverflowCallbackFunction newFunction, void * context = NULL); 48 | void attach(uint8_t command, stringCallbackFunction newFunction, void * context = NULL); 49 | void attach(uint8_t command, sysexCallbackFunction newFunction, void * context = NULL); 50 | void attach(uint8_t command, systemCallbackFunction newFunction, void * context = NULL); 51 | void attach(uint8_t command, versionCallbackFunction newFunction, void * context = NULL); 52 | void detach(uint8_t command); 53 | void detach(dataBufferOverflowCallbackFunction); 54 | 55 | private: 56 | /* input message handling */ 57 | bool allowBufferUpdate; 58 | uint8_t * dataBuffer; // multi-byte data 59 | size_t dataBufferSize; 60 | uint8_t executeMultiByteCommand; // execute this after getting multi-byte data 61 | uint8_t multiByteChannel; // channel data for multiByteCommands 62 | size_t waitForData; // this flag says the next serial input will be data 63 | 64 | /* sysex */ 65 | bool parsingSysex; 66 | size_t sysexBytesRead; 67 | 68 | /* callback context */ 69 | void * currentAnalogCallbackContext; 70 | void * currentDigitalCallbackContext; 71 | void * currentReportAnalogCallbackContext; 72 | void * currentReportDigitalCallbackContext; 73 | void * currentPinModeCallbackContext; 74 | void * currentPinValueCallbackContext; 75 | void * currentReportFirmwareCallbackContext; 76 | void * currentReportVersionCallbackContext; 77 | void * currentDataBufferOverflowCallbackContext; 78 | void * currentStringCallbackContext; 79 | void * currentSysexCallbackContext; 80 | void * currentSystemResetCallbackContext; 81 | 82 | /* callback functions */ 83 | callbackFunction currentAnalogCallback; 84 | callbackFunction currentDigitalCallback; 85 | callbackFunction currentReportAnalogCallback; 86 | callbackFunction currentReportDigitalCallback; 87 | callbackFunction currentPinModeCallback; 88 | callbackFunction currentPinValueCallback; 89 | dataBufferOverflowCallbackFunction currentDataBufferOverflowCallback; 90 | stringCallbackFunction currentStringCallback; 91 | sysexCallbackFunction currentSysexCallback; 92 | versionCallbackFunction currentReportFirmwareCallback; 93 | systemCallbackFunction currentReportVersionCallback; 94 | systemCallbackFunction currentSystemResetCallback; 95 | 96 | /* private methods ------------------------------ */ 97 | bool bufferDataAtPosition(const uint8_t data, const size_t pos); 98 | size_t decodeByteStream(size_t bytec, uint8_t * bytev); 99 | void processSysexMessage(void); 100 | void systemReset(void); 101 | }; 102 | 103 | } // firmata 104 | 105 | #endif /* FirmataParser_h */ 106 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean test 2 | 3 | # use this so we can call make again robustly if needed 4 | THIS_FILE := $(lastword $(MAKEFILE_LIST)) 5 | 6 | # use this so we can run NVM for tests 7 | NVM = [[ -s $$HOME/.nvm/nvm.sh ]] && . $$HOME/.nvm/nvm.sh 8 | 9 | FIRMWARE_DIR = ./firmware 10 | BUILD_DIR = $(FIRMWARE_DIR)/build 11 | BIN_DIR = $(FIRMWARE_DIR)/bin 12 | SRC_DIR = $(FIRMWARE_DIR)/src 13 | LIBS_DIR = $(SRC_DIR)/libs 14 | 15 | help: 16 | @echo "Development actions:" 17 | @echo "--------------------" 18 | @echo "clean: Cleans up everything" 19 | @echo "clean-node: Cleans all of the node modules up" 20 | @echo "clean-build: Cleans up all of the build folders" 21 | @echo "clean-compiled: Cleans up all of the compiled files" 22 | @echo "install: Installs all of the packages" 23 | @echo "lint: Runs the linter" 24 | @echo "test: Runs all of the tests" 25 | @echo "test [12,14]: Runs the tests for a particular node target" 26 | @echo "" 27 | @echo "Build actions:" 28 | @echo "--------------" 29 | @echo "build: Builds all of the codebase" 30 | @echo "compile: Compiles all of the binaries" 31 | @echo "compile : Compiles the binaries for a particular target board eg uno, nano" 32 | @echo "" 33 | @echo "Release actions:" 34 | @echo "----------------" 35 | @echo "release-test: Does a dry run version of the release" 36 | @echo "release: Builds a version, create tags and update changelog" 37 | @echo "publish: Pushes tags to github and publish to npm" 38 | @echo "" 39 | @echo "CI actions:" 40 | @echo "----------------" 41 | @echo "test-ci: Runs the test suite against local node version for CI matrix builds" 42 | @echo "" 43 | 44 | clean: clean-node clean-build clean-compiled 45 | @echo "Run npm install to get started" 46 | 47 | clean-node: 48 | # remove node files 49 | rm -rf node_modules/ 50 | 51 | clean-build-backpack: 52 | @# remove the firmware build items 53 | rm -rf $(BUILD_DIR)/backpack 54 | 55 | clean-build-firmata: 56 | @# remove the firmware build items 57 | rm -rf $(BUILD_DIR)/node_pixel_firmata 58 | 59 | clean-build: clean-build-backpack clean-build-firmata 60 | @# remove the compiled bins 61 | rm -rf $(BIN_DIR)/firmata/* 62 | rm -rf $(BIN_DIR)/backpack/* 63 | 64 | clean-compiled: 65 | @# removes all of the compilation intermediate files 66 | for f in $(BIN_DIR)/*/*/*; \ 67 | do \ 68 | if [[ $${f} != *.ino.hex ]]; \ 69 | then \ 70 | rm -rf $${f}; \ 71 | fi; \ 72 | done 73 | 74 | @# remove the intermediate build files from the build directory too 75 | rm -rf $(BUILD_DIR)/backpack/build 76 | rm -rf $(BUILD_DIR)/node_pixel_firmata/build 77 | 78 | install: clean 79 | @echo "Installing all of the packages needed" 80 | npm install 81 | 82 | lint: 83 | npm run lint 84 | 85 | # What node versions do we want to have support for 86 | NODE_TARGETS = 12 14 87 | 88 | $(NODE_TARGETS): 89 | @echo "Testing Node $@" 90 | @$(NVM) && nvm use $@ && npm run test 91 | 92 | test: $(NODE_TARGETS) 93 | 94 | test-ci: 95 | npm run test 96 | 97 | # make the Firmata build process to copy the files to the right place 98 | FIRMATA_DEST_DIR = $(BUILD_DIR)/node_pixel_firmata 99 | FIRMATA_DIR = $(LIBS_DIR)/firmata/arduino 100 | 101 | build-firmata: clean-build-firmata 102 | @echo "Creating firmata build files" 103 | mkdir $(FIRMATA_DEST_DIR) 104 | cp $(FIRMATA_DIR)/*.h $(FIRMATA_DEST_DIR)/ 105 | cp $(FIRMATA_DIR)/*.cpp $(FIRMATA_DEST_DIR)/ 106 | cp $(LIBS_DIR)/ws2812/* $(FIRMATA_DEST_DIR)/ 107 | cp $(LIBS_DIR)/lightws2812/* $(FIRMATA_DEST_DIR)/ 108 | cp $(SRC_DIR)/controller_src/firmata/* $(FIRMATA_DEST_DIR)/ 109 | @echo "Firmata build files ready" 110 | 111 | 112 | # Make the backpack build process to copy the files to the right place 113 | BACKPACK_DEST_DIR = $(BUILD_DIR)/backpack 114 | 115 | build-backpack: clean-build-backpack 116 | @echo "Creating backpack build files" 117 | mkdir $(BACKPACK_DEST_DIR) 118 | cp $(LIBS_DIR)/ws2812/* $(BACKPACK_DEST_DIR)/ 119 | cp $(LIBS_DIR)/lightws2812/* $(BACKPACK_DEST_DIR)/ 120 | cp $(SRC_DIR)/controller_src/backpack/* $(BACKPACK_DEST_DIR)/ 121 | @echo "Backpack build files ready" 122 | 123 | build: build-backpack build-firmata 124 | @echo "run make compile to compile" 125 | 126 | 127 | # Set out all of the targets and info to build the bins from 128 | BOARD_TGTS = uno nano pro-mini mega diecimila leonardo micro 129 | PKG_uno = "arduino:avr:uno" 130 | PKG_nano = "arduino:avr:nano:cpu=atmega328" 131 | PKG_pro-mini = "arduino:avr:pro:cpu=16MHzatmega328" 132 | PKG_mega = "arduino:avr:mega:cpu=atmega2560" 133 | PKG_diecimila = "arduino:avr:diecimila:cpu=atmega328" 134 | PKG_leonardo = "arduino:avr:leonardo" 135 | PKG_micro = "arduino:avr:micro" 136 | 137 | FIRMATA_INO = $(FIRMATA_DEST_DIR)/node_pixel_firmata.ino 138 | BACKPACK_INO = $(BACKPACK_DEST_DIR)/backpack.ino 139 | 140 | compile: build-backpack build-firmata $(BOARD_TGTS) 141 | @# clean up all of the garbage files 142 | @$(MAKE) -f $(THIS_FILE) clean-compiled 143 | 144 | $(BOARD_TGTS): 145 | @# make the firmata bin for this target board 146 | arduino-cli compile --verbose -b $(PKG_$@) \ 147 | --build-path=$$PWD/$(BIN_DIR)/firmata/$@ $(FIRMATA_INO) 148 | 149 | @# make the firmata bin for this target board 150 | arduino-cli compile --verbose -b $(PKG_$@) \ 151 | --build-path=$$PWD/$(BIN_DIR)/backpack/$@ $(BACKPACK_INO) 152 | 153 | 154 | test-release: 155 | npm run release -- --dry-run 156 | 157 | release: 158 | npm run release 159 | 160 | publish: 161 | git push --follow-tags origin master && npm publish 162 | -------------------------------------------------------------------------------- /lib/controllers/backpack.js: -------------------------------------------------------------------------------- 1 | const { 2 | PIXEL_SHIFT_WRAP, 3 | PIXEL_CONFIG, 4 | PIXEL_SHOW, 5 | PIXEL_SET_STRIP, 6 | PIXEL_SHIFT, 7 | MAX_STRIPS, 8 | I2C_DEFAULT, 9 | COLOR_ORDER, 10 | GAMMA_DEFAULT, 11 | FIRMATA_7BIT_MASK 12 | } = require('../constants'); 13 | const { Pixel } = require('../pixel'); 14 | const { create_gamma_table } = require('../utils'); 15 | 16 | const IC2Backpack = { 17 | initialize: { 18 | value(opts, strips) { 19 | const MAX_PIXELS = 500; // based on # bytes available in firmata 20 | const strip_length = opts.length || 6; // just an arbitrary val 21 | const strip_definition = opts.strips || new Array(); 22 | const color_order = opts.color_order || COLOR_ORDER.GRB; // default GRB 23 | const gamma = opts.gamma || GAMMA_DEFAULT; // Changing to 2.8 in v0.10 24 | 25 | // set up the gamma table 26 | const gtable = create_gamma_table(256, gamma, this.dep_warning.gamma); 27 | 28 | const io = opts.firmata || opts.board.io; 29 | 30 | if (!opts.address) { 31 | opts.address = I2C_DEFAULT; 32 | } 33 | 34 | if (io == undefined) { 35 | const err = new Error('An IO object is required to I2C controller'); 36 | err.name = 'NoIOError'; 37 | throw err; 38 | } 39 | 40 | // work out the map of strips and pixels. 41 | if (typeof(strip_definition[0]) == 'undefined') { 42 | // there is nothing specified so it's probably a single strip 43 | // using the length and colour type. 44 | strip_definition.push( { 45 | color_order, 46 | length: strip_length 47 | }); 48 | } else if (parseInt(strip_definition[0], 10) != NaN) { 49 | // we have the array of pin lengths but do we have the colour 50 | 51 | for (let i = 0; i< strip_definition.length; i++) { 52 | const len = strip_definition[i]; 53 | strip_definition[i] = { 54 | color_order, 55 | length: len 56 | }; 57 | } 58 | } 59 | 60 | // put in check if it's gone over. 61 | if (strip_definition.length > MAX_STRIPS) { 62 | const err = new RangeError('Maximum number of strips ' + MAX_STRIPS + ' exceeded'); 63 | this.emit('error', err); 64 | } 65 | 66 | let total_length = 0; 67 | strip_definition.forEach(function(data) { 68 | total_length += data.length; 69 | }); 70 | 71 | // put in check if there are too many pixels. 72 | if (total_length > MAX_PIXELS) { 73 | const err = new RangeError('Maximum number of pixels ' + MAX_PIXELS + ' exceeded'); 74 | this.emit('error', err); 75 | } 76 | 77 | const pixel_list = []; 78 | 79 | for (let i=0; i < total_length; i++) { 80 | pixel_list.push(new Pixel({ 81 | addr: i, 82 | io, 83 | controller: 'I2CBACKPACK', 84 | i2c_address: opts.address, 85 | strip: this 86 | }, strips) ); 87 | } 88 | 89 | strips.set(this, { 90 | pixels: pixel_list, 91 | io, 92 | i2c_address: opts.address, 93 | gtable, 94 | gamma 95 | }); 96 | 97 | this.strips_internal = strips; 98 | 99 | // now send the config message with length and data point. 100 | const data = []; 101 | 102 | data.push(PIXEL_CONFIG); 103 | strip_definition.forEach(function(strip) { 104 | data.push( (strip.color_order << 5) | strip.pin); 105 | data.push( strip.length & FIRMATA_7BIT_MASK); 106 | data.push( (strip.length >> 7) & FIRMATA_7BIT_MASK); 107 | }); 108 | // send the I2C config message. 109 | io.i2cConfig(opts); 110 | process.nextTick(function() { 111 | try { 112 | io.i2cWrite(opts.address, data); 113 | } catch (e) { 114 | if (e instanceof Error && e.name == 'EIO') { 115 | this.emit('np_i2c_write_error', data); 116 | } 117 | } 118 | process.nextTick(function() { 119 | this.emit('ready', null) 120 | }.bind(this) ); 121 | }.bind(this) ); 122 | } 123 | }, 124 | show: { 125 | value() { 126 | const strip = this.strips_internal.get(this); 127 | try { 128 | strip.io.i2cWrite(strip.i2c_address, [PIXEL_SHOW]); 129 | } catch (e) { 130 | if (e instanceof Error && e.name == 'EIO') { 131 | this.emit('np_i2c_write_error', 'PIXEL_SHOW'); 132 | } 133 | } 134 | } 135 | }, 136 | strip_color: { 137 | value(color) { 138 | const strip = this.strips_internal.get(this); 139 | const data = []; 140 | 141 | data[0] = PIXEL_SET_STRIP; 142 | 143 | data[1] = color & FIRMATA_7BIT_MASK; 144 | data[2] = (color >> 7) & FIRMATA_7BIT_MASK; 145 | data[3] = (color >> 14) & FIRMATA_7BIT_MASK; 146 | data[4] = (color >> 21) & FIRMATA_7BIT_MASK; 147 | try { 148 | strip.io.i2cWrite(strip.i2c_address, data); 149 | } catch (e) { 150 | if (e instanceof Error && e.name == 'EIO') { 151 | this.emit('np_i2c_write_error', data); 152 | } 153 | } 154 | } 155 | }, 156 | _shift: { 157 | value(amt, direction, wrap) { 158 | // shifts the strip in the appropriate direction. 159 | // 160 | const wrap_val = wrap ? PIXEL_SHIFT_WRAP : 0; 161 | const strip = this.strips_internal.get(this); 162 | const data = []; 163 | data[0] = PIXEL_SHIFT; 164 | data[1] = (amt | direction | wrap_val) & FIRMATA_7BIT_MASK; 165 | try { 166 | strip.io.i2cWrite(strip.i2c_address, data); 167 | } catch (e) { 168 | if (e instanceof Error && e.name == 'EIO') { 169 | this.emit('np_i2c_write_error', data); 170 | } 171 | } 172 | } 173 | } 174 | } 175 | 176 | module.exports = { 177 | IC2Backpack 178 | } 179 | -------------------------------------------------------------------------------- /firmware/build/node_pixel_firmata/FirmataConstants.h: -------------------------------------------------------------------------------- 1 | /* 2 | FirmataConstants.h 3 | Copyright (c) 2006-2008 Hans-Christoph Steiner. All rights reserved. 4 | Copyright (C) 2009-2017 Jeff Hoefs. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | See file LICENSE.txt for further informations on licensing terms. 12 | */ 13 | 14 | #ifndef FirmataConstants_h 15 | #define FirmataConstants_h 16 | 17 | namespace firmata { 18 | /* Version numbers for the Firmata library. 19 | * The firmware version will not always equal the protocol version going forward. 20 | * Query using the REPORT_FIRMWARE message. 21 | */ 22 | static const int FIRMWARE_MAJOR_VERSION = 2; 23 | static const int FIRMWARE_MINOR_VERSION = 5; 24 | static const int FIRMWARE_BUGFIX_VERSION = 7; 25 | 26 | /* Version numbers for the protocol. The protocol is still changing, so these 27 | * version numbers are important. 28 | * Query using the REPORT_VERSION message. 29 | */ 30 | static const int PROTOCOL_MAJOR_VERSION = 2; // for non-compatible changes 31 | static const int PROTOCOL_MINOR_VERSION = 5; // for backwards compatible changes 32 | static const int PROTOCOL_BUGFIX_VERSION = 1; // for bugfix releases 33 | 34 | static const int MAX_DATA_BYTES = 64; // max number of data bytes in incoming messages 35 | 36 | // message command bytes (128-255/0x80-0xFF) 37 | 38 | static const int DIGITAL_MESSAGE = 0x90; // send data for a digital port (collection of 8 pins) 39 | static const int ANALOG_MESSAGE = 0xE0; // send data for an analog pin (or PWM) 40 | static const int REPORT_ANALOG = 0xC0; // enable analog input by pin # 41 | static const int REPORT_DIGITAL = 0xD0; // enable digital input by port pair 42 | // 43 | static const int SET_PIN_MODE = 0xF4; // set a pin to INPUT/OUTPUT/PWM/etc 44 | static const int SET_DIGITAL_PIN_VALUE = 0xF5; // set value of an individual digital pin 45 | // 46 | static const int REPORT_VERSION = 0xF9; // report protocol version 47 | static const int SYSTEM_RESET = 0xFF; // reset from MIDI 48 | // 49 | static const int START_SYSEX = 0xF0; // start a MIDI Sysex message 50 | static const int END_SYSEX = 0xF7; // end a MIDI Sysex message 51 | 52 | // extended command set using sysex (0-127/0x00-0x7F) 53 | /* 0x00-0x0F reserved for user-defined commands */ 54 | 55 | static const int SERIAL_DATA = 0x60; // communicate with serial devices, including other boards 56 | static const int ENCODER_DATA = 0x61; // reply with encoders current positions 57 | static const int SERVO_CONFIG = 0x70; // set max angle, minPulse, maxPulse, freq 58 | static const int STRING_DATA = 0x71; // a string message with 14-bits per char 59 | static const int STEPPER_DATA = 0x72; // control a stepper motor 60 | static const int ONEWIRE_DATA = 0x73; // send an OneWire read/write/reset/select/skip/search request 61 | static const int SHIFT_DATA = 0x75; // a bitstream to/from a shift register 62 | static const int I2C_REQUEST = 0x76; // send an I2C read/write request 63 | static const int I2C_REPLY = 0x77; // a reply to an I2C read request 64 | static const int I2C_CONFIG = 0x78; // config I2C settings such as delay times and power pins 65 | static const int REPORT_FIRMWARE = 0x79; // report name and version of the firmware 66 | static const int EXTENDED_ANALOG = 0x6F; // analog write (PWM, Servo, etc) to any pin 67 | static const int PIN_STATE_QUERY = 0x6D; // ask for a pin's current mode and value 68 | static const int PIN_STATE_RESPONSE = 0x6E; // reply with pin's current mode and value 69 | static const int CAPABILITY_QUERY = 0x6B; // ask for supported modes and resolution of all pins 70 | static const int CAPABILITY_RESPONSE = 0x6C; // reply with supported modes and resolution 71 | static const int ANALOG_MAPPING_QUERY = 0x69; // ask for mapping of analog to pin numbers 72 | static const int ANALOG_MAPPING_RESPONSE = 0x6A; // reply with mapping info 73 | static const int SAMPLING_INTERVAL = 0x7A; // set the poll rate of the main loop 74 | static const int SCHEDULER_DATA = 0x7B; // send a createtask/deletetask/addtotask/schedule/querytasks/querytask request to the scheduler 75 | static const int SYSEX_NON_REALTIME = 0x7E; // MIDI Reserved for non-realtime messages 76 | static const int SYSEX_REALTIME = 0x7F; // MIDI Reserved for realtime messages 77 | 78 | // pin modes 79 | static const int PIN_MODE_INPUT = 0x00; // same as INPUT defined in Arduino.h 80 | static const int PIN_MODE_OUTPUT = 0x01; // same as OUTPUT defined in Arduino.h 81 | static const int PIN_MODE_ANALOG = 0x02; // analog pin in analogInput mode 82 | static const int PIN_MODE_PWM = 0x03; // digital pin in PWM output mode 83 | static const int PIN_MODE_SERVO = 0x04; // digital pin in Servo output mode 84 | static const int PIN_MODE_SHIFT = 0x05; // shiftIn/shiftOut mode 85 | static const int PIN_MODE_I2C = 0x06; // pin included in I2C setup 86 | static const int PIN_MODE_ONEWIRE = 0x07; // pin configured for 1-wire 87 | static const int PIN_MODE_STEPPER = 0x08; // pin configured for stepper motor 88 | static const int PIN_MODE_ENCODER = 0x09; // pin configured for rotary encoders 89 | static const int PIN_MODE_SERIAL = 0x0A; // pin configured for serial communication 90 | static const int PIN_MODE_PULLUP = 0x0B; // enable internal pull-up resistor for pin 91 | static const int PIN_MODE_IGNORE = 0x7F; // pin configured to be ignored by digitalWrite and capabilityResponse 92 | 93 | static const int TOTAL_PIN_MODES = 13; 94 | 95 | } // namespace firmata 96 | 97 | #endif // FirmataConstants_h 98 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Node-Pixel Installation Guide 2 | 3 | ## Considerations 4 | 5 | Depending on the board you are using and what you are trying to achieve, there 6 | are two ways of using node-pixel. 7 | 8 | The `custom firmata` method assumes you are using an arduino and you just want 9 | to add addressable LED support to the way firmata works already. This is a good 10 | option if you want to play with NeoPixels and you aren't doing much more than 11 | controlling LEDs. The downside of this approach is that Firmata has a lot of 12 | support already in it for a range of devices so you will be limited in the number 13 | of pixels you can control. In addition, the pixel library will block at times, so your 14 | framerate will always be limited. 15 | 16 | The `backpack` method provides I2C support to a strip of WS2812 pixels and will work 17 | with any [board that has I2C IO support](http://johnny-five.io/platform-support/). 18 | The benefit of this approach is that it offloads the LED processing to a dedicated 19 | board and can support more pixels and much higher framerate. The downside is you will 20 | require an arduino or other atmega board of some type (eg Pro Mini, Nano etc) to 21 | run the pixels. These boards can be had for about $2 with a bit of searching. 22 | 23 | ## Prerequisites 24 | 25 | - Arduino or similar board 26 | - Adafruit NeoPixels or standard WS2812b LEDs 27 | - NodeJS 28 | - Johnny-Five 29 | 30 | For the purposes of this guide I will assume you're using Adafruit NeoPixels and I'm going 31 | to assume you've read the [NeoPixel Uber Guide](http://learn.adafruit.com/adafruit-neopixel-uberguide/overview) 32 | as it has just about everything you need to know in it from a hardware perspective. 33 | Standard WS2812b LEDs from any other supplier will work just as well. 34 | 35 | I'm going to assume you have NodeJS all ready to go too and you've gone through 36 | the [Johnny-Five getting started guide](http://johnny-five.io). 37 | 38 | ## Software Installation 39 | 40 | ### 1. Install the node-pixel code 41 | 42 | As per most NodeJS projects you can either clone the repo; 43 | 44 | ``` 45 | git clone https://github.com/ajfisher/node-pixel 46 | cd node-pixel 47 | npm install 48 | ``` 49 | 50 | or just install from npm 51 | 52 | ``` 53 | npm install node-pixel 54 | ``` 55 | 56 | ### 2. Install firmware to your board 57 | 58 | The first thing you'll need is Interchange to manage the firmware for your 59 | target board. [Find out more about what Interchange does here.](http://github.com/j5js/nodebots-interchange). 60 | 61 | To install, run `npm install -g nodebots-interchange` from your project folder. 62 | 63 | Now plug in your board and you can install the firmware to it. For the purposes 64 | of this doc it's assumed you have an Arduino Nano. 65 | 66 | #### A. I2C Backpack 67 | 68 | This is the preferred set up as it can run more pixels faster than a normal 69 | arduino having to manage pixels AND firmata at the same time. To install: 70 | 71 | ``` 72 | interchange install git+https://github.com/ajfisher/node-pixel -a nano 73 | ``` 74 | 75 | #### B. Node Pixel Firmata 76 | 77 | If you don't have a spare board to dedicate to your LEDs, you can run the 78 | node-pixel firmware inside Firmata. This does limit how many pixels you can 79 | drive and your framerate but is a good starting out option. 80 | 81 | ``` 82 | interchange install git+https://github.com/ajfisher/node-pixel -a uno --firmata 83 | ``` 84 | 85 | If everything proceeds without error then you should be good to go. 86 | 87 | ## Hardware Installation 88 | 89 | ### Standard Installation 90 | 91 | With the hardware off, attach your pixels to the arduino. Usually this involves 92 | getting a 5V source, a ground and then attaching the data line to an arduino 93 | digital pin. The default configuration assumes PIN 6 however this is configurable. 94 | 95 | _Wiring diagram_ 96 | 97 | ![Custom Firmata Diagram](breadboard/custom_firmata_bb.png) 98 | 99 | ### I2C Backpack 100 | 101 | This installation method installs a custom "Backpack" firmware onto an arduino 102 | Nano or Pro Mini which is then connected to the board using I2C connections. 103 | 104 | Open up the arduino IDE, navigate to `firmware/build/backpack/` and open `backpack.ino`. 105 | This will bring in all the required dependencies. 106 | 107 | Note that before you compile you may need to change your board type and port 108 | if you are used to using only Uno boards. 109 | 110 | Compile, check for errors and then upload to your arduino. Assuming no errors 111 | you should be ready to go. 112 | 113 | #### Hardware installation 114 | 115 | For the purposes of this `Board` will mean the board that is talking to NodeJS 116 | and `Backpack` will mean the custom arduino used to provide I2C support. 117 | 118 | With the hardware off, attach your pixels to the Backpack. Usually this involves 119 | getting a 5V source, a ground and then attaching the data line to an arduino 120 | digital pin. The default configuration assumes PIN 0 (NOTE this is NOT configurable). 121 | 122 | Connect the grounds of the Board and Backpack as well as the SDA and SCK pins. (You 123 | will need to look at your board and backpack pin out diagrams to see what these are). 124 | 125 | Depending on your Board you may be able to power the Backpack by providing 5V out 126 | to the 5v pin on the Backpack or you may need to supply that separately. 127 | 128 | *Note: Because of an [I2C bug](http://www.advamation.com/knowhow/raspberrypi/rpi-i2c-bug.html) 129 | you must change the I2C baudrate on the Raspberry Pi and Pi 2. Edit `/boot/config.txt` 130 | to include the following line and reboot your Pi:* 131 | 132 | ```bash 133 | dtparam=i2c_arm_baudrate=10000 134 | ``` 135 | 136 | ![I2C Backpack Diagram](breadboard/i2c_backpack_bb.png) 137 | 138 | ## Using the library 139 | 140 | Now you're set up, it's time to use some JS to manipulate the LEDs. 141 | 142 | To use the library, require it per normal: 143 | 144 | ```javascript 145 | var pixel = require("node-pixel"); 146 | ``` 147 | 148 | The [main readme](../README.md) has more detailed instructions around the api. 149 | 150 | -------------------------------------------------------------------------------- /lib/strip.js: -------------------------------------------------------------------------------- 1 | const events = require('events'); 2 | const util = require('util'); 3 | const { ColorString } = require('./pixel'); 4 | const { 5 | SHIFT_FORWARD 6 | } = require('./constants'); 7 | const { Firmata } = require('./controllers/firmata'); 8 | const { IC2Backpack } = require('./controllers/backpack'); 9 | 10 | const strips = new WeakMap(); 11 | 12 | const Controllers = { 13 | FIRMATA: Firmata, 14 | I2CBACKPACK: IC2Backpack 15 | }; 16 | 17 | function Strip(opts) { 18 | // opts contains an object with. 19 | // data: data pin for the pixel strip // DEPRECATED will be phased out. 20 | // length: length of the pixel strip. // DEPRECATED, will be phased out. 21 | // board: johnny five board object. 22 | // controller: controller type to use 23 | // firmata: actual firmata object if using firmata 24 | // stripShape: an array that contains lengths or optionally data pins and 25 | // lengths for each of them. 26 | // eg: [ [6, 30], [12, 20], [7, 10] ] which would be 3 strips attached 27 | // to pins 6, 12 and 7 and make a strip 60 pixels long. 28 | // Otherwise [ 30, 20, 10 ] which would be 3 strips on PORTD 0-2 but 29 | // still a strip 60 pixels long 30 | // gamma: A user specified value for gamma correction for the strip. 31 | // default is 1.0 but will be changed to 2.8 over versions 32 | 33 | if (!(this instanceof Strip)) { 34 | return new Strip(opts); 35 | } 36 | 37 | let controller; 38 | 39 | if (typeof opts.controller === 'string') { 40 | controller = Controllers[opts.controller]; 41 | } else { 42 | controller = opts.controller || Controllers['FIRMATA']; 43 | } 44 | 45 | this.dep_warning = { 46 | stripLength: false, 47 | gammaValue: (! typeof opts.gamma === 'undefined') 48 | }; 49 | 50 | Object.defineProperties(this, controller); 51 | 52 | Object.defineProperty(this, 'length', { 53 | get() { 54 | const strip = strips.get(this); 55 | return strip.pixels.length; 56 | } 57 | }); 58 | 59 | Object.defineProperty(this, 'gamma', { 60 | get() { 61 | const strip = strips.get(this); 62 | return strip.gamma; 63 | } 64 | }); 65 | 66 | Object.defineProperty(this, 'gtable', { 67 | get() { 68 | const strip = strips.get(this); 69 | return strip.gtable; 70 | } 71 | }); 72 | 73 | if (typeof this.initialize === 'function') { 74 | this.initialize(opts, strips); 75 | } 76 | } 77 | 78 | util.inherits(Strip, events.EventEmitter); 79 | 80 | Strip.prototype.pixel = function(addr) { 81 | const strip = strips.get(this); 82 | 83 | return strip.pixels[addr]; 84 | }; 85 | 86 | Strip.prototype.colour = Strip.prototype.color = function(color, opts) { 87 | // sets the color of the entire strip 88 | // use a particular form to set the color either 89 | // color = hex value or named colors 90 | // or set color null and set opt which is an object as {rgb: [rx, gx, bx]} 91 | // values where x is an 8-bit value (0-255); 92 | const strip = strips.get(this); 93 | 94 | let stripcolor = null; 95 | 96 | if (color) { 97 | // use text to determine the color 98 | if (typeof(color) === 'object') { 99 | // we have an RGB array value 100 | stripcolor = color; 101 | } else { 102 | try { 103 | stripcolor = ColorString.get(color).value; 104 | } catch (e) { 105 | if (e instanceof TypeError && ColorString.get(color) === null ) { 106 | stripcolor = null; 107 | } 108 | } 109 | } 110 | } 111 | 112 | if (stripcolor != null) { 113 | // fill out the values for the pixels and then update the strip 114 | 115 | for (let i = 0; i < strip.pixels.length; i++) { 116 | strip.pixels[i].color(color, {sendmsg: false}); 117 | } 118 | 119 | // set the whole strip color to the appropriate int value 120 | this.strip_color(ColorString.colorValue(stripcolor, strip.gtable)); 121 | } else { 122 | console.log("Supplied colour couldn't be parsed: " + color); 123 | } 124 | } 125 | 126 | Strip.prototype.off = Strip.prototype.clear = function() { 127 | // sets the strip to 'black', effectively setting it to 'off' 128 | this.color([0, 0, 0]); 129 | this.show(); 130 | }; 131 | 132 | Strip.prototype.shift = function(amt, direction, wrap) { 133 | // public version of the shift function independent of the controller. 134 | // this looks after the actual internal shifting of the pixels within the 135 | // js side and then calls the controller to mirror the same function. 136 | 137 | if (amt > 0) { 138 | const strip = strips.get(this); 139 | 140 | // take a copy of the pixels at the end that is being towards 141 | let start_element = 0; 142 | if (direction == SHIFT_FORWARD) { 143 | start_element = this.length - amt; 144 | } 145 | const tmp_pixels = strip.pixels.splice(start_element, amt); 146 | 147 | while (tmp_pixels.length > 0) { 148 | const px = tmp_pixels.pop(); 149 | 150 | // set the pixel off if not wrapping. 151 | if (! wrap) { 152 | px.color('#000'); 153 | } 154 | 155 | if (direction == SHIFT_FORWARD) { 156 | strip.pixels.unshift(px); 157 | } else { 158 | strip.pixels.push(px); 159 | } 160 | } 161 | 162 | // renumber the items so the addresses are correct for display 163 | strip.pixels.forEach((px, index) => { 164 | px.address = index; 165 | }); 166 | 167 | // now get the firmware to update appropriately as well. 168 | this._shift(amt, direction, wrap); 169 | } 170 | }; 171 | 172 | Strip.prototype.stripLength = function() { 173 | // gets the number of pixels in the strip 174 | if (! this.dep_warning.stripLength) { 175 | console.info('ERROR: strip.stripLength() is deprecated in favour of strip.length'); 176 | console.info('0.8 - notice'); 177 | console.info('0.9 - error'); 178 | console.info('0.10 - removal'); 179 | this.dep_warning.stripLength = true; 180 | } 181 | 182 | throw new Error({ 183 | name: 'NotImplemented', 184 | message: 'stripLength is no longer supported, use strip.length', 185 | toString() { return 'NotImplemented: stripLength is no longer supported' } 186 | }); 187 | }; 188 | 189 | module.exports = { 190 | Strip 191 | }; 192 | -------------------------------------------------------------------------------- /lib/controllers/firmata.js: -------------------------------------------------------------------------------- 1 | const { 2 | PIXEL_SHIFT_WRAP, 3 | PIXEL_CONFIG, 4 | PIXEL_SHOW, 5 | PIXEL_SET_STRIP, 6 | PIXEL_SHIFT, 7 | MAX_STRIPS, 8 | PIN_DEFAULT, 9 | COLOR_ORDER, 10 | GAMMA_DEFAULT, 11 | FIRMATA_7BIT_MASK, 12 | START_SYSEX, 13 | END_SYSEX, 14 | PIXEL_COMMAND 15 | } = require('../constants'); 16 | const { create_gamma_table } = require('../utils'); 17 | const { Pixel } = require('../pixel'); 18 | 19 | const Firmata = { 20 | initialize: { 21 | value(opts, strips) { 22 | const MAX_PIXELS = 216; // based on # bytes available in firmata 23 | const strip_length = opts.length || 6; // just an arbitrary val 24 | const data_pin = opts.data || PIN_DEFAULT; 25 | const color_order = opts.color_order || COLOR_ORDER.GRB; // default GRB 26 | const strip_definition = opts.strips || new Array(); 27 | const skip_firmware_check = !!opts.skip_firmware_check; 28 | // do firmata / IO checks 29 | let firmata = opts.firmata || undefined; 30 | if (firmata == undefined) { 31 | try { 32 | firmata = opts.board.io; 33 | } catch (e) { 34 | if (e instanceof TypeError) { 35 | // there's no board 36 | firmata = undefined; 37 | } 38 | } 39 | } 40 | // check if we're *still* undefined 41 | if (firmata == undefined) { 42 | const err = new Error('A firmata or board object is required'); 43 | err.name = 'NoFirmataError'; 44 | throw err; 45 | } 46 | 47 | if (firmata.firmware.name !== 'node_pixel_firmata.ino' && !skip_firmware_check) { 48 | const err = new Error('Please upload NodePixel Firmata to the board'); 49 | err.name = 'IncorrectFirmataVersionError'; 50 | throw err; 51 | } 52 | 53 | // figure out where we are writing to 54 | const port = firmata.transport || firmata.sp || firmata; 55 | 56 | if (port.write === undefined) { 57 | const err = new Error('Node Pixel FIRMATA controller requires IO that can write out'); 58 | err.name = 'NoWritablePortError'; 59 | throw err; 60 | } 61 | 62 | const gamma = opts.gamma || GAMMA_DEFAULT; // Changing to 2.8 in v0.10 63 | 64 | // set up the gamma table 65 | const gtable = create_gamma_table(256, gamma, this.dep_warning.gamma); 66 | 67 | 68 | // work out the map of strips and pixels. 69 | if (typeof(strip_definition[0]) == 'undefined') { 70 | // there is nothing specified so it's probably a single strip 71 | // using the length and pin shorthand 72 | strip_definition.push( { 73 | pin: data_pin, 74 | color_order, 75 | length: strip_length 76 | }); 77 | } 78 | 79 | // put in check if it's gone over value 80 | if (strip_definition.length > MAX_STRIPS) { 81 | const err = new RangeError('Maximum number of strips ' + MAX_STRIPS + ' exceeded'); 82 | this.emit('error', err); 83 | } 84 | 85 | let total_length = 0; 86 | strip_definition.forEach(function(data) { 87 | total_length += data.length; 88 | }); 89 | 90 | // put in check if there are too many pixels. 91 | if (total_length > MAX_PIXELS) { 92 | const err = new RangeError('Maximum number of pixels ' + MAX_PIXELS + ' exceeded'); 93 | this.emit('error', err); 94 | } 95 | 96 | const pixel_list = []; 97 | 98 | for (let i=0; i< total_length; i++) { 99 | pixel_list.push(new Pixel({ 100 | addr: i, 101 | firmata, 102 | port, 103 | controller: 'FIRMATA', 104 | strip: this 105 | }, strips) ); 106 | } 107 | 108 | strips.set(this, { 109 | pixels: pixel_list, 110 | data: data_pin, 111 | firmata, 112 | port, 113 | gtable, 114 | gamma 115 | }); 116 | 117 | this.strips_internal = strips; 118 | 119 | // now send the config message with length and data point. 120 | const data = []; 121 | 122 | data[0] = START_SYSEX; 123 | data[1] = PIXEL_COMMAND; 124 | data[2] = PIXEL_CONFIG; 125 | strip_definition.forEach(function(strip) { 126 | data.push( (strip.color_order << 5) | strip.pin); 127 | data.push( strip.length & FIRMATA_7BIT_MASK); 128 | data.push( (strip.length >> 7) & FIRMATA_7BIT_MASK); 129 | }); 130 | data.push(END_SYSEX); 131 | 132 | port.write(Buffer.from(data), function(error, res) { 133 | let err = null; 134 | if (error) { 135 | err = error; 136 | this.emit('error', err); 137 | } 138 | // there is a weird bug in OSX which sometimes causes 139 | // a segfault if you try to write to fast. As such 140 | // just delay the ready event by 1msec because even this 141 | // is faster than hooman will perceive as a delay 142 | setTimeout(() => { 143 | this.emit('ready', err); 144 | }, 1); 145 | }.bind(this) ); 146 | } 147 | }, 148 | show: { 149 | value() { 150 | // call the frame on the strip. 151 | const strip = this.strips_internal.get(this); 152 | 153 | const data = []; 154 | data[0] = START_SYSEX; 155 | data[1] = PIXEL_COMMAND; 156 | data[2] = PIXEL_SHOW; 157 | data[3] = END_SYSEX; 158 | 159 | // now just write that to the port and it should show the frame. 160 | strip.port.write(Buffer.from(data)); 161 | } 162 | }, 163 | strip_color: { 164 | value(color) { 165 | // colour work is already done this just sets it the appropriate 166 | // way. 167 | const strip = this.strips_internal.get(this); 168 | const data = []; 169 | 170 | data[0] = START_SYSEX; 171 | data[1] = PIXEL_COMMAND; 172 | data[2] = PIXEL_SET_STRIP; 173 | data[3] = color & FIRMATA_7BIT_MASK; 174 | data[4] = (color >> 7) & FIRMATA_7BIT_MASK; 175 | data[5] = (color >> 14) & FIRMATA_7BIT_MASK; 176 | data[6] = (color >> 21) & FIRMATA_7BIT_MASK; 177 | data[7] = END_SYSEX; 178 | 179 | strip.port.write(Buffer.from(data)); 180 | } 181 | }, 182 | _shift: { 183 | value(amt, direction, wrap) { 184 | // shifts the strip in the appropriate direction. 185 | // 186 | const wrap_val = wrap ? PIXEL_SHIFT_WRAP : 0; 187 | const strip = this.strips_internal.get(this); 188 | const data = []; 189 | data[0] = START_SYSEX; 190 | data[1] = PIXEL_COMMAND; 191 | data[2] = PIXEL_SHIFT; 192 | data[3] = (amt | direction | wrap_val) & FIRMATA_7BIT_MASK; 193 | data[4] = END_SYSEX; 194 | 195 | strip.port.write(Buffer.from(data)); 196 | } 197 | } 198 | } 199 | 200 | module.exports = { 201 | Firmata 202 | } 203 | -------------------------------------------------------------------------------- /test/i2cbackpack.js: -------------------------------------------------------------------------------- 1 | global.IS_TEST_MODE = true; 2 | const mocks = require('mock-firmata'); 3 | const MockFirmata = mocks.Firmata; 4 | const MockSerialPort = mocks.SerialPort; 5 | 6 | const sinon = require('sinon'); 7 | 8 | const five = require('johnny-five'); 9 | const pixel = require('../lib/index.js'); 10 | 11 | const Board = five.Board; 12 | 13 | function newBoard() { 14 | const sp = new MockSerialPort('/dev/test'); 15 | const io = new MockFirmata(sp); 16 | 17 | io.emit('connect'); 18 | io.emit('ready'); 19 | 20 | const board = new Board({ 21 | io, 22 | debug: false, 23 | repl: false 24 | }); 25 | 26 | return board; 27 | } 28 | 29 | function restore(target) { 30 | for (const prop in target) { 31 | if (Array.isArray(target[prop])) { 32 | continue; 33 | } 34 | 35 | if (target[prop] != null && typeof target[prop].restore === 'function') { 36 | target[prop].restore(); 37 | } 38 | 39 | if (typeof target[prop] === 'object') { 40 | restore(target[prop]); 41 | } 42 | } 43 | } 44 | 45 | exports['Strip - I2C'] = { 46 | setUp(done) { 47 | this.board = newBoard(); 48 | this.clock = sinon.useFakeTimers(); 49 | this.i2cConfig = sinon.spy(MockFirmata.prototype, 'i2cConfig'); 50 | this.i2cWrite = sinon.stub(MockFirmata.prototype, 'i2cWrite').callsFake(function(i2caddr, data) { 51 | return; 52 | }); 53 | 54 | done(); 55 | }, 56 | 57 | tearDown(done) { 58 | this.i2cConfig.restore(); 59 | this.i2cWrite.restore(); 60 | this.clock.restore(); 61 | done(); 62 | }, 63 | 64 | i2cControllerConfig(test) { 65 | // ensures that the configuration of the controller works correctly 66 | 67 | test.expect(1); 68 | 69 | test.throws( 70 | () => { 71 | const strip = new pixel.Strip({ 72 | data: 6, 73 | length: 8, 74 | board: {}, 75 | controller: 'I2CBACKPACK' 76 | }); 77 | }, 78 | function(err) { 79 | if (err) { 80 | if (err.name == 'NoIOError') { 81 | return true; 82 | } 83 | return false; 84 | } 85 | return false; 86 | }, 87 | 'If IO is not present an error should be thrown' 88 | ); 89 | 90 | test.done(); 91 | }, 92 | 93 | stripReady(test) { 94 | // tests if the strip emits the ready event properly. 95 | 96 | test.expect(3); 97 | const strip = new pixel.Strip({ 98 | data: 6, 99 | length: 8, 100 | board: this.board, 101 | controller: 'I2CBACKPACK' 102 | }); 103 | 104 | // emit the ready event ahead of time. 105 | 106 | test.equal(this.i2cConfig.callCount, 1, 107 | 'I2C Config should be called only once during config.'); 108 | 109 | strip.on('ready', function() { 110 | test.equal(this.i2cWrite.callCount, 1, 111 | 'I2C Write should be called once as part of config'); 112 | test.ok(true, 113 | 'If configuration is complete a ready even should be emitted'); 114 | test.done(); 115 | }.bind(this)); 116 | }, 117 | 118 | maxNumberOfStrips(test) { 119 | test.expect(1); 120 | 121 | test.throws( 122 | () => { 123 | const strip = new pixel.Strip({ 124 | board: this.board, 125 | controller: 'I2CBACKPACK', 126 | strips: [8, 8, 8, 8, 8, 8, 8, 8, 8] 127 | }); 128 | }, 129 | function(err) { 130 | if (err instanceof RangeError) { 131 | return true; 132 | } 133 | }, 134 | 'Excessive number of strips should throw a RangeError' 135 | ); 136 | 137 | test.done(); 138 | }, 139 | 140 | maxNumberOfPixels(test) { 141 | test.expect(2); 142 | 143 | test.throws( 144 | () => { 145 | const strip1 = new pixel.Strip({ 146 | board: this.board, 147 | controller: 'I2CBACKPACK', 148 | strips: [ 600 ] 149 | }); 150 | }, 151 | function(err) { 152 | if (err instanceof RangeError) { 153 | return true; 154 | } 155 | }, 156 | 'Excess pixels in a single strip should throw a RangeError' 157 | ); 158 | 159 | test.throws( 160 | () => { 161 | const strip2 = new pixel.Strip({ 162 | board: this.board, 163 | controller: 'I2CBACKPACK', 164 | strips: [ 100, 100, 100, 100, 100, 100, 100 ] 165 | }); 166 | }, 167 | function(err) { 168 | if (err instanceof RangeError) { 169 | return true; 170 | } 171 | }, 172 | 'Excess pixels in multiple strips should throw a RangeError' 173 | ); 174 | test.done(); 175 | }, 176 | show(test) { 177 | // tests if the strip calls the show out to I2C properly. 178 | // 179 | test.expect(1); 180 | 181 | const strip = new pixel.Strip({ 182 | data: 6, 183 | length: 8, 184 | board: this.board, 185 | controller: 'I2CBACKPACK' 186 | }); 187 | 188 | strip.on('ready', function() { 189 | strip.show(); 190 | // first call count will be for the setup call 191 | test.equal(this.i2cWrite.callCount, 2, 192 | 'i2cWrite should be called only once during show'); 193 | test.done(); 194 | }.bind(this)); 195 | }, 196 | 197 | color(test) { 198 | // aims for coverage tests to ensure that colours are set properly. 199 | // 200 | test.expect(2); 201 | 202 | const strip = new pixel.Strip({ 203 | data: 6, 204 | length: 8, 205 | board: this.board, 206 | controller: 'I2CBACKPACK' 207 | }); 208 | 209 | strip.on('ready', function() { 210 | strip.color('red'); 211 | // first call count will be for the setup call 212 | test.equal(this.i2cWrite.callCount, 2, 213 | 'i2cWrite should be called only once during colour setting'); 214 | 215 | strip.shift(1, pixel.FORWARDS, true); 216 | test.equal(this.i2cWrite.callCount, 3, 217 | 'i2cWrite should be called only once during shift call'); 218 | 219 | test.done(); 220 | }.bind(this)); 221 | } 222 | }; 223 | 224 | exports['Pixel - I2C'] = { 225 | setUp(done) { 226 | this.board = newBoard(); 227 | this.clock = sinon.useFakeTimers(); 228 | this.i2cConfig = sinon.spy(MockFirmata.prototype, 'i2cConfig'); 229 | this.i2cWrite = sinon.stub(MockFirmata.prototype, 'i2cWrite').callsFake(function(i2caddr, data) { 230 | return; 231 | }); 232 | 233 | this.strip = new pixel.Strip({ 234 | data: 6, 235 | length: 4, 236 | board: this.board, 237 | controller: 'I2CBACKPACK' 238 | }); 239 | 240 | done(); 241 | }, 242 | 243 | tearDown(done) { 244 | this.i2cConfig.restore(); 245 | this.i2cWrite.restore(); 246 | this.clock.restore(); 247 | done(); 248 | }, 249 | 250 | writing(test) { 251 | // tests to see whether the write to the pixel is going out properly 252 | test.expect(1); 253 | this.strip.pixel(0).color('#FFF'); 254 | test.equal(this.i2cWrite.callCount, 1, 255 | 'i2cWrite should only call once to write a pixel value'); 256 | test.done() 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /lib/pixel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Defines a set of WS2812 LED Pixels for use 4 | 5 | // TODO: 6 | // Pixels can be arranged into different structures // NICE TO HAVE 7 | // Do we have a grid which can be 1D, 2D or 3D and any size 8 | // Pixels needs to have a length, various deets on where it is (clock etc) 9 | // Keyframing // NICE TO HAVE 10 | // Pixel grid should be able to: 11 | // - Set pixels in a range from X->Y a colour 12 | 13 | const ColorString = require('color-string'); // used for color parsing 14 | const { 15 | START_SYSEX, 16 | END_SYSEX, 17 | FIRMATA_7BIT_MASK, 18 | PIXEL_COMMAND, 19 | PIXEL_SET_PIXEL 20 | } = require('./constants'); 21 | 22 | function colorValue(colors, g_table) { 23 | // colors are assumed to be an array of [r, g, b] bytes 24 | // colorValue returns a packed value able to be pushed to firmata rather than 25 | // text values. 26 | // if gtable is passed then it should use the supplied gamma 27 | // correction table to correct the received value. 28 | 29 | // before sending, account for gamma correction. 30 | const gammaCorrectedColor = Object.assign({}, colors); 31 | 32 | gammaCorrectedColor[0] = g_table[gammaCorrectedColor[0]]; 33 | gammaCorrectedColor[1] = g_table[gammaCorrectedColor[1]]; 34 | gammaCorrectedColor[2] = g_table[gammaCorrectedColor[2]]; 35 | 36 | return ((gammaCorrectedColor[0] << 16) + (gammaCorrectedColor[1] << 8) + (gammaCorrectedColor[2])); 37 | } 38 | 39 | // create a helper to output an int so messages can be shorter 40 | ColorString.colorValue = colorValue; 41 | 42 | const pixels = new WeakMap(); 43 | 44 | const Pixel_Controllers = { 45 | FIRMATA: { 46 | initialize: { 47 | value(opts) { 48 | // initialises the base object 49 | 50 | const pixel = { 51 | address: opts.addr, 52 | id: opts.addr, 53 | color: { 54 | r: 0, g: 0, b: 0, hexcode: '#000000', color: 'black', rgb: [0,0,0] 55 | }, 56 | firmata: opts.firmata, 57 | port: opts.port, 58 | parent: opts.strip 59 | }; 60 | 61 | return pixel; 62 | } 63 | }, 64 | pixel_color: { 65 | value(color) { 66 | // sets the actual pixel colour 67 | const pixel = pixels.get(this); 68 | 69 | const data = []; 70 | 71 | data.push(START_SYSEX); 72 | data.push(PIXEL_COMMAND); 73 | data.push(PIXEL_SET_PIXEL); 74 | data.push(pixel.address & FIRMATA_7BIT_MASK); 75 | data.push((pixel.address >> 7) & FIRMATA_7BIT_MASK); 76 | data.push(color & FIRMATA_7BIT_MASK); 77 | data.push((color >> 7) & FIRMATA_7BIT_MASK); 78 | data.push((color >> 14) & FIRMATA_7BIT_MASK); 79 | data.push((color >> 21) & FIRMATA_7BIT_MASK); 80 | data.push(END_SYSEX); 81 | 82 | pixel.port.write(Buffer.from(data)); 83 | } 84 | } 85 | }, 86 | I2CBACKPACK: { 87 | initialize: { 88 | value(opts) { 89 | // initialises the base object 90 | 91 | const pixel = { 92 | address: opts.addr, 93 | id: opts.addr, 94 | color: { 95 | r: 0, g: 0, b: 0, hexcode: '#000000', color: 'black', rgb: [0,0,0] 96 | }, 97 | io: opts.io, 98 | i2c_address: opts.i2c_address, 99 | parent: opts.strip 100 | }; 101 | 102 | return pixel; 103 | } 104 | }, 105 | pixel_color: { 106 | value(color) { 107 | // sets the actual pixel colour 108 | const pixel = pixels.get(this); 109 | 110 | const data = []; 111 | 112 | data.push(PIXEL_SET_PIXEL); 113 | data.push(pixel.address & FIRMATA_7BIT_MASK); 114 | data.push((pixel.address >> 7) & FIRMATA_7BIT_MASK); 115 | data.push(color & FIRMATA_7BIT_MASK); 116 | data.push((color >> 7) & FIRMATA_7BIT_MASK); 117 | data.push((color >> 14) & FIRMATA_7BIT_MASK); 118 | data.push((color >> 21) & FIRMATA_7BIT_MASK); 119 | 120 | pixel.io.i2cWrite(pixel.i2c_address, data); 121 | } 122 | } 123 | } 124 | }; 125 | 126 | function Pixel(opts, stripsInstance = new WeakMap()) { 127 | if (!(this instanceof Pixel)) { 128 | return new Pixel(opts, stripsInstance); 129 | } 130 | 131 | // we can assume this is set because the controller is set by the strip. 132 | const controller = Pixel_Controllers[opts.controller]; 133 | 134 | Object.defineProperties(this, controller); 135 | 136 | // we use this to be able to update the address of the 137 | // pixel in the array if we do shift operations. 138 | Object.defineProperty(this, 'address', { 139 | get() { 140 | const pixel = pixels.get(this); 141 | return pixel.address; 142 | }, 143 | set(newAddress) { 144 | const pixel = pixels.get(this); 145 | pixel.address = newAddress; 146 | } 147 | }); 148 | 149 | pixels.set(this, this.initialize(opts, stripsInstance)); 150 | } 151 | 152 | Pixel.prototype.off = Pixel.prototype.clear = function() { 153 | // sets the pixel value to [0, 0, 0]. Equivalent to calling 154 | // `strip.off()` but for an individual pixel. 155 | this.color([0, 0, 0]); 156 | }; 157 | 158 | Pixel.prototype.colour = Pixel.prototype.color = function(color, opts) { 159 | // use a particular form to set the color either 160 | // color = hex value or named colors or array of colors 161 | // opts can contain _sendmsg_ as bool. If set to false message won't be 162 | // sent to firmata - useful for strip level updates to keep message choke down 163 | 164 | const pixel = pixels.get(this); 165 | 166 | const options = opts || {}; 167 | let sendmsg = true; 168 | if (options.sendmsg != undefined) { sendmsg = options.sendmsg; } 169 | 170 | let pixelcolor = null; 171 | 172 | if (color) { 173 | // get the color based on a string 174 | if (typeof(color) === 'object') { 175 | // we have an RGB array value 176 | pixelcolor = { 177 | model: 'rgb', 178 | value: color 179 | }; 180 | } else { 181 | pixelcolor = ColorString.get(color); 182 | } 183 | } else { 184 | return pixel.color; 185 | } 186 | 187 | if (pixelcolor != null) { 188 | // fill out the values for the pixel and then send the message to update 189 | // it on the strip 190 | 191 | pixel.color.r = pixelcolor.value[0]; 192 | pixel.color.g = pixelcolor.value[1]; 193 | pixel.color.b = pixelcolor.value[2]; 194 | pixel.color.hexcode = ColorString.to.hex(pixelcolor.value); 195 | pixel.color.color = ColorString.to.keyword(pixelcolor.value); 196 | if (pixelcolor.value.length == 4) { 197 | pixelcolor.value.pop(); 198 | } 199 | pixel.color.rgb = pixelcolor.value; 200 | 201 | color = ColorString.colorValue(pixelcolor.value, pixel.parent.gtable); 202 | if (sendmsg) { 203 | // TODO probably should be pulling the color off the obj rather than 204 | // sending it to this function.... 205 | this.pixel_color(color); 206 | } 207 | } else { 208 | console.log("Color supplied couldn't be parsed: " + color); 209 | } 210 | }; 211 | 212 | // controllers for the pixel side as well. 213 | module.exports = { 214 | Pixel, 215 | ColorString, 216 | colorValue 217 | }; 218 | -------------------------------------------------------------------------------- /test/firmata.js: -------------------------------------------------------------------------------- 1 | global.IS_TEST_MODE = true; 2 | const mocks = require('mock-firmata'); 3 | const MockFirmata = mocks.Firmata; 4 | const MockSerialPort = mocks.SerialPort; 5 | 6 | const sinon = require('sinon'); 7 | 8 | const five = require('johnny-five'); 9 | const pixel = require('../lib/index.js'); 10 | 11 | const Board = five.Board; 12 | 13 | function newBoard() { 14 | const sp = new MockSerialPort('/dev/test'); 15 | const io = new MockFirmata(sp); 16 | io['firmware'] = { name: 'node_pixel_firmata.ino' }; 17 | 18 | io.emit('connect'); 19 | io.emit('ready'); 20 | 21 | const board = new Board({ 22 | io, 23 | debug: false, 24 | repl: false 25 | }); 26 | 27 | return board; 28 | } 29 | 30 | function restore(target) { 31 | for (const prop in target) { 32 | if (Array.isArray(target[prop])) { 33 | continue; 34 | } 35 | 36 | if (target[prop] != null && typeof target[prop].restore === 'function') { 37 | target[prop].restore(); 38 | } 39 | 40 | if (typeof target[prop] === 'object') { 41 | restore(target[prop]); 42 | } 43 | } 44 | } 45 | 46 | exports['Firmata - Initialisation'] = { 47 | 48 | setUp(done) { 49 | this.board = newBoard(); 50 | done(); 51 | }, 52 | 53 | tearDown(done) { 54 | Board.purge(); 55 | restore(this); 56 | done(); 57 | }, 58 | 59 | firmataInitialisation(test) { 60 | test.expect(4); 61 | const mock_firmata = { 62 | firmware: { 63 | name: 'node_pixel_firmata.ino' 64 | } 65 | }; 66 | 67 | test.doesNotThrow( 68 | () => { 69 | const strip = new pixel.Strip({ 70 | data: 6, 71 | length: 8, 72 | board: this.board 73 | }); 74 | }, 75 | 'If no controller is provided strip should default to firmata without error' 76 | ); 77 | 78 | // check error states. 79 | test.throws( 80 | () => { 81 | const strip = new pixel.Strip({ 82 | data: 6, 83 | length: 8, 84 | controller: 'FIRMATA' 85 | }); 86 | }, 87 | function(err) { 88 | if (err) { 89 | if (err.name == 'NoFirmataError') { 90 | return true; 91 | } 92 | return false; 93 | } 94 | return false; 95 | }, 96 | 'If board is not present an error should be thrown' 97 | ); 98 | 99 | test.throws( 100 | () => { 101 | // build with an empty firmata to test non-writable port. 102 | // 103 | 104 | const strip = new pixel.Strip({ 105 | data: 6, 106 | length: 8, 107 | firmata: mock_firmata, 108 | controller: 'FIRMATA' 109 | }); 110 | }, 111 | function(err) { 112 | if (err) { 113 | if (err.name == 'NoWritablePortError') { 114 | return true; 115 | } 116 | return false; 117 | } 118 | return false; 119 | }, 120 | 'If there is no writable port, controller should throw an error' 121 | ); 122 | 123 | test.throws( 124 | () => { 125 | // build with an empty firmata to test bad naming. 126 | mock_firmata.firmware.name = 'StandardFirmata.ino'; 127 | 128 | const strip = new pixel.Strip({ 129 | data: 6, 130 | length: 8, 131 | firmata: mock_firmata, 132 | controller: 'FIRMATA' 133 | }); 134 | }, 135 | function(err) { 136 | if (err) { 137 | if (err.name == 'IncorrectFirmataVersionError') { 138 | return true; 139 | } 140 | return false; 141 | } 142 | return false; 143 | }, 144 | 'If firmware name is incorrect, controller should throw an error' 145 | ); 146 | 147 | test.done(); 148 | } 149 | 150 | 151 | }; 152 | 153 | exports['Strip - Firmata'] = { 154 | setUp(done) { 155 | this.write = sinon.stub(MockSerialPort.prototype, 'write').callsFake( function(buffer, callback) { 156 | if (typeof callback === 'function') { 157 | process.nextTick(callback); 158 | } else { 159 | return; 160 | } 161 | }); 162 | 163 | this.board = newBoard(); 164 | done(); 165 | }, 166 | 167 | tearDown(done) { 168 | Board.purge(); 169 | restore(this); 170 | done(); 171 | }, 172 | 173 | stripReady(test) { 174 | // tests if the strip emits the ready event properly. 175 | test.expect(2); 176 | 177 | const strip = new pixel.Strip({ 178 | data: 6, 179 | length: 8, 180 | board: this.board, 181 | controller: 'FIRMATA' 182 | }); 183 | 184 | test.equal(this.write.callCount, 1, 185 | 'During initialisation serial write should occur only once'); 186 | 187 | strip.on('ready', function() { 188 | test.ok(true, 'If initialisation is complete a ready event should be emitted'); 189 | test.done(); 190 | }); 191 | }, 192 | 193 | maxNumberOfStrips(test) { 194 | test.expect(1); 195 | 196 | test.throws( 197 | () => { 198 | const strip = new pixel.Strip({ 199 | board: this.board, 200 | controller: 'FIRMATA', 201 | strips: [8, 8, 8, 8, 8, 8, 8, 8, 8] 202 | }); 203 | }, 204 | function(err) { 205 | if (err instanceof RangeError) { 206 | return true; 207 | } 208 | }, 209 | 'Excessive number of strips should throw a RangeError' 210 | ); 211 | 212 | test.done(); 213 | }, 214 | 215 | maxNumberOfPixels(test) { 216 | test.expect(2); 217 | 218 | test.throws( 219 | () => { 220 | const strip1 = new pixel.Strip({ 221 | board: this.board, 222 | controller: 'FIRMATA', 223 | strips: [ {pin: 6, length: 300} ] 224 | }); 225 | }, 226 | function(err) { 227 | if (err instanceof RangeError) { 228 | return true; 229 | } 230 | }, 231 | 'Excess pixels in a single strip should throw a RangeError' 232 | ); 233 | 234 | test.throws( 235 | () => { 236 | const strip2 = new pixel.Strip({ 237 | board: this.board, 238 | controller: 'FIRMATA', 239 | strips: [ {pin: 2, length: 64}, 240 | {pin: 2, length: 64}, 241 | {pin: 2, length: 64}, 242 | {pin: 2, length: 64}, 243 | {pin: 2, length: 64} 244 | ] // more than 256 245 | }); 246 | }, 247 | function(err) { 248 | if (err instanceof RangeError) { 249 | return true; 250 | } 251 | }, 252 | 'Excess pixels in multiple strips should throw a RangeError' 253 | ); 254 | test.done(); 255 | }, 256 | 257 | show(test) { 258 | // tests if the strip calls the show out to I2C properly. 259 | // 260 | test.expect(2); 261 | 262 | const strip = new pixel.Strip({ 263 | data: 6, 264 | length: 8, 265 | board: this.board, 266 | controller: 'FIRMATA' 267 | }); 268 | 269 | strip.on('ready', function() { 270 | test.equal(this.write.callCount, 1, 271 | 'Firmata should call serial write only once during setup'); 272 | strip.show(); 273 | test.equal(this.write.callCount, 2, 274 | 'Show should call serial write once after setup is complete.'); 275 | test.done(); 276 | }.bind(this)); 277 | } 278 | }; 279 | 280 | exports['Pixel - Firmata'] = { 281 | setUp(done) { 282 | this.write = sinon.stub(MockSerialPort.prototype, 'write').callsFake(function(buffer, callback) { 283 | if (typeof callback === 'function') { 284 | process.nextTick(callback); 285 | } else { 286 | return; 287 | } 288 | }); 289 | 290 | this.board = newBoard(); 291 | 292 | this.strip = new pixel.Strip({ 293 | data: 6, 294 | length: 4, 295 | board: this.board, 296 | controller: 'FIRMATA' 297 | }); 298 | 299 | done(); 300 | }, 301 | 302 | tearDown(done) { 303 | Board.purge(); 304 | restore(this); 305 | done(); 306 | }, 307 | 308 | writing(test) { 309 | // tests to see whether the write to the pixel is going out properly 310 | test.expect(1); 311 | this.strip.pixel(0).color('#FFF'); 312 | test.equal(this.write.callCount, 2, 313 | 'Setting the pixel value should make a single serial call'); 314 | test.done() 315 | } 316 | }; 317 | 318 | -------------------------------------------------------------------------------- /firmware/build/node_pixel_firmata/Firmata.h: -------------------------------------------------------------------------------- 1 | /* 2 | Firmata.h - Firmata library v2.5.8 - 2018-04-15 3 | Copyright (c) 2006-2008 Hans-Christoph Steiner. All rights reserved. 4 | Copyright (C) 2009-2017 Jeff Hoefs. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | See file LICENSE.txt for further informations on licensing terms. 12 | */ 13 | 14 | #ifndef Firmata_h 15 | #define Firmata_h 16 | 17 | #include "Boards.h" /* Hardware Abstraction Layer + Wiring/Arduino */ 18 | #include "FirmataDefines.h" 19 | #include "FirmataMarshaller.h" 20 | #include "FirmataParser.h" 21 | 22 | /* DEPRECATED as of Firmata v2.5.1. As of 2.5.1 there are separate version numbers for 23 | * the protocol version and the firmware version. 24 | */ 25 | #define FIRMATA_MAJOR_VERSION 2 // same as FIRMATA_PROTOCOL_MAJOR_VERSION 26 | #define FIRMATA_MINOR_VERSION 5 // same as FIRMATA_PROTOCOL_MINOR_VERSION 27 | #define FIRMATA_BUGFIX_VERSION 1 // same as FIRMATA_PROTOCOL_BUGFIX_VERSION 28 | 29 | // extended command set using sysex (0-127/0x00-0x7F) 30 | /* 0x00-0x0F reserved for user-defined commands */ 31 | // these are DEPRECATED to make the naming more consistent 32 | #define FIRMATA_STRING 0x71 // same as STRING_DATA 33 | #define SYSEX_I2C_REQUEST 0x76 // same as I2C_REQUEST 34 | #define SYSEX_I2C_REPLY 0x77 // same as I2C_REPLY 35 | #define SYSEX_SAMPLING_INTERVAL 0x7A // same as SAMPLING_INTERVAL 36 | 37 | // pin modes 38 | //#define INPUT 0x00 // defined in Arduino.h 39 | //#define OUTPUT 0x01 // defined in Arduino.h 40 | // DEPRECATED as of Firmata v2.5 41 | #define ANALOG 0x02 // same as PIN_MODE_ANALOG 42 | #define PWM 0x03 // same as PIN_MODE_PWM 43 | #define SERVO 0x04 // same as PIN_MODE_SERVO 44 | #define SHIFT 0x05 // same as PIN_MODE_SHIFT 45 | #define I2C 0x06 // same as PIN_MODE_I2C 46 | #define ONEWIRE 0x07 // same as PIN_MODE_ONEWIRE 47 | #define STEPPER 0x08 // same as PIN_MODE_STEPPER 48 | #define ENCODER 0x09 // same as PIN_MODE_ENCODER 49 | #define IGNORE 0x7F // same as PIN_MODE_IGNORE 50 | 51 | namespace firmata { 52 | 53 | // TODO make it a subclass of a generic Serial/Stream base class 54 | class FirmataClass 55 | { 56 | public: 57 | typedef void (*callbackFunction)(uint8_t, int); 58 | typedef void (*systemCallbackFunction)(void); 59 | typedef void (*stringCallbackFunction)(char *); 60 | typedef void (*sysexCallbackFunction)(uint8_t command, uint8_t argc, uint8_t *argv); 61 | 62 | FirmataClass(); 63 | 64 | /* Arduino constructors */ 65 | void begin(); 66 | void begin(long); 67 | void begin(Stream &s); 68 | 69 | /* querying functions */ 70 | void printVersion(void); 71 | void blinkVersion(void); 72 | void printFirmwareVersion(void); 73 | 74 | //void setFirmwareVersion(byte major, byte minor); // see macro below 75 | void setFirmwareNameAndVersion(const char *name, byte major, byte minor); 76 | void disableBlinkVersion(); 77 | 78 | /* serial receive handling */ 79 | int available(void); 80 | void processInput(void); 81 | void parse(unsigned char value); 82 | boolean isParsingMessage(void); 83 | 84 | /* serial send handling */ 85 | void sendAnalog(byte pin, int value); 86 | void sendDigital(byte pin, int value); // TODO implement this 87 | void sendDigitalPort(byte portNumber, int portData); 88 | void sendString(const char *string); 89 | void sendString(byte command, const char *string); 90 | void sendSysex(byte command, byte bytec, byte *bytev); 91 | void write(byte c); 92 | 93 | /* attach & detach callback functions to messages */ 94 | void attach(uint8_t command, callbackFunction newFunction); 95 | void attach(uint8_t command, systemCallbackFunction newFunction); 96 | void attach(uint8_t command, stringCallbackFunction newFunction); 97 | void attach(uint8_t command, sysexCallbackFunction newFunction); 98 | void detach(uint8_t command); 99 | 100 | /* access pin state and config */ 101 | byte getPinMode(byte pin); 102 | void setPinMode(byte pin, byte config); 103 | 104 | /* access pin state */ 105 | int getPinState(byte pin); 106 | void setPinState(byte pin, int state); 107 | 108 | /* utility methods */ 109 | void sendValueAsTwo7bitBytes(int value); 110 | void startSysex(void); 111 | void endSysex(void); 112 | 113 | private: 114 | uint8_t parserBuffer[MAX_DATA_BYTES]; 115 | FirmataMarshaller marshaller; 116 | FirmataParser parser; 117 | Stream *FirmataStream; 118 | 119 | /* firmware name and version */ 120 | byte firmwareVersionCount; 121 | byte *firmwareVersionVector; 122 | 123 | /* pin configuration */ 124 | byte pinConfig[TOTAL_PINS]; 125 | int pinState[TOTAL_PINS]; 126 | 127 | boolean blinkVersionDisabled; 128 | 129 | /* private methods ------------------------------ */ 130 | void strobeBlinkPin(byte pin, int count, int onInterval, int offInterval); 131 | friend void FirmataMarshaller::encodeByteStream (size_t bytec, uint8_t * bytev, size_t max_bytes) const; 132 | 133 | /* callback functions */ 134 | static callbackFunction currentAnalogCallback; 135 | static callbackFunction currentDigitalCallback; 136 | static callbackFunction currentPinModeCallback; 137 | static callbackFunction currentPinValueCallback; 138 | static callbackFunction currentReportAnalogCallback; 139 | static callbackFunction currentReportDigitalCallback; 140 | static stringCallbackFunction currentStringCallback; 141 | static sysexCallbackFunction currentSysexCallback; 142 | static systemCallbackFunction currentSystemResetCallback; 143 | 144 | /* static callbacks */ 145 | inline static void staticAnalogCallback (void *, uint8_t command, uint16_t value) { if ( currentAnalogCallback ) { currentAnalogCallback(command,(int)value); } } 146 | inline static void staticDigitalCallback (void *, uint8_t command, uint16_t value) { if ( currentDigitalCallback ) { currentDigitalCallback(command, (int)value); } } 147 | inline static void staticPinModeCallback (void *, uint8_t command, uint16_t value) { if ( currentPinModeCallback ) { currentPinModeCallback(command, (int)value); } } 148 | inline static void staticPinValueCallback (void *, uint8_t command, uint16_t value) { if ( currentPinValueCallback ) { currentPinValueCallback(command, (int)value); } } 149 | inline static void staticReportAnalogCallback (void *, uint8_t command, uint16_t value) { if ( currentReportAnalogCallback ) { currentReportAnalogCallback(command, (int)value); } } 150 | inline static void staticReportDigitalCallback (void *, uint8_t command, uint16_t value) { if ( currentReportDigitalCallback ) { currentReportDigitalCallback(command, (int)value); } } 151 | inline static void staticStringCallback (void *, const char * c_str) { if ( currentStringCallback ) { currentStringCallback((char *)c_str); } } 152 | inline static void staticSysexCallback (void *, uint8_t command, size_t argc, uint8_t *argv) { if ( currentSysexCallback ) { currentSysexCallback(command, (uint8_t)argc, argv); } } 153 | inline static void staticReportFirmwareCallback (void * context, size_t, size_t, const char *) { if ( context ) { ((FirmataClass *)context)->printFirmwareVersion(); } } 154 | inline static void staticReportVersionCallback (void * context) { if ( context ) { ((FirmataClass *)context)->printVersion(); } } 155 | inline static void staticSystemResetCallback (void *) { if ( currentSystemResetCallback ) { currentSystemResetCallback(); } } 156 | }; 157 | 158 | } // namespace firmata 159 | 160 | extern "C" { 161 | // callback function types 162 | typedef firmata::FirmataClass::callbackFunction callbackFunction; 163 | typedef firmata::FirmataClass::systemCallbackFunction systemCallbackFunction; 164 | typedef firmata::FirmataClass::stringCallbackFunction stringCallbackFunction; 165 | typedef firmata::FirmataClass::sysexCallbackFunction sysexCallbackFunction; 166 | } 167 | 168 | extern firmata::FirmataClass Firmata; 169 | 170 | /*============================================================================== 171 | * MACROS 172 | *============================================================================*/ 173 | 174 | /* shortcut for setFirmwareNameAndVersion() that uses __FILE__ to set the 175 | * firmware name. It needs to be a macro so that __FILE__ is included in the 176 | * firmware source file rather than the library source file. 177 | */ 178 | #define setFirmwareVersion(x, y) setFirmwareNameAndVersion(__FILE__, x, y) 179 | 180 | #endif /* Firmata_h */ 181 | -------------------------------------------------------------------------------- /firmware/build/node_pixel_firmata/FirmataDefines.h: -------------------------------------------------------------------------------- 1 | /* 2 | FirmataDefines.h 3 | Copyright (c) 2006-2008 Hans-Christoph Steiner. All rights reserved. 4 | Copyright (C) 2009-2016 Jeff Hoefs. All rights reserved. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | See file LICENSE.txt for further informations on licensing terms. 12 | */ 13 | 14 | #ifndef FirmataDefines_h 15 | #define FirmataDefines_h 16 | 17 | #include "FirmataConstants.h" 18 | 19 | /* Version numbers for the Firmata library. 20 | * The firmware version will not always equal the protocol version going forward. 21 | * Query using the REPORT_FIRMWARE message. 22 | */ 23 | #define FIRMATA_FIRMWARE_MAJOR_VERSION firmata::FIRMWARE_MAJOR_VERSION 24 | #define FIRMATA_FIRMWARE_MINOR_VERSION firmata::FIRMWARE_MINOR_VERSION 25 | #define FIRMATA_FIRMWARE_BUGFIX_VERSION firmata::FIRMWARE_BUGFIX_VERSION 26 | 27 | /* Version numbers for the protocol. The protocol is still changing, so these 28 | * version numbers are important. 29 | * Query using the REPORT_VERSION message. 30 | */ 31 | #define FIRMATA_PROTOCOL_MAJOR_VERSION firmata::PROTOCOL_MAJOR_VERSION // for non-compatible changes 32 | #define FIRMATA_PROTOCOL_MINOR_VERSION firmata::PROTOCOL_MINOR_VERSION // for backwards compatible changes 33 | #define FIRMATA_PROTOCOL_BUGFIX_VERSION firmata::PROTOCOL_BUGFIX_VERSION // for bugfix releases 34 | 35 | #ifdef MAX_DATA_BYTES 36 | #undef MAX_DATA_BYTES 37 | #endif 38 | #define MAX_DATA_BYTES firmata::MAX_DATA_BYTES // max number of data bytes in incoming messages 39 | 40 | // message command bytes (128-255/0x80-0xFF) 41 | 42 | #ifdef DIGITAL_MESSAGE 43 | #undef DIGITAL_MESSAGE 44 | #endif 45 | #define DIGITAL_MESSAGE firmata::DIGITAL_MESSAGE // send data for a digital port (collection of 8 pins) 46 | 47 | #ifdef ANALOG_MESSAGE 48 | #undef ANALOG_MESSAGE 49 | #endif 50 | #define ANALOG_MESSAGE firmata::ANALOG_MESSAGE // send data for an analog pin (or PWM) 51 | 52 | #ifdef REPORT_ANALOG 53 | #undef REPORT_ANALOG 54 | #endif 55 | #define REPORT_ANALOG firmata::REPORT_ANALOG // enable analog input by pin # 56 | 57 | #ifdef REPORT_DIGITAL 58 | #undef REPORT_DIGITAL 59 | #endif 60 | #define REPORT_DIGITAL firmata::REPORT_DIGITAL // enable digital input by port pair 61 | 62 | // 63 | 64 | #ifdef SET_PIN_MODE 65 | #undef SET_PIN_MODE 66 | #endif 67 | #define SET_PIN_MODE firmata::SET_PIN_MODE // set a pin to INPUT/OUTPUT/PWM/etc 68 | 69 | #ifdef SET_DIGITAL_PIN_VALUE 70 | #undef SET_DIGITAL_PIN_VALUE 71 | #endif 72 | #define SET_DIGITAL_PIN_VALUE firmata::SET_DIGITAL_PIN_VALUE // set value of an individual digital pin 73 | 74 | // 75 | 76 | #ifdef REPORT_VERSION 77 | #undef REPORT_VERSION 78 | #endif 79 | #define REPORT_VERSION firmata::REPORT_VERSION // report protocol version 80 | 81 | #ifdef SYSTEM_RESET 82 | #undef SYSTEM_RESET 83 | #endif 84 | #define SYSTEM_RESET firmata::SYSTEM_RESET // reset from MIDI 85 | 86 | // 87 | 88 | #ifdef START_SYSEX 89 | #undef START_SYSEX 90 | #endif 91 | #define START_SYSEX firmata::START_SYSEX // start a MIDI Sysex message 92 | 93 | #ifdef END_SYSEX 94 | #undef END_SYSEX 95 | #endif 96 | #define END_SYSEX firmata::END_SYSEX // end a MIDI Sysex message 97 | 98 | // extended command set using sysex (0-127/0x00-0x7F) 99 | /* 0x00-0x0F reserved for user-defined commands */ 100 | 101 | #ifdef SERIAL_MESSAGE 102 | #undef SERIAL_MESSAGE 103 | #endif 104 | #define SERIAL_MESSAGE firmata::SERIAL_DATA // communicate with serial devices, including other boards 105 | 106 | #ifdef ENCODER_DATA 107 | #undef ENCODER_DATA 108 | #endif 109 | #define ENCODER_DATA firmata::ENCODER_DATA // reply with encoders current positions 110 | 111 | #ifdef SERVO_CONFIG 112 | #undef SERVO_CONFIG 113 | #endif 114 | #define SERVO_CONFIG firmata::SERVO_CONFIG // set max angle, minPulse, maxPulse, freq 115 | 116 | #ifdef STRING_DATA 117 | #undef STRING_DATA 118 | #endif 119 | #define STRING_DATA firmata::STRING_DATA // a string message with 14-bits per char 120 | 121 | #ifdef STEPPER_DATA 122 | #undef STEPPER_DATA 123 | #endif 124 | #define STEPPER_DATA firmata::STEPPER_DATA // control a stepper motor 125 | 126 | #ifdef ONEWIRE_DATA 127 | #undef ONEWIRE_DATA 128 | #endif 129 | #define ONEWIRE_DATA firmata::ONEWIRE_DATA // send an OneWire read/write/reset/select/skip/search request 130 | 131 | #ifdef SHIFT_DATA 132 | #undef SHIFT_DATA 133 | #endif 134 | #define SHIFT_DATA firmata::SHIFT_DATA // a bitstream to/from a shift register 135 | 136 | #ifdef I2C_REQUEST 137 | #undef I2C_REQUEST 138 | #endif 139 | #define I2C_REQUEST firmata::I2C_REQUEST // send an I2C read/write request 140 | 141 | #ifdef I2C_REPLY 142 | #undef I2C_REPLY 143 | #endif 144 | #define I2C_REPLY firmata::I2C_REPLY // a reply to an I2C read request 145 | 146 | #ifdef I2C_CONFIG 147 | #undef I2C_CONFIG 148 | #endif 149 | #define I2C_CONFIG firmata::I2C_CONFIG // config I2C settings such as delay times and power pins 150 | 151 | #ifdef REPORT_FIRMWARE 152 | #undef REPORT_FIRMWARE 153 | #endif 154 | #define REPORT_FIRMWARE firmata::REPORT_FIRMWARE // report name and version of the firmware 155 | 156 | #ifdef EXTENDED_ANALOG 157 | #undef EXTENDED_ANALOG 158 | #endif 159 | #define EXTENDED_ANALOG firmata::EXTENDED_ANALOG // analog write (PWM, Servo, etc) to any pin 160 | 161 | #ifdef PIN_STATE_QUERY 162 | #undef PIN_STATE_QUERY 163 | #endif 164 | #define PIN_STATE_QUERY firmata::PIN_STATE_QUERY // ask for a pin's current mode and value 165 | 166 | #ifdef PIN_STATE_RESPONSE 167 | #undef PIN_STATE_RESPONSE 168 | #endif 169 | #define PIN_STATE_RESPONSE firmata::PIN_STATE_RESPONSE // reply with pin's current mode and value 170 | 171 | #ifdef CAPABILITY_QUERY 172 | #undef CAPABILITY_QUERY 173 | #endif 174 | #define CAPABILITY_QUERY firmata::CAPABILITY_QUERY // ask for supported modes and resolution of all pins 175 | 176 | #ifdef CAPABILITY_RESPONSE 177 | #undef CAPABILITY_RESPONSE 178 | #endif 179 | #define CAPABILITY_RESPONSE firmata::CAPABILITY_RESPONSE // reply with supported modes and resolution 180 | 181 | #ifdef ANALOG_MAPPING_QUERY 182 | #undef ANALOG_MAPPING_QUERY 183 | #endif 184 | #define ANALOG_MAPPING_QUERY firmata::ANALOG_MAPPING_QUERY // ask for mapping of analog to pin numbers 185 | 186 | #ifdef ANALOG_MAPPING_RESPONSE 187 | #undef ANALOG_MAPPING_RESPONSE 188 | #endif 189 | #define ANALOG_MAPPING_RESPONSE firmata::ANALOG_MAPPING_RESPONSE // reply with mapping info 190 | 191 | #ifdef SAMPLING_INTERVAL 192 | #undef SAMPLING_INTERVAL 193 | #endif 194 | #define SAMPLING_INTERVAL firmata::SAMPLING_INTERVAL // set the poll rate of the main loop 195 | 196 | #ifdef SCHEDULER_DATA 197 | #undef SCHEDULER_DATA 198 | #endif 199 | #define SCHEDULER_DATA firmata::SCHEDULER_DATA // send a createtask/deletetask/addtotask/schedule/querytasks/querytask request to the scheduler 200 | 201 | #ifdef SYSEX_NON_REALTIME 202 | #undef SYSEX_NON_REALTIME 203 | #endif 204 | #define SYSEX_NON_REALTIME firmata::SYSEX_NON_REALTIME // MIDI Reserved for non-realtime messages 205 | 206 | #ifdef SYSEX_REALTIME 207 | #undef SYSEX_REALTIME 208 | #endif 209 | #define SYSEX_REALTIME firmata::SYSEX_REALTIME // MIDI Reserved for realtime messages 210 | 211 | // pin modes 212 | 213 | #ifdef PIN_MODE_INPUT 214 | #undef PIN_MODE_INPUT 215 | #endif 216 | #define PIN_MODE_INPUT firmata::PIN_MODE_INPUT // same as INPUT defined in Arduino.h 217 | 218 | #ifdef PIN_MODE_OUTPUT 219 | #undef PIN_MODE_OUTPUT 220 | #endif 221 | #define PIN_MODE_OUTPUT firmata::PIN_MODE_OUTPUT // same as OUTPUT defined in Arduino.h 222 | 223 | #ifdef PIN_MODE_ANALOG 224 | #undef PIN_MODE_ANALOG 225 | #endif 226 | #define PIN_MODE_ANALOG firmata::PIN_MODE_ANALOG // analog pin in analogInput mode 227 | 228 | #ifdef PIN_MODE_PWM 229 | #undef PIN_MODE_PWM 230 | #endif 231 | #define PIN_MODE_PWM firmata::PIN_MODE_PWM // digital pin in PWM output mode 232 | 233 | #ifdef PIN_MODE_SERVO 234 | #undef PIN_MODE_SERVO 235 | #endif 236 | #define PIN_MODE_SERVO firmata::PIN_MODE_SERVO // digital pin in Servo output mode 237 | 238 | #ifdef PIN_MODE_SHIFT 239 | #undef PIN_MODE_SHIFT 240 | #endif 241 | #define PIN_MODE_SHIFT firmata::PIN_MODE_SHIFT // shiftIn/shiftOut mode 242 | 243 | #ifdef PIN_MODE_I2C 244 | #undef PIN_MODE_I2C 245 | #endif 246 | #define PIN_MODE_I2C firmata::PIN_MODE_I2C // pin included in I2C setup 247 | 248 | #ifdef PIN_MODE_ONEWIRE 249 | #undef PIN_MODE_ONEWIRE 250 | #endif 251 | #define PIN_MODE_ONEWIRE firmata::PIN_MODE_ONEWIRE // pin configured for 1-wire 252 | 253 | #ifdef PIN_MODE_STEPPER 254 | #undef PIN_MODE_STEPPER 255 | #endif 256 | #define PIN_MODE_STEPPER firmata::PIN_MODE_STEPPER // pin configured for stepper motor 257 | 258 | #ifdef PIN_MODE_ENCODER 259 | #undef PIN_MODE_ENCODER 260 | #endif 261 | #define PIN_MODE_ENCODER firmata::PIN_MODE_ENCODER // pin configured for rotary encoders 262 | 263 | #ifdef PIN_MODE_SERIAL 264 | #undef PIN_MODE_SERIAL 265 | #endif 266 | #define PIN_MODE_SERIAL firmata::PIN_MODE_SERIAL // pin configured for serial communication 267 | 268 | #ifdef PIN_MODE_PULLUP 269 | #undef PIN_MODE_PULLUP 270 | #endif 271 | #define PIN_MODE_PULLUP firmata::PIN_MODE_PULLUP // enable internal pull-up resistor for pin 272 | 273 | #ifdef PIN_MODE_IGNORE 274 | #undef PIN_MODE_IGNORE 275 | #endif 276 | #define PIN_MODE_IGNORE firmata::PIN_MODE_IGNORE // pin configured to be ignored by digitalWrite and capabilityResponse 277 | 278 | #ifdef TOTAL_PIN_MODES 279 | #undef TOTAL_PIN_MODES 280 | #endif 281 | #define TOTAL_PIN_MODES firmata::TOTAL_PIN_MODES 282 | 283 | #endif // FirmataConstants_h 284 | -------------------------------------------------------------------------------- /test/core.js: -------------------------------------------------------------------------------- 1 | global.IS_TEST_MODE = true; 2 | const mocks = require('mock-firmata'); 3 | const MockFirmata = mocks.Firmata; 4 | const MockSerialPort = mocks.SerialPort; 5 | 6 | const sinon = require('sinon'); 7 | 8 | const five = require('johnny-five'); 9 | const pixel = require('../lib/index.js'); 10 | const { colorValue } = require('../lib/pixel'); 11 | const { create_gamma_table } = require('../lib/utils'); 12 | 13 | const Board = five.Board; 14 | 15 | function newBoard() { 16 | const sp = new MockSerialPort('/dev/test'); 17 | const io = new MockFirmata(sp); 18 | io['firmware'] = { name: 'node_pixel_firmata.ino' }; 19 | 20 | io.emit('connect'); 21 | io.emit('ready'); 22 | 23 | const board = new Board({ 24 | io, 25 | debug: false, 26 | repl: false 27 | }); 28 | 29 | return board; 30 | } 31 | 32 | function restore(target) { 33 | for (const prop in target) { 34 | if (Array.isArray(target[prop])) { 35 | continue; 36 | } 37 | 38 | if (target[prop] != null && typeof target[prop].restore === 'function') { 39 | target[prop].restore(); 40 | } 41 | 42 | if (typeof target[prop] === 'object') { 43 | restore(target[prop]); 44 | } 45 | } 46 | } 47 | 48 | exports['Test Mode configured'] = { 49 | setUp(done) { 50 | done(); 51 | }, 52 | 53 | tearDown(done) { 54 | done(); 55 | }, 56 | 57 | testMode(test) { 58 | // tests that the env variable is set properly 59 | test.expect(1); 60 | test.equal(process.env.IS_TEST_MODE, 'true', 'Test mode should be configured'); 61 | test.done(); 62 | } 63 | }; 64 | 65 | exports['Color Value'] = { 66 | setUp(done) { 67 | done(); 68 | }, 69 | tearDown(done) { 70 | done(); 71 | }, 72 | leak(test) { 73 | test.expect(1); 74 | const internalGTable = create_gamma_table(256, 2.8, false); 75 | const baseColor = [123,123,123]; 76 | colorValue(baseColor, internalGTable); 77 | test.deepEqual(baseColor, [123,123,123], 'The input should not be modified at all by the gamma correction'); 78 | test.done(); 79 | } 80 | }; 81 | 82 | exports['Strip'] = { 83 | // used for the main strip tests. 84 | setUp(done) { 85 | this.board = newBoard(); 86 | this.strip = new pixel.Strip({ 87 | data: 6, 88 | length: 8, 89 | board: this.board, 90 | controller: 'FIRMATA' 91 | }); 92 | done(); 93 | }, 94 | 95 | tearDown(done) { 96 | Board.purge(); 97 | restore(this); 98 | done(); 99 | }, 100 | 101 | length(test) { 102 | // tests length of the strip properly. 103 | test.expect(3); 104 | 105 | const strip = new pixel.Strip({ 106 | board: this.board, 107 | controller: 'FIRMATA', 108 | strips: [{pin: 2, length: 100}] 109 | }); 110 | test.equal(strip.length, 100, 'Single strip length should be equal to provided length'); 111 | 112 | const strip2 = new pixel.Strip({ 113 | board: this.board, 114 | controller: 'FIRMATA', 115 | strips: [{pin: 2, length: 50}, {pin: 3, length: 50}] 116 | }); 117 | test.equal(strip2.length, 100, 'Multiple strips length should be equal to sum of lengths'); 118 | 119 | const strip3 = new pixel.Strip({ 120 | board: this.board, 121 | controller: 'FIRMATA', 122 | pin: 3, 123 | length: 150 124 | }); 125 | 126 | test.throws(() => { 127 | strip3.stripLength() 128 | }, 129 | /NotImplemented/, 130 | 'Deprecated stripLength() should throw NotImplemented error'); 131 | 132 | test.done(); 133 | }, 134 | 135 | colour(test) { 136 | // tests if the colour sequences are working okay 137 | test.expect(4); 138 | 139 | let colourcheck = { 140 | r: 255, g: 255, b: 255, 141 | hexcode: '#FFFFFF', 142 | color: 'white', 143 | rgb: [255, 255, 255] 144 | }; 145 | 146 | this.strip.color('#FFFFFF'); 147 | test.deepEqual(this.strip.pixel(0).color(), colourcheck, 148 | 'If colour is set with full hex colour, colour object should be updated'); 149 | 150 | colourcheck = { 151 | r: 0, g: 255, b: 0, 152 | hexcode: '#00FF00', 153 | color: 'lime', 154 | rgb: [0, 255, 0] 155 | }; 156 | 157 | this.strip.color([0, 255, 0]); 158 | test.deepEqual(this.strip.pixel(3).color(), colourcheck, 159 | 'If setting colour by RGB array, the colour object should be updated'); 160 | 161 | test.doesNotThrow( 162 | () => { 163 | this.strip.colour('QWERTYUIOP'); 164 | }, 165 | undefined, 166 | 'An invalid color should be ignored not throw an error' 167 | ); 168 | 169 | test.doesNotThrow( 170 | () => { 171 | this.strip.color(); 172 | }, 173 | undefined, 174 | 'When no colour is provided it should be ignored and not throw an error' 175 | ); 176 | 177 | test.done(); 178 | }, 179 | 180 | off(test) { 181 | // tests if setting strip off results in black pixel colour 182 | test.expect(1); 183 | 184 | this.strip.color('#FF0000'); 185 | 186 | const colourcheck = { 187 | r: 0, g: 0, b: 0, 188 | hexcode: '#000000', 189 | color: 'black', 190 | rgb: [0, 0, 0] 191 | }; 192 | 193 | this.strip.off(); 194 | test.deepEqual(this.strip.pixel(0).color(), colourcheck, 195 | 'If setting a colour then turning the strip off, the colour should revert to off state.'); 196 | test.done(); 197 | }, 198 | 199 | gamma(test) { 200 | // tests if setting the gamma works as expected 201 | test.expect(4); 202 | 203 | // test gamma being set 204 | const strip = new pixel.Strip({ 205 | board: this.board, 206 | controller: 'FIRMATA', 207 | strips: [{pin: 2, length: 1}], 208 | gamma: 2.3 209 | }); 210 | test.equal(strip.gamma, 2.3, 211 | 'If setting gamma in constructor, the gamma value should be retained'); 212 | 213 | test.equal(strip.gtable.length, 256, 214 | 'If setting gamma in constructor, the Gamma Table should be built'); 215 | 216 | test.equal(strip.gtable[18], 1, 217 | 'If setting gamma, the gamma table values should be built correctly'); 218 | 219 | // now check that a non gamma returns the right values 220 | const strip2 = new pixel.Strip({ 221 | board: this.board, 222 | controller: 'FIRMATA', 223 | strips: [{pin: 2, length: 1}] 224 | }); 225 | 226 | test.equal(strip2.gtable[18], 18, 227 | 'If gamma is not set, the gamma values should be built using default'); 228 | 229 | test.done(); 230 | }, 231 | 232 | shift(test) { 233 | // tests that wrapping behaviour is consistent 234 | 235 | test.expect(12); 236 | 237 | const strip = new pixel.Strip({ 238 | board: this.board, 239 | controller: 'FIRMATA', 240 | strips: [{ pin: 2, length: 8}] 241 | }); 242 | 243 | // set up a pixel on either end of the strip 244 | // in preparation for movement. 245 | strip.pixel(1).color('red'); 246 | strip.pixel(6).color('blue'); 247 | 248 | // call a shift but it shouldn't do anything 249 | strip.shift(0, pixel.FORWARD, false); 250 | test.equal(strip.pixel(1).color().color, 'red', 251 | 'If pixels are advanced by 0 elements, pixel 1 should stay the same'); 252 | 253 | // advance the pixels one step along the strip. 254 | strip.shift(1, pixel.FORWARD, false); 255 | 256 | test.equal(strip.pixel(7).color().color, 'blue', 257 | 'If pixels are advanced one position, pixel 6 value should be on pixel 7'); 258 | 259 | test.equal(strip.pixel(7).address, 7, 260 | 'After pixels are shifted, pixel address should be updated again'); 261 | 262 | test.equal(strip.pixel(6).color().color, 'black', 263 | 'If pixels are advanced one position, pixel 5 value should overwrite pixel 6'); 264 | 265 | test.equal(strip.pixel(0).color().color, 'black', 266 | 'If pixels advance with no wrapping, pixel 0 value should be off'); 267 | 268 | strip.shift(1, pixel.BACKWARD, false); 269 | 270 | test.equal(strip.pixel(6).color().color, 'blue', 271 | 'If pixels are reversed one position, pixel 7 value should be on pixel 6'); 272 | 273 | test.equal(strip.pixel(5).color().color, 'black', 274 | 'If pixels are reversed one position, pixel 6 value should overwrite pixel 5'); 275 | 276 | test.equal(strip.pixel(7).color().color, 'black', 277 | 'If pixels reverse with no wrapping, pixel 7 value should be off'); 278 | 279 | // now we are back to starting spot let's do a multistep advancement 280 | // with a wrap around. 281 | 282 | strip.shift(2, pixel.FORWARD, true); 283 | 284 | test.equal(strip.pixel(3).color().color, 'red', 285 | 'If pixels are advanced 2 positions & wrapped, pixel 1 value should be on pixel 3'); 286 | 287 | test.equal(strip.pixel(0).color().color, 'blue', 288 | 'If pixels are advanced 2 positions & wrapped, pixel 6 value should be on pixel 0'); 289 | 290 | // now let's test a jump over the length of the strip. 291 | strip.shift(9, pixel.BACKWARD, true); 292 | 293 | test.equal(strip.pixel(7).color().color, 'blue', 294 | 'If pixels are reversed more than strip length (9), pixel 0 should be on pixel 7'); 295 | 296 | // make sure there's nothing dangling behind. 297 | test.equal(strip.pixel(6).color().color, 'black', 298 | 'If pixels are shifted and wrapped, original pixel should have moved'); 299 | 300 | test.done(); 301 | } 302 | }; 303 | 304 | exports['Pixel'] = { 305 | setUp(done) { 306 | this.board = newBoard(); 307 | 308 | this.strip = new pixel.Strip({ 309 | data: 6, 310 | length: 4, 311 | board: this.board, 312 | controller: 'FIRMATA' 313 | }); 314 | 315 | done(); 316 | }, 317 | 318 | tearDown(done) { 319 | Board.purge(); 320 | restore(this); 321 | done(); 322 | }, 323 | 324 | colour(test) { 325 | // tests if the colour sequences are working okay 326 | test.expect(3); 327 | 328 | let colourcheck = { 329 | r: 255, g: 255, b: 255, 330 | hexcode: '#FFFFFF', 331 | color: 'white', 332 | rgb: [255, 255, 255] 333 | }; 334 | 335 | this.strip.pixel(0).color('#FFFFFF'); 336 | test.deepEqual(this.strip.pixel(0).color(), colourcheck, 337 | 'If pixel colour is set, the pixel colour object should be updated'); 338 | 339 | colourcheck = { 340 | r: 0, g: 255, b: 0, 341 | hexcode: '#00FF00', 342 | color: 'lime', 343 | rgb: [0, 255, 0] 344 | }; 345 | 346 | this.strip.pixel(3).color([0, 255, 0]); 347 | test.deepEqual(this.strip.pixel(3).color(), colourcheck, 348 | 'If setting the pixel colour using RGB array, the pixel colour object should be updated'); 349 | 350 | test.doesNotThrow( 351 | () => { 352 | this.strip.pixel(1).colour('QWERTYUIOP'); 353 | }, 354 | undefined, 355 | 'An invalid color should be ignored not throw an error' 356 | ); 357 | 358 | test.done(); 359 | }, 360 | 361 | off(test) { 362 | // tests if setting strip off results in black pixel colour 363 | test.expect(1); 364 | 365 | this.strip.color('#FF0000'); 366 | 367 | const colourcheck = { 368 | r: 0, g: 0, b: 0, 369 | hexcode: '#000000', 370 | color: 'black', 371 | rgb: [0, 0, 0] 372 | }; 373 | 374 | this.strip.pixel(1).off(); 375 | test.deepEqual(this.strip.pixel(1).color(), colourcheck, 376 | 'If setting a colour then turning a pixel off, the colour should revert to off state.'); 377 | test.done(); 378 | } 379 | }; 380 | -------------------------------------------------------------------------------- /firmware/build/backpack/ws2812.cpp: -------------------------------------------------------------------------------- 1 | #include "ws2812.h" 2 | #include "Arduino.h" 3 | 4 | bool isBackpack = false; 5 | bool isShifting = false; // used when we're doing memory intensive shifting 6 | bool writingFrame = false; 7 | WS2812 strips[MAX_STRIPS]; 8 | 9 | uint16_t strip_lengths[MAX_STRIPS]; // now long each strip is. 10 | bool strip_changed[MAX_STRIPS]; // used to optimise strip writes. 11 | 12 | uint8_t *px; 13 | uint16_t px_count; 14 | uint8_t strip_count = 0; // number of strips being used. 15 | uint8_t color_depth = 3; // Bytes used to hold one pixel 16 | 17 | uint8_t offsetRed; 18 | uint8_t offsetGreen; 19 | uint8_t offsetBlue; 20 | 21 | void ws2812_initialise() { 22 | // initialises the strip defaults. 23 | 24 | strip_count = 0; 25 | px_count = 0; 26 | for (uint8_t i = 0; i < MAX_STRIPS; i++) { 27 | strip_lengths[i] = 0; 28 | strip_changed[i] = false; 29 | } 30 | 31 | #if DEBUG 32 | serialport.println("Initialising WS2812 library"); 33 | #endif 34 | } 35 | 36 | void ws2812_initialise(bool backpack) { 37 | // if backpack is true then set the strips up a little differently 38 | 39 | isBackpack = backpack; 40 | ws2812_initialise(); 41 | if (isBackpack) { 42 | for (uint8_t i = 0; i< MAX_STRIPS; i++) { 43 | strips[i].setOutput(i+STRIP_START_PIN); 44 | }; 45 | } 46 | } 47 | 48 | void initialise_pixels(uint16_t num_pixels) { 49 | // called to set up the pixel array, allocate memory etc. 50 | 51 | if (px) { 52 | free (px); 53 | px_count = 0; 54 | } 55 | 56 | if (num_pixels > 0) { 57 | if (px = (uint8_t *)malloc(num_pixels*color_depth)) { 58 | memset(px, 0, num_pixels*color_depth); 59 | px_count = num_pixels; 60 | } else { 61 | px_count = 0; 62 | } 63 | } 64 | 65 | #if DEBUG 66 | serialport.print("Initialising "); 67 | serialport.print(strip_count); 68 | serialport.print(" strips "); 69 | serialport.print(px_count); 70 | serialport.println(" pixels"); 71 | #endif 72 | } 73 | 74 | uint8_t set_rgb_at(uint16_t index, uint32_t px_value) { 75 | // takes a packed 24 bit value and sets the pixel appropriately. 76 | if (index < px_count) { 77 | uint16_t tmp_pixel; 78 | tmp_pixel = index * color_depth; 79 | 80 | px[OFFSET_R(tmp_pixel)] = (uint8_t)(px_value >> 16); 81 | px[OFFSET_G(tmp_pixel)] = (uint8_t)(px_value >> 8); 82 | px[OFFSET_B(tmp_pixel)] = (uint8_t)px_value; 83 | 84 | return 0; 85 | } 86 | return 1; 87 | } 88 | 89 | void shift_pixels(uint8_t amt, bool shift_forwards, bool wrap) { 90 | // take the pixel array and shift the items along the array 91 | // shift forwards determines direction of travel and wrap determines 92 | // if the values need to be wrapped around again. 93 | 94 | uint8_t *tmp_px; 95 | uint16_t slice_index; 96 | uint8_t copy_byte_length; 97 | if (amt > 0) { 98 | copy_byte_length = amt*color_depth; 99 | } else { 100 | return; 101 | } 102 | 103 | isShifting = true; 104 | 105 | if (wrap) { 106 | // need to allocate and then copy the memory from end of the array 107 | // into temporary array before we move it. 108 | if (tmp_px = (uint8_t *)malloc(copy_byte_length)) { 109 | memset(tmp_px, 0, copy_byte_length); 110 | } 111 | 112 | if (shift_forwards) { 113 | // grab from the end of the array; 114 | slice_index = (px_count - amt); 115 | } else { 116 | // grab from the start of the array; 117 | slice_index = 0; 118 | } 119 | memcpy(tmp_px, px+slice_index*color_depth, copy_byte_length); 120 | } 121 | // now memmove the data appropriately 122 | if (shift_forwards) { 123 | // memmove data down the array from 0 to length-amt 124 | memmove(px+copy_byte_length, px, (px_count - amt) * color_depth); 125 | } else { 126 | // memmove data up the array from amt to length to pos 0 127 | memmove(px, px+copy_byte_length, (px_count - amt) * color_depth); 128 | } 129 | 130 | if (wrap) { 131 | uint16_t copy_index = 0; 132 | if (! shift_forwards) { 133 | // get the position at the end to drop this in on. 134 | copy_index = px_count - amt; 135 | } 136 | 137 | memcpy(px+copy_index*color_depth, tmp_px, copy_byte_length); 138 | free(tmp_px); 139 | } else { 140 | // if we're not wrapping around then we'll need to fill the rest 141 | // with 0s 142 | uint16_t fill_index = 0; 143 | if (! shift_forwards) { 144 | // get position at the end to fill from 145 | fill_index = px_count - amt; 146 | } 147 | 148 | memset(px+fill_index*color_depth, 0, copy_byte_length); 149 | } 150 | 151 | isShifting = false; 152 | } 153 | 154 | 155 | void process_command(byte argc, byte *argv){ 156 | // this takes a pixel command that has been determined and then 157 | // processes it appropriately. 158 | 159 | uint8_t command = 0xF & argv[0]; 160 | 161 | // now process the command. 162 | switch (command) { 163 | case PIXEL_SHOW: { 164 | // iterate over the strips and show those required. 165 | 166 | if (! writingFrame && ! isShifting) { 167 | writingFrame = true; 168 | for (uint8_t i = 0; i < strip_count; i++) { 169 | if (strip_changed[i]) { 170 | strips[i].sync(px, color_depth); 171 | } 172 | strip_changed[i] = false; 173 | } 174 | writingFrame = false; 175 | } 176 | break; 177 | } 178 | case PIXEL_SET_STRIP: { 179 | // sets the entirety of the strip to one colour 180 | 181 | uint32_t strip_colour = (uint32_t)argv[1] + 182 | ((uint32_t)argv[2]<<7) + 183 | ((uint32_t)argv[3]<<14) + 184 | ((uint32_t)argv[4] << 21); 185 | 186 | if (! isShifting) { 187 | if (strip_colour == 0) { 188 | // set all of the pixels back to 0 189 | memset(px, 0, px_count * color_depth); 190 | } else { 191 | for (uint16_t i = 0; i < px_count; i++) { 192 | set_rgb_at(i, strip_colour); 193 | } 194 | } 195 | // set all the strips dirty for update 196 | memset(strip_changed, true, strip_count); 197 | } 198 | break; 199 | } 200 | case PIXEL_SET_PIXEL: { 201 | // sets the pixel given by the index to the given colour 202 | uint16_t index = (uint16_t)argv[1] + ((uint16_t)argv[2]<<7); 203 | uint32_t colour = (uint32_t)argv[3] + ((uint32_t)argv[4]<<7) + 204 | ((uint32_t)argv[5]<<14) + ((uint32_t)argv[6] << 21); 205 | 206 | if (isShifting) { 207 | break; 208 | } 209 | 210 | set_rgb_at(index, colour); 211 | 212 | for (uint8_t i = 0; i < strip_count; i++) { 213 | // find the strip where this pixel is located and 214 | // then mark it dirty - but then bail out so you don't 215 | // overprocess. 216 | if (index < strip_lengths[i]) { 217 | strip_changed[i] = true; 218 | break; 219 | } 220 | } 221 | 222 | break; 223 | } 224 | case PIXEL_CONFIG: { 225 | // Sets the pin that the strip is on as well as it's length and color type 226 | 227 | if (argv[0] > 0x01) { 228 | // you get a weird boundary case in I2C where sometimes a message 229 | // is relayed from firmata without the command packet. 230 | // Just ignore this and move along 231 | break; 232 | } 233 | 234 | // check to ensure we have at least 3 arg bytes (1 for pin & 2 for len) 235 | if (argc >= 3) { 236 | // set everything back to initial state. 237 | ws2812_initialise(isBackpack); 238 | 239 | // loop over each group of 3 bytes and pull out the details 240 | // around the pin and strand length. 241 | for (uint8_t i = 0; i < (argc / 3); i ++) { 242 | // calc the argv offset as x3 for each group & +1 due to the 243 | // PIXEL_CONFIG command at argv[0] 244 | uint8_t argv_offset = i * 3; 245 | 246 | if (!isBackpack) { 247 | // we can specify the pin, otherwise it's determined. 248 | uint8_t pin = (uint8_t)argv[argv_offset+1] & 0x1F; 249 | strips[i].setOutput(pin); 250 | } 251 | 252 | // get the top two bits for the colour order type. 253 | uint8_t colour_type = (uint8_t)argv[argv_offset+1]>>5; 254 | switch (colour_type) { 255 | case PIXEL_COLOUR_GRB: 256 | setColorOrderGRB(); 257 | break; 258 | case PIXEL_COLOUR_RGB: 259 | setColorOrderRGB(); 260 | break; 261 | case PIXEL_COLOUR_BRG: 262 | setColorOrderBRG(); 263 | break; 264 | } 265 | 266 | // now get the strand length and set it 267 | strips[i].set_length((uint16_t)(argv[argv_offset+2]+(argv[argv_offset+3]<<7))); 268 | uint16_t prev_strip_length = 0; 269 | if (i > 0) { 270 | prev_strip_length = strip_lengths[i-1]; 271 | } 272 | strip_lengths[i] = strips[i].get_length() + prev_strip_length; 273 | // set the strip's offset so it knows where it is in the 274 | // 1D pixel array 275 | strips[i].set_offset(prev_strip_length); 276 | px_count = px_count + strips[i].get_length(); 277 | 278 | strip_count++; 279 | } 280 | // now initialise our pixel count 281 | initialise_pixels(px_count); 282 | } 283 | 284 | break; 285 | }// end config case 286 | case PIXEL_SHIFT: { 287 | 288 | // grab the number of pixels to shift by (bottom 5 bits) 289 | uint8_t shift_amt = argv[1] & 0x1F; 290 | // do we go forwards or backwards (bit 6) 291 | bool direction = (bool) (argv[1] & 0x20); 292 | // do we wrap around (bit 7) 293 | bool wrap = (bool) (argv[1] & 0x40); 294 | 295 | shift_pixels(shift_amt, direction, wrap); 296 | // set all the strips dirty. 297 | memset(strip_changed, true, strip_count); 298 | break; 299 | } 300 | } 301 | } 302 | 303 | void setColorOrderGRB() { // Default color order 304 | offsetGreen = 0; 305 | offsetRed = 1; 306 | offsetBlue = 2; 307 | } 308 | 309 | void setColorOrderRGB() { 310 | offsetRed = 0; 311 | offsetGreen = 1; 312 | offsetBlue = 2; 313 | } 314 | 315 | void setColorOrderBRG() { 316 | offsetBlue = 0; 317 | offsetRed = 1; 318 | offsetGreen = 2; 319 | } 320 | 321 | #if DEBUG 322 | void print_pixels() { 323 | // prints out the array of pixel values 324 | serialport.println(F("Pixel values")); 325 | for (uint8_t i=0; i < px_count; i++) { 326 | 327 | uint16_t index = i * color_depth; 328 | 329 | serialport.print(i); 330 | serialport.print(": "); 331 | serialport.print(px[OFFSET_R(index)]); 332 | serialport.print(" "); 333 | serialport.print(px[OFFSET_G(index)]); 334 | serialport.print(" "); 335 | serialport.print(px[OFFSET_B(index)]); 336 | serialport.println(); 337 | } 338 | } 339 | 340 | int freeRam () { 341 | extern int __heap_start, *__brkval; 342 | int v; 343 | return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 344 | } 345 | #endif 346 | -------------------------------------------------------------------------------- /firmware/src/libs/ws2812/ws2812.cpp: -------------------------------------------------------------------------------- 1 | #include "ws2812.h" 2 | #include "Arduino.h" 3 | 4 | bool isBackpack = false; 5 | bool isShifting = false; // used when we're doing memory intensive shifting 6 | bool writingFrame = false; 7 | WS2812 strips[MAX_STRIPS]; 8 | 9 | uint16_t strip_lengths[MAX_STRIPS]; // now long each strip is. 10 | bool strip_changed[MAX_STRIPS]; // used to optimise strip writes. 11 | 12 | uint8_t *px; 13 | uint16_t px_count; 14 | uint8_t strip_count = 0; // number of strips being used. 15 | uint8_t color_depth = 3; // Bytes used to hold one pixel 16 | 17 | uint8_t offsetRed; 18 | uint8_t offsetGreen; 19 | uint8_t offsetBlue; 20 | 21 | void ws2812_initialise() { 22 | // initialises the strip defaults. 23 | 24 | strip_count = 0; 25 | px_count = 0; 26 | for (uint8_t i = 0; i < MAX_STRIPS; i++) { 27 | strip_lengths[i] = 0; 28 | strip_changed[i] = false; 29 | } 30 | 31 | #if DEBUG 32 | serialport.println("Initialising WS2812 library"); 33 | #endif 34 | } 35 | 36 | void ws2812_initialise(bool backpack) { 37 | // if backpack is true then set the strips up a little differently 38 | 39 | isBackpack = backpack; 40 | ws2812_initialise(); 41 | if (isBackpack) { 42 | for (uint8_t i = 0; i< MAX_STRIPS; i++) { 43 | strips[i].setOutput(i+STRIP_START_PIN); 44 | }; 45 | } 46 | } 47 | 48 | void initialise_pixels(uint16_t num_pixels) { 49 | // called to set up the pixel array, allocate memory etc. 50 | 51 | if (px) { 52 | free (px); 53 | px_count = 0; 54 | } 55 | 56 | if (num_pixels > 0) { 57 | if (px = (uint8_t *)malloc(num_pixels*color_depth)) { 58 | memset(px, 0, num_pixels*color_depth); 59 | px_count = num_pixels; 60 | } else { 61 | px_count = 0; 62 | } 63 | } 64 | 65 | #if DEBUG 66 | serialport.print("Initialising "); 67 | serialport.print(strip_count); 68 | serialport.print(" strips "); 69 | serialport.print(px_count); 70 | serialport.println(" pixels"); 71 | #endif 72 | } 73 | 74 | uint8_t set_rgb_at(uint16_t index, uint32_t px_value) { 75 | // takes a packed 24 bit value and sets the pixel appropriately. 76 | if (index < px_count) { 77 | uint16_t tmp_pixel; 78 | tmp_pixel = index * color_depth; 79 | 80 | px[OFFSET_R(tmp_pixel)] = (uint8_t)(px_value >> 16); 81 | px[OFFSET_G(tmp_pixel)] = (uint8_t)(px_value >> 8); 82 | px[OFFSET_B(tmp_pixel)] = (uint8_t)px_value; 83 | 84 | return 0; 85 | } 86 | return 1; 87 | } 88 | 89 | void shift_pixels(uint8_t amt, bool shift_forwards, bool wrap) { 90 | // take the pixel array and shift the items along the array 91 | // shift forwards determines direction of travel and wrap determines 92 | // if the values need to be wrapped around again. 93 | 94 | uint8_t *tmp_px; 95 | uint16_t slice_index; 96 | uint8_t copy_byte_length; 97 | if (amt > 0) { 98 | copy_byte_length = amt*color_depth; 99 | } else { 100 | return; 101 | } 102 | 103 | isShifting = true; 104 | 105 | if (wrap) { 106 | // need to allocate and then copy the memory from end of the array 107 | // into temporary array before we move it. 108 | if (tmp_px = (uint8_t *)malloc(copy_byte_length)) { 109 | memset(tmp_px, 0, copy_byte_length); 110 | } 111 | 112 | if (shift_forwards) { 113 | // grab from the end of the array; 114 | slice_index = (px_count - amt); 115 | } else { 116 | // grab from the start of the array; 117 | slice_index = 0; 118 | } 119 | memcpy(tmp_px, px+slice_index*color_depth, copy_byte_length); 120 | } 121 | // now memmove the data appropriately 122 | if (shift_forwards) { 123 | // memmove data down the array from 0 to length-amt 124 | memmove(px+copy_byte_length, px, (px_count - amt) * color_depth); 125 | } else { 126 | // memmove data up the array from amt to length to pos 0 127 | memmove(px, px+copy_byte_length, (px_count - amt) * color_depth); 128 | } 129 | 130 | if (wrap) { 131 | uint16_t copy_index = 0; 132 | if (! shift_forwards) { 133 | // get the position at the end to drop this in on. 134 | copy_index = px_count - amt; 135 | } 136 | 137 | memcpy(px+copy_index*color_depth, tmp_px, copy_byte_length); 138 | free(tmp_px); 139 | } else { 140 | // if we're not wrapping around then we'll need to fill the rest 141 | // with 0s 142 | uint16_t fill_index = 0; 143 | if (! shift_forwards) { 144 | // get position at the end to fill from 145 | fill_index = px_count - amt; 146 | } 147 | 148 | memset(px+fill_index*color_depth, 0, copy_byte_length); 149 | } 150 | 151 | isShifting = false; 152 | } 153 | 154 | 155 | void process_command(byte argc, byte *argv){ 156 | // this takes a pixel command that has been determined and then 157 | // processes it appropriately. 158 | 159 | uint8_t command = 0xF & argv[0]; 160 | 161 | // now process the command. 162 | switch (command) { 163 | case PIXEL_SHOW: { 164 | // iterate over the strips and show those required. 165 | 166 | if (! writingFrame && ! isShifting) { 167 | writingFrame = true; 168 | for (uint8_t i = 0; i < strip_count; i++) { 169 | if (strip_changed[i]) { 170 | strips[i].sync(px, color_depth); 171 | } 172 | strip_changed[i] = false; 173 | } 174 | writingFrame = false; 175 | } 176 | break; 177 | } 178 | case PIXEL_SET_STRIP: { 179 | // sets the entirety of the strip to one colour 180 | 181 | uint32_t strip_colour = (uint32_t)argv[1] + 182 | ((uint32_t)argv[2]<<7) + 183 | ((uint32_t)argv[3]<<14) + 184 | ((uint32_t)argv[4] << 21); 185 | 186 | if (! isShifting) { 187 | if (strip_colour == 0) { 188 | // set all of the pixels back to 0 189 | memset(px, 0, px_count * color_depth); 190 | } else { 191 | for (uint16_t i = 0; i < px_count; i++) { 192 | set_rgb_at(i, strip_colour); 193 | } 194 | } 195 | // set all the strips dirty for update 196 | memset(strip_changed, true, strip_count); 197 | } 198 | break; 199 | } 200 | case PIXEL_SET_PIXEL: { 201 | // sets the pixel given by the index to the given colour 202 | uint16_t index = (uint16_t)argv[1] + ((uint16_t)argv[2]<<7); 203 | uint32_t colour = (uint32_t)argv[3] + ((uint32_t)argv[4]<<7) + 204 | ((uint32_t)argv[5]<<14) + ((uint32_t)argv[6] << 21); 205 | 206 | if (isShifting) { 207 | break; 208 | } 209 | 210 | set_rgb_at(index, colour); 211 | 212 | for (uint8_t i = 0; i < strip_count; i++) { 213 | // find the strip where this pixel is located and 214 | // then mark it dirty - but then bail out so you don't 215 | // overprocess. 216 | if (index < strip_lengths[i]) { 217 | strip_changed[i] = true; 218 | break; 219 | } 220 | } 221 | 222 | break; 223 | } 224 | case PIXEL_CONFIG: { 225 | // Sets the pin that the strip is on as well as it's length and color type 226 | 227 | if (argv[0] > 0x01) { 228 | // you get a weird boundary case in I2C where sometimes a message 229 | // is relayed from firmata without the command packet. 230 | // Just ignore this and move along 231 | break; 232 | } 233 | 234 | // check to ensure we have at least 3 arg bytes (1 for pin & 2 for len) 235 | if (argc >= 3) { 236 | // set everything back to initial state. 237 | ws2812_initialise(isBackpack); 238 | 239 | // loop over each group of 3 bytes and pull out the details 240 | // around the pin and strand length. 241 | for (uint8_t i = 0; i < (argc / 3); i ++) { 242 | // calc the argv offset as x3 for each group & +1 due to the 243 | // PIXEL_CONFIG command at argv[0] 244 | uint8_t argv_offset = i * 3; 245 | 246 | if (!isBackpack) { 247 | // we can specify the pin, otherwise it's determined. 248 | uint8_t pin = (uint8_t)argv[argv_offset+1] & 0x1F; 249 | strips[i].setOutput(pin); 250 | } 251 | 252 | // get the top two bits for the colour order type. 253 | uint8_t colour_type = (uint8_t)argv[argv_offset+1]>>5; 254 | switch (colour_type) { 255 | case PIXEL_COLOUR_GRB: 256 | setColorOrderGRB(); 257 | break; 258 | case PIXEL_COLOUR_RGB: 259 | setColorOrderRGB(); 260 | break; 261 | case PIXEL_COLOUR_BRG: 262 | setColorOrderBRG(); 263 | break; 264 | } 265 | 266 | // now get the strand length and set it 267 | strips[i].set_length((uint16_t)(argv[argv_offset+2]+(argv[argv_offset+3]<<7))); 268 | uint16_t prev_strip_length = 0; 269 | if (i > 0) { 270 | prev_strip_length = strip_lengths[i-1]; 271 | } 272 | strip_lengths[i] = strips[i].get_length() + prev_strip_length; 273 | // set the strip's offset so it knows where it is in the 274 | // 1D pixel array 275 | strips[i].set_offset(prev_strip_length); 276 | px_count = px_count + strips[i].get_length(); 277 | 278 | strip_count++; 279 | } 280 | // now initialise our pixel count 281 | initialise_pixels(px_count); 282 | } 283 | 284 | break; 285 | }// end config case 286 | case PIXEL_SHIFT: { 287 | 288 | // grab the number of pixels to shift by (bottom 5 bits) 289 | uint8_t shift_amt = argv[1] & 0x1F; 290 | // do we go forwards or backwards (bit 6) 291 | bool direction = (bool) (argv[1] & 0x20); 292 | // do we wrap around (bit 7) 293 | bool wrap = (bool) (argv[1] & 0x40); 294 | 295 | shift_pixels(shift_amt, direction, wrap); 296 | // set all the strips dirty. 297 | memset(strip_changed, true, strip_count); 298 | break; 299 | } 300 | } 301 | } 302 | 303 | void setColorOrderGRB() { // Default color order 304 | offsetGreen = 0; 305 | offsetRed = 1; 306 | offsetBlue = 2; 307 | } 308 | 309 | void setColorOrderRGB() { 310 | offsetRed = 0; 311 | offsetGreen = 1; 312 | offsetBlue = 2; 313 | } 314 | 315 | void setColorOrderBRG() { 316 | offsetBlue = 0; 317 | offsetRed = 1; 318 | offsetGreen = 2; 319 | } 320 | 321 | #if DEBUG 322 | void print_pixels() { 323 | // prints out the array of pixel values 324 | serialport.println(F("Pixel values")); 325 | for (uint8_t i=0; i < px_count; i++) { 326 | 327 | uint16_t index = i * color_depth; 328 | 329 | serialport.print(i); 330 | serialport.print(": "); 331 | serialport.print(px[OFFSET_R(index)]); 332 | serialport.print(" "); 333 | serialport.print(px[OFFSET_G(index)]); 334 | serialport.print(" "); 335 | serialport.print(px[OFFSET_B(index)]); 336 | serialport.println(); 337 | } 338 | } 339 | 340 | int freeRam () { 341 | extern int __heap_start, *__brkval; 342 | int v; 343 | return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 344 | } 345 | #endif 346 | --------------------------------------------------------------------------------