node-red-contrib-ax25 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.eslintignore +5 -0
  2. package/.prettierignore +7 -0
  3. package/ARCHITECTURE.md +174 -0
  4. package/CONTEXT.md +90 -0
  5. package/MESSAGES.md +314 -0
  6. package/README.md +317 -0
  7. package/examples/beacons.json +130 -0
  8. package/examples/beacons.png +0 -0
  9. package/examples/bye_subflow.json +107 -0
  10. package/examples/bye_subflow.png +0 -0
  11. package/examples/delete_all_my_messages.json +491 -0
  12. package/examples/delete_all_my_messages.png +0 -0
  13. package/examples/get_message_list_subflow.json +129 -0
  14. package/examples/get_message_list_subflow.png +0 -0
  15. package/examples/send_message_subflow.json +367 -0
  16. package/examples/send_message_subflow.png +0 -0
  17. package/examples/send_test_message.json +643 -0
  18. package/examples/send_test_message.png +0 -0
  19. package/jsconfig.json +37 -0
  20. package/lib/agwpe-client-transport.js +99 -0
  21. package/lib/agwpe-frame-builder.js +176 -0
  22. package/lib/agwpe-frame-pretty.js +107 -0
  23. package/lib/ax25-codec.js +382 -0
  24. package/lib/frame-router.js +95 -0
  25. package/lib/frame-segmentation.js +53 -0
  26. package/lib/message-utils.js +59 -0
  27. package/lib/runtime-store.js +94 -0
  28. package/lib/session-registry.js +142 -0
  29. package/local/buffer_compare.json +135 -0
  30. package/local/debug-d-frame.js +84 -0
  31. package/local/raw-out-test.json +128 -0
  32. package/nodes/agwpe-client.html +70 -0
  33. package/nodes/agwpe-client.js +771 -0
  34. package/nodes/agwpe-client.js.bak +871 -0
  35. package/nodes/connect.html +128 -0
  36. package/nodes/connect.js +450 -0
  37. package/nodes/decode.html +83 -0
  38. package/nodes/decode.js +56 -0
  39. package/nodes/disconnect.html +55 -0
  40. package/nodes/disconnect.js +47 -0
  41. package/nodes/encode.html +117 -0
  42. package/nodes/encode.js +164 -0
  43. package/nodes/monitor-in.html +48 -0
  44. package/nodes/monitor-in.js +42 -0
  45. package/nodes/raw-in.html +50 -0
  46. package/nodes/raw-in.js +72 -0
  47. package/nodes/raw-out.html +76 -0
  48. package/nodes/raw-out.js +144 -0
  49. package/nodes/send.html +91 -0
  50. package/nodes/send.js +373 -0
  51. package/nodes/ui-in.html +64 -0
  52. package/nodes/ui-in.js +68 -0
  53. package/nodes/ui-out.html +80 -0
  54. package/nodes/ui-out.js +133 -0
  55. package/package.json +47 -0
