mirror of
				https://github.com/node-red/node-red-nodes.git
				synced 2025-03-01 10:37:43 +00:00 
			
		
		
		
	Add sensehat simulator node
This commit is contained in:
		
							
								
								
									
										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")); | ||||
|     }); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user