From 5ebbf546d21b36dcda79343a59f837c706c21a76 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 11 Jan 2016 21:22:30 +0000 Subject: [PATCH] Add Pi Neopixel node --- hardware/neopixel/LICENSE | 177 ++++++++++++++++++++++ hardware/neopixel/README.md | 42 ++++++ hardware/neopixel/colours.js | 204 ++++++++++++++++++++++++++ hardware/neopixel/neopix | 17 +++ hardware/neopixel/neopix.py | 114 ++++++++++++++ hardware/neopixel/neopixel.html | 88 +++++++++++ hardware/neopixel/neopixel.js | 149 +++++++++++++++++++ hardware/neopixel/package.json | 26 ++++ hardware/neopixel/scripts/checklib.js | 13 ++ 9 files changed, 830 insertions(+) create mode 100644 hardware/neopixel/LICENSE create mode 100644 hardware/neopixel/README.md create mode 100644 hardware/neopixel/colours.js create mode 100755 hardware/neopixel/neopix create mode 100755 hardware/neopixel/neopix.py create mode 100644 hardware/neopixel/neopixel.html create mode 100644 hardware/neopixel/neopixel.js create mode 100644 hardware/neopixel/package.json create mode 100755 hardware/neopixel/scripts/checklib.js diff --git a/hardware/neopixel/LICENSE b/hardware/neopixel/LICENSE new file mode 100644 index 00000000..f433b1a5 --- /dev/null +++ b/hardware/neopixel/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/hardware/neopixel/README.md b/hardware/neopixel/README.md new file mode 100644 index 00000000..d3d2fb0c --- /dev/null +++ b/hardware/neopixel/README.md @@ -0,0 +1,42 @@ +node-red-node-pi-neopixel +========================= + +A Node-RED node to drive a strip +of Neopixel or WS2812 LEDs. + +Pre-requisites +-------------- + +The Unicorn HAT python drivers need to be pre-installed... This is the easiest way to get the neopixel driver installed... see the + +Pimorini Getting Started with Unicorn HAT page. + + curl -sS get.pimoroni.com/unicornhat | bash + +Install +------- + +Run the following command in the root directory of your Node-RED install. +Usually this is `~/.node-red` + + npm install node-red-node-pi-neopixel + +Usage +----- + +Defaults to a bar chart style mode using configured foreground and background colours. + +It can accept a number in **msg.payload** that can be either the number of pixels, or a percentage of the total length. + +If you want to change the foregound colour, you can set **msg.payload** to a comma separated string of `html_colour,length`. + +To set the background just set **msg.payload** `html_colour` name. +Here +is a list of html_colour names. + +The `nth` pixel is set by **msg.payload** with a CSV string `n,r,g,b` + +A range of pixels from `x` to `y` can be set by **msg.payload** +with a CSV string `x,y,r,g,b` + +The pixels data line should be connected to Pi physical pin 12 - GPIO 18. *Note:* this may conflict with audio playback. diff --git a/hardware/neopixel/colours.js b/hardware/neopixel/colours.js new file mode 100644 index 00000000..6f60c26e --- /dev/null +++ b/hardware/neopixel/colours.js @@ -0,0 +1,204 @@ +/** + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var colours = { + 'aqua':'#00FFFF', + 'aliceblue':'#F0F8FF', + 'antiquewhite':'#FAEBD7', + 'black':'#000000', + 'off':'#000000', + 'blue':'#0000FF', + 'cyan':'#00FFFF', + 'darkblue':'#00008B', + 'darkcyan':'#008B8B', + 'darkgreen':'#006400', + 'darkturquoise':'#00CED1', + 'deepskyblue':'#00BFFF', + 'green':'#008000', + 'lime':'#00FF00', + 'mediumblue':'#0000CD', + 'mediumspringgreen':'#00FA9A', + 'navy':'#000080', + 'springgreen':'#00FF7F', + 'teal':'#008080', + 'midnightblue':'#191970', + 'dodgerblue':'#1E90FF', + 'lightseagreen':'#20B2AA', + 'forestgreen':'#228B22', + 'seagreen':'#2E8B57', + 'darkslategray':'#2F4F4F', + 'darkslategrey':'#2F4F4F', + 'limegreen':'#32CD32', + 'mediumseagreen':'#3CB371', + 'turquoise':'#40E0D0', + 'royalblue':'#4169E1', + 'steelblue':'#4682B4', + 'darkslateblue':'#483D8B', + 'mediumturquoise':'#48D1CC', + 'indigo':'#4B0082', + 'darkolivegreen':'#556B2F', + 'cadetblue':'#5F9EA0', + 'cornflowerblue':'#6495ED', + 'mediumaquamarine':'#66CDAA', + 'dimgray':'#696969', + 'dimgrey':'#696969', + 'slateblue':'#6A5ACD', + 'olivedrab':'#6B8E23', + 'slategray':'#708090', + 'slategrey':'#708090', + 'lightslategray':'#778899', + 'lightslategrey':'#778899', + 'mediumslateblue':'#7B68EE', + 'lawngreen':'#7CFC00', + 'aquamarine':'#7FFFD4', + 'chartreuse':'#7FFF00', + 'gray':'#808080', + 'grey':'#808080', + 'maroon':'#800000', + 'olive':'#808000', + 'purple':'#800080', + 'lightskyblue':'#87CEFA', + 'skyblue':'#87CEEB', + 'blueviolet':'#8A2BE2', + 'darkmagenta':'#8B008B', + 'darkred':'#8B0000', + 'saddlebrown':'#8B4513', + 'darkseagreen':'#8FBC8F', + 'lightgreen':'#90EE90', + 'mediumpurple':'#9370DB', + 'darkviolet':'#9400D3', + 'palegreen':'#98FB98', + 'darkorchid':'#9932CC', + 'yellowgreen':'#9ACD32', + 'sienna':'#A0522D', + 'brown':'#A52A2A', + 'darkgray':'#A9A9A9', + 'darkgrey':'#A9A9A9', + 'greenyellow':'#ADFF2F', + 'lightblue':'#ADD8E6', + 'paleturquoise':'#AFEEEE', + 'lightsteelblue':'#B0C4DE', + 'powderblue':'#B0E0E6', + 'firebrick':'#B22222', + 'darkgoldenrod':'#B8860B', + 'mediumorchid':'#BA55D3', + 'rosybrown':'#BC8F8F', + 'darkkhaki':'#BDB76B', + 'silver':'#C0C0C0', + 'mediumvioletred':'#C71585', + 'indianred':'#CD5C5C', + 'peru':'#CD853F', + 'chocolate':'#D2691E', + 'tan':'#D2B48C', + 'lightgray':'#D3D3D3', + 'lightgrey':'#D3D3D3', + 'thistle':'#D8BFD8', + 'goldenrod':'#DAA520', + 'orchid':'#DA70D6', + 'palevioletred':'#DB7093', + 'crimson':'#DC143C', + 'gainsboro':'#DCDCDC', + 'plum':'#DDA0DD', + 'burlywood':'#DEB887', + 'lightcyan':'#E0FFFF', + 'lavender':'#E6E6FA', + 'darksalmon':'#E9967A', + 'palegoldenrod':'#EEE8AA', + 'violet':'#EE82EE', + 'azure':'#F0FFFF', + 'honeydew':'#F0FFF0', + 'khaki':'#F0E68C', + 'lightcoral':'#F08080', + 'sandybrown':'#F4A460', + 'beige':'#F5F5DC', + 'mintcream':'#F5FFFA', + 'wheat':'#F5DEB3', + 'whitesmoke':'#F5F5F5', + 'ghostwhite':'#F8F8FF', + 'lightgoldenrodyellow':'#FAFAD2', + 'linen':'#FAF0E6', + 'salmon':'#FA8072', + 'oldlace':'#FDF5E6', + 'warmwhite':'#FDF5E6', + 'bisque':'#FFE4C4', + 'blanchedalmond':'#FFEBCD', + 'coral':'#FF7F50', + 'cornsilk':'#FFF8DC', + 'darkorange':'#FF8C00', + 'deeppink':'#FF1493', + 'floralwhite':'#FFFAF0', + 'fuchsia':'#FF00FF', + 'gold':'#FFD700', + 'hotpink':'#FF69B4', + 'ivory':'#FFFFF0', + 'lavenderblush':'#FFF0F5', + 'lemonchiffon':'#FFFACD', + 'lightpink':'#FFB6C1', + 'lightsalmon':'#FFA07A', + 'lightyellow':'#FFFFE0', + 'magenta':'#FF00FF', + 'mistyrose':'#FFE4E1', + 'moccasin':'#FFE4B5', + 'navajowhite':'#FFDEAD', + 'orange':'#FFA500', + 'orangered':'#FF4500', + 'papayawhip':'#FFEFD5', + 'peachpuff':'#FFDAB9', + 'pink':'#FFC0CB', + 'red':'#FF0000', + 'seashell':'#FFF5EE', + 'snow':'#FFFAFA', + 'tomato':'#FF6347', + 'white':'#FFFFFF', + 'yellow':'#FFFF00', + 'amber':'#FFD200' +}; + +var p1 = /^\#[A-Fa-f0-9]{6}$/ + +module.exports.getRGB = function(col) { + col = col.toString().toLowerCase(); + if (col in colours) { + col = colours[col]; + } + if (p1.test(col)) { + r = parseInt(col.slice(1,3),16); + g = parseInt(col.slice(3,5),16); + b = parseInt(col.slice(5),16); + return r+","+g+","+b; + } + else { + return null; + } +} + +module.exports.getHex = function(col) { + col = col.toString().toLowerCase(); + if (col in colours) { + return colours[col]; + } + else { return null; } +} + +module.exports.HexRGB = function(hex) { + try { + r = parseInt(hex.slice(1,3),16); + g = parseInt(hex.slice(3,5),16); + b = parseInt(hex.slice(5),16); + return r+","+g+","+b; + } + catch(e) { return null; } +} diff --git a/hardware/neopixel/neopix b/hardware/neopixel/neopix new file mode 100755 index 00000000..9f33b101 --- /dev/null +++ b/hardware/neopixel/neopix @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 IBM Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +BASEDIR=$(dirname $0) +sudo python -u $BASEDIR/neopix.py $@ diff --git a/hardware/neopixel/neopix.py b/hardware/neopixel/neopix.py new file mode 100755 index 00000000..f114f34f --- /dev/null +++ b/hardware/neopixel/neopix.py @@ -0,0 +1,114 @@ +#!/usr/bin/python +# +# Copyright 2016 IBM Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Import library functions we need +import sys +import time +from neopixel import * + +# LED strip configuration: +LED_COUNT = 8 # Number of LED pixels. +LED_PIN = 18 # GPIO pin connected to the pixels (must support PWM!). +LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz) +LED_DMA = 5 # DMA channel to use for generating signal (try 5) +LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest +LED_INVERT = False # True to invert the signal (when using NPN transistor level shift) + +if sys.version_info >= (3,0): + print("Sorry - currently only configured to work with python 2.x") + sys.exit(1) + +LED_COUNT = int(sys.argv[1]) +WAIT_MS = int(sys.argv[2]) + +# Define functions which animate LEDs in various ways. +def setPixel(strip, i, color): + """Set a single pixel""" + strip.setPixelColor(i, color) + strip.show() + +def setPixels(strip, s, e, color, wait_ms=30): + """Set pixels from s(tart) to e(nd)""" + for i in range(s, e+1): + strip.setPixelColor(i, color) + strip.show() + time.sleep(wait_ms/1000.0) + +def colorWipe(strip, color, wait_ms=30): + """Wipe color across display a pixel at a time.""" + for i in range(strip.numPixels()): + strip.setPixelColor(i, color) + strip.show() + time.sleep(wait_ms/1000.0) + +def wheel(pos): + """Generate rainbow colors across 0-255 positions.""" + if pos < 85: + return Color(pos * 3, 255 - pos * 3, 0) + elif pos < 170: + pos -= 85 + return Color(255 - pos * 3, 0, pos * 3) + else: + pos -= 170 + return Color(0, pos * 3, 255 - pos * 3) + +def rainbow(strip, wait_ms=20, iterations=2): + """Draw rainbow that fades across all pixels at once.""" + for j in range(256*iterations): + for i in range(strip.numPixels()): + strip.setPixelColor(i, wheel((i+j) & 255)) + strip.show() + time.sleep(wait_ms/1000.0) + +def rainbowCycle(strip, wait_ms=20, iterations=2): + """Draw rainbow that uniformly distributes itself across all pixels.""" + for j in range(256*iterations): + for i in range(strip.numPixels()): + strip.setPixelColor(i, wheel(((i * 256 / strip.numPixels()) + j) & 255)) + strip.show() + time.sleep(wait_ms/1000.0) + +# Main loop: +if __name__ == '__main__': + # Create NeoPixel object with appropriate configuration. + strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS) + # Intialize the library (must be called once before other functions). + strip.begin() + + ## Color wipe animations. + colorWipe(strip, Color(127, 0, 0), WAIT_MS) # Red wipe + colorWipe(strip, Color(0, 127, 0), WAIT_MS) # Green wipe + colorWipe(strip, Color(0, 0, 127), WAIT_MS) # Blue wipe + colorWipe(strip, Color(0, 0, 0), WAIT_MS) # Off wipe + + ## Rainbow animations. + #rainbow(strip) + #rainbowCycle(strip) + #colorWipe(strip, Color(0, 0, 0)) # Off wipe + + while True: + try: + data = raw_input() + bits = data.split(',') + if len(bits) == 3: + colorWipe(strip, Color(int(bits[0]), int(bits[1]), int(bits[2])), WAIT_MS) + if len(bits) == 4: + setPixel(strip, int(bits[0]), Color(int(bits[1]), int(bits[2]), int(bits[3]) )) + if len(bits) == 5: + setPixels(strip, int(bits[0]), int(bits[1]), Color(int(bits[2]), int(bits[3]), int(bits[4]) ), WAIT_MS) + except (EOFError, SystemExit): # hopefully always caused by us sigint'ing the program + sys.exit(0) + except Exception as ex: + print "bad data: "+data diff --git a/hardware/neopixel/neopixel.html b/hardware/neopixel/neopixel.html new file mode 100644 index 00000000..ac6b749d --- /dev/null +++ b/hardware/neopixel/neopixel.html @@ -0,0 +1,88 @@ + + + + + + + diff --git a/hardware/neopixel/neopixel.js b/hardware/neopixel/neopixel.js new file mode 100644 index 00000000..bf5740b0 --- /dev/null +++ b/hardware/neopixel/neopixel.js @@ -0,0 +1,149 @@ +/** + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +module.exports = function(RED) { + "use strict"; + var spawn = require('child_process').spawn; + var fs = require('fs'); + var colors = require('./colours.js'); + + var piCommand = __dirname+'/neopix'; + + if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi + //RED.log.info(RED._("rpi-gpio.errors.ignorenode")); + throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); + } + + if (!fs.existsSync('/usr/local/lib/python2.7/dist-packages/neopixel.py')) { + RED.log.warn("Can't find neopixel.py python library"); + throw "Warning : Can't find neopixel.py python library"; + } + + if ( !(1 & parseInt ((fs.statSync(piCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) { + RED.log.error(piCommand + " command is not executable"); + throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable"); + } + + // the magic to make python print stuff immediately + process.env.PYTHONUNBUFFERED = 1; + + function piNeopixelNode(n) { + RED.nodes.createNode(this,n); + this.pixels = n.pixels || 1; + this.bgnd = n.bgnd || "0,0,0"; + this.fgnd = n.fgnd || "128,128,128"; + this.mode = n.mode || "pcent"; + this.wipe = Number(n.wipe || 40); + if (this.wipe < 0) { this.wipe = 0; } + var node = this; + var p1 = /^\#[A-Fa-f0-9]{6}$/ + var p2 = /^[0-9]+,[0-9]+,[0-9]+$/ + var p3 = /^[0-9]+,[0-9]+,[0-9]+,[0-9]+$/ + var p4 = /^[0-9]+,[0-9]+,[0-9]+,[0-9]+,[0-9]+$/ + + function inputlistener(msg) { + if (msg.hasOwnProperty("payload")) { + var pay = msg.payload.toString().toUpperCase(); + var parts = pay.split(","); + if (parts.length <= 2) { + if (parts.length === 2) { // it's a colour and length + if (isNaN(parseInt(parts[1]))) { parts = parts.reverse(); } + if (colors.getRGB(parts[0])) { + node.fgnd = colors.getRGB(parts[0]); + var l = parts[1]; + if (node.mode === "pcent") { l = parseInt(l / 100 * node.pixels + 0.5); } + l = l - 1; + pay = "0,"+l+","+node.fgnd+"\n"+(l+1)+","+(node.pixels-1)+","+node.bgnd; + } + else { node.warn("Invalid payload : "+pay); return; } + } + else { + if (isNaN(pay)) { // it's a single colour word so set background + if (colors.getRGB(pay)) { + node.bgnd = colors.getRGB(pay); + pay = node.bgnd; + } + else { node.warn("Invalid payload : "+pay); return; } + } + else { // it's a single number so just draw bar + var l = pay; + if (node.mode === "pcent") { l = parseInt(l / 100 * node.pixels + 0.5); } + l = l - 1; + pay = "0,"+l+","+node.fgnd+"\n"+(l+1)+","+(node.pixels-1)+","+node.bgnd; + } + } + } + if ((parts.length <= 2) || p2.test(pay) || p3.test(pay) || p4.test(pay) ) { + if (parts.length === 3) { node.bgnd = pay; } + node.child.stdin.write(pay+"\n"); // handle 3 parts, 4 part and 5 parts in the python + } + else { node.warn("Invalid payload : "+pay); } + } + } + + node.child = spawn(piCommand, [node.pixels, node.wipe]); + node.status({fill:"green",shape:"dot",text:"ok"}); + + node.on("input", inputlistener); + + node.child.stdout.on('data', function (data) { + if (RED.settings.verbose) { node.log("out: "+data+" :"); } + }); + + node.child.stderr.on('data', function (data) { + if (RED.settings.verbose) { node.log("err: "+data+" :"); } + }); + + node.child.on('close', function (code) { + node.child = null; + if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); } + if (node.done) { + node.status({fill:"grey",shape:"ring",text:"closed"}); + node.done(); + } + else { node.status({fill:"red",shape:"ring",text:"stopped"}); } + }); + + node.child.on('error', function (err) { + if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); } + else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); } + else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); } + }); + + node.on("close", function(done) { + node.status({fill:"grey",shape:"ring",text:"closed"}); + if (node.child != null) { + node.done = done; + node.child.kill('SIGKILL'); + } + else { done(); } + }); + + if (node.bgnd) { + if (node.bgnd.split(',').length === 1) { + node.bgnd = colors.getRGB(node.bgnd); + } + node.child.stdin.write(node.bgnd+"\n"); + } + + if (node.fgnd) { + if (node.fgnd.split(',').length === 1) { + node.fgnd = colors.getRGB(node.fgnd); + } + } + } + RED.nodes.registerType("rpi-neopixels",piNeopixelNode); +} diff --git a/hardware/neopixel/package.json b/hardware/neopixel/package.json new file mode 100644 index 00000000..e1e25013 --- /dev/null +++ b/hardware/neopixel/package.json @@ -0,0 +1,26 @@ +{ + "name" : "node-red-node-pi-neopixel", + "version" : "0.0.1", + "description" : "A Node-RED node to output to a neopixel (ws2812) string of LEDS from a Raspberry Pi.", + "dependencies" : { + }, + "repository" : { + "type":"git", + "url":"https://github.com/node-red/node-red-nodes/tree/master/hardware/neopixel" + }, + "license": "Apache-2.0", + "keywords": [ "node-red", "ws2812", "neopixel" ], + "node-red" : { + "nodes" : { + "rpi-neopixels": "neopixel.js" + } + }, + "scripts" : { + "postinstall" : "scripts/checklib.js" + }, + "author": { + "name": "Dave Conway-Jones", + "email": "ceejay@vnet.ibm.com", + "url": "http://nodered.org" + } +} diff --git a/hardware/neopixel/scripts/checklib.js b/hardware/neopixel/scripts/checklib.js new file mode 100755 index 00000000..bdd3bf0f --- /dev/null +++ b/hardware/neopixel/scripts/checklib.js @@ -0,0 +1,13 @@ +#!/usr/bin/env node +var fs = require('fs'); + +if (!fs.existsSync('/usr/local/lib/python2.7/dist-packages/neopixel.py')) { + console.warn("WARNING : Can't find neopixel.py python library"); + console.warn("WARNING : Please install using the following command"); + console.warn("WARNING : Note: this uses root..."); + console.warn("WARNING : curl -sS get.pimoroni.com/unicornhat | bash\n"); + //process.exit(1); +} +else { + console.log("Neopixel Python library found OK.\n") +}