package/README.md ADDED
@@ -0,0 +1,317 @@
1
+ # node-red-contrib-ax25
2
+
3
+ Node-RED nodes for AX.25 packet radio connectivity via an AGWPE (AGW Packet Engine) server. Configure a connection to a local or remote AGWPE instance and use the provided nodes to establish connected sessions, exchange unconnected UI frames, monitor traffic, and encode/decode raw AX.25 frames — all from Node-RED flows without writing socket code.
4
+
5
+ ## Use cases
6
+
7
+ ### Connected mode — BBS and remote stations
8
+
9
+ Use the `connect` and `send` nodes to establish an AX.25 connected session to a remote station such as a bulletin board system (BBS). The `connect` node sends the AX.25 connection request and emits lifecycle events and received data as the session progresses. Chain `send` nodes after it to exchange data: each `send` takes over the data output for the session so it receives the response to its own transmission. Use `waitFor` patterns to synchronize with BBS prompts without writing polling loops.
10
+
11
+ ```
12
+ [inject connect] → [connect] → output 1: lifecycle events (connected / disconnected / timeout)
13
+ → output 2: received data
14
+
15
+ [inject "send payload"] → [send] → output 2: response to this sent payload
16
+
17
+ [inject] → [disconnect] → output 1: disconnecting event
18
+ (disconnected fires on [connect] output 1 when TNC confirms)
19
+ ```
20
+
21
+ ### APRS — UI frame monitoring and transmission
22
+
23
+ APRS uses AX.25 UI (unconnected) frames. Enable raw mode on the `agwpe-client` config node and use `ui-in` to receive decoded APRS packets (source, destination, via path, payload) and `ui-out` to transmit UI frames.
24
+
25
+ Use `ui-in` and `ui-out` to quickly prototype/develop APRS services or gateways.
26
+
27
+ ---
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ npm install node-red-contrib-ax25
33
+ ```
34
+
35
+ Restart Node-RED. The nodes appear in the **agwpe** category in the palette.
36
+
37
+ ---
38
+
39
+ ## Nodes
40
+
41
+ ### agwpe-client (config node)
42
+
43
+ Configure the AGWPE server connection in the Node-RED editor. The connection is established automatically when the flow deploys.
44
+
45
+ Any software that exposes the AGWPE TCP interface works as a backend, including:
46
+
47
+ - **[Direwolf](https://github.com/wb2osz/direwolf)** — software TNC for sound cards; widely used for APRS and packet radio on Linux, macOS, and Windows.
48
+ - **[Soundmodem](https://uz7.ho.ua/packetradio.htm)** — UZ7HO's sound card TNC with AGWPE server support.
49
+ - **[esp-tnc](https://github.com/n7get/esp-tnc)** — AX.25 router and BBS for ESP microcontrollers with AGWPE TCP server support.
50
+ - **[go-ax25](https://github.com/n7get/go-ax25)** — Go library for AX.25.
51
+
52
+ **Editor fields:**
53
+
54
+ | Field | Default | Description |
55
+ |-------|---------|-------------|
56
+ | Host | `127.0.0.1` | AGWPE server hostname or IP |
57
+ | Port | `8000` | AGWPE server TCP port |
58
+ | Callsigns | — | Comma-separated callsigns to register with AGWPE on connect |
59
+ | Username / Password | — | Optional AGWPE credentials |
60
+ | Monitor Mode | off | Enable monitor frame delivery to `monitor-in` nodes |
61
+ | Raw Mode | off | Enable raw frame delivery to `raw-in` nodes and `ui-in` / `ui-out` |
62
+ | Auto-Reconnect | on | Automatically reconnect after a dropped connection |
63
+ | Reconnect Delay | `5000` ms | Delay before each reconnect attempt |
64
+
65
+ The `connect`, `ui-out`, `ui-in`, `monitor-in`, `raw-out`, and `raw-in` nodes each select which `agwpe-client` to bind to using a **Client** dropdown in their editor. The `send` node does not need a client selection — it gets the `agwpe-client` from the `sessionId`.
66
+
67
+ ---
68
+
69
+ ### connect
70
+
71
+ Establishes an AX.25 connected session to a remote station.
72
+
73
+ **Input:** any message with `msg.destination` set (or configured in the node editor).
74
+
75
+ ```json
76
+ { "destination": "N0CALL-1" }
77
+ ```
78
+
79
+ `source` defaults to the first callsign registered on the `agwpe-client`. `destination` can also be set in the node editor and overridden per-message. `via` accepts a comma-separated string, array of strings, or array of `{ callsign, hasBeenRepeated }` objects.
80
+
81
+ ```json
82
+ { "source": "N0CALL", "destination": "N0CALL-1", "via": "RELAY" }
83
+ ```
84
+
85
+ **Output 1 — lifecycle events** (`msg.event`):
86
+
87
+ | Event | Meaning |
88
+ |-------|---------|
89
+ | `connecting` | Connect request sent |
90
+ | `connected` | Remote station accepted |
91
+ | `disconnecting` | Disconnect in progress |
92
+ | `disconnected` | Session closed |
93
+ | `timeout` | No activity (sent or received) within timeout period |
94
+ | `failed` | Transport or session error |
95
+
96
+ **Output 2 — received data:**
97
+
98
+ Controlled by the **Mode** editor field (default: `line`):
99
+
100
+ - **line mode** — incoming bytes are buffered and split on CR or CR+LF. Each complete line is emitted as `msg.payload` (string). If `waitFor` is set (a regex string), lines accumulate until one matches; `msg.payload` is then an array of the preceding lines and `msg.match` is the matching line. On timeout, `msg.event: "timeout"` is emitted on output 1 with the current buffer contents.
101
+ - **binary mode** — each received frame is emitted immediately with `msg.payload` as a Buffer.
102
+
103
+ `mode`, `waitFor`, `timeout`, `source`, `destination`, and `via` can all be set in the node editor and overridden per-message with the corresponding `msg` properties.
104
+
105
+ The `sessionId` assigned to the session is included on every output message and must be passed to subsequent `send` commands.
106
+
107
+ ---
108
+
109
+ ### send
110
+
111
+ Sends data on an established session. When a `send` node processes an input message, it takes over the data output (output 2) for that session so it receives the response to its own transmission.
112
+
113
+ The `send` node gets the correct `agwpe-client` at runtime from the `sessionId`, so it works with any session from any client automatically.
114
+
115
+ **Input:**
116
+
117
+ ```json
118
+ { "sessionId": "sess-abc", "payload": "HELLO\r" }
119
+ ```
120
+
121
+ `payload` may be a string or a Buffer. Arrays of strings/Buffers are also accepted; each item is sent as a separate frame. Payloads longer than 255 bytes are segmented automatically.
122
+
123
+ **Output 1** — lifecycle events (same structure as `connect` output 1).
124
+ **Output 2** — received data (same line/binary behaviour as `connect`; `waitFor` and `timeout` can be set in the node editor or per-message).
125
+
126
+ ---
127
+
128
+ ### disconnect
129
+
130
+ Initiates a graceful disconnect for an established session.
131
+
132
+ **Input:** any message with `msg.sessionId` set.
133
+
134
+ ```json
135
+ { "sessionId": "sess-abc" }
136
+ ```
137
+
138
+ **Output 1** — emits `disconnecting` immediately on success, or `SESSION_NOT_FOUND` on error. The `disconnected` event fires on the originating `connect` node's output 1 once the TNC confirms with a `d` frame.
139
+
140
+ ---
141
+
142
+ ### ui-out
143
+
144
+ Encodes and transmits an AX.25 UI frame via the AGWPE raw (K-frame) transport. **Raw mode must be enabled** on the `agwpe-client`.
145
+
146
+ Set `source`, `destination`, `via`, and `payload` in the node editor. Any triggering message causes the frame to be sent; individual fields can be overridden per-message:
147
+
148
+ ```json
149
+ { "payload": "status text" }
150
+ ```
151
+
152
+ `via` accepts the same formats as `connect`. `msg.agwpePort` overrides the AGWPE port byte (default `0`).
153
+
154
+ Output: a single message with `event: "ui-sent"` on success, or an error envelope on failure.
155
+
156
+ ---
157
+
158
+ ### ui-in
159
+
160
+ Receives AGWPE raw (K-frame) traffic, decodes the embedded AX.25 UI frames, and emits one message per frame. **Raw mode must be enabled** on the `agwpe-client`.
161
+
162
+ Output message fields: `source`, `destination`, `via` (array of callsigns), `payload`.
163
+
164
+ The **Payload Output** editor option selects `string` (UTF-8, default) or `buffer` (raw bytes).
165
+
166
+ Non-UI frames are silently discarded.
167
+
168
+ ---
169
+
170
+ ### monitor-in
171
+
172
+ Receives all frames seen by the AGWPE server in monitor mode. **Monitor mode must be enabled** on the `agwpe-client`.
173
+
174
+ Output: one message per frame with `source`, `destination`, `payload`, and AGWPE frame metadata.
175
+
176
+ ---
177
+
178
+ ### raw-out
179
+
180
+ Transmits a raw AX.25 frame inside an AGWPE K-frame. **Raw mode must be enabled.**
181
+
182
+ `msg.payload` accepts:
183
+
184
+ - `Buffer`
185
+ - `Uint8Array`
186
+ - byte array (numbers 0–255)
187
+ - hex string (`"82 a0 a8"` or `"82a0a8"`)
188
+
189
+ `msg.agwpePort` (or the editor **AGWPE Port** field, default `0`) sets the leading port byte of the K-frame.
190
+
191
+ ---
192
+
193
+ ### raw-in
194
+
195
+ Receives raw AGWPE K-frames. **Raw mode must be enabled.**
196
+
197
+ Output: `msg.payload` is a Buffer of the raw AX.25 wire bytes (leading AGWPE port byte stripped and exposed separately as `msg.agwpePort`).
198
+
199
+ ---
200
+
201
+ ### decode
202
+
203
+ Decodes a raw AX.25 frame Buffer into its constituent fields.
204
+
205
+ **Input:** `msg.payload` must be a Buffer containing AX.25 wire bytes (e.g. from `raw-in`).
206
+
207
+ **Output:**
208
+
209
+ ```json
210
+ {
211
+ "source": "N0CALL",
212
+ "destination": "CQ",
213
+ "via": [{ "callsign": "WIDE1-1", "hasBeenRepeated": false }],
214
+ "control": 3,
215
+ "pid": 240,
216
+ "payload": "decoded payload here"
217
+ }
218
+ ```
219
+
220
+ The **Payload Output** editor option selects `string` (UTF-8, default) or `buffer`.
221
+
222
+ ---
223
+
224
+ ### encode
225
+
226
+ Encodes structured fields into a raw AX.25 frame Buffer. Editor defaults are pre-set for a UI frame (control `0x03`, PID `0xF0`). All fields can be overridden per-message.
227
+
228
+ **Input fields** (message overrides editor defaults):
229
+
230
+ | Field | Example | Notes |
231
+ |-------|---------|-------|
232
+ | `source` | `"N0CALL"` | |
233
+ | `destination` | `"CQ"` | |
234
+ | `via` | `"WIDE1-1,WIDE2-1"` | string, array of strings, or array of `{callsign, hasBeenRepeated}` |
235
+ | `control` | `3` | byte value, or use `frameType` (`"U"`, `"I"`, `"S"`) |
236
+ | `pid` | `240` | byte value |
237
+ | `payload` | `"text"` | string or Buffer |
238
+
239
+ **Output:** `msg.payload` is the encoded AX.25 frame as a Buffer, ready to pass to `raw-out`.
240
+
241
+ ---
242
+
243
+ ## Examples
244
+
245
+ Ready-to-import flows are provided in the [`examples/`](examples/) directory. Import any `.json` file via **Menu → Import** in the Node-RED editor.
246
+
247
+ ---
248
+
249
+ ### Beacons
250
+
251
+ **File:** [`examples/beacons.json`](examples/beacons.json)
252
+
253
+ ![Beacons flow](examples/beacons.png)
254
+
255
+ Receives and transmits AX.25 UI frames using the `ui-in` and `ui-out` nodes. Demonstrates APRS-style beacon monitoring and periodic transmission.
256
+
257
+ ---
258
+
259
+ ### Send message test
260
+
261
+ **File:** [`examples/send_test_message.json`](examples/send_test_message.json)
262
+
263
+ ![Send message test flow](examples/send_test_message.png)
264
+
265
+ A complete flow that connects to a packet BBS, composes and sends a test message using the **Send message** subflow, and disconnects via the **Bye** subflow. Demonstrates end-to-end message delivery from connection through graceful teardown.
266
+
267
+ ---
268
+
269
+ ### Send message subflow
270
+
271
+ **File:** [`examples/send_message_subflow.json`](examples/send_message_subflow.json)
272
+
273
+ ![Send message subflow](examples/send_message_subflow.png)
274
+
275
+ A reusable subflow that composes and sends a message to a packet BBS: sets the addressee, subject, and body via a sequence of `send` nodes, each waiting for the BBS prompt before advancing.
276
+
277
+ ---
278
+
279
+ ### Get message list subflow
280
+
281
+ **File:** [`examples/get_message_list_subflow.json`](examples/get_message_list_subflow.json)
282
+
283
+ ![Get message list subflow](examples/get_message_list_subflow.png)
284
+
285
+ A reusable subflow that retrieves the message list from a packet BBS using the `send` / `waitFor` pattern and returns a parsed list.
286
+
287
+ ---
288
+
289
+ ### Delete all my messages
290
+
291
+ **File:** [`examples/delete_all_my_messages.json`](examples/delete_all_my_messages.json)
292
+
293
+ ![Delete all my messages flow](examples/delete_all_my_messages.png)
294
+
295
+ A full flow that connects to a packet BBS, retrieves the message list using the **Get message list** subflow, deletes every message addressed to the registered callsign, and disconnects cleanly. Demonstrates composing subflows into a complete automated task.
296
+
297
+ ---
298
+
299
+ ### Bye subflow
300
+
301
+ **File:** [`examples/bye_subflow.json`](examples/bye_subflow.json)
302
+
303
+ ![Bye subflow](examples/bye_subflow.png)
304
+
305
+ A reusable subflow that sends a `BYE` command and waits for the BBS to close the session gracefully. Intended to be composed into larger BBS flows.
306
+
307
+ ---
308
+
309
+ ## Testing
310
+
311
+ ```bash
312
+ npm test
313
+ ```
314
+ ## TODO
315
+
316
+ Implement `listen` and `accept` nodes for creating AX.25 servers.
317
+
@@ -0,0 +1,130 @@
1
+ [
2
+ {
3
+ "id": "65a0d887c03a3e96",
4
+ "type": "tab",
5
+ "label": "Beacons",
6
+ "disabled": false,
7
+ "info": "",
8
+ "env": []
9
+ },
10
+ {
11
+ "id": "a4494b4c19e5a930",
12
+ "type": "ui-in",
13
+ "z": "65a0d887c03a3e96",
14
+ "client": "9563d0689b7ce1af",
15
+ "name": "",
16
+ "payloadOutput": "string",
17
+ "x": 164,
18
+ "y": 100,
19
+ "wires": [
20
+ [
21
+ "799c276c66fa1dce"
22
+ ]
23
+ ]
24
+ },
25
+ {
26
+ "id": "24e57122cb89257d",
27
+ "type": "debug",
28
+ "z": "65a0d887c03a3e96",
29
+ "name": "debug 1",
30
+ "active": true,
31
+ "tosidebar": true,
32
+ "console": true,
33
+ "tostatus": false,
34
+ "complete": "true",
35
+ "targetType": "full",
36
+ "statusVal": "",
37
+ "statusType": "auto",
38
+ "x": 564,
39
+ "y": 100,
40
+ "wires": []
41
+ },
42
+ {
43
+ "id": "799c276c66fa1dce",
44
+ "type": "function",
45
+ "z": "65a0d887c03a3e96",
46
+ "name": "function 2",
47
+ "func": "\nconst vias = msg.via.map( v => v.callsign + (v.hasBeenRepeated ? '*' : '') ).join(' ');\n\nreturn {\n source: msg.source,\n destination: msg.destination,\n vias,\n payload: msg.payload,\n};",
48
+ "outputs": 1,
49
+ "timeout": 0,
50
+ "noerr": 0,
51
+ "initialize": "",
52
+ "finalize": "",
53
+ "libs": [],
54
+ "x": 354,
55
+ "y": 100,
56
+ "wires": [
57
+ [
58
+ "24e57122cb89257d"
59
+ ]
60
+ ]
61
+ },
62
+ {
63
+ "id": "69164145ed38640a",
64
+ "type": "inject",
65
+ "z": "65a0d887c03a3e96",
66
+ "name": "",
67
+ "props": [],
68
+ "repeat": "",
69
+ "crontab": "",
70
+ "once": false,
71
+ "onceDelay": 0.1,
72
+ "topic": "",
73
+ "x": 190,
74
+ "y": 180,
75
+ "wires": [
76
+ [
77
+ "3207efd50fdb02f8"
78
+ ]
79
+ ]
80
+ },
81
+ {
82
+ "id": "3207efd50fdb02f8",
83
+ "type": "ui-out",
84
+ "z": "65a0d887c03a3e96",
85
+ "client": "9563d0689b7ce1af",
86
+ "name": "",
87
+ "source": "N0CALL-7",
88
+ "destination": "APX100",
89
+ "via": "WIDE1-1,WIDE2-1",
90
+ "payload": "!3612.97N/11517.07W-node-red-contrib-ax25 test",
91
+ "x": 350,
92
+ "y": 180,
93
+ "wires": [
94
+ [
95
+ "de2571a8cf9037f8"
96
+ ]
97
+ ]
98
+ },
99
+ {
100
+ "id": "de2571a8cf9037f8",
101
+ "type": "debug",
102
+ "z": "65a0d887c03a3e96",
103
+ "name": "debug 7",
104
+ "active": true,
105
+ "tosidebar": true,
106
+ "console": true,
107
+ "tostatus": false,
108
+ "complete": "true",
109
+ "targetType": "full",
110
+ "statusVal": "",
111
+ "statusType": "auto",
112
+ "x": 550,
113
+ "y": 180,
114
+ "wires": []
115
+ },
116
+ {
117
+ "id": "9563d0689b7ce1af",
118
+ "type": "agwpe-client",
119
+ "name": "",
120
+ "host": "192.168.68.20",
121
+ "port": 8000,
122
+ "callsigns": "N0CALL",
123
+ "username": "",
124
+ "password": "",
125
+ "monitor": false,
126
+ "raw": true,
127
+ "reconnect": true,
128
+ "reconnectDelay": 5000
129
+ }
130
+ ]
Binary file
@@ -0,0 +1,107 @@
1
+ [
2
+ {
3
+ "id": "f1e3c30bea1bbe94",
4
+ "type": "subflow",
5
+ "name": "Bye",
6
+ "info": "",
7
+ "category": "",
8
+ "in": [
9
+ {
10
+ "x": 80,
11
+ "y": 120,
12
+ "wires": [
13
+ {
14
+ "id": "0813fedcec47e075"
15
+ }
16
+ ]
17
+ }
18
+ ],
19
+ "out": [],
20
+ "env": [],
21
+ "meta": {},
22
+ "color": "#DDAA99"
23
+ },
24
+ {
25
+ "id": "0813fedcec47e075",
26
+ "type": "function",
27
+ "z": "f1e3c30bea1bbe94",
28
+ "name": "B",
29
+ "func": "\"use strict\";\n\nif (!msg.sessionId) {\n const errorCode = 'NO_SESSION_ID';\n const errorText = \"sessionID not found\";\n\n node.error(`[${errorCode}] ${errorText}`);\n\n return [{\n status: 'error',\n errorCode,\n errorText,\n msg,\n }, null];\n}\n\nreturn [null, {\n sessionId: msg.sessionId,\n command: 'send',\n payload: 'B',\n timeout: 30000,\n}];\n",
30
+ "outputs": 2,
31
+ "timeout": 0,
32
+ "noerr": 0,
33
+ "initialize": "",
34
+ "finalize": "",
35
+ "libs": [],
36
+ "x": 210,
37
+ "y": 120,
38
+ "wires": [
39
+ [
40
+ "637170b1c7227488"
41
+ ],
42
+ [
43
+ "1e49e3218b1dd308"
44
+ ]
45
+ ]
46
+ },
47
+ {
48
+ "id": "1e49e3218b1dd308",
49
+ "type": "send",
50
+ "z": "f1e3c30bea1bbe94",
51
+ "name": "",
52
+ "timeout": "",
53
+ "waitFor": "",
54
+ "x": 390,
55
+ "y": 160,
56
+ "wires": [
57
+ [
58
+ "e3ab85e4461b92a5"
59
+ ],
60
+ [
61
+ "fed58202ceb48e13"
62
+ ]
63
+ ]
64
+ },
65
+ {
66
+ "id": "e3ab85e4461b92a5",
67
+ "type": "function",
68
+ "z": "f1e3c30bea1bbe94",
69
+ "name": "check error",
70
+ "func": "\"use strict\";\n\nif (msg.status === 'error') {\n node.error(`[${msg.errorCode}] ${msg.errorText}`, msg);\n return msg;\n}\nreturn null;",
71
+ "outputs": 1,
72
+ "timeout": 0,
73
+ "noerr": 0,
74
+ "initialize": "",
75
+ "finalize": "",
76
+ "libs": [],
77
+ "x": 610,
78
+ "y": 140,
79
+ "wires": [
80
+ [
81
+ "fed58202ceb48e13"
82
+ ]
83
+ ]
84
+ },
85
+ {
86
+ "id": "fed58202ceb48e13",
87
+ "type": "disconnect",
88
+ "z": "f1e3c30bea1bbe94",
89
+ "name": "",
90
+ "x": 810,
91
+ "y": 180,
92
+ "wires": [
93
+ []
94
+ ]
95
+ },
96
+ {
97
+ "id": "637170b1c7227488",
98
+ "type": "disconnect",
99
+ "z": "f1e3c30bea1bbe94",
100
+ "name": "",
101
+ "x": 410,
102
+ "y": 80,
103
+ "wires": [
104
+ []
105
+ ]
106
+ }
107
+ ]
Binary file