mirror of
https://github.com/node-red/node-red-nodes.git
synced 2023-10-10 13:36:58 +02:00
Add sensehat simulator node
This commit is contained in:
parent
99e9c540ef
commit
76430a84f0
177
hardware/sensehatsim/LICENSE
Normal file
177
hardware/sensehatsim/LICENSE
Normal file
@ -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
|
126
hardware/sensehatsim/README.md
Normal file
126
hardware/sensehatsim/README.md
Normal file
@ -0,0 +1,126 @@
|
||||
node-red-node-pi-sense-hat-simulator
|
||||
====================================
|
||||
|
||||
A <a href="http://nodered.org" target="_new">Node-RED</a> node to simulate a
|
||||
Raspberry Pi Sense HAT.
|
||||
|
||||
This allows you to create flows that interact with a virtual Sense HAT without the actual
|
||||
hardware - whether you're running on a Raspberry Pi, a laptop or elsewhere.
|
||||
|
||||
Once a Sense HAT simulator node has been deployed, the web page that provides
|
||||
a UI to the simulator can be accessed by clicking the link in the node's info panel.
|
||||
|
||||
## Install
|
||||
|
||||
Run the following command in your Node-RED user directory (typically `~/.node-red`):
|
||||
|
||||
npm install node-red-node-pi-sense-hat-simulator
|
||||
|
||||
## Usage
|
||||
|
||||
### 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.
|
||||
|
||||
#### Motion events
|
||||
|
||||
*** The current version of the simulator does not support Motion events ***
|
||||
|
||||
Motion events include readings from the accelerometer, gyroscope and magnetometer,
|
||||
as well as the current compass heading. They are sent at a rate of approximately 10
|
||||
per second. The `topic` is set to `motion` and the `payload` is an object with the
|
||||
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
|
||||
|
||||
#### Environment events
|
||||
|
||||
Environment events include readings from the temperature, humidity and pressure
|
||||
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
|
||||
|
||||
#### 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
|
||||
|
||||
|
||||
### 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 from 0 to 7, a `*` to indicate the entire row
|
||||
or column, or a range such as `3-6`.
|
||||
|
||||
`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`
|
||||
|
||||
To set a 3-pixel wide column to purple: `4-6,*,purple`
|
||||
|
||||
|
||||
#### Rotate the screen
|
||||
|
||||
Format: `R<angle>`
|
||||
|
||||
`angle` must be 0, 90, 180 or 270.
|
||||
|
||||
#### Flip the screen
|
||||
|
||||
Format: `R<axis>`
|
||||
|
||||
`axis` must be either `H` or `V` to flip on the horizontal or vertical axis respectively.
|
||||
|
||||
#### Scroll a message
|
||||
|
||||
*** The current version of the simulator does not support displaying text ***
|
||||
|
||||
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.
|
||||
|
||||
If the text is a single character, it will be displayed without scrolling. To
|
||||
scroll a single character, append a blank space after it - `"A "`.</p>
|
||||
|
||||
The following message properties can be used to customise the appearance:
|
||||
|
||||
- `msg.color` - 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`
|
||||
|
||||
#### Set the screen brightness
|
||||
|
||||
Format: `D<level>`
|
||||
|
||||
`level` must be 0 (low) or 1 (high).
|
220
hardware/sensehatsim/colours.js
Normal file
220
hardware/sensehatsim/colours.js
Normal 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; }
|
||||
}
|
1
hardware/sensehatsim/examples/Clock.json
Normal file
1
hardware/sensehatsim/examples/Clock.json
Normal file
@ -0,0 +1 @@
|
||||
[{"id":"8b346a30.651a88","type":"rpi-sensehat out","z":"7756eff1.08d0a","name":"","x":530,"y":220,"wires":[]},{"id":"c2e7a15d.237bd","type":"function","z":"7756eff1.08d0a","name":"Simple graphical clock","func":"\n// array to hold \"random\" pixels\nvar ranNums = [];\n\n// create a non-overlapping array of random numbers 0-8\nfunction rerand() {\n var nums = [0,1,2,3,4,5,6,7,8];\n var i = nums.length;\n var j;\n ranNums = [];\n while (i--) {\n j = Math.floor(Math.random() * (i+1));\n ranNums.push(nums[j]);\n nums.splice(j,1);\n }\n}\n\n// Get the hours and minutes and split into tens and units\nvar d = new Date();\nvar s = d.getSeconds();\nvar su = s%4;\nif (su === 0) {\n var h = d.getHours();\n var m = d.getMinutes();\n var hu = h%10;\n h = parseInt(h/10);\n var mu = m%10;\n m = parseInt(m/10);\n \n // Do the tens of hours (red)\n rerand();\n node.send({payload:\"1-3,1-3,0,0,0\"});\n for (var i=0; i<h; i++) {\n node.send({payload:(1+ranNums[i]%3)+\",\"+(1+parseInt(ranNums[i]/3))+\",255,0,0\"});\n }\n \n // Do the units of hours (green)\n rerand();\n node.send({payload:\"4-6,1-3,0,0,0\"});\n for (var i=0; i<hu; i++) {\n node.send({payload:(4+ranNums[i]%3)+\",\"+(1+parseInt(ranNums[i]/3))+\",0,255,0\"});\n }\n \n // Do the tens of minutes (yellow)\n rerand();\n node.send({payload:\"1-3,4-6,0,0,0\"});\n for (var i=0; i<m; i++) {\n node.send({payload:(1+ranNums[i]%3)+\",\"+(4+parseInt(ranNums[i]/3))+\",255,255,0\"});\n }\n \n // Do the unit of minutes (blue)\n rerand();\n node.send({payload:\"4-6,4-6,0,0,0\"});\n for (var i=0; i<mu; i++) {\n node.send({payload:(4+ranNums[i]%3)+\",\"+(4+parseInt(ranNums[i]/3))+\",0,0,255\"});\n }\n \n}\nnode.send({payload:\"0,0,\"+(su===0?\"purple\":\"off\")+\",7,0,\"+(su===1?\"purple\":\"off\")+\",7,7,\"+(su===2?\"purple\":\"off\")+\",0,7,\"+(su===3?\"purple\":\"off\")})\n\n\n// nothing left to do\nreturn null;","outputs":1,"noerr":0,"x":320,"y":220,"wires":[["8b346a30.651a88"]]},{"id":"fc3808df.6cbcf8","type":"comment","z":"7756eff1.08d0a","name":"Simple graphical clock for Sense Hat","info":"Generates a graphical clock, showing hours and minutes. \n\nHours are shown by the red and green pixels, minutes\nare the yellow and blue pixels.\n\nUses the local time of the Pi - you may need to set your local\ntimezone using\n\n sudo dpkg-reconfigure tzdata\n","x":210,"y":180,"wires":[]},{"id":"f33ea1a7.27216","type":"inject","z":"7756eff1.08d0a","name":"","topic":"","payload":"","payloadType":"date","repeat":"1","crontab":"","once":true,"x":120,"y":220,"wires":[["c2e7a15d.237bd"]]}]
|
1
hardware/sensehatsim/examples/Compass.json
Normal file
1
hardware/sensehatsim/examples/Compass.json
Normal file
@ -0,0 +1 @@
|
||||
[{"id":"104a54e7.6656cb","type":"rpi-sensehat out","z":"7756eff1.08d0a","name":"","x":470,"y":300,"wires":[]},{"id":"a3786fbb.b3ff9","type":"rpi-sensehat in","z":"7756eff1.08d0a","name":"","motion":true,"env":false,"stick":false,"x":120,"y":300,"wires":[["931798d.9640d68"]]},{"id":"931798d.9640d68","type":"function","z":"7756eff1.08d0a","name":"Compass","func":"// Based on the Compass example provided by\n// the Sense HAT python library\n// https://github.com/RPi-Distro/python-sense-hat/tree/master/examples\n\nvar led_loop = [4, 5, 6, 7, 15, 23, 31, 39, 47, 55, 63, 62, 61, 60, 59, 58, 57, 56, 48, 40, 32, 24, 16, 8, 0, 1, 2, 3];\nvar led_degree_ratio = led_loop.length / 360.0;\nvar dir = (360 - msg.payload.compass)%360;\n\nfunction getXY(dir) {\n var led_index = Math.floor(led_degree_ratio * (dir%360));\n var offset = led_loop[led_index];\n return [offset % 8,Math.floor(offset / 8)];\n}\n\nvar previous = context.get('previous');\nmsg.payload = '';\n\nvar position = getXY(dir);\nif (!previous || position[0] != previous[0][0] || position[1] != previous[0][1]) {\n if (previous) {\n msg.payload = previous[0][0]+\",\"+previous[0][1]+\",off,\"+\n previous[1][0]+\",\"+previous[1][1]+\",off,\";\n } else {\n msg.payload = \"*,*,off,\";\n }\n previous = [\n position,\n getXY(dir+180)\n ];\n msg.payload += previous[0][0]+\",\"+previous[0][1]+\",red,\"+\n previous[1][0]+\",\"+previous[1][1]+\",white\"\n \n context.set('previous',previous);\n return msg;\n}\nreturn null;\n\n","outputs":1,"noerr":0,"x":280,"y":300,"wires":[["104a54e7.6656cb"]]},{"id":"434680a8.c2e54","type":"comment","z":"7756eff1.08d0a","name":"Simple compass for Sense Hat","info":"Generates a compass, showing north (red) and south (white).\n\nBased on the python example provided with the Sense Hat: https://github.com/RPi-Distro/python-sense-hat/tree/master/examples\n\nThe accuracy of the compass will depend on callibrating the Sense Hat\ninternal compass. Search the Sense Hat forums for a guide on how to\ndo this.\n","x":190,"y":260,"wires":[]}]
|
24
hardware/sensehatsim/package.json
Normal file
24
hardware/sensehatsim/package.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name" : "node-red-node-pi-sense-hat-simulator",
|
||||
"version" : "0.0.1",
|
||||
"description" : "A Node-RED node to simulate a Raspberry Pi Sense HAT",
|
||||
"repository" : {
|
||||
"type":"git",
|
||||
"url":"https://github.com/node-red/node-red-nodes/tree/master/hardware/sensehatsim"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": "0.8.1"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [ "node-red", "sensehat", "astropi" ],
|
||||
"node-red" : {
|
||||
"nodes" : {
|
||||
"sensehatsim": "sensehatsim.js"
|
||||
}
|
||||
},
|
||||
"author": {
|
||||
"name": "Nick O'Leary",
|
||||
"email": "nick.oleary@gmail.com",
|
||||
"url": "http://nodered.org"
|
||||
}
|
||||
}
|
367
hardware/sensehatsim/public/index.html
Normal file
367
hardware/sensehatsim/public/index.html
Normal file
@ -0,0 +1,367 @@
|
||||
<html>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<head>
|
||||
<style>
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
body {
|
||||
text-align: center;
|
||||
background: #003300;
|
||||
font-family: Arial;
|
||||
|
||||
}
|
||||
#display {
|
||||
display:inline-block;
|
||||
border-radius: 5px;
|
||||
width: auto;
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
background: #00AA00;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 2px 2px 1px #99cc99;
|
||||
|
||||
}
|
||||
.led {
|
||||
background-color: #000;
|
||||
border: 1px solid #eee;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 3px;
|
||||
margin: 1px;
|
||||
box-shadow: 1px 1px 1px #666;
|
||||
}
|
||||
#controls {
|
||||
}
|
||||
#environment {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#joystick {
|
||||
margin-left: 10px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.envvar {
|
||||
margin: 10px auto;
|
||||
}
|
||||
.envvar>span {
|
||||
display: block;
|
||||
color: silver;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.envvar>div {
|
||||
border: 1px solid #999;
|
||||
border-radius: 3px;
|
||||
display:inline-block;
|
||||
font-size: 18px;
|
||||
background: #000;
|
||||
box-shadow: 1px 1px 1px #eee;
|
||||
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 38px;
|
||||
line-height: 40px;
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: silver;
|
||||
text-decoration: none;
|
||||
vertical-align: top;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.envvar a {
|
||||
margin: none;
|
||||
}
|
||||
.envvar a:first-of-type {
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
a:active {
|
||||
background: grey;
|
||||
}
|
||||
|
||||
.envvar .value {
|
||||
vertical-align: top;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
color: silver;
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
background: #000;
|
||||
box-shadow: inset 1px 1px 2px #ddd;
|
||||
|
||||
}
|
||||
|
||||
#joystick a {
|
||||
display: inline-block;
|
||||
width: 43px;
|
||||
height: 43px;
|
||||
border: 1px solid #999;
|
||||
margin: 3px;
|
||||
line-height: 50px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 1px 1px 1px #eee;
|
||||
|
||||
}
|
||||
#joystick a.up {}
|
||||
#joystick a.right {
|
||||
box-shadow: 1px -1px 1px #eee;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
#joystick a.down {
|
||||
box-shadow: -1px -1px 1px #eee;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
#joystick a.left {
|
||||
box-shadow: -1px 1px 1px #eee;
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
#joystick a.enter {
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="display">
|
||||
</div>
|
||||
<div id="controls">
|
||||
<div id="environment">
|
||||
<div class="envvar">
|
||||
<span>Temperature</span>
|
||||
<div>
|
||||
<a href="" onclick="changeTemperature(-0.2); return false;">-</a><div class="value" id="temperature"></div><a href="" onclick="changeTemperature(0.2); return false;">+</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="envvar">
|
||||
<span>Humidity</span>
|
||||
<div>
|
||||
<a href="" onclick="changeHumidity(-1); return false;">-</a><div class="value" id="humidity"></div><a href="" onclick="changeHumidity(1); return false;">+</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="envvar">
|
||||
<span>Pressure</span>
|
||||
<div>
|
||||
<a href="" onclick="changePressure(-5); return false;">-</a><div class="value" id="pressure"></div><a href="" onclick="changePressure(5); return false;">+</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="joystick">
|
||||
<div><a href="" id="j-up" class="up" onclick="return false;">^</a></div>
|
||||
<div><a href="" id="j-left" class="left" onclick="return false;">^</a><a id="j-enter" href="" class="enter" onclick="return false;"></a><a id="j-right" href="" class="right" onclick="return false;">^</a></div>
|
||||
<div><a href="" id="j-down" class="down" onclick="return false;">^</a></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var temperature = 29;
|
||||
var humidity = 80;
|
||||
var pressure = 1000;
|
||||
var rotation = 0;
|
||||
var flipH = false;
|
||||
var flipV = false;
|
||||
|
||||
function updateTemperature(d) {
|
||||
temperature = Number(d.toFixed(1));
|
||||
document.getElementById('temperature').innerHTML = temperature.toFixed(1)+"°C";
|
||||
}
|
||||
function changeTemperature(d) {
|
||||
updateTemperature(temperature+d);
|
||||
publishEnvironment();
|
||||
}
|
||||
function updateHumidity(d) {
|
||||
humidity = d;
|
||||
document.getElementById('humidity').innerHTML = humidity+"%";
|
||||
}
|
||||
function changeHumidity(d) {
|
||||
updateHumidity(humidity+d);
|
||||
publishEnvironment();
|
||||
}
|
||||
function updatePressure(d) {
|
||||
pressure = d;
|
||||
document.getElementById('pressure').innerHTML = pressure+"mb";
|
||||
}
|
||||
function changePressure(d) {
|
||||
updatePressure(pressure+d);
|
||||
publishEnvironment();
|
||||
}
|
||||
function publishEnvironment() {
|
||||
if (ws && ws.readyState === ws.OPEN) {
|
||||
ws.send("Y"+temperature+","+humidity+","+pressure);
|
||||
}
|
||||
}
|
||||
|
||||
var k = document.getElementById("j-up");
|
||||
k.onmousedown = function() { keyDown('U'); }
|
||||
k.onmouseup = function() { keyUp('U'); }
|
||||
|
||||
k = document.getElementById("j-down");
|
||||
k.onmousedown = function() { keyDown('D'); }
|
||||
k.onmouseup = function() { keyUp('D'); }
|
||||
|
||||
k = document.getElementById("j-left");
|
||||
k.onmousedown = function() { keyDown('L'); }
|
||||
k.onmouseup = function() { keyUp('L'); }
|
||||
|
||||
k = document.getElementById("j-right");
|
||||
k.onmousedown = function() { keyDown('R'); }
|
||||
k.onmouseup = function() { keyUp('R'); }
|
||||
|
||||
k = document.getElementById("j-enter");
|
||||
k.onmousedown = function() { keyDown('E'); }
|
||||
k.onmouseup = function() { keyUp('E'); }
|
||||
|
||||
var keyTimer = null;
|
||||
function keyDown(evt) {
|
||||
// K[U|L|R|D|E][0|1|2] - joystick event: direction,state
|
||||
ws.send("K"+evt+"1");
|
||||
keyTimer = setInterval(function() {
|
||||
ws.send("K"+evt+"2");
|
||||
},200);
|
||||
|
||||
}
|
||||
function keyUp(evt) {
|
||||
// K[U|L|R|D|E][0|1|2] - joystick event: direction,state
|
||||
clearInterval(keyTimer);
|
||||
ws.send("K"+evt+"0");
|
||||
}
|
||||
|
||||
var display = document.getElementById("display");
|
||||
var cells = [];
|
||||
|
||||
for (var y=0;y<8;y++) {
|
||||
cells.push([]);
|
||||
var r = document.createElement("div");
|
||||
r.className = "row";
|
||||
display.appendChild(r);
|
||||
for (var x=0;x<8;x++) {
|
||||
var d = document.createElement("div");
|
||||
d.className = "led";
|
||||
d.id = "led_"+x+"_"+y;
|
||||
r.appendChild(d);
|
||||
cells[y].push(d);
|
||||
}
|
||||
}
|
||||
|
||||
var ws;
|
||||
function connect() {
|
||||
var location = document.location.toString().replace(/^http/,"ws")+"/ws";
|
||||
ws = new WebSocket(location);
|
||||
ws.onopen = function() {
|
||||
}
|
||||
ws.onclose = function() {
|
||||
setTimeout(connect,5000);
|
||||
}
|
||||
ws.onmessage = function(msg) {
|
||||
var command = msg.data[0];
|
||||
var data = msg.data.substring(1);
|
||||
var parts;
|
||||
var x,y,t;
|
||||
if (command === 'P') {
|
||||
parts = data.split(",");
|
||||
for (var i=0;i<parts.length;i+=5) {
|
||||
x = parts[i];
|
||||
y = parts[i+1];
|
||||
if (flipH) { x = 7-x; }
|
||||
if (flipV) { y = 7-y; }
|
||||
if (rotation === 180) {
|
||||
x = 7-x;
|
||||
y = 7-y;
|
||||
} else if (rotation === 90) {
|
||||
if (y<4 && x<4) { t = y; y = x; x = 7-t; }
|
||||
else if (y<4 && x>3) { t=x; x = 7-y; y = t; }
|
||||
else if (y>3 && x>3) { t=x; x = 7-y; y = t; }
|
||||
else if (y>3 && x<4) { t=y; y = x; x = 7-t; }
|
||||
} else if (rotation === 270) {
|
||||
if (y<4 && x<4) { t = x; x = y; y = 7-t; }
|
||||
else if (y<4 && x>3) { t=y; y = 7-x; x = t; }
|
||||
else if (y>3 && x>3) { t=y; y = 7-x; x = t; }
|
||||
else if (y>3 && x<4) { t=x; x = y; y = 7-t; }
|
||||
}
|
||||
cells[y][x].style.backgroundColor = "rgb("+parts[i+2]+","+parts[i+3]+","+parts[i+4]+")"
|
||||
}
|
||||
} else if (command === 'Y') {
|
||||
parts = data.split(",");
|
||||
updateTemperature(Number(parts[0]));
|
||||
updateHumidity(Number(parts[1]));
|
||||
updatePressure(Number(parts[2]));
|
||||
} else if (command === 'R') {
|
||||
var delta = Number(data) - rotation;
|
||||
if (delta < 0) {
|
||||
delta += 360;
|
||||
}
|
||||
if (delta === 90) {
|
||||
for (y=0;y<4;y++) {
|
||||
for (x=0;x<4;x++) {
|
||||
t = cells[y][x].style.backgroundColor;
|
||||
cells[y][x].style.backgroundColor = cells[7-y][x].style.backgroundColor;
|
||||
cells[7-y][x].style.backgroundColor = cells[7-x][7-y].style.backgroundColor;
|
||||
cells[7-x][7-y].style.backgroundColor = cells[y][7-x].style.backgroundColor;
|
||||
cells[y][7-x].style.backgroundColor = t;
|
||||
|
||||
}
|
||||
}
|
||||
} else if (delta === 270) {
|
||||
for (y=0;y<4;y++) {
|
||||
for (x=0;x<4;x++) {
|
||||
t = cells[y][x].style.backgroundColor;
|
||||
cells[y][x].style.backgroundColor = cells[y][7-x].style.backgroundColor;
|
||||
cells[y][7-x].style.backgroundColor = cells[7-x][7-y].style.backgroundColor;
|
||||
cells[7-x][7-y].style.backgroundColor = cells[7-y][x].style.backgroundColor;
|
||||
cells[7-y][x].style.backgroundColor = t;
|
||||
|
||||
}
|
||||
}
|
||||
} else if (delta === 180) {
|
||||
for (y=0;y<4;y++) {
|
||||
for (x=0;x<4;x++) {
|
||||
t = cells[y][x].style.backgroundColor;
|
||||
cells[y][x].style.backgroundColor = cells[7-y][7-x].style.backgroundColor;
|
||||
cells[7-y][7-x].style.backgroundColor = t;
|
||||
|
||||
t = cells[7-y][x].style.backgroundColor;
|
||||
cells[7-y][x].style.backgroundColor = cells[y][7-x].style.backgroundColor;
|
||||
cells[y][7-x].style.backgroundColor = t;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
rotation = Number(data);
|
||||
//data = 0,90,180,270
|
||||
} else if (command === 'F') {
|
||||
if (data === 'H') {
|
||||
flipH = !flipH;
|
||||
for (y=0;y<8;y++) {
|
||||
for (x=0;x<4;x++) {
|
||||
t = cells[y][x].style.backgroundColor;
|
||||
cells[y][x].style.backgroundColor = cells[y][7-x].style.backgroundColor;
|
||||
cells[y][7-x].style.backgroundColor = t;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flipV = !flipV;
|
||||
for (x=0;x<8;x++) {
|
||||
for (y=0;y<4;y++) {
|
||||
t = cells[y][x].style.backgroundColor;
|
||||
cells[y][x].style.backgroundColor = cells[7-y][x].style.backgroundColor;
|
||||
cells[7-y][x].style.backgroundColor = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
connect();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
182
hardware/sensehatsim/sensehatsim.html
Normal file
182
hardware/sensehatsim/sensehatsim.html
Normal file
@ -0,0 +1,182 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="rpi-sensehatsim in">
|
||||
<div class="form-row">
|
||||
<label><i class="fa fa-arrow-right"></i> Outputs</label>
|
||||
<label style="width: auto" for="node-input-motion"><input style="vertical-align: top; width: auto; margin-right: 5px;" type="checkbox" id="node-input-motion" disabled> Motion events</label>
|
||||
<div style="padding-left: 125px; margin-top: -5px; color: #bbb;">accelerometer, gyroscope, magnetometer, compass</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<label style="width: auto" for="node-input-env"><input style="vertical-align: top; width: auto; margin-right: 5px;" type="checkbox" id="node-input-env"> Environment events</label>
|
||||
<div style="padding-left: 125px; margin-top: -5px; color: #bbb;">temperature, humidity, pressure</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label></label>
|
||||
<label style="width: auto" for="node-input-stick"><input style="vertical-align: top; width: auto; margin-right: 5px;" type="checkbox" id="node-input-stick"> Joystick events</label>
|
||||
</div>
|
||||
|
||||
<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-template-name="rpi-sensehatsim 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-sensehatsim in">
|
||||
<p>Raspberry Pi Sense HAT Simulator input node.</p>
|
||||
<p>This node simulates readings from the various sensors on the Sense HAT,
|
||||
grouped into three sets; motion events, environment events and joystick events.</p>
|
||||
<p>Once deployed, the simulator can be accessed <a href="sensehat-simulator" target="_new">here</a>.</p>
|
||||
<p><b>Motion events</b> - <i>not currently supported by the simulator</i></p>
|
||||
<p>Motion events include readings from the accelerometer, gyroscope and magnetometer,
|
||||
as well as the current compass heading. They are sent at a rate of approximately 10
|
||||
per second. The <code>topic</code> is set to <code>motion</code> and the
|
||||
<code>payload</code> is an object with the following values:</p>
|
||||
<ul>
|
||||
<li><code>acceleration.x/y/z</code> : the acceleration intensity in Gs</li>
|
||||
<li><code>gyroscope.x/y/z</code> : the rotational intensity in radians/s</li>
|
||||
<li><code>orientation.roll/pitch/yaw</code> : the angle of the axis in degrees</li>
|
||||
<li><code>compass</code> : the direction of North in degrees</li>
|
||||
</ul>
|
||||
<p><b>Environment events</b></p>
|
||||
<p>Environment events include readings from the temperature, humidity and pressure
|
||||
sensors. They are sent at a rate of approximately 1 per second. The <code>topic</code>
|
||||
is set to <code>environment</code> and the <code>payload</code> is an object
|
||||
with the following values:</p>
|
||||
<ul>
|
||||
<li><code>temperature</code> : degrees Celsius</li>
|
||||
<li><code>humidity</code> : percentage of relative humidity</li>
|
||||
<li><code>pressure</code> : Millibars</li>
|
||||
</ul>
|
||||
<p><b>Joystick events</b></p>
|
||||
<p>Joystick events are sent when the Sense HAT joystick is interacted with. The
|
||||
<code>topic</code> is set to <code>joystick</code> and the <code>payload</code>
|
||||
is an object with the following values:</p>
|
||||
<ul>
|
||||
<li><code>key</code> : one of <code>UP</code>, <code>DOWN</code>, <code>LEFT</code>, <code>RIGHT</code>, <code>ENTER</code></li>
|
||||
<li><code>state</code> : the state of the key:
|
||||
<ul>
|
||||
<li><code>0</code> : the key has been released</li>
|
||||
<li><code>1</code> : the key has been pressed</li>
|
||||
<li><code>2</code> : the key is being held down</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="rpi-sensehatsim out">
|
||||
<p>Raspberry Pi Sense HAT Simulator output node.</p>
|
||||
<p>This node sends commands to the 8x8 LED display on the Sense HAT simulator.</p>
|
||||
<p>Once deployed, the simulator can be accessed <a href="sensehat-simulator" target="_new">here</a>.</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><x>,<y>,<colour></code>
|
||||
<p><code>x</code> and <code>y</code> must either be a value from 0 to 7, a
|
||||
<code>*</code> to indicate the entire row or column, or a range such as <code>3-6</code>.</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>To set a 3-pixel wide column to purple:</p>
|
||||
<p><code>4-6,*,purple</code></p>
|
||||
|
||||
<p><b>Rotate the screen</b></p>
|
||||
<p>Format: <code>R<angle></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<axis></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> - <i>not currently supported by the simulator</i></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>If the text is a single character, it will be displayed without scrolling.
|
||||
To scroll a single character, append a blank space after it - <code>"A "</code>.</p>
|
||||
<p>The following message properties can be used to customise the appearance:</p>
|
||||
<ul>
|
||||
<li><code>msg.color</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>
|
||||
|
||||
<p><b>Set the screen brightness</b> - <i>not currently supported by the simulator</i></p>
|
||||
<p>Format: <code>D<level></code></p>
|
||||
<p><code>level</code> must be 0 (low) or 1 (high).</p>
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('rpi-sensehatsim in',{
|
||||
category: 'Raspberry Pi',
|
||||
paletteLabel: "Sense HAT Sim",
|
||||
color:"#c6dbef",
|
||||
defaults: {
|
||||
name: { value:"" },
|
||||
motion: { value: false },
|
||||
env: { value: true },
|
||||
stick: { value: true }
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "rpi.png",
|
||||
label: function() {
|
||||
return this.name||"Sense HAT Sim";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
|
||||
RED.nodes.registerType('rpi-sensehatsim out',{
|
||||
category: 'Raspberry Pi',
|
||||
paletteLabel: "Sense HAT Sim",
|
||||
color:"#c6dbef",
|
||||
defaults: {
|
||||
name: { value:"" },
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "rpi.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name||"Sense HAT Sim";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
395
hardware/sensehatsim/sensehatsim.js
Normal file
395
hardware/sensehatsim/sensehatsim.js
Normal file
@ -0,0 +1,395 @@
|
||||
/**
|
||||
* 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 path = require("path");
|
||||
var ws = require("ws");
|
||||
var colours = require('./colours');
|
||||
|
||||
// Xaccel.x,y,z,gyro.x,y,z,orientation.roll,pitch,yaw,compass
|
||||
var HF_RE = /^X(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+)$/;
|
||||
// Ytemperature,humidity,pressure
|
||||
var LF_RE = /^Y(.+),(.+),(.+)$/;
|
||||
// K[U|L|R|D|E][0|1|2] - joystick event: direction,state
|
||||
var KEY_RE = /^K(.)(.)$/;
|
||||
var KEY_MAP = {
|
||||
"U":"UP",
|
||||
"D":"DOWN",
|
||||
"L":"LEFT",
|
||||
"R":"RIGHT",
|
||||
"E":"ENTER"
|
||||
};
|
||||
|
||||
var currentFlipH = false;
|
||||
var currentFlipV = false;
|
||||
var currentRotation = "R0";
|
||||
var currentDisplay = [];
|
||||
var HAT = (function() {
|
||||
var hatWS = null;
|
||||
var wsServerListeners = {};
|
||||
var wsConnections = {};
|
||||
var currentEnvironment = {temperature: 20, humidity: 80, pressure: 1000};
|
||||
var hat = null;
|
||||
var onclose = null;
|
||||
var users = [];
|
||||
var motionUsers = 0;
|
||||
var envUsers = 0;
|
||||
var reconnectTimer = null;
|
||||
var envTimer = null;
|
||||
|
||||
var connect = function() {
|
||||
if (!hatWS) {
|
||||
currentFlipH = false;
|
||||
currentFlipV = false;
|
||||
currentRotation = "R0";
|
||||
currentDisplay = [];
|
||||
for (var y=0;y<8;y++) {
|
||||
currentDisplay.push([]);
|
||||
for (var x=0;x<8;x++) {
|
||||
currentDisplay[y].push('0,0,0');
|
||||
}
|
||||
}
|
||||
var wsPath = RED.settings.httpNodeRoot || "/";
|
||||
wsPath = wsPath + (wsPath.slice(-1) == "/" ? "":"/") + "sensehat-simulator/ws"
|
||||
|
||||
var storeListener = function(event,listener){
|
||||
if(event == "error" || event == "upgrade" || event == "listening"){
|
||||
wsServerListeners[event] = listener;
|
||||
}
|
||||
}
|
||||
RED.server.addListener('newListener',storeListener);
|
||||
// Create a WebSocket Server
|
||||
hatWS = new ws.Server({
|
||||
server:RED.server,
|
||||
path:wsPath,
|
||||
// Disable the deflate option due to this issue
|
||||
// https://github.com/websockets/ws/pull/632
|
||||
// that is fixed in the 1.x release of the ws module
|
||||
// that we cannot currently pickup as it drops node 0.10 support
|
||||
perMessageDeflate: false
|
||||
});
|
||||
RED.server.removeListener('newListener',storeListener);
|
||||
hatWS.on('connection', function(socket) {
|
||||
var id = (1+Math.random()*4294967295).toString(16);
|
||||
wsConnections[id] = socket;
|
||||
socket.send("Y"+currentEnvironment.temperature+","+currentEnvironment.humidity+","+currentEnvironment.pressure);
|
||||
socket.send(currentRotation);
|
||||
if (currentFlipV) {
|
||||
socket.send("FV");
|
||||
}
|
||||
if (currentFlipH) {
|
||||
socket.send("FH");
|
||||
}
|
||||
var cmd = "";
|
||||
for (var y=0;y<8;y++) {
|
||||
for (var x=0;x<8;x++) {
|
||||
cmd += ","+x+","+y+","+currentDisplay[y][x];
|
||||
}
|
||||
}
|
||||
socket.send("P"+cmd.substring(1));
|
||||
|
||||
|
||||
socket.on('close',function() {
|
||||
delete wsConnections[id];
|
||||
});
|
||||
socket.on('message',function(data,flags){
|
||||
var m;
|
||||
var msg;
|
||||
if ((m = LF_RE.exec(data)) !== null) {
|
||||
currentEnvironment = {temperature: Number(m[1]), humidity: Number(m[2]), pressure: Number(m[3])};
|
||||
msg = "Y"+currentEnvironment.temperature+","+currentEnvironment.humidity+","+currentEnvironment.pressure;
|
||||
for (var id in wsConnections) {
|
||||
if (wsConnections.hasOwnProperty(id)) {
|
||||
var client = wsConnections[id];
|
||||
if (client !== socket) {
|
||||
client.send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ((m = KEY_RE.exec(data)) !== null) {
|
||||
msg = {
|
||||
topic: "joystick",
|
||||
payload: {key: KEY_MAP[m[1]], state: Number(m[2])}
|
||||
}
|
||||
for (var j=0;j<users.length;j++) {
|
||||
var node = users[j];
|
||||
if (node.stick) {
|
||||
node.send(RED.util.cloneMessage(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
socket.on('error', function(err) {
|
||||
delete wsConnections[id];
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var disconnect = function(done) {
|
||||
if (hatWS !== null) {
|
||||
var listener = null;
|
||||
for (var event in wsServerListeners) {
|
||||
if (wsServerListeners.hasOwnProperty(event)) {
|
||||
listener = wsServerListeners[event];
|
||||
if (typeof listener === "function") {
|
||||
RED.server.removeListener(event,listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
wsServerListeners = {};
|
||||
wsConnections = {};
|
||||
hatWS.close();
|
||||
hatWS = null;
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
open: function(node) {
|
||||
if (!hatWS) {
|
||||
connect();
|
||||
}
|
||||
if (!reconnectTimer) {
|
||||
node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
|
||||
}
|
||||
|
||||
if (node.motion) {
|
||||
motionUsers++;
|
||||
}
|
||||
if (node.env) {
|
||||
envUsers++;
|
||||
}
|
||||
users.push(node);
|
||||
if (envUsers === 1) {
|
||||
envTimer = setInterval(function() {
|
||||
var msg = {
|
||||
topic: "environment",
|
||||
payload: {temperature: currentEnvironment.temperature, humidity: currentEnvironment.humidity, pressure: currentEnvironment.pressure}
|
||||
};
|
||||
for (var j=0;j<users.length;j++) {
|
||||
var node = users[j];
|
||||
if (node.env) {
|
||||
node.send(RED.util.cloneMessage(msg));
|
||||
}
|
||||
}
|
||||
},1000);
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
close: function(node,done) {
|
||||
if (node.motion) {
|
||||
motionUsers--;
|
||||
}
|
||||
if (node.env) {
|
||||
envUsers--;
|
||||
}
|
||||
if (envUsers === 0) {
|
||||
clearTimeout(envTimer);
|
||||
}
|
||||
users.splice(users.indexOf(node),1);
|
||||
if (users.length === 0) {
|
||||
disconnect(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
},
|
||||
send: function(msg) {
|
||||
for (var id in wsConnections) {
|
||||
if (wsConnections.hasOwnProperty(id)) {
|
||||
var client = wsConnections[id];
|
||||
client.send(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
function SenseHatInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.motion = n.motion;
|
||||
this.env = n.env;
|
||||
this.stick = n.stick;
|
||||
var node = this;
|
||||
node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"});
|
||||
HAT.open(this);
|
||||
|
||||
node.on("close", function(done) {
|
||||
HAT.close(this,done);
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("rpi-sensehatsim in",SenseHatInNode);
|
||||
|
||||
function SenseHatOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"});
|
||||
|
||||
HAT.open(this);
|
||||
|
||||
node.on("close", function(done) {
|
||||
HAT.close(this,done);
|
||||
});
|
||||
var handleTextMessage = function(line,msg) {
|
||||
var textCol = colours.getRGB(msg.color||msg.colour)||"255,255,255";
|
||||
var backCol = colours.getRGB(msg.background)||"0,0,0";
|
||||
var speed = null;
|
||||
if (!isNaN(msg.speed)) {
|
||||
speed = msg.speed;
|
||||
}
|
||||
var 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;
|
||||
return command;
|
||||
}
|
||||
|
||||
node.on("input",function(msg) {
|
||||
var command;
|
||||
var parts;
|
||||
var col;
|
||||
if (typeof msg.payload === 'number') {
|
||||
HAT.send(handleTextMessage(""+msg.payload,msg));
|
||||
} else if (typeof msg.payload === 'string') {
|
||||
var lines = msg.payload.split("\n");
|
||||
lines.forEach(function(line) {
|
||||
command = null;
|
||||
if ( /^(([0-7]|\*|[0-7]-[0-7]),([0-7]|\*|[0-7]-[0-7]),(\d{1,3},\d{1,3},\d{1,3}|#[a-f0-9]{3,6}|[a-z]+))(,([0-7]|\*|[0-7]-[0-7]),([0-7]|\*|[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 === '*') {
|
||||
x = "0-7";
|
||||
}
|
||||
if (y === '*') {
|
||||
y = "0-7";
|
||||
}
|
||||
var x0,x1;
|
||||
var y0,y1;
|
||||
if (x.indexOf("-") === -1) {
|
||||
x0 = x1 = parseInt(x);
|
||||
} else {
|
||||
var px = x.split("-");
|
||||
x0 = parseInt(px[0]);
|
||||
x1 = parseInt(px[1]);
|
||||
if (x1<x0) {
|
||||
j = x1;
|
||||
x1 = x0;
|
||||
x0 = j;
|
||||
}
|
||||
}
|
||||
if (y.indexOf("-") === -1) {
|
||||
y0 = y1 = parseInt(y);
|
||||
} else {
|
||||
var py = y.split("-");
|
||||
y0 = parseInt(py[0]);
|
||||
y1 = parseInt(py[1]);
|
||||
if (y1<y0) {
|
||||
j = y1;
|
||||
y1 = y0;
|
||||
y0 = j;
|
||||
}
|
||||
}
|
||||
x = x0;
|
||||
while(x<=x1) {
|
||||
y = y0;
|
||||
while(y<=y1) {
|
||||
expanded.push([x,y,col]);
|
||||
y++;
|
||||
}
|
||||
x++;
|
||||
}
|
||||
}
|
||||
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(","));
|
||||
currentDisplay[Number(rule[1])][Number(rule[0])] = rule[2];
|
||||
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();
|
||||
currentRotation = command;
|
||||
} else if (/^F(H|V)$/i.test(line)) {
|
||||
command = line.toUpperCase();
|
||||
if (command === 'FH') {
|
||||
currentFlipH = !currentFlipH;
|
||||
} else {
|
||||
currentFlipV = !currentFlipV;
|
||||
}
|
||||
} else if (/^D(0|1)$/i.test(line)) {
|
||||
command = line.toUpperCase();
|
||||
} else {
|
||||
command = handleTextMessage(line,msg);
|
||||
}
|
||||
}
|
||||
if (command) {
|
||||
//console.log(command);
|
||||
HAT.send(command);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("rpi-sensehatsim out",SenseHatOutNode);
|
||||
|
||||
RED.httpAdmin.get("/sensehat-simulator",function(req,res) {
|
||||
res.sendFile(path.join(__dirname,"public","index.html"));
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user