Add out node

This commit is contained in:
Nick O'Leary 2016-03-04 17:12:02 +00:00
parent 20f14a698c
commit 0cc33db58f
5 changed files with 573 additions and 47 deletions

View File

@ -28,7 +28,8 @@ Run the following command in your Node-RED user directory (typically `~/.node-re
### Input Node
This node sends readings from the various sensors on the Sense HAT, grouped into three sets; motion events, environment events and joystick events.
This node sends readings from the various sensors on the Sense HAT, grouped into
three sets; motion events, environment events and joystick events.
#### Motion events
@ -38,9 +39,9 @@ per second. The `topic` is set to `motion` and the `payload` is an object with t
following values:
- `acceleration.x/y/z` : the acceleration intensity in Gs
- `gyroscope.x/y/z` : the rotational intensity in radians/s
- `orientation.roll/pitch/yaw` : the angle of the axis in degrees
- `compass` : the direction of North in degrees
-`gyroscope.x/y/z` : the rotational intensity in radians/s
-`orientation.roll/pitch/yaw` : the angle of the axis in degrees
-`compass` : the direction of North in degrees
#### Environment events
@ -49,17 +50,69 @@ sensors. They are sent at a rate of approximately 1 per second. The `topic`
is set to `environment` and the `payload` is an object
with the following values:
- `temperature` : degrees Celsius
- `humidity` : percentage of relative humidity
- `pressure` : Millibars
-`temperature` : degrees Celsius
-`humidity` : percentage of relative humidity
-`pressure` : Millibars
#### Joystick events
Joystick events are sent when the Sense HAT joystick is interacted with. The
`topic` is set to `joystick` and the `payload` is an object with the following values:
- `key` : one of `UP`, `DOWN`, `LEFT`, `RIGHT`, `ENTER`
- `state` : the state of the key:
- `0` : the key has been released
- `1` : the key has been pressed
- `2` : the key is being held down
-`key` : one of `UP`, `DOWN`, `LEFT`, `RIGHT`, `ENTER`
-`state` : the state of the key:
-`0` : the key has been released
-`1` : the key has been pressed
-`2` : the key is being held down
### Output Node
This node sends commands to the 8x8 LED display on the Sense HAT.
Commands are sent to the node in `msg.payload`. Multiple commands can
be sent in a single message by separating them with newline (\n) characters.
#### Set the colour of individual pixels
Format: `<x>,<y>,<colour>`
`x` and `y` must either be a value in the range 0-7, or `*` to indicate the entire row or column.
`colour` must be one of:
- the well-known <a href="https://en.wikipedia.org/wiki/Web_colors" target="_new">HTML colour names</a>
- eg `red` or `aquamarine`,
- the <a href="http://cheerlights.com/cheerlights-api/">CheerLights colour names</a>,
- a HEX colour value - eg `#aa9900`
- an RGB triple - `190,255,0`
- or simply `off`
To set the entire screen to red: `*,*,red`
To set the four corners of the display to red, green (#00ff00), yellow and blue (0,0,255):
`0,0,red,0,7,#00ff00,7,7,yellow,7,0,0,0,255`
#### Rotate the screen
Format: `R&lt;angle&gt;`
`angle` must be 0, 90, 180 or 270.
#### Flip the screen
Format: `R&lt;axis&gt;`
`axis` must be either `H` or `V` to flip on the horizontal or vertical axis respectively.
#### Scroll a message
If `msg.payload` is not recognised as any of the above commands, it is treated
as a text message to be scrolled across the screen.
The following message properties can be used to customise the appearance:
- `msg.colour` - the colour of the text, default: `white`
- `msg.background` - the colour of the background, default: `off`
- `msg.speed` - the scroll speed. A value in the range 1 (slower) to 5 (faster), default: `3`

View File

@ -0,0 +1,220 @@
/**
* 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 hexColour = /^#([0-9A-F][0-9A-F][0-9A-F]){1,2}$/i;
module.exports.getRGB = function(col,rgb) {
if (!col) {
return null;
}
if (/^\d{1,3},\d{1,3},\d{1,3}$/.test(col)) {
return col;
}
col = col.toString().toLowerCase();
if (col in colours) {
col = colours[col];
}
if (hexColour.test(col)) {
if (col.length === 4) {
col = "#"+col[1]+col[1]+col[2]+col[2]+col[3]+col[3];
}
if (rgb === "grb") {
g = parseInt(col.slice(1,3),16);
r = parseInt(col.slice(3,5),16);
b = parseInt(col.slice(5),16);
}
else {
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; }
}

View File

@ -36,6 +36,13 @@
</div>
</script>
<script type="text/x-red" data-template-name="rpi-sensehat out">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name">
</div>
</script>
<script type="text/x-red" data-help-name="rpi-sensehat in">
<p>Raspberry Pi Sense HAT input node.</p>
<p>This node sends readings from the various sensors on the Sense HAT,
@ -75,10 +82,53 @@ is an object with the following values:</p>
</ul>
</li>
</ul>
</script>
<script type="text/x-red" data-help-name="rpi-sensehat out">
<p>Raspberry Pi Sense HAT output node.</p>
<p>This node sends commands to the 8x8 LED display on the Sense HAT.</p>
<p>Commands are sent to the node in <code>msg.payload</code>. Multiple commands can
be sent in a single message by separating them with newline (\n) characters.<p>
<p><b>Set the colour of individual pixels</b></p>
<p>Format: <code>&lt;x&gt;,&lt;y&gt;,&lt;colour&gt;</code>
<p><code>x</code> and <code>y</code> must either be a value in the range 0-7, or
<code>*</code> to indicate the entire row or column.</p>
<p><code>colour</code> must be one of:
<ul>
<li>the well-known <a href="https://en.wikipedia.org/wiki/Web_colors" target="_new">HTML colour names</a>
- eg <code>red</code> or <code>aquamarine</code>,</li>
<li>the <a href="http://cheerlights.com/cheerlights-api/">CheerLights colour names</a>,</li>
<li>a HEX colour value - eg <code>#aa9900</code></li>
<li>an RGB triple - <code>190,255,0</code></li>
<li>or simply <code>off</code></li>
</ul>
<p><i>Examples</i></p>
<p>To set the entire screen to red:</p>
<p><code>*,*,red</code></p>
<p>To set the four corners of the display to red, green (#00ff00), yellow and blue (0,0,255):</p>
<p><code>0,0,red,0,7,#00ff00,7,7,yellow,7,0,0,0,255</code></p>
<p><b>Rotate the screen</b></p>
<p>Format: <code>R&lt;angle&gt;</code></p>
<p><code>angle</code> must be 0, 90, 180 or 270.</p>
<p><b>Flip the screen</b></p>
<p>Format: <code>R&lt;axis&gt;</code></p>
<p><code>axis</code> must be either <code>H</code> or <code>V</code> to flip on
the horizontal or vertical axis respectively.</p>
<p><b>Scroll a message</b></p>
<p>If <code>msg.payload</code> is not recognised as any of the above commands,
it is treated as a text message to be scrolled across the screen.</p>
<p>The following message properties can be used to customise the appearance:</p>
<ul>
<li><code>msg.colour</code> - the colour of the text, default: <code>white</code></li>
<li><code>msg.background</code> - the colour of the background, default: <code>off</code></li>
<li><code>msg.speed</code> - the scroll speed. A value in the range 1 (slower) to 5 (faster), default: <code>3</code></li>
</ul>
</script>
<script type="text/javascript">
RED.nodes.registerType('rpi-sensehat in',{
category: 'Raspberry Pi',
@ -100,4 +150,23 @@ is an object with the following values:</p>
return this.name?"node_label_italic":"";
}
});
RED.nodes.registerType('rpi-sensehat out',{
category: 'Raspberry Pi',
paletteLabel: "Sense HAT",
color:"#c6dbef",
defaults: {
name: { value:"" },
},
inputs:1,
outputs:0,
icon: "rpi.png",
align: "right",
label: function() {
return this.name||"Sense HAT";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
}
});
</script>

View File

@ -18,6 +18,7 @@ module.exports = function(RED) {
"use strict";
var fs = require('fs');
var spawn = require('child_process').spawn;
var colours = require('./colours');
var hatCommand = __dirname+'/sensehat';
@ -198,10 +199,16 @@ module.exports = function(RED) {
} else {
done();
}
},
send: function(msg) {
if (hat) {
hat.stdin.write(msg+'\n');
}
}
}
})();
function SenseHatInNode(n) {
RED.nodes.createNode(this,n);
this.motion = n.motion;
@ -215,4 +222,115 @@ module.exports = function(RED) {
});
}
RED.nodes.registerType("rpi-sensehat in",SenseHatInNode);
function SenseHatOutNode(n) {
RED.nodes.createNode(this,n);
var node = this;
HAT.open(this);
node.on("close", function(done) {
HAT.close(this,done);
});
node.on("input",function(msg) {
var command;
var parts;
var col;
if (typeof msg.payload === 'string') {
var lines = msg.payload.split("\n");
lines.forEach(function(line) {
command = null;
col = colours.getRGB(line);
if ( /^(([0-7]|\*),([0-7]|\*),(\d{1,3},\d{1,3},\d{1,3}|#[a-f0-9]{3,6}|[a-z]+))(,([0-7]|\*),([0-7]|\*),(\d{1,3},\d{1,3},\d{1,3}|#[a-f0-9]{3,6}|[a-z]+))*$/i.test(line)) {
parts = line.split(",");
var expanded = [];
var i=0;
var j=0;
while (i<parts.length) {
var x = parts[i++];
var y = parts[i++];
col = parts[i++];
if (/#[a-f0-9]{3,6}|[a-z]/i.test(col)) {
col = colours.getRGB(col);
if (col === null) {
// invalid colour, go no further
return;
}
} else {
col += ","+parts[i++]+","+parts[i++];
}
if (x!=='*' && y!=='*') {
expanded.push([x,y,col]);
} else if (x == '*' && y == '*') {
for (j=0;j<8;j++) {
for (var k=0;k<8;k++) {
expanded.push([j,k,col]);
}
}
} else if (x == '*') {
for (j=0;j<8;j++) {
expanded.push([j,y,col]);
}
} else if (y == '*') {
for (j=0;j<8;j++) {
expanded.push([x,j,col]);
}
}
}
if (expanded.length > 0) {
var pixels = {};
var rules = [];
for (i=expanded.length-1;i>=0;i--) {
var rule = expanded[i];
if (!pixels[rule[0]+","+rule[1]]) {
rules.unshift(rule.join(","));
pixels[rule[0]+","+rule[1]] = true;
}
}
if (rules.length > 0) {
command = "P"+rules.join(",");
}
}
}
if (!command) {
if (/^R(0|90|180|270)$/i.test(line)) {
command = line.toUpperCase();
} else if (/^F(H|V)$/i.test(line)) {
command = line.toUpperCase();
} else {
var textCol = colours.getRGB(msg.color);
var backCol = colours.getRGB(msg.background);
var speed = null;
if (!isNaN(msg.speed)) {
speed = msg.speed;
}
command = "T";
if (textCol) {
command += textCol;
if (backCol) {
command += ","+backCol;
}
}
if (speed) {
var s = parseInt(speed);
if (s >= 1 && s <= 5) {
s = 0.1 + (3-s)*0.03;
}
command = command + ((command.length === 1)?"":",") + s;
}
command += ":" + line;
}
}
if (command) {
//console.log(command);
HAT.send(command);
}
});
}
});
}
RED.nodes.registerType("rpi-sensehat out",SenseHatOutNode);
}

View File

@ -16,7 +16,7 @@
# C[R,G,B] - clear to colour (or off if no RGB provided)
# R[rot] - rotate by rot (0,90,180,270)
# P[x,y,R,G,B]+ - set individual pixel(s) to a colour
# T[R,G,B:]Message - scroll a message (nb: if message contains ':' it must be prefixed with ':')
# T[R,G,B[,R,G,B][,S]:]Message - scroll a message (nb: if message contains ':' it must be prefixed with ':')
# F[H|V] - flip horizontal|vertical
# X[0|1] - high frequency reporting (accel/gyro/orientation/compass) off|on
# Y[0|1] - low frequency reporting (temperature/humidity/pressure) off|on
@ -32,8 +32,11 @@ import sys
import glob
import time
import errno
import ctypes
import select
import struct
import inspect
import threading
from sense_hat import SenseHat
@ -69,40 +72,48 @@ lf_interval = 1
hf_enabled = False
lf_enabled = False
scroll = None
class ScrollThread(threading.Thread):
def __init__(self,fcol,bcol,speed,message):
threading.Thread.__init__(self)
self.fcol = fcol
self.bcol = bcol
self.message = message
self.speed = speed
def run(self):
global SH
old_rotation = SH.rotation
try:
SH.show_message(self.message,text_colour=self.fcol,back_colour=self.bcol,scroll_speed=self.speed)
except:
SH.set_rotation(old_rotation,False)
SH.clear(self.bcol);
pass
def interrupt(self):
if not self.isAlive():
raise threading.ThreadError()
for thread_id, thread_object in threading._active.items():
if thread_object == self:
r = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id,ctypes.py_object(StandardError))
if r == 1:
pass
else:
if r > 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
raise SystemError()
return
def process_command(data):
global hf_enabled, lf_enabled
if data[0] == "R":
SH.set_rotation(float(data[1:]))
elif data[0] == "C":
data = data[1:].strip()
if len(data) > 0:
s = data.split(",")
col = (int(s[0]),int(s[1]),int(s[2]))
else:
col = (0,0,0)
SH.clear(col)
elif data[0] == "P":
data = data[1:].strip()
s = data.split(',')
for p in range(0,len(s),5):
SH.set_pixel(int(s[p]),int(s[p+1]),int(s[p+2]),int(s[p+3]),int(s[p+4]))
elif data[0] == "T":
data = data[1:].strip()
col = (255,255,255)
s = data.split(':',1)
if len(s) == 2:
data = s[1]
if len(s[0]) > 0:
c = s[0].split(",")
col = (int(c[0]),int(c[1]),int(c[2]))
SH.show_message(data,text_colour=col)
elif data[0] == "F":
if data[1] == "H":
SH.flip_h()
elif data[1] == "V":
SH.flip_v()
elif data[0] == "X":
global hf_enabled, lf_enabled,scroll
if data[0] == "X":
if data[1] == '0':
hf_enabled = False
else:
@ -112,6 +123,61 @@ def process_command(data):
lf_enabled = False
else:
lf_enabled = True
else:
if threading.activeCount() == 2:
scroll.interrupt()
while scroll.isAlive():
time.sleep(0.01)
try:
scroll.interrupt()
except:
pass
if data[0] == "R":
SH.set_rotation(float(data[1:]))
elif data[0] == "C":
data = data[1:].strip()
if len(data) > 0:
s = data.split(",")
col = (int(s[0]),int(s[1]),int(s[2]))
else:
col = (0,0,0)
SH.clear(col)
elif data[0] == "P":
data = data[1:].strip()
s = data.split(',')
for p in range(0,len(s),5):
SH.set_pixel(int(s[p]),int(s[p+1]),int(s[p+2]),int(s[p+3]),int(s[p+4]))
elif data[0] == "T":
data = data[1:].strip()
tcol = (255,255,255)
bcol = (0,0,0)
speed = 0.1
s = data.split(':',1)
if len(s) == 2:
data = s[1]
if len(s[0]) > 0:
c = s[0].split(",")
if len(c) == 1:
speed = float(c[0])
elif len(c) == 3:
tcol = (int(c[0]),int(c[1]),int(c[2]))
if len(c) == 4:
tcol = (int(c[0]),int(c[1]),int(c[2]))
speed = float(c[3])
elif len(c) == 6:
tcol = (int(c[0]),int(c[1]),int(c[2]))
bcol = (int(c[3]),int(c[4]),int(c[5]))
elif len(c) == 7:
tcol = (int(c[0]),int(c[1]),int(c[2]))
bcol = (int(c[3]),int(c[4]),int(c[5]))
speed = float(c[6])
scroll = ScrollThread(tcol,bcol,speed,data);
scroll.start()
elif data[0] == "F":
if data[1] == "H":
SH.flip_h()
elif data[1] == "V":
SH.flip_v()
def idle_work():
global last_hf_time, last_lf_time