├── .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 | 
12 |
13 | ## Switch relays based on complex conditions (example turns on/off the anchor light and ACR automatically)
14 | 
15 |
16 | ## Send an alarm when wind speed is high
17 | 
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 | 
21 |
22 |
23 | ## Send an alarm when the AC input voltage is low and the boat is in its slip
24 | 
25 |
26 | ## Send a tweet when leaving or arriving at the slip
27 | 
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 | 
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 |
--------------------------------------------------------------------------------