├── .github └── workflows │ ├── publish.yml │ ├── release_on_tag.yml │ └── require_pr_label.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── ac-in-voltage-alarm.json ├── adjust-depth.json ├── filter-out-n2k-src.json ├── gpio.json ├── high-wind-speed-alarm.json ├── preffered-n2k-src.json ├── put-handler.json ├── starter-voltage-alarm.json ├── switch-automation.json └── tweet-when-leaving.json ├── package.json ├── screens ├── ac-in-voltage-alarm.jpeg ├── adjust-depth.jpeg ├── delta-input-handler.jpeg ├── gpio.jpeg ├── high-wind-speed-alarm.jpeg ├── starter-voltage-alarm.jpeg ├── switch-automation.jpeg └── tweet-when-leaving.jpeg ├── signalk-app-event.html ├── signalk-app-event.js ├── signalk-delay.html ├── signalk-delay.js ├── signalk-filter-delta.html ├── signalk-filter-delta.js ├── signalk-flatten-delta.html ├── signalk-flatten-delta.js ├── signalk-geofence-switch.html ├── signalk-geofence-switch.js ├── signalk-geofence.html ├── signalk-geofence.js ├── signalk-input-handler-next.html ├── signalk-input-handler-next.js ├── signalk-input-handler.html ├── signalk-input-handler.js ├── signalk-notification.html ├── signalk-notification.js ├── signalk-on-delta.html ├── signalk-on-delta.js ├── signalk-put-handler.html ├── signalk-put-handler.js ├── signalk-send-delta.html ├── signalk-send-delta.js ├── signalk-send-nmea0183.html ├── signalk-send-nmea0183.js ├── signalk-send-nmea2000.html ├── signalk-send-nmea2000.js ├── signalk-send-notification.html ├── signalk-send-notification.js ├── signalk-send-pathvalue.html ├── signalk-send-pathvalue.js ├── signalk-send-put.html ├── signalk-send-put.js ├── signalk-subscribe.html └── signalk-subscribe.js /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to npm 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-node@v1 11 | with: 12 | node-version: '18.x' 13 | registry-url: 'https://registry.npmjs.org' 14 | - run: npm publish --access public 15 | env: 16 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/release_on_tag.yml: -------------------------------------------------------------------------------- 1 | name: 'Release on tag' 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | jobs: 8 | release: 9 | permissions: 10 | contents: write 11 | if: startsWith(github.ref, 'refs/tags/') 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Build Changelog 15 | id: github_release 16 | uses: mikepenz/release-changelog-builder-action@v5 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }} 19 | 20 | - name: Create Release 21 | uses: actions/create-release@v1 22 | with: 23 | tag_name: ${{ github.ref }} 24 | release_name: ${{ github.ref }} 25 | body: ${{steps.github_release.outputs.changelog}} 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }} -------------------------------------------------------------------------------- /.github/workflows/require_pr_label.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Labels 2 | on: 3 | pull_request: 4 | types: [opened, labeled, unlabeled, synchronize] 5 | jobs: 6 | label: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: mheap/github-action-required-labels@v1 10 | with: 11 | mode: exactly 12 | count: 1 13 | labels: "fix, feature, doc, chore, test, ignore, other, dependencies" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Change Log 2 | 3 | ### v2.17.2 (2021/03/17 16:10 +00:00) 4 | - [#26](https://github.com/SignalK/node-red-embedded/pull/26) fix: use the msg.topic for put path if none is entered (@sbender9) 5 | 6 | ### v2.17.1 (2021/03/14 17:17 +00:00) 7 | - [#25](https://github.com/SignalK/node-red-embedded/pull/25) fix: errors with existing send-pathvalue without meta data (@sbender9) 8 | 9 | ### v2.17.0 (2021/03/14 16:59 +00:00) 10 | - [#23](https://github.com/SignalK/node-red-embedded/pull/23) fix: source could be "no_source" for notifications (@sbender9) 11 | - [#24](https://github.com/SignalK/node-red-embedded/pull/24) feature: allow meta data to be specified for send-pathvalue (@sbender9) 12 | 13 | ### v2.16.0 (2020/05/19 17:15 +00:00) 14 | - [#19](https://github.com/SignalK/node-red-embedded/pull/19) feature: add inputs for location and distance to geofence nodes (@sbender9) 15 | 16 | ### v2.15.0 (2020/04/23 16:57 +00:00) 17 | - [#16](https://github.com/SignalK/node-red-embedded/pull/16) feature: simplify node names and move to Signal K section of pallete (@sbender9) 18 | 19 | ### v2.14.0 (2020/04/15 14:43 +00:00) 20 | - [#15](https://github.com/SignalK/node-red-embedded/pull/15) feature: add support to specify $source for puts (@sbender9) 21 | 22 | ### v2.13.1 (2020/03/14 01:32 +00:00) 23 | - [#14](https://github.com/SignalK/node-red-embedded/pull/14) fix: send-put node showing an error when it worked (@sbender9) 24 | - [#13](https://github.com/SignalK/node-red-embedded/pull/13) chore: remove debug logging (@sbender9) 25 | 26 | ### v2.13.0 (2020/03/14 00:25 +00:00) 27 | - [#12](https://github.com/SignalK/node-red-embedded/pull/12) feature: allow the $source to be specified for send-notification (@sbender9) 28 | 29 | ### v2.12.2 (2019/07/05 14:46 +00:00) 30 | - [#11](https://github.com/SignalK/node-red-embedded/pull/11) fix: put handler returning incorrect status (@sbender9) 31 | 32 | ### v2.12.1 (2019/05/02 15:52 +00:00) 33 | - [#10](https://github.com/SignalK/node-red-embedded/pull/10) fix: signalk-handler-next not passing on data (@sbender9) 34 | 35 | ### v2.12.0 (2019/05/02 14:38 +00:00) 36 | - [#9](https://github.com/SignalK/node-red-embedded/pull/9) feature: show feedback on the status of sending a put (@sbender9) 37 | 38 | ### v2.11.0 (2018/09/11 19:07 +00:00) 39 | - [#7](https://github.com/SignalK/node-red-embedded/pull/7) feature: allow subscriptions to paths coming from node red nodes (@sbender9) 40 | 41 | ### v2.10.0 (2018/09/11 18:14 +00:00) 42 | - [#6](https://github.com/SignalK/node-red-embedded/pull/6) feature: add ability to receive events from the signalk-node-server app (@sbender9) 43 | 44 | ### v2.9.0 (2018/08/16 16:42 +00:00) 45 | - [#5](https://github.com/SignalK/node-red-embedded/pull/5) feature: include timestamp in flattened subscriptions (@sbender9) 46 | 47 | ### v2.8.1 (2018/08/13 14:40 +00:00) 48 | - [#4](https://github.com/SignalK/node-red-embedded/pull/4) docs: update input handler docs (@sbender9) 49 | - [#3](https://github.com/SignalK/node-red-embedded/pull/3) docs: update input handler docs (@sbender9) 50 | - [#2](https://github.com/SignalK/node-red-embedded/pull/2) Update node, signalk-send-pathvalue, help. (@MatsA) 51 | 52 | ### v2.8.0 (2018/06/24 19:44 +00:00) 53 | - [#1](https://github.com/SignalK/node-red-embedded/pull/1) feature: add ability to filter/change deltas before they get into the server (@sbender9) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-red-embedded 2 | Node red nodes used by the [signalk-node-red](https://github.com/SignalK/signalk-node-red) plugin which embeds node-red into [signalk-node-server](https://github.com/SignalK/signalk-server-node). 3 | 4 | Please note that these nodes only work when node-red is embedded in signalk-node-server. 5 | 6 | See the examples folder for flows from the following examples.. 7 | 8 | # Examples 9 | 10 | ## Send an adjusted depth value 11 | ![Alt text](https://raw.githubusercontent.com/SignalK/node-red-embedded/master/screens/adjust-depth.jpeg) 12 | 13 | ## Switch relays based on complex conditions (example turns on/off the anchor light and ACR automatically) 14 | ![Alt text](https://raw.githubusercontent.com/SignalK/node-red-embedded/master/screens/switch-automation.jpeg) 15 | 16 | ## Send an alarm when wind speed is high 17 | ![Alt text](https://raw.githubusercontent.com/SignalK/node-red-embedded/master/screens/high-wind-speed-alarm.jpeg) 18 | 19 | ## Send an alarm when the starter battery voltage is low for more and 60 seconds. (this keeps the alarm from going off while starting the engine) 20 | ![Alt text](https://raw.githubusercontent.com/SignalK/node-red-embedded/master/screens/starter-voltage-alarm.jpeg) 21 | 22 | 23 | ## Send an alarm when the AC input voltage is low and the boat is in its slip 24 | ![Alt text](https://raw.githubusercontent.com/SignalK/node-red-embedded/master/screens/ac-in-voltage-alarm.jpeg) 25 | 26 | ## Send a tweet when leaving or arriving at the slip 27 | ![Alt text](https://raw.githubusercontent.com/SignalK/node-red-embedded/master/screens/tweet-when-leaving.jpeg) 28 | 29 | ## GPIO and put handler 30 | Sends the state of a digital gpio pin into Signal K and allows at PUT request to the server to set the state of a pin. 31 | ![Alt text](https://raw.githubusercontent.com/SignalK/node-red-embedded/master/screens/gpio.jpeg) 32 | 33 | # Available Nodes 34 | 35 | ## signalk-on-delta 36 | 37 | Input that sends messages for every delta the server receives. Optionally flatten the deltas (see signalk-flatten-delta below) 38 | 39 | ## signalk-subscribe 40 | 41 | Input that sends messages for every delta from the configured path. Optionally flatten the deltas (see signalk-flatten-delta below) 42 | 43 | ## signalk-flatten-delta 44 | 45 | Function that flatten deltas from signalk-on-delta. 46 | 47 | The output payload will be the value and the topic will be the path, The payload will also include context, $source, and source. 48 | 49 | ``` 50 | { 51 | "topic": "navigation.speedOverGround", 52 | "payload": 2.45, 53 | "source": {"label":"actisense","type":"NMEA2000","pgn":129026,"src":"3"}, 54 | "context": "vessels.self", 55 | "$source": "actisense.3" 56 | } 57 | ``` 58 | 59 | ## signalk-input-handler 60 | 61 | Input that allows a flow to modify or filter deltas before they get to the server. Once the data is modified, it should be connected to the signalk-input-handler-next node. 62 | 63 | ## signalk-input-handler-next 64 | 65 | Output that should be used to send a delta on after processing using the signalk-input-handler node. 66 | 67 | ## signalk-geofence-switch 68 | 69 | Function that checks if a vessel is inside of the specified geofence 70 | 71 | ## signalk-delay 72 | 73 | Function that will delay sending the input until the value has been the same for the given timout 74 | 75 | For example, you could use this to send a low starter battery voltage alarm only if it stays low for 5 seconds. 76 | 77 | ## signalk-send-pathvalue 78 | 79 | Output that sends a delta through the server. Input should be path and value 80 | 81 | ``` 82 | { 83 | "path":"navigation.speedOverGround", 84 | "value":20.45 85 | } 86 | ``` 87 | 88 | ## signalk-notification 89 | 90 | Input that sends a message when a notification is received. Configuration allows messages for a specific notification or any notification. The notification state can also be specified. Payload will be the notification path and value. 91 | 92 | ``` 93 | { 94 | "path: "notifications.anchorAlarm", 95 | "value" : { 96 | "state" : "emergency", 97 | "method" : [ 98 | "visual", 99 | "sound" 100 | ], 101 | "timestamp" : "2018-06-15T15:01:55.007Z", 102 | "message" : "Anchor Alarm - Emergency" 103 | } 104 | } 105 | ``` 106 | 107 | ## signalk-geofence 108 | 109 | Input that checks if a vessel is inside of the specified geofence 110 | 111 | ## signalk-put-handler 112 | 113 | Input that sends messages when a PUT is sent to the server for the given path 114 | 115 | ## signalk-send-delta 116 | 117 | Output that sends a delta to the server. Input should be a fully formed SignalK delta 118 | 119 | ## signalk-send-put 120 | 121 | Output that send a SignalK put request via `app.putSelfPath`. Input should be the value to put. 122 | 123 | ## signalk-send-notification 124 | 125 | Output that sends a SignalK notification 126 | 127 | If the input payload is an object, then it will use the keys path, state, method, and message. Example below. Otherwise it will use the configured values. 128 | 129 | To specify all the info, send: 130 | ``` 131 | {payload: { 132 | "path":"notifications.testNotification", 133 | "state":"alarm", 134 | "method":["visual","sound"], 135 | "message":"this is a notification message" 136 | }} 137 | ``` 138 | 139 | Or to specify some of the info, send: 140 | ``` 141 | {payload: { 142 | "state":"normal", 143 | }} 144 | ``` 145 | 146 | ## signalk-send-nmea2000 147 | 148 | Output that sends out NMEA 2000 messages. Input payload can be canboat json format or a raw Actisense formatted string 149 | 150 | ## signalk-send-nmea0183 151 | 152 | Output that sends out NMEA 0183 messages. Input payload can be an 0183 formatted string. 153 | -------------------------------------------------------------------------------- /examples/ac-in-voltage-alarm.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path" : "electrical.inverters.261.acin.voltage", 4 | "x" : 410, 5 | "name" : "clear AC in alarm", 6 | "id" : "e515e21.ad267a", 7 | "y" : 200, 8 | "z" : "9d05e907.a5601", 9 | "type" : "signalk-send-notification", 10 | "wires" : [], 11 | "state" : "normal", 12 | "visual" : true, 13 | "message" : "Not at Dock 1", 14 | "sound" : true 15 | }, 16 | { 17 | "id" : "8407584d.56fce8", 18 | "y" : 120, 19 | "x" : 90, 20 | "path" : "electrical.inverters.261.acin.voltage", 21 | "name" : "AC in voltage", 22 | "context" : "vessels.self", 23 | "flatten" : true, 24 | "period" : "30000", 25 | "z" : "9d05e907.a5601", 26 | "type" : "signalk-subscribe", 27 | "wires" : [ 28 | [ 29 | "fb88306c.be22f8" 30 | ] 31 | ] 32 | }, 33 | { 34 | "lon" : "-76.4872533333333", 35 | "type" : "signalk-geofence-switch", 36 | "lat" : "39.063125", 37 | "z" : "9d05e907.a5601", 38 | "wires" : [ 39 | [ 40 | "46da94f5.8e7024" 41 | ], 42 | [ 43 | "e515e21.ad267a" 44 | ] 45 | ], 46 | "context" : "vessels.self", 47 | "x" : 260, 48 | "name" : "at slip?", 49 | "distance" : "15.24", 50 | "myposition" : false, 51 | "y" : 120, 52 | "id" : "fb88306c.be22f8" 53 | }, 54 | { 55 | "delay" : "600000", 56 | "type" : "signalk-delay", 57 | "z" : "9d05e907.a5601", 58 | "wires" : [ 59 | [ 60 | "b451f168.d20ff" 61 | ] 62 | ], 63 | "y" : 280, 64 | "id" : "aeb535ba.3236d8", 65 | "x" : 580, 66 | "name" : "10 minute delay" 67 | }, 68 | { 69 | "property" : "payload", 70 | "outputLabels" : [ 71 | "off", 72 | "on" 73 | ], 74 | "wires" : [ 75 | [ 76 | "3d33e1e8.848616" 77 | ], 78 | [ 79 | "cc84650b.92a908" 80 | ] 81 | ], 82 | "checkall" : "false", 83 | "type" : "switch", 84 | "outputs" : 2, 85 | "z" : "9d05e907.a5601", 86 | "propertyType" : "msg", 87 | "y" : 120, 88 | "id" : "46da94f5.8e7024", 89 | "rules" : [ 90 | { 91 | "t" : "lt", 92 | "v" : "100", 93 | "vt" : "str" 94 | }, 95 | { 96 | "vt" : "str", 97 | "t" : "gte", 98 | "v" : "100" 99 | } 100 | ], 101 | "name" : "check voltage", 102 | "x" : 460, 103 | "repair" : false 104 | }, 105 | { 106 | "reg" : false, 107 | "wires" : [ 108 | [ 109 | "aeb535ba.3236d8" 110 | ] 111 | ], 112 | "z" : "9d05e907.a5601", 113 | "type" : "change", 114 | "property" : "", 115 | "to" : "", 116 | "name" : "alarm", 117 | "rules" : [ 118 | { 119 | "pt" : "msg", 120 | "tot" : "json", 121 | "t" : "set", 122 | "to" : "{\"state\":\"alarm\",\"message\":\"The power at dock 1 is OUT!\"}", 123 | "p" : "payload" 124 | } 125 | ], 126 | "x" : 630, 127 | "id" : "3d33e1e8.848616", 128 | "y" : 80, 129 | "action" : "", 130 | "from" : "" 131 | }, 132 | { 133 | "from" : "", 134 | "id" : "cc84650b.92a908", 135 | "y" : 160, 136 | "action" : "", 137 | "x" : 630, 138 | "name" : "normal", 139 | "rules" : [ 140 | { 141 | "t" : "set", 142 | "to" : "{\"state\":\"normal\",\"message\":\"The power is on at Dock 1\"}", 143 | "p" : "payload", 144 | "pt" : "msg", 145 | "tot" : "json" 146 | } 147 | ], 148 | "to" : "", 149 | "property" : "", 150 | "z" : "9d05e907.a5601", 151 | "type" : "change", 152 | "reg" : false, 153 | "wires" : [ 154 | [ 155 | "aeb535ba.3236d8" 156 | ] 157 | ] 158 | }, 159 | { 160 | "sound" : true, 161 | "message" : "", 162 | "visual" : true, 163 | "state" : "normal", 164 | "wires" : [], 165 | "z" : "9d05e907.a5601", 166 | "type" : "signalk-send-notification", 167 | "id" : "b451f168.d20ff", 168 | "y" : 280, 169 | "name" : "AC in alarm", 170 | "x" : 750, 171 | "path" : "electrical.inverters.261.acin.voltage" 172 | } 173 | ] 174 | -------------------------------------------------------------------------------- /examples/adjust-depth.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "disabled" : false, 4 | "label" : "Flow 1", 5 | "info" : "", 6 | "id" : "8bdea88.abe9658", 7 | "type" : "tab" 8 | }, 9 | { 10 | "action" : "", 11 | "from" : "", 12 | "x" : 420, 13 | "wires" : [ 14 | [ 15 | "e1a68487.819c28" 16 | ] 17 | ], 18 | "to" : "", 19 | "reg" : false, 20 | "rules" : [ 21 | { 22 | "pt" : "msg", 23 | "t" : "set", 24 | "tot" : "jsonata", 25 | "to" : "payload + 1.0", 26 | "p" : "payload" 27 | } 28 | ], 29 | "y" : 60, 30 | "type" : "change", 31 | "name" : "Add 1.0", 32 | "z" : "8bdea88.abe9658", 33 | "property" : "", 34 | "id" : "ca4ac3ec.4bba3" 35 | }, 36 | { 37 | "y" : 60, 38 | "id" : "e1a68487.819c28", 39 | "type" : "signalk-send-pathvalue", 40 | "name" : "", 41 | "x" : 630, 42 | "wires" : [], 43 | "z" : "8bdea88.abe9658" 44 | }, 45 | { 46 | "wires" : [ 47 | [ 48 | "ca4ac3ec.4bba3" 49 | ] 50 | ], 51 | "x" : 170, 52 | "y" : 60, 53 | "type" : "signalk-subscribe", 54 | "name" : "environment.depth.belowSurface", 55 | "period" : 1000, 56 | "z" : "8bdea88.abe9658", 57 | "path" : "environment.depth.belowSurface", 58 | "id" : "298fb1f.153a74e", 59 | "flatten" : true, 60 | "context" : "vessels.self" 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /examples/filter-out-n2k-src.json: -------------------------------------------------------------------------------- 1 | [{"id":"ddac5b0c.41def","type":"tab","label":"Flow 1","disabled":true,"info":""},{"id":"7791f14c.ab67e","type":"signalk-input-handler","z":"ddac5b0c.41def","name":"","context":"vessels.self","path":"navigation.position","source":"","x":160,"y":220,"wires":[["b56dae59.ad98b"]]},{"id":"b56dae59.ad98b","type":"switch","z":"ddac5b0c.41def","name":"ignore src==45","property":"source.src","propertyType":"msg","rules":[{"t":"neq","v":"45","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":400,"y":220,"wires":[["31c07a54.73b9de"]]},{"id":"31c07a54.73b9de","type":"signalk-input-handler-next","z":"ddac5b0c.41def","name":"","x":640,"y":220,"wires":[]}] 2 | -------------------------------------------------------------------------------- /examples/gpio.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name" : "", 4 | "read" : true, 5 | "y" : 360, 6 | "id" : "4da5f9f7.17b368", 7 | "z" : "cf96057a.038f88", 8 | "wires" : [ 9 | [ 10 | "dccea2ff.76b6a8" 11 | ] 12 | ], 13 | "debounce" : "25", 14 | "type" : "rpi-gpio in", 15 | "pin" : "11", 16 | "intype" : "tri", 17 | "x" : 90 18 | }, 19 | { 20 | "id" : "dccea2ff.76b6a8", 21 | "z" : "cf96057a.038f88", 22 | "y" : 360, 23 | "action" : "", 24 | "name" : "", 25 | "x" : 300, 26 | "property" : "", 27 | "to" : "", 28 | "type" : "change", 29 | "rules" : [ 30 | { 31 | "p" : "topic", 32 | "pt" : "msg", 33 | "to" : "electrical.switches.pin-7.state", 34 | "t" : "set", 35 | "tot" : "str" 36 | } 37 | ], 38 | "from" : "", 39 | "wires" : [ 40 | [ 41 | "9d2990b.d36fc7" 42 | ] 43 | ], 44 | "reg" : false 45 | }, 46 | { 47 | "y" : 360, 48 | "z" : "cf96057a.038f88", 49 | "id" : "9d2990b.d36fc7", 50 | "name" : "", 51 | "type" : "signalk-send-pathvalue", 52 | "x" : 530, 53 | "wires" : [], 54 | "source" : "" 55 | }, 56 | { 57 | "name" : "", 58 | "id" : "d65248aa.5b2bf", 59 | "z" : "cf96057a.038f88", 60 | "y" : 440, 61 | "wires" : [ 62 | [ 63 | "8d42add0.344ac" 64 | ] 65 | ], 66 | "x" : 130, 67 | "path" : "electrical.switches.pin-11.state", 68 | "type" : "signalk-put-handler" 69 | }, 70 | { 71 | "name" : "", 72 | "y" : 440, 73 | "z" : "cf96057a.038f88", 74 | "id" : "8d42add0.344ac", 75 | "out" : "out", 76 | "wires" : [], 77 | "type" : "rpi-gpio out", 78 | "pin" : "11", 79 | "set" : "", 80 | "x" : 380, 81 | "freq" : "", 82 | "level" : "0" 83 | } 84 | ] 85 | -------------------------------------------------------------------------------- /examples/high-wind-speed-alarm.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "label" : "Flow 2", 4 | "type" : "tab", 5 | "info" : "", 6 | "id" : "38336563.2cc8e2", 7 | "disabled" : false 8 | }, 9 | { 10 | "rules" : [ 11 | { 12 | "t" : "gt", 13 | "vt" : "str", 14 | "v" : "5.14" 15 | }, 16 | { 17 | "v" : "5.14", 18 | "vt" : "str", 19 | "t" : "lte" 20 | } 21 | ], 22 | "id" : "2d45410c.6ca7ae", 23 | "type" : "switch", 24 | "z" : "38336563.2cc8e2", 25 | "property" : "payload", 26 | "propertyType" : "msg", 27 | "y" : 100, 28 | "repair" : false, 29 | "x" : 430, 30 | "checkall" : "true", 31 | "outputs" : 2, 32 | "wires" : [ 33 | [ 34 | "a5b5aa6.69c9258" 35 | ], 36 | [ 37 | "14796038.fb8e78" 38 | ] 39 | ], 40 | "name" : "if over 5.14" 41 | }, 42 | { 43 | "y" : 60, 44 | "visual" : true, 45 | "wires" : [], 46 | "message" : "The wind speed is over 10 knots", 47 | "name" : "send alarm", 48 | "path" : "highWindSpeed", 49 | "x" : 630, 50 | "state" : "alarm", 51 | "z" : "38336563.2cc8e2", 52 | "type" : "signalk-send-notification", 53 | "id" : "a5b5aa6.69c9258", 54 | "sound" : true 55 | }, 56 | { 57 | "sound" : true, 58 | "type" : "signalk-send-notification", 59 | "z" : "38336563.2cc8e2", 60 | "id" : "14796038.fb8e78", 61 | "state" : "normal", 62 | "name" : "clear alarm", 63 | "wires" : [], 64 | "message" : "The wind speed is normal", 65 | "path" : "highWindSpeed", 66 | "x" : 630, 67 | "visual" : true, 68 | "y" : 160 69 | }, 70 | { 71 | "type" : "signalk-subscribe", 72 | "z" : "38336563.2cc8e2", 73 | "context" : "vessels.self", 74 | "id" : "ef25bd1f.baa4b", 75 | "flatten" : true, 76 | "name" : "environment.wind.speedApparent", 77 | "wires" : [ 78 | [ 79 | "2d45410c.6ca7ae" 80 | ] 81 | ], 82 | "path" : "environment.wind.speedApparent", 83 | "x" : 180, 84 | "y" : 100, 85 | "period" : "5000" 86 | } 87 | ] 88 | -------------------------------------------------------------------------------- /examples/preffered-n2k-src.json: -------------------------------------------------------------------------------- 1 | [{"id":"ddac5b0c.41def","type":"tab","label":"Flow 1","disabled":true,"info":""},{"id":"7791f14c.ab67e","type":"signalk-input-handler","z":"ddac5b0c.41def","name":"navigation.position","context":"vessels.self","path":"navigation.position","source":"","x":150,"y":220,"wires":[["4549d768.e6aa6"]]},{"id":"31c07a54.73b9de","type":"signalk-input-handler-next","z":"ddac5b0c.41def","name":"","x":640,"y":220,"wires":[]},{"id":"4549d768.e6aa6","type":"function","z":"ddac5b0c.41def","name":"prefer src 43","func":"\nconst timeout = 60\nconst prefered = '43'\nlet lastSeen = context.get('lastSeen')\n\nif ( msg.source.src === prefered )\n{\n node.send(msg)\n context.set('lastSeen', Date.now())\n} else if ( !lastSeen ) {\n node.send(msg)\n} else if ( Date.now() - lastSeen > (timeout *60)) {\n node.send(msg)\n}\n\n","outputs":1,"noerr":0,"x":390,"y":220,"wires":[["31c07a54.73b9de"]]}] 2 | -------------------------------------------------------------------------------- /examples/put-handler.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "219b9c95.4e0d5c", 4 | "type": "inject", 5 | "z": "1a65e795a2e4022a", 6 | "name": "", 7 | "props": [ 8 | { 9 | "p": "payload" 10 | }, 11 | { 12 | "p": "topic", 13 | "vt": "str" 14 | } 15 | ], 16 | "repeat": "10", 17 | "crontab": "", 18 | "once": false, 19 | "onceDelay": 0.1, 20 | "topic": "red.autoLights.state", 21 | "payload": "autoLights", 22 | "payloadType": "global", 23 | "x": 210, 24 | "y": 680, 25 | "wires": [ 26 | [ 27 | "d863107a.7222a" 28 | ] 29 | ] 30 | }, 31 | { 32 | "id": "d863107a.7222a", 33 | "type": "signalk-send-pathvalue", 34 | "z": "1a65e795a2e4022a", 35 | "name": "send autoLights", 36 | "path": "", 37 | "source": "", 38 | "meta": "{\"units\": \"bool\"}", 39 | "x": 660, 40 | "y": 680, 41 | "wires": [] 42 | }, 43 | { 44 | "id": "c1701506.ea4a48", 45 | "type": "inject", 46 | "z": "1a65e795a2e4022a", 47 | "name": "", 48 | "props": [ 49 | { 50 | "p": "payload" 51 | }, 52 | { 53 | "p": "topic", 54 | "vt": "str" 55 | } 56 | ], 57 | "repeat": "", 58 | "crontab": "", 59 | "once": true, 60 | "onceDelay": 0.1, 61 | "topic": "", 62 | "payload": "true", 63 | "payloadType": "bool", 64 | "x": 170, 65 | "y": 720, 66 | "wires": [ 67 | [ 68 | "abd5ad13.d9ec6" 69 | ] 70 | ] 71 | }, 72 | { 73 | "id": "abd5ad13.d9ec6", 74 | "type": "change", 75 | "z": "1a65e795a2e4022a", 76 | "name": "", 77 | "rules": [ 78 | { 79 | "t": "set", 80 | "p": "autoLights", 81 | "pt": "global", 82 | "to": "payload", 83 | "tot": "msg" 84 | }, 85 | { 86 | "t": "set", 87 | "p": "topic", 88 | "pt": "msg", 89 | "to": "red.autoLights.state", 90 | "tot": "str" 91 | } 92 | ], 93 | "action": "", 94 | "property": "", 95 | "from": "", 96 | "to": "", 97 | "reg": false, 98 | "x": 380, 99 | "y": 740, 100 | "wires": [ 101 | [ 102 | "d863107a.7222a" 103 | ] 104 | ] 105 | }, 106 | { 107 | "id": "fdaeb90a.e8809", 108 | "type": "signalk-put-handler", 109 | "z": "1a65e795a2e4022a", 110 | "name": "", 111 | "path": "red.autoLights.state", 112 | "x": 190, 113 | "y": 780, 114 | "wires": [ 115 | [ 116 | "abd5ad13.d9ec6" 117 | ] 118 | ] 119 | } 120 | ] 121 | -------------------------------------------------------------------------------- /examples/starter-voltage-alarm.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "x" : 90, 4 | "y" : 440, 5 | "id" : "e61bd5ff.ed0d18", 6 | "type" : "signalk-subscribe", 7 | "wires" : [ 8 | [ 9 | "39178c77.3c94d4" 10 | ] 11 | ], 12 | "name" : "starter voltage", 13 | "period" : "5000", 14 | "z" : "9d05e907.a5601", 15 | "path" : "electrical.batteries.260-second.voltage", 16 | "flatten" : true, 17 | "context" : "vessels.self" 18 | }, 19 | { 20 | "repair" : false, 21 | "x" : 280, 22 | "checkall" : "false", 23 | "id" : "39178c77.3c94d4", 24 | "rules" : [ 25 | { 26 | "t" : "lt", 27 | "vt" : "str", 28 | "v" : "12" 29 | }, 30 | { 31 | "v" : "12", 32 | "vt" : "str", 33 | "t" : "gte" 34 | } 35 | ], 36 | "y" : 440, 37 | "type" : "switch", 38 | "outputLabels" : [ 39 | "low", 40 | "normal" 41 | ], 42 | "wires" : [ 43 | [ 44 | "a567da8f.c89878" 45 | ], 46 | [ 47 | "78395455.529b6c" 48 | ] 49 | ], 50 | "propertyType" : "msg", 51 | "outputs" : 2, 52 | "name" : "check voltage", 53 | "z" : "9d05e907.a5601", 54 | "property" : "payload" 55 | }, 56 | { 57 | "name" : "alarm", 58 | "z" : "9d05e907.a5601", 59 | "action" : "", 60 | "from" : "", 61 | "property" : "", 62 | "reg" : false, 63 | "to" : "", 64 | "x" : 450, 65 | "y" : 400, 66 | "rules" : [ 67 | { 68 | "tot" : "json", 69 | "t" : "set", 70 | "p" : "payload", 71 | "pt" : "msg", 72 | "to" : "{\"state\":\"alarm\",\"message\":\"The starter battery voltage is low\"}" 73 | } 74 | ], 75 | "id" : "a567da8f.c89878", 76 | "type" : "change", 77 | "wires" : [ 78 | [ 79 | "f253f084.ade38" 80 | ] 81 | ] 82 | }, 83 | { 84 | "to" : "", 85 | "x" : 450, 86 | "y" : 480, 87 | "rules" : [ 88 | { 89 | "t" : "set", 90 | "pt" : "msg", 91 | "to" : "{\"state\":\"normal\",\"message\":\"The starter battery voltage is normal\"}", 92 | "p" : "payload", 93 | "tot" : "json" 94 | } 95 | ], 96 | "id" : "78395455.529b6c", 97 | "type" : "change", 98 | "wires" : [ 99 | [ 100 | "f253f084.ade38" 101 | ] 102 | ], 103 | "name" : "normal", 104 | "z" : "9d05e907.a5601", 105 | "action" : "", 106 | "from" : "", 107 | "property" : "", 108 | "reg" : false 109 | }, 110 | { 111 | "x" : 600, 112 | "id" : "f253f084.ade38", 113 | "y" : 440, 114 | "type" : "signalk-delay", 115 | "delay" : "60000", 116 | "wires" : [ 117 | [ 118 | "7e8e3f4d.763338" 119 | ] 120 | ], 121 | "name" : "60s delay", 122 | "z" : "9d05e907.a5601" 123 | }, 124 | { 125 | "name" : "voltage alarm", 126 | "z" : "9d05e907.a5601", 127 | "sound" : true, 128 | "path" : "electrical.batteries.260-second.voltage", 129 | "state" : "alarm", 130 | "x" : 760, 131 | "y" : 440, 132 | "id" : "7e8e3f4d.763338", 133 | "visual" : true, 134 | "type" : "signalk-send-notification", 135 | "message" : "", 136 | "wires" : [] 137 | } 138 | ] 139 | -------------------------------------------------------------------------------- /examples/switch-automation.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "disabled" : false, 4 | "label" : "Switches", 5 | "info" : "", 6 | "id" : "cf96057a.038f88", 7 | "type" : "tab" 8 | }, 9 | { 10 | "once" : false, 11 | "payloadType" : "date", 12 | "id" : "4f82f0b0.ea4f5", 13 | "x" : 110, 14 | "onceDelay" : 0.1, 15 | "name" : "trigger poll", 16 | "wires" : [ 17 | [ 18 | "235e9b3d.b4f944", 19 | "d991232d.ef7938" 20 | ] 21 | ], 22 | "payload" : "", 23 | "crontab" : "", 24 | "type" : "inject", 25 | "z" : "cf96057a.038f88", 26 | "topic" : "", 27 | "y" : 120, 28 | "repeat" : "5" 29 | }, 30 | { 31 | "outputs" : 1, 32 | "z" : "cf96057a.038f88", 33 | "func" : "\n\nlet _ = global.get('lodash')\nlet app = global.get('app')\n\nlet sog = app.getSelfPath('navigation.speedOverGround.value')\nlet rpm = app.getSelfPath('propulsion.port.revolutions.value')\nlet sun = app.getSelfPath('environment.sun.value')\nlet acVoltage = app.getSelfPath('electrical.inverters.261.acin.voltage.value')\nlet state = app.getSelfPath('electrical.switches.venus-1.state.value')\n\nlet res = false\nif ( !_.isUndefined(acVoltage) && !_.isUndefined(sog) && !_.isUndefined(sun) ) {\n res = (_.isUndefined(rpm) || rpm === 0) && sog < 0.5 && sun !== 'day' && acVoltage === 0\n //res = 1\n}\n\nif ( res === false ) {\n node.status({fill:\"red\",shape:\"ring\",text:\"off\"});\n} else {\n node.status({fill:\"green\",shape:\"dot\",text:\"on\"});\n}\n\nif ( state != res ) {\n return {payload: res ? 1 : 0 }\n}", 34 | "y" : 120, 35 | "wires" : [ 36 | [ 37 | "8cd816d5.e67028" 38 | ] 39 | ], 40 | "noerr" : 0, 41 | "type" : "function", 42 | "name" : "check anchor light", 43 | "x" : 490, 44 | "id" : "f3cc3b94.c5e49" 45 | }, 46 | { 47 | "id" : "8cd816d5.e67028", 48 | "name" : "anchor light", 49 | "x" : 690, 50 | "wires" : [], 51 | "path" : "electrical.switches.venus-1.state", 52 | "type" : "signalk-send-put", 53 | "z" : "cf96057a.038f88", 54 | "y" : 120 55 | }, 56 | { 57 | "y" : 180, 58 | "func" : "\nlet _ = global.get('lodash')\nlet app = global.get('app')\n\nlet mode = app.getSelfPath('electrical.chargers.261.chargingMode.value')\nlet rpm = app.getSelfPath('propulsion.port.revolutions.value')\nlet state = app.getSelfPath('electrical.switches.venus-0.state.value')\n\nlet res = false\nif ( !_.isUndefined(mode) && !_.isUndefined(rpm) ) {\n res = (mode === 'off' || mode === 'inverting') && rpm > 0\n}\n\nif ( res === false ) {\n node.status({fill:\"red\",shape:\"ring\",text:\"off\"});\n} else {\n node.status({fill:\"green\",shape:\"dot\",text:\"on\"});\n}\n\nif ( state != res ) {\n return {payload: res ? 1 : 0 }\n}", 59 | "outputs" : 1, 60 | "z" : "cf96057a.038f88", 61 | "type" : "function", 62 | "noerr" : 0, 63 | "wires" : [ 64 | [ 65 | "222ecb4.3b9b634" 66 | ] 67 | ], 68 | "x" : 460, 69 | "name" : "check acr", 70 | "id" : "235e9b3d.b4f944" 71 | }, 72 | { 73 | "z" : "cf96057a.038f88", 74 | "y" : 180, 75 | "path" : "electrical.switches.venus-0.state", 76 | "wires" : [], 77 | "type" : "signalk-send-put", 78 | "x" : 670, 79 | "name" : "acr", 80 | "id" : "222ecb4.3b9b634" 81 | }, 82 | { 83 | "wires" : [ 84 | [ 85 | "c7feddf8.8abed" 86 | ] 87 | ], 88 | "type" : "change", 89 | "from" : "", 90 | "to" : "", 91 | "z" : "cf96057a.038f88", 92 | "y" : 40, 93 | "reg" : false, 94 | "rules" : [ 95 | { 96 | "p" : "payload", 97 | "to" : "0", 98 | "pt" : "msg", 99 | "t" : "set", 100 | "tot" : "num" 101 | } 102 | ], 103 | "id" : "b1355a1c.6b9e", 104 | "action" : "", 105 | "name" : "turn off", 106 | "x" : 460, 107 | "property" : "" 108 | }, 109 | { 110 | "id" : "d991232d.ef7938", 111 | "myposition" : false, 112 | "x" : 300, 113 | "name" : "at slip?", 114 | "lat" : "39.063125", 115 | "lon" : "-76.4872533333333", 116 | "distance" : "15.24", 117 | "type" : "signalk-geofence-switch", 118 | "context" : "vessels.self", 119 | "wires" : [ 120 | [ 121 | "b1355a1c.6b9e" 122 | ], 123 | [ 124 | "f3cc3b94.c5e49" 125 | ] 126 | ], 127 | "y" : 80, 128 | "z" : "cf96057a.038f88" 129 | }, 130 | { 131 | "y" : 40, 132 | "timeoutUnits" : "seconds", 133 | "rateUnits" : "second", 134 | "nbRateUnits" : "60", 135 | "id" : "c7feddf8.8abed", 136 | "randomUnits" : "seconds", 137 | "randomLast" : "5", 138 | "randomFirst" : "1", 139 | "z" : "cf96057a.038f88", 140 | "wires" : [ 141 | [ 142 | "8cd816d5.e67028" 143 | ] 144 | ], 145 | "pauseType" : "rate", 146 | "type" : "delay", 147 | "timeout" : "5", 148 | "drop" : true, 149 | "name" : "", 150 | "x" : 640, 151 | "rate" : "1" 152 | } 153 | ] 154 | -------------------------------------------------------------------------------- /examples/tweet-when-leaving.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "disabled" : false, 4 | "label" : "Flow 1", 5 | "info" : "", 6 | "id" : "8bdea88.abe9658", 7 | "type" : "tab" 8 | }, 9 | { 10 | "wires" : [ 11 | [ 12 | "e1a68487.819c28" 13 | ] 14 | ], 15 | "type" : "change", 16 | "property" : "", 17 | "y" : 60, 18 | "to" : "", 19 | "from" : "", 20 | "name" : "Add 1.0", 21 | "rules" : [ 22 | { 23 | "to" : "payload + 1.0", 24 | "t" : "set", 25 | "tot" : "jsonata", 26 | "p" : "payload", 27 | "pt" : "msg" 28 | } 29 | ], 30 | "action" : "", 31 | "id" : "ca4ac3ec.4bba3", 32 | "z" : "8bdea88.abe9658", 33 | "x" : 420, 34 | "reg" : false 35 | }, 36 | { 37 | "source" : "", 38 | "type" : "signalk-send-pathvalue", 39 | "wires" : [], 40 | "y" : 60, 41 | "name" : "", 42 | "x" : 630, 43 | "z" : "8bdea88.abe9658", 44 | "id" : "e1a68487.819c28" 45 | }, 46 | { 47 | "id" : "298fb1f.153a74e", 48 | "z" : "8bdea88.abe9658", 49 | "period" : 1000, 50 | "x" : 170, 51 | "name" : "environment.depth.belowSurface", 52 | "context" : "vessels.self", 53 | "y" : 60, 54 | "path" : "environment.depth.belowSurface", 55 | "type" : "signalk-subscribe", 56 | "wires" : [ 57 | [ 58 | "45de362b.566a" 59 | ] 60 | ], 61 | "flatten" : true 62 | }, 63 | { 64 | "context" : "vessels.self", 65 | "name" : "", 66 | "y" : 460, 67 | "myposition" : false, 68 | "lon" : "-77.4872533333333", 69 | "id" : "99b572f6.2f8c2", 70 | "z" : "8bdea88.abe9658", 71 | "period" : "1000", 72 | "x" : 100, 73 | "distance" : "15", 74 | "lat" : "39.063125", 75 | "wires" : [ 76 | [], 77 | [], 78 | [ 79 | "aef0ca4c.06048" 80 | ] 81 | ], 82 | "type" : "signalk-geofence" 83 | }, 84 | { 85 | "tosidebar" : true, 86 | "console" : false, 87 | "wires" : [], 88 | "type" : "debug", 89 | "complete" : "false", 90 | "name" : "", 91 | "y" : 280, 92 | "id" : "c131f134.027278", 93 | "tostatus" : false, 94 | "z" : "8bdea88.abe9658", 95 | "active" : true, 96 | "x" : 710 97 | }, 98 | { 99 | "distance" : 10, 100 | "lat" : "39.063125", 101 | "type" : "signalk-geofence-switch", 102 | "wires" : [ 103 | [ 104 | "9954903e.c3801" 105 | ], 106 | [ 107 | "ca4ac3ec.4bba3" 108 | ] 109 | ], 110 | "y" : 220, 111 | "name" : "", 112 | "context" : "vessels.self", 113 | "myposition" : false, 114 | "id" : "45de362b.566a", 115 | "z" : "8bdea88.abe9658", 116 | "lon" : "-76.4872533333333", 117 | "x" : 260, 118 | "period" : 10000 119 | }, 120 | { 121 | "name" : "", 122 | "y" : 240, 123 | "tostatus" : false, 124 | "z" : "8bdea88.abe9658", 125 | "id" : "9954903e.c3801", 126 | "active" : true, 127 | "x" : 560, 128 | "tosidebar" : true, 129 | "console" : false, 130 | "complete" : "false", 131 | "wires" : [], 132 | "type" : "debug" 133 | }, 134 | { 135 | "func" : "rbei", 136 | "type" : "rbe", 137 | "wires" : [ 138 | [ 139 | "343ef262.95945e" 140 | ] 141 | ], 142 | "property" : "payload", 143 | "gap" : "", 144 | "start" : "", 145 | "inout" : "out", 146 | "z" : "8bdea88.abe9658", 147 | "id" : "aef0ca4c.06048", 148 | "x" : 320, 149 | "y" : 460, 150 | "name" : "block unless changed" 151 | }, 152 | { 153 | "repair" : false, 154 | "checkall" : "true", 155 | "name" : "", 156 | "rules" : [ 157 | { 158 | "vt" : "str", 159 | "v" : "outside", 160 | "t" : "eq" 161 | }, 162 | { 163 | "vt" : "str", 164 | "v" : "inside", 165 | "t" : "eq" 166 | } 167 | ], 168 | "y" : 560, 169 | "x" : 370, 170 | "outputs" : 2, 171 | "z" : "8bdea88.abe9658", 172 | "id" : "343ef262.95945e", 173 | "propertyType" : "msg", 174 | "outputLabels" : [ 175 | "outside", 176 | "inside" 177 | ], 178 | "type" : "switch", 179 | "wires" : [ 180 | [ 181 | "ec735054.a2362" 182 | ], 183 | [ 184 | "916b417.377c4c" 185 | ] 186 | ], 187 | "property" : "payload" 188 | }, 189 | { 190 | "twitter" : "", 191 | "wires" : [], 192 | "type" : "twitter out", 193 | "name" : "Tweet", 194 | "y" : 560, 195 | "x" : 770, 196 | "id" : "c97f4062.dd2498", 197 | "z" : "8bdea88.abe9658" 198 | }, 199 | { 200 | "property" : "", 201 | "wires" : [ 202 | [ 203 | "c97f4062.dd2498" 204 | ] 205 | ], 206 | "type" : "change", 207 | "x" : 570, 208 | "reg" : false, 209 | "z" : "8bdea88.abe9658", 210 | "action" : "", 211 | "id" : "ec735054.a2362", 212 | "from" : "", 213 | "name" : "I'm heading out to sea!!", 214 | "rules" : [ 215 | { 216 | "to" : "I'm heading out to sea!!", 217 | "pt" : "msg", 218 | "p" : "payload", 219 | "t" : "set", 220 | "tot" : "str" 221 | } 222 | ], 223 | "to" : "", 224 | "y" : 520 225 | }, 226 | { 227 | "x" : 560, 228 | "reg" : false, 229 | "id" : "916b417.377c4c", 230 | "action" : "", 231 | "z" : "8bdea88.abe9658", 232 | "from" : "", 233 | "rules" : [ 234 | { 235 | "to" : "I'm back at the slip :(", 236 | "pt" : "msg", 237 | "p" : "payload", 238 | "t" : "set", 239 | "tot" : "str" 240 | } 241 | ], 242 | "name" : "I'm back at the slip :(", 243 | "y" : 600, 244 | "to" : "", 245 | "property" : "", 246 | "wires" : [ 247 | [ 248 | "c97f4062.dd2498" 249 | ] 250 | ], 251 | "type" : "change" 252 | } 253 | ] 254 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@signalk/node-red-embedded", 3 | "version": "2.18.1", 4 | "description": "Node red nodes for use with the signalk-node-red plugin", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Scott Bender", 10 | "license": "Apache-2.0", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/SignalK/node-red-embedded" 14 | }, 15 | "keywords": [ 16 | "node-red" 17 | ], 18 | "node-red": { 19 | "nodes": { 20 | "signalk-subscribe": "signalk-subscribe.js", 21 | "signalk-notification-": "signalk-notification.js", 22 | "signalk-on-delta": "signalk-on-delta.js", 23 | "signalk-app-event": "signalk-app-event.js", 24 | "signalk-flatten-delta": "signalk-flatten-delta.js", 25 | "signalk-send-pathvalue": "signalk-send-pathvalue.js", 26 | "signalk-send-delta": "signalk-send-delta.js", 27 | "signalk-send-notification": "signalk-send-notification.js", 28 | "signalk-send-put": "signalk-send-put.js", 29 | "signalk-send-nmea2000": "signalk-send-nmea2000.js", 30 | "signalk-send-nmea0183": "signalk-send-nmea0183.js", 31 | "signalk-geofence": "signalk-geofence.js", 32 | "signalk-geofence-switch": "signalk-geofence-switch.js", 33 | "signalk-delay": "signalk-delay.js", 34 | "signalk-put-handler": "signalk-put-handler.js", 35 | "signalk-input-handler-next": "signalk-input-handler-next.js", 36 | "signalk-input-handler": "signalk-input-handler.js" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /screens/ac-in-voltage-alarm.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignalK/node-red-embedded/2419af58eef1b8aecce008e9fcf663315a3336fa/screens/ac-in-voltage-alarm.jpeg -------------------------------------------------------------------------------- /screens/adjust-depth.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignalK/node-red-embedded/2419af58eef1b8aecce008e9fcf663315a3336fa/screens/adjust-depth.jpeg -------------------------------------------------------------------------------- /screens/delta-input-handler.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignalK/node-red-embedded/2419af58eef1b8aecce008e9fcf663315a3336fa/screens/delta-input-handler.jpeg -------------------------------------------------------------------------------- /screens/gpio.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignalK/node-red-embedded/2419af58eef1b8aecce008e9fcf663315a3336fa/screens/gpio.jpeg -------------------------------------------------------------------------------- /screens/high-wind-speed-alarm.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignalK/node-red-embedded/2419af58eef1b8aecce008e9fcf663315a3336fa/screens/high-wind-speed-alarm.jpeg -------------------------------------------------------------------------------- /screens/starter-voltage-alarm.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignalK/node-red-embedded/2419af58eef1b8aecce008e9fcf663315a3336fa/screens/starter-voltage-alarm.jpeg -------------------------------------------------------------------------------- /screens/switch-automation.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignalK/node-red-embedded/2419af58eef1b8aecce008e9fcf663315a3336fa/screens/switch-automation.jpeg -------------------------------------------------------------------------------- /screens/tweet-when-leaving.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignalK/node-red-embedded/2419af58eef1b8aecce008e9fcf663315a3336fa/screens/tweet-when-leaving.jpeg -------------------------------------------------------------------------------- /signalk-app-event.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | 30 | 33 | -------------------------------------------------------------------------------- /signalk-app-event.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function SignalKOnEvent(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | 7 | var app = node.context().global.get('app') 8 | 9 | let showingStatus = false 10 | function showStatus() { 11 | if ( ! showingStatus ) { 12 | node.status({fill:"green",shape:"dot",text:"sending"}); 13 | showingStatus = true; 14 | setTimeout( () => { 15 | node.status({}); 16 | showingStatus = false 17 | }, 1000) 18 | } 19 | } 20 | 21 | function on_event(data) { 22 | showStatus() 23 | node.send({ payload: data }) 24 | } 25 | 26 | app.on(config.event, on_event) 27 | 28 | node.on('close', function() { 29 | signalk.removeListener(config.event, on_event) 30 | }) 31 | } 32 | RED.nodes.registerType("signalk-app-event", SignalKOnEvent); 33 | } 34 | -------------------------------------------------------------------------------- /signalk-delay.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | 30 | 37 | -------------------------------------------------------------------------------- /signalk-delay.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function signalK(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | 7 | node.on('input', msg => { 8 | const _ = node.context().global.get('lodash') 9 | 10 | let firstMessage = node.context().get('firstMessage') 11 | let lastValue = node.context().get('lastValue') 12 | 13 | if ( lastValue && !_.isEqual(msg.payload, lastValue)) 14 | { 15 | firstMessage = null 16 | } 17 | 18 | if ( !firstMessage ) { 19 | node.context().set('firstMessage', Date.now()) 20 | node.context().set('lastValue', msg.payload) 21 | } else { 22 | let diff = Date.now() - firstMessage 23 | if ( diff > config.delay ) { 24 | node.send(msg) 25 | node.status({fill:"green",shape:"dot",text:`sent`}); 26 | } else { 27 | node.status({fill:"green",shape:"dot",text:`${diff/1000}s`}); 28 | } 29 | } 30 | 31 | }) 32 | } 33 | RED.nodes.registerType("signalk-delay", signalK); 34 | } 35 | -------------------------------------------------------------------------------- /signalk-filter-delta.html: -------------------------------------------------------------------------------- 1 | 19 | 20 | 34 | 35 | 38 | -------------------------------------------------------------------------------- /signalk-filter-delta.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function signalKFilterDelta(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | 7 | node.on('input', msg => { 8 | let delta = msg.payload 9 | if ( delta.updates ) { 10 | delta.updates.forEach(update => { 11 | if ( !update.$source || !update.$source.startsWith('signalk-node-red') ) { 12 | if ( update.values ) { 13 | update.values.forEach(pathValue => { 14 | if ( pathValue.path == config.path ) { 15 | let copy = JSON.parse(JSON.stringify(pathValue)) 16 | copy.$source = update.$source 17 | copy.source = update.source 18 | node.send({ payload: copy}) 19 | } 20 | }) 21 | } 22 | } 23 | }) 24 | } 25 | }) 26 | } 27 | RED.nodes.registerType("signalk-filter-delta", signalKFilterDelta); 28 | } 29 | -------------------------------------------------------------------------------- /signalk-flatten-delta.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | 24 | 25 | 40 | -------------------------------------------------------------------------------- /signalk-flatten-delta.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function signalKFlattenDelta(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | 7 | node.on('input', msg => { 8 | let delta = msg.payload 9 | if ( delta.updates ) { 10 | delta.updates.forEach(update => { 11 | if ( update.values ) { 12 | update.values.forEach(pathValue => { 13 | node.send({ 14 | $source: update.$source, 15 | source: update.source, 16 | context: delta.context, 17 | payload: pathValue.value, 18 | topic: pathValue.path 19 | }) 20 | }) 21 | } 22 | }) 23 | } 24 | }) 25 | } 26 | RED.nodes.registerType("signalk-flatten-delta", signalKFlattenDelta); 27 | } 28 | -------------------------------------------------------------------------------- /signalk-geofence-switch.html: -------------------------------------------------------------------------------- 1 | 23 | 24 | 50 | 51 | 75 | -------------------------------------------------------------------------------- /signalk-geofence-switch.js: -------------------------------------------------------------------------------- 1 | module.exports = function(RED) { 2 | function signalk(config) { 3 | RED.nodes.createNode(this,config); 4 | var node = this; 5 | var unsubscribes = [] 6 | 7 | const geodist = node.context().global.get('geodist') 8 | const app = node.context().global.get('app') 9 | const context = node.context() 10 | 11 | node.on('input', (msg) => { 12 | 13 | if ( msg.topic === 'signalk-config' ) { 14 | context.latitude = msg.payload.latitude 15 | context.longitude = msg.payload.longitude 16 | context.distance = msg.payload.distance 17 | return 18 | } 19 | 20 | var pos; 21 | 22 | if ( config.context !== 'vessels.self' ) { 23 | pos = app.getPath(config.context + '.navigation.position.value') 24 | } else { 25 | pos = app.getSelfPath('navigation.position.value') 26 | } 27 | 28 | if ( !pos || !pos.latitude || !pos.longitude ) { 29 | node.status({fill:"red",shape:"dot",text:"no position"}); 30 | return 31 | } 32 | 33 | var fencePos = null; 34 | if ( config.myposition ) { 35 | var mypos = app.getSelfPath('navigation.position.value') 36 | if ( mypos && mypos.latitude && mypos.longitude ) { 37 | fencePos = { lat: mypos.latitude, lon: mypos.longitude } 38 | } 39 | } else { 40 | if ( msg.latitude ) { 41 | fencePos = { lat: msg.latitude, lon: msg.longitude } 42 | } else if ( context.latitude ) { 43 | fencePos = { lat: context.latitude, lon: context.longitude } 44 | } else { 45 | fencePos = { lat: config.lat, lon: config.lon } 46 | } 47 | if ( fencePos.lat === 0 && fencePos.lon === 0 ) { 48 | node.status({fill:"red",shape:"dot",text:"no lat/lon"}); 49 | return 50 | } 51 | } 52 | 53 | 54 | if ( fencePos ) { 55 | let curPos = {lat: pos.latitude, lon: pos.longitude} 56 | let dist = geodist(fencePos, 57 | curPos, 58 | { unit: 'meters'}) 59 | let distance 60 | if ( msg.distance ) { 61 | distance = msg.distance 62 | } else if ( context.distance ) { 63 | distance = context.distance 64 | } else { 65 | distance = config.distance 66 | } 67 | 68 | if ( dist > distance ) { 69 | node.status({fill:"green",shape:"dot",text:"outside fence"}); 70 | node.send([null, msg ]) 71 | } else { 72 | node.status({fill:"green",shape:"dot",text:"inside fence"}); 73 | node.send([msg, null]) 74 | } 75 | } 76 | }) 77 | } 78 | RED.nodes.registerType("signalk-geofence-switch", signalk); 79 | } 80 | -------------------------------------------------------------------------------- /signalk-geofence.html: -------------------------------------------------------------------------------- 1 | 26 | 27 | 65 | 66 | 90 | -------------------------------------------------------------------------------- /signalk-geofence.js: -------------------------------------------------------------------------------- 1 | module.exports = function(RED) { 2 | function signalk(config) { 3 | RED.nodes.createNode(this,config); 4 | var node = this; 5 | var unsubscribes = [] 6 | 7 | const geodist = node.context().global.get('geodist') 8 | const subscriptionmanager = node.context().global.get('subscriptionmanager') 9 | const app = node.context().global.get('app') 10 | const context = node.context() 11 | 12 | node.on('input', msg => { 13 | if ( msg.payload.latitude ) { 14 | context.latitude = msg.payload.latitude 15 | context.longitude = msg.payload.longitude 16 | } 17 | if ( msg.payload.distance ) { 18 | context.distance = msg.payload.distance 19 | } 20 | }) 21 | 22 | var command = { 23 | context: config.context, 24 | subscribe: [{ 25 | path: 'navigation.position', 26 | period: config.period 27 | }] 28 | } 29 | 30 | //wait a second because the node is not yet wired up 31 | setTimeout(() => { 32 | subscriptionmanager.subscribe(command, unsubscribes, error => { 33 | node.error('subscription error: ' + error) 34 | }, delta => { 35 | let pos = delta.updates[0].values[0] 36 | 37 | if ( !pos.value || !pos.value.latitude || !pos.value.longitude ) { 38 | node.status({fill:"red",shape:"dot",text:"no position"}); 39 | return 40 | } 41 | pos = pos.value 42 | 43 | var fencePos = null; 44 | if ( config.myposition ) { 45 | var mypos = app.getSelfPath('navigation.position.value') 46 | if ( mypos && mypos.latitude && mypos.longitude ) { 47 | fencePos = { lat: mypos.latitude, lon: mypos.longitude } 48 | } 49 | } else { 50 | if ( context.latitude ) { 51 | fencePos = { lat: context.latitude, lon: context.longitude } 52 | } else { 53 | fencePos = { lat: config.lat, lon: config.lon } 54 | } 55 | if ( fencePos.lat === 0 && fencePos.lon === 0 ) { 56 | return 57 | } 58 | } 59 | 60 | if ( fencePos ) { 61 | let dist = geodist(fencePos, 62 | {lat: pos.latitude, lon: pos.longitude}, 63 | { unit: 'meters'}) 64 | let status 65 | let payload 66 | const distance = context.distance ? context.distance : config.distance 67 | if ( dist > distance ) { 68 | status = {fill:"green",shape:"dot",text:"outside fence"} 69 | payload = [null, { payload: 'outside' }, { payload: 'outside' }] 70 | } else { 71 | status = {fill:"green",shape:"dot",text:"inside fence"} 72 | payload = [{ payload: 'inside' }, null, { payload: 'inside' }] 73 | } 74 | 75 | let last = node.context().get('lastValue') 76 | let current = payload[2].payload 77 | //console.log(`${last} ${current} ${config.mode}`) 78 | if ( !last && config.mode === 'sendChangesIgnore' ) { 79 | node.context().set('lastValue', current) 80 | return 81 | } else if ( !config.mode || config.mode === 'sendAll' || !last 82 | || last != current ) { 83 | node.context().set('lastValue', current) 84 | node.status(status); 85 | node.send(payload) 86 | } 87 | } 88 | }) 89 | }, 5000) 90 | 91 | node.on('close', function() { 92 | unsubscribes.forEach(function(func) { func() }) 93 | unsubscribes = [] 94 | }) 95 | } 96 | RED.nodes.registerType("signalk-geofence", signalk); 97 | } 98 | -------------------------------------------------------------------------------- /signalk-input-handler-next.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | 26 | 29 | -------------------------------------------------------------------------------- /signalk-input-handler-next.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function SignalK(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | 7 | var app = node.context().global.get('app') 8 | 9 | node.on('input', msg => { 10 | let next = node.context().flow.get('signalk-input-handler.next') 11 | if ( msg.next ) { 12 | next = msg.next 13 | } 14 | if ( msg.topic ) { 15 | let delta = { 16 | context: msg.context, 17 | updates: [ 18 | { 19 | source: msg.source, 20 | $source: msg.$source, 21 | timestamp: msg.timestamp, 22 | values: [ 23 | { 24 | value: msg.payload, 25 | path: msg.topic 26 | } 27 | ] 28 | } 29 | ] 30 | } 31 | /* 32 | if ( msg.source && msg.source.length > 0 ) { 33 | delta.updates[0].$source = msg.source 34 | } 35 | */ 36 | //node.error(JSON.stringify(delta)) 37 | //console.log(JSON.stringify(delta)) 38 | next(delta) 39 | } else { 40 | //next(msg.payload) 41 | } 42 | }) 43 | } 44 | RED.nodes.registerType("signalk-input-handler-next", SignalK); 45 | } 46 | 47 | -------------------------------------------------------------------------------- /signalk-input-handler.html: -------------------------------------------------------------------------------- 1 | 20 | 21 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /signalk-input-handler.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function SignalK(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | 7 | var plugin = node.context().global.get('plugin') 8 | 9 | let onClose = plugin.registerDeltaInputHandler(config.context, 10 | config.path, 11 | config.source, 12 | (pv, next) => { 13 | node.context().flow.set('signalk-input-handler.next', next) 14 | pv.next = next 15 | node.send(pv) 16 | }) 17 | 18 | node.on('close', function() { 19 | onClose() 20 | }) 21 | } 22 | RED.nodes.registerType("signalk-input-handler", SignalK); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /signalk-notification.html: -------------------------------------------------------------------------------- 1 | 19 | 20 | 41 | 42 | 60 | -------------------------------------------------------------------------------- /signalk-notification.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function SignalKNotification(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | var unsubscribes = [] 7 | 8 | let showingStatus = false 9 | function showStatus() { 10 | if ( ! showingStatus ) { 11 | node.status({fill:"green",shape:"dot",text:"sending"}); 12 | showingStatus = true; 13 | setTimeout( () => { 14 | node.status({}); 15 | showingStatus = false 16 | }, 1000) 17 | } 18 | } 19 | 20 | var subscriptionmanager = node.context().global.get('subscriptionmanager') 21 | 22 | var path = config.notification === 'any' || config.notification.length === 0 ? 'notifications.*' : 'notifications.' + config.notification 23 | 24 | var command = { 25 | context: "vessels.self", 26 | subscribe: [{ 27 | path: path, 28 | policy: 'instant' 29 | }] 30 | } 31 | 32 | subscriptionmanager.subscribe(command, unsubscribes, error => { 33 | node.error('subscription error: ' + error) 34 | }, delta => { 35 | let notification = delta.updates[0].values[0] 36 | 37 | if ( config.state === 'any' || (notification.value && notification.value.state == config.state) ) { 38 | showStatus() 39 | node.send({ payload: notification}) 40 | } 41 | }) 42 | 43 | node.on('close', function() { 44 | unsubscribes.forEach(function(func) { func() }) 45 | unsubscribes = [] 46 | }) 47 | } 48 | RED.nodes.registerType("signalk-notification", SignalKNotification); 49 | } 50 | -------------------------------------------------------------------------------- /signalk-on-delta.html: -------------------------------------------------------------------------------- 1 | 19 | 20 | 34 | 35 | 38 | -------------------------------------------------------------------------------- /signalk-on-delta.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function SignalKOnDelta(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | 7 | var signalk = node.context().global.get('signalk') 8 | var app = node.context().global.get('app') 9 | 10 | let showingStatus = false 11 | function showStatus() { 12 | if ( ! showingStatus ) { 13 | node.status({fill:"green",shape:"dot",text:"sending"}); 14 | showingStatus = true; 15 | setTimeout( () => { 16 | node.status({}); 17 | showingStatus = false 18 | }, 1000) 19 | } 20 | } 21 | 22 | function on_delta(delta) { 23 | if ( delta.updates ) { 24 | if ( delta.context === config.context || 25 | (config.context === 'vessels.self' && 26 | delta.context == app.selfContext) ) { 27 | if ( typeof config.flatten === 'undefined' || !config.flatten ) { 28 | var copy = JSON.parse(JSON.stringify(delta)) 29 | copy.updates = [] 30 | delta.updates.forEach(update => { 31 | if ( update.values && 32 | (!update.$source || !update.$source.startsWith('signalk-node-red') )) { 33 | copy.updates.push(update) 34 | } 35 | }) 36 | 37 | if ( copy.updates.length > 0 ) { 38 | showStatus() 39 | if ( copy.context == app.selfContext ) { 40 | copy.context = 'vessels.self' 41 | } 42 | node.send({ payload: copy }) 43 | } 44 | } else { 45 | delta.updates.forEach(update => { 46 | if ( update.values && 47 | (!update.$source || !update.$source.startsWith('signalk-node-red') )) { 48 | showStatus() 49 | update.values.forEach(pathValue => { 50 | node.send({ 51 | $source: update.$source, 52 | source: update.source, 53 | context: delta.context == app.selfContext ? 'vessels.self' : delta.context, 54 | payload: pathValue.value, 55 | topic: pathValue.path 56 | }) 57 | }) 58 | } 59 | }) 60 | } 61 | } 62 | } 63 | } 64 | 65 | signalk.on('delta', on_delta) 66 | 67 | node.on('close', function() { 68 | signalk.removeListener('delta', on_delta) 69 | }) 70 | } 71 | RED.nodes.registerType("signalk-on-delta", SignalKOnDelta); 72 | } 73 | -------------------------------------------------------------------------------- /signalk-put-handler.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | 30 | 33 | -------------------------------------------------------------------------------- /signalk-put-handler.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function SignalKOnDelta(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | 7 | var app = node.context().global.get('app') 8 | 9 | function handlePut(context, path, value, cb) { 10 | node.send({topic: path, payload: value}) 11 | return { state: 'SUCCESS' } 12 | } 13 | 14 | let deReg = app.registerActionHandler('vessels.self', config.path, 15 | handlePut) 16 | 17 | node.on('close', function() { 18 | //deReg() 19 | }) 20 | } 21 | RED.nodes.registerType("signalk-put-handler", SignalKOnDelta); 22 | } 23 | -------------------------------------------------------------------------------- /signalk-send-delta.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | 26 | 29 | -------------------------------------------------------------------------------- /signalk-send-delta.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function signalKSendDelta(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | 7 | let app = node.context().global.get('app') 8 | let source = config.name ? 'node-red-' + config.name : 'node-red' 9 | 10 | let showingStatus = false 11 | function showStatus() { 12 | if ( ! showingStatus ) { 13 | node.status({fill:"green",shape:"dot",text:"sent"}); 14 | showingStatus = true; 15 | setTimeout( () => { 16 | node.status({}); 17 | showingStatus = false 18 | }, 1000) 19 | } 20 | } 21 | 22 | node.on('input', msg => { 23 | app.handleMessage(source, msg.payload) 24 | showStatus() 25 | }) 26 | } 27 | RED.nodes.registerType("signalk-send-delta", signalKSendDelta); 28 | } 29 | -------------------------------------------------------------------------------- /signalk-send-nmea0183.html: -------------------------------------------------------------------------------- 1 | 19 | 20 | 30 | 31 | 34 | -------------------------------------------------------------------------------- /signalk-send-nmea0183.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function(RED) { 4 | function send(config) { 5 | RED.nodes.createNode(this,config); 6 | var node = this; 7 | 8 | let app = node.context().global.get('app') 9 | 10 | let showingStatus = false 11 | function showStatus() { 12 | if ( ! showingStatus ) { 13 | node.status({fill:"green",shape:"dot",text:"sending"}); 14 | showingStatus = true; 15 | setTimeout( () => { 16 | node.status({}); 17 | showingStatus = false 18 | }, 1000) 19 | } 20 | } 21 | 22 | node.on('input', msg => { 23 | showStatus() 24 | app.emit(config.nmea0183Event, msg.payload) 25 | }) 26 | } 27 | RED.nodes.registerType("signalk-send-nmea0183", send); 28 | } 29 | -------------------------------------------------------------------------------- /signalk-send-nmea2000.html: -------------------------------------------------------------------------------- 1 | 20 | 21 | 35 | 36 | 39 | -------------------------------------------------------------------------------- /signalk-send-nmea2000.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function(RED) { 4 | function send(config) { 5 | RED.nodes.createNode(this,config); 6 | var node = this; 7 | 8 | let app = node.context().global.get('app') 9 | let _ = node.context().global.get('lodash') 10 | 11 | let showingStatus = false 12 | function showStatus() { 13 | if ( ! showingStatus ) { 14 | node.status({fill:"green",shape:"dot",text:"sending"}); 15 | showingStatus = true; 16 | setTimeout( () => { 17 | node.status({}); 18 | showingStatus = false 19 | }, 1000) 20 | } 21 | } 22 | 23 | node.on('input', msg => { 24 | showStatus() 25 | if ( _.isObject(msg.payload) ) { 26 | app.emit(config.nmea2000JsonEvent, msg.payload) 27 | } else { 28 | app.emit(config.nmea2000Event, msg.payload) 29 | } 30 | }) 31 | } 32 | RED.nodes.registerType("signalk-send-nmea2000", send); 33 | } 34 | -------------------------------------------------------------------------------- /signalk-send-notification.html: -------------------------------------------------------------------------------- 1 | 24 | 25 | 61 | 62 | 88 | -------------------------------------------------------------------------------- /signalk-send-notification.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function signalKSendNotification(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | 7 | var app = node.context().global.get('app') 8 | const _ = node.context().global.get('lodash') 9 | 10 | let showingStatus = false 11 | function showStatus(text) { 12 | if ( ! showingStatus ) { 13 | node.status({fill:"green",shape:"dot",text:text}); 14 | showingStatus = true; 15 | setTimeout( () => { 16 | node.status({}); 17 | showingStatus = false 18 | }, 1000) 19 | } 20 | } 21 | 22 | node.on('input', msg => { 23 | let info = _.isObject(msg.payload) ? msg.payload : null 24 | let path = info && info.path ? info.path : config.path 25 | let state = info && info.state ? info.state : config.state 26 | let message = info && info.message ? info.message : config.message 27 | let source = info && info.$source ? info.$source : config.source 28 | let method 29 | if ( info && info.method ){ 30 | method = info.method 31 | } else { 32 | method = [] 33 | if ( config.visual ) { 34 | method.push('visual') 35 | } 36 | if ( config.sound ) { 37 | method.push('sound') 38 | } 39 | } 40 | 41 | if ( typeof source !== 'undefined' && source.length === 0 ) { 42 | source = undefined 43 | } 44 | 45 | if ( !path.startsWith('notifications.') ) { 46 | path = 'notifications.' + path 47 | } 48 | 49 | let delta = { 50 | updates: [ 51 | { 52 | $source: source, 53 | values: [ 54 | { 55 | path, 56 | value: { 57 | state: state, 58 | method: method, 59 | message: message 60 | } 61 | } 62 | ] 63 | } 64 | ] 65 | } 66 | showStatus(state) 67 | app.handleMessage('signalk-node-red', delta) 68 | //node.send({payload: delta}) 69 | }) 70 | } 71 | RED.nodes.registerType("signalk-send-notification", signalKSendNotification); 72 | } 73 | -------------------------------------------------------------------------------- /signalk-send-pathvalue.html: -------------------------------------------------------------------------------- 1 | 27 | 28 | 46 | 47 | 58 | -------------------------------------------------------------------------------- /signalk-send-pathvalue.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function signalKSendPathValue(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | 7 | var app = node.context().global.get('app') 8 | var source = config.name ? 'node-red-' + config.name : 'node-red' 9 | var sentMeta = {} 10 | 11 | function showStatus(text) { 12 | node.status({fill:"green",shape:"dot",text:text}); 13 | } 14 | 15 | node.on('input', msg => { 16 | if ( !msg.topic ) { 17 | node.error('no topic for incomming message') 18 | return 19 | } 20 | 21 | let path = config.path ? config.path : msg.topic 22 | 23 | if ( typeof config.meta !== 'undefined' && config.meta !== "" && !sentMeta[path] ) { 24 | let delta = { 25 | updates: [ 26 | { 27 | meta: [ 28 | { 29 | value: JSON.parse(config.meta), 30 | path 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | if ( config.source && config.source.length > 0 ) { 37 | delta.updates[0].$source = config.source 38 | } 39 | app.handleMessage(source, delta) 40 | sentMeta[path] = true 41 | } 42 | 43 | let delta = { 44 | updates: [ 45 | { 46 | values: [ 47 | { 48 | value: msg.payload, 49 | path 50 | } 51 | ] 52 | } 53 | ] 54 | } 55 | if ( config.source && config.source.length > 0 ) { 56 | delta.updates[0].$source = config.source 57 | } 58 | let c = path.lastIndexOf('.') 59 | showStatus(`${path.substring(c+1)}: ${msg.payload}`) 60 | app.handleMessage(source, delta) 61 | }) 62 | } 63 | RED.nodes.registerType("signalk-send-pathvalue", signalKSendPathValue); 64 | } 65 | -------------------------------------------------------------------------------- /signalk-send-put.html: -------------------------------------------------------------------------------- 1 | 20 | 21 | 35 | 36 | 39 | -------------------------------------------------------------------------------- /signalk-send-put.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function signalKSendPut(config) { 4 | RED.nodes.createNode(this,config) 5 | var node = this 6 | 7 | var app = node.context().global.get('app') 8 | 9 | function onError(err) { 10 | node.error(err) 11 | console.error(err.stack) 12 | node.status({fill:"red",shape:"dot",text:err.message}) 13 | } 14 | 15 | node.on('input', msg => { 16 | node.status({fill:"yellow",shape:"dot",text:`sending...`}) 17 | try { 18 | const path = config.path && config.path.length > 0 ? config.path : msg.topic 19 | let res = app.putSelfPath(path, msg.payload, (reply) => { 20 | if ( reply.state === 'COMPLETED' ) { 21 | if ( reply.statusCode === 200 ) { 22 | node.status({fill:'green',shape:"dot",text:`value: ${msg.payload}`}) 23 | } else { 24 | node.status({fill:'red',shape:"dot",text:`error`}) 25 | node.error(`put error ${reply.statusCode} ${reply.message || ''}`) 26 | } 27 | } 28 | }, config.source && config.source.length > 0 ? config.source : undefined) 29 | Promise.resolve(res) 30 | .then(reply => { 31 | let fill 32 | let text 33 | if ( !app.queryRequest || (reply.state === 'COMPLETED' && reply.statusCode === 200) ) { 34 | fill = 'green' 35 | text = `value: ${msg.payload}` 36 | } else if ( reply.state === 'PENDING' ) { 37 | fill = 'yellow' 38 | text = 'pending...' 39 | } else { 40 | fill = 'red' 41 | text = `error : ${reply.statusCode} ${reply.message || ''}` 42 | } 43 | node.status({fill:fill,shape:"dot",text:text}) 44 | }) 45 | .catch(onError) 46 | } catch (err) { 47 | onError(err) 48 | } 49 | }) 50 | } 51 | RED.nodes.registerType("signalk-send-put", signalKSendPut) 52 | } 53 | -------------------------------------------------------------------------------- /signalk-subscribe.html: -------------------------------------------------------------------------------- 1 | 23 | 24 | 58 | 59 | 62 | -------------------------------------------------------------------------------- /signalk-subscribe.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(RED) { 3 | function input(config) { 4 | RED.nodes.createNode(this,config); 5 | var node = this; 6 | 7 | var signalk = node.context().global.get('signalk') 8 | var app = node.context().global.get('app') 9 | var smanager = node.context().global.get('subscriptionmanager') 10 | let _ = node.context().global.get('lodash') 11 | var onStop = [] 12 | 13 | if ( !config.path || config.path.length == 0 ) { 14 | node.error('no path specified') 15 | return 16 | } 17 | 18 | let subscription = { 19 | "context": config.context, 20 | subscribe: [{ 21 | path: config.path, 22 | period: config.period 23 | }] 24 | } 25 | 26 | let showingStatus = false 27 | function showStatus(value, title) { 28 | if ( ! showingStatus ) { 29 | if ( !title ) { 30 | title = 'sending' 31 | } 32 | node.status({fill:"green",shape:"dot",text: value != null ? `${title} ${value}` : "sending"}); 33 | showingStatus = true; 34 | setTimeout( () => { 35 | node.status({}); 36 | showingStatus = false 37 | }, 1000) 38 | } 39 | } 40 | 41 | function on_delta(delta) { 42 | if ( delta.updates ) { 43 | try { 44 | 45 | if ( typeof config.flatten === 'undefined' || !config.flatten ) { 46 | var copy = JSON.parse(JSON.stringify(delta)) 47 | copy.updates = [] 48 | delta.updates.forEach(update => { 49 | if ( update.values && 50 | (!update.$source || 51 | (!update.$source.startsWith('signalk-node-red') 52 | || config.source === 'signalk-node-red') ) 53 | && (!config.source || update.$source == config.source) ) { 54 | 55 | let last = node.context()[update.values[0].path] 56 | let current = update.values[0].value 57 | if ( typeof last === 'undefined' && config.mode === 'sendChangesIgnore' ) { 58 | showStatus(current, 'ignoring') 59 | node.context()[update.values[0].path] = current 60 | return 61 | } else if ( !config.mode 62 | || config.mode === 'sendAll' 63 | || typeof last === 'undefined' 64 | || !_.isEqual(last, current) ) { 65 | node.context()[update.values[0].path] = current 66 | copy.updates.push(update) 67 | } else { 68 | showStatus(current, 'ignoring') 69 | } 70 | } 71 | }) 72 | 73 | if ( copy.updates.length > 0 ) { 74 | showStatus(copy.updates[0].values[0].value) 75 | if ( copy.context == app.selfContext ) { 76 | copy.context = 'vessels.self' 77 | } 78 | node.send({ payload: copy }) 79 | } 80 | } else { 81 | delta.updates.forEach(update => { 82 | if ( update.values && 83 | (!update.$source || 84 | (!update.$source.startsWith('signalk-node-red') 85 | || config.source === 'signalk-node-red') ) 86 | && ((!config.source || config.source.length === 0) 87 | || update.$source == config.source) ) { 88 | update.values.forEach(pathValue => { 89 | let last = node.context()[pathValue.path] 90 | let current = pathValue.value 91 | if ( typeof last === 'undefined' 92 | && config.mode === 'sendChangesIgnore' ) { 93 | node.context()[pathValue.path] = current 94 | showStatus(current, 'ignoring') 95 | return 96 | } else if ( !config.mode 97 | || config.mode === 'sendAll' 98 | || typeof last === 'undefined' 99 | || !_.isEqual(last, current) ) { 100 | showStatus(pathValue.value) 101 | node.context()[pathValue.path] = current 102 | node.send({ 103 | $source: update.$source, 104 | source: update.source, 105 | context: delta.context == app.selfContext ? 'vessels.self' : delta.context, 106 | payload: pathValue.value, 107 | topic: pathValue.path, 108 | timestamp: update.timestamp 109 | }) 110 | } else { 111 | showStatus(current, 'ignoring') 112 | } 113 | }) 114 | } 115 | }) 116 | } 117 | } catch ( err ) { 118 | node.error(err) 119 | console.error(err.stack); 120 | } 121 | } 122 | } 123 | 124 | setTimeout( () => { 125 | smanager.subscribe(subscription, 126 | onStop, 127 | (err) => { 128 | node.error('subscription error', err) 129 | }, 130 | on_delta); 131 | }, 5000) 132 | 133 | 134 | node.on('close', function() { 135 | onStop.forEach(f => f()); 136 | onStop = [] 137 | }) 138 | } 139 | RED.nodes.registerType("signalk-subscribe", input); 140 | } 141 | --------------------------------------------------------------------------------