node-red-contrib-config-files 0.1.0 → 0.2.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## [0.2.0] - 2026-01-30
4
+ ### Added
5
+ - If directory `configDir` doesn't exist or is not accessible, a node error is throw. This error can be received with a `catch` node, for example.
6
+ - If no file was found with `configFileExt`, a node warning is created. This warning may appear in the “Debug Messages” sidebar.
7
+
8
+ ### Changed
9
+ - A filename extension can be specified for searching configuration files. In the previous version, a search pattern could be configured. The corresponding property has renamed to `configFileEx` (previously: `configFilePattern`).
10
+ - Dependency from `node-red-contrib-filesystem` has been removed. Only the Node.js core module [File System](https://nodejs.org/api/fs.html#file-system) (fs) is now used. This eliminates dependencies on third-party modules.
11
+
12
+ ### Fixed
13
+ nothing
package/HELP.md ADDED
@@ -0,0 +1,29 @@
1
+ Read and merge configuration files (JSON).
2
+
3
+ ### Inputs
4
+
5
+ Directory and filename extension used to search for configuration
6
+ files can be configured using the node properties and/or with the
7
+ input message.
8
+
9
+ : configDir (string) : Directory with config files. Absolute or relative path, default: `/config`. Input message `msg.configDir` overwrites the node property.
10
+ : configFileExt (string) : Filename extension for config files, default: `.json`. Input message `msg.configFileExt` overwrites the node property.
11
+
12
+ ### Outputs
13
+
14
+ : payload (object) : Merged content from config files.
15
+
16
+ ### Details
17
+ The node pproperty `defaultConfig` can be used to define configuration
18
+ values that are missing in the configuration files.
19
+
20
+ The `configDir` is searched non-recursively for configuration files using
21
+ the filename extension `configFilePattern`.
22
+ The files are sorted by their file names and merged in this order.
23
+
24
+ The content of the `defaultConfig` property and the configuration files
25
+ must by valid JSON.
26
+
27
+ ### References
28
+
29
+ - [GitHub](https://github.com/schaeren/node-red-contrib-config-files) - source code
package/LICENSE CHANGED
@@ -1 +1,21 @@
1
- Apache-2.0
1
+ MIT License
2
+
3
+ Copyright (c) [year] [fullname]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,17 +1,20 @@
1
1
  # node-red-contrib-config-files
2
- [Node-Red](https://nodered.org/) node for reading and merging configuration files with JSON content. The output returns the resulting configuration.
2
+ [Node-Red](https://nodered.org/) node for reading and merging configuration files with JSON content. The output returns the resulting configuration object.
3
3
 
4
- Directory and search patterns for configuration files can be configured using the node properties and/or with the input message.
4
+ Directory and filename extension used to search for configuration files can be configured using the node properties and/or with the input message.
5
5
 
6
6
  The files are sorted by their file names and merged in this order. The `defaultConfig` property can be used to define configuration values that are missing in the files.
7
7
 
8
8
  If no configuration files are found, the `defaultConfig` is returned.
9
9
 
10
10
  The node status displays one of the following two messages:
11
- - Found {count} config files in '{configDir}' with pattern '{configFilePattern}'
12
- - No config files found in '{configDir}' with pattern '{configFilePattern}' -> using default config
11
+ - Found {count} config files in '{configDir}' with extension '{configFileExt}'
12
+ - No config files found in '{configDir}' with extension '{configFileExt}' -> using default config
13
+
14
+ If directory `configDir` doesn't exist or is not accessible, a node error is throw. This error can be received with a `catch` node, for example.
15
+
16
+ If no file was found with `configFileExt`, a node warning is created. This warning may appear in the “Debug Messages” sidebar.
13
17
 
14
18
  ## Internals
15
19
  - This custom node is created from a subflow.
16
- - It uses `node-red-contrib-filesystem : fs-list` to find the config files.
17
20
  - It uses `lodash.merge()` to merge the `defaultConfig` with the contents of the files found.
package/install.js CHANGED
@@ -2,7 +2,7 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
 
4
4
  module.exports = function(RED) {
5
- const subflowFile = path.join(__dirname,"subflow.json");
5
+ const subflowFile = path.join(__dirname, "subflow.json");
6
6
  const subflowContents = fs.readFileSync(subflowFile);
7
7
  const subflowJSON = JSON.parse(subflowContents);
8
8
  RED.nodes.registerSubflow(subflowJSON);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "node-red-contrib-config-files",
3
- "version": "0.1.0",
4
- "description": "Read and merge configuration files (JSON). The files are sorted by their file names and merged in this order. The “defaultConfig” property can be used to define configuration values that are missing in the files.",
3
+ "version": "0.2.0",
4
+ "description": "Read and merge configuration files (JSON).",
5
5
  "main": "install.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -20,16 +20,11 @@
20
20
  "type": "git",
21
21
  "url": "https://github.com/schaeren/node-red-contrib-config-files"
22
22
  },
23
- "license": "Apache-2.0",
23
+ "homepage": "https://github.com/schaeren/node-red-contrib-config-files",
24
+ "license": "MIT",
24
25
  "node-red": {
25
26
  "nodes": {
26
27
  "config-files": "install.js"
27
- },
28
- "dependencies": [
29
- "node-red-contrib-filesystem"
30
- ]
31
- },
32
- "dependencies": {
33
- "node-red-contrib-filesystem": "*"
28
+ }
34
29
  }
35
30
  }
package/subflow.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
- "id": "0d7c13f79e4a7aee",
2
+ "id": "ec796389903c1d0f",
3
3
  "type": "subflow",
4
4
  "name": "config-files",
5
- "info": "Read and merge configuration files (JSON). \r\n\r\n### Inputs\r\nIf the node receives any message, it will read the \r\nconfig files and merge their contents.\r\n\r\nOptionally a directory and file search pattern can \r\nbe specified in the input message. These will \r\noverwrite the corresponding node properties.\r\n\r\n: configDir (string) : Directory with config files. Absolute or relative path.\r\n: configFilePattern (string) : Search pattern for config files, e.g. `\"*.json\"`\r\n\r\n### Outputs\r\n\r\n: payload (object) : Merged content from config files.\r\n\r\n### Details\r\nThe `defaultConfig` property can be used to define \r\nconfiguration values that are missing in the \r\nconfiguration files.\r\n\r\nThe `configDir` is searched non-recursively for \r\nconfiguration files using the filename pattern \r\n`configFilePattern`.\r\nThe files are sorted by their file names and merged in \r\nthis order. \r\n\r\nThe content of the `defaultConfig` property and the \r\nconfiguration files must by valid JSON.\r\n\r\n### References\r\n\r\n - [GitHub](https://github.com/schaeren/node-red-contrib-config-files) - source code\r\n ",
5
+ "info": "Read and merge configuration files (JSON).\n\n### Inputs\n \nDirectory and filename extension used to search for configuration \nfiles can be configured using the node properties and/or with the \ninput message.\n \n: configDir (string) : Directory with config files. Absolute or relative path, default: `/config`. Input message `msg.configDir` overwrites the node property.\n: configFileExt (string) : Filename extension for config files, default: `.json`. Input message `msg.configFileExt` overwrites the node property.\n \n### Outputs\n \n: payload (object) : Merged content from config files.\n\n### Details\nThe node pproperty `defaultConfig` can be used to define configuration \nvalues that are missing in the configuration files.\n\nThe `configDir` is searched non-recursively for configuration files using \nthe filename extension `configFilePattern`.\nThe files are sorted by their file names and merged in this order. \n\nThe content of the `defaultConfig` property and the configuration files \nmust by valid JSON.\n\n### References\n\n- [GitHub](https://github.com/schaeren/node-red-contrib-config-files) - source code",
6
6
  "category": "",
7
7
  "in": [
8
8
  {
@@ -17,11 +17,11 @@
17
17
  ],
18
18
  "out": [
19
19
  {
20
- "x": 720,
21
- "y": 240,
20
+ "x": 660,
21
+ "y": 300,
22
22
  "wires": [
23
23
  {
24
- "id": "81b18d7aad495891",
24
+ "id": "86c429715975c781",
25
25
  "port": 0
26
26
  }
27
27
  ]
@@ -34,9 +34,9 @@
34
34
  "value": "/config"
35
35
  },
36
36
  {
37
- "name": "configFilePattern",
37
+ "name": "configFileExt",
38
38
  "type": "str",
39
- "value": "*.json"
39
+ "value": ".json"
40
40
  },
41
41
  {
42
42
  "name": "defaultConfig",
@@ -47,17 +47,17 @@
47
47
  "meta": {
48
48
  "module": "node-red-contrib-config-files",
49
49
  "type": "config-files",
50
- "version": "0.1.0",
51
- "author": "Peter Schären <peter.schaeren@gmail.com>",
52
50
  "desc": "Read and merge configuration files (JSON).",
51
+ "version": "0.2.0",
52
+ "author": "Peter Schären <peter.schaeren@gmail.com>",
53
53
  "keywords": "node-red,config-files,config,json",
54
- "license": "Apache-2.0"
54
+ "license": "MIT"
55
55
  },
56
56
  "color": "#b0c2ba",
57
57
  "icon": "node-red/parser-json.svg",
58
58
  "status": {
59
- "x": 720,
60
- "y": 120,
59
+ "x": 660,
60
+ "y": 140,
61
61
  "wires": [
62
62
  {
63
63
  "id": "73c98834cb90c714",
@@ -66,32 +66,10 @@
66
66
  ]
67
67
  },
68
68
  "flow": [
69
- {
70
- "id": "2b5d6f663d02b82f",
71
- "type": "fs-list",
72
- "z": "0d7c13f79e4a7aee",
73
- "name": "Find config files",
74
- "path": "configDir",
75
- "pathType": "msg",
76
- "pattern": "configFilePattern",
77
- "patternType": "msg",
78
- "filter": "files",
79
- "recursive": false,
80
- "follow": false,
81
- "property": "payload",
82
- "propertyType": "msg",
83
- "x": 220,
84
- "y": 80,
85
- "wires": [
86
- [
87
- "73c98834cb90c714"
88
- ]
89
- ]
90
- },
91
69
  {
92
70
  "id": "b46481b92cb28e07",
93
71
  "type": "file in",
94
- "z": "0d7c13f79e4a7aee",
72
+ "z": "ec796389903c1d0f",
95
73
  "name": "read file",
96
74
  "filename": "payload",
97
75
  "filenameType": "msg",
@@ -101,7 +79,7 @@
101
79
  "encoding": "utf8",
102
80
  "allProps": false,
103
81
  "x": 200,
104
- "y": 200,
82
+ "y": 300,
105
83
  "wires": [
106
84
  [
107
85
  "f3c260d6c082e422"
@@ -111,7 +89,7 @@
111
89
  {
112
90
  "id": "bebafe14f9cc467b",
113
91
  "type": "split",
114
- "z": "0d7c13f79e4a7aee",
92
+ "z": "ec796389903c1d0f",
115
93
  "name": "",
116
94
  "splt": "\\n",
117
95
  "spltType": "str",
@@ -120,35 +98,11 @@
120
98
  "stream": false,
121
99
  "addname": "",
122
100
  "property": "payload",
123
- "x": 330,
124
- "y": 160,
125
- "wires": [
126
- [
127
- "ef0d2e4ce8988133"
128
- ]
129
- ]
130
- },
131
- {
132
- "id": "ef0d2e4ce8988133",
133
- "type": "function",
134
- "z": "0d7c13f79e4a7aee",
135
- "name": "make paths absolute",
136
- "func": "const configDir = env.get('configDir');\nmsg.payload = path.join(configDir, msg.payload);\n\nreturn msg;\n",
137
- "outputs": 1,
138
- "timeout": 0,
139
- "noerr": 0,
140
- "initialize": "",
141
- "finalize": "",
142
- "libs": [
143
- {
144
- "var": "path",
145
- "module": "path"
146
- }
147
- ],
148
- "x": 520,
149
- "y": 160,
101
+ "x": 390,
102
+ "y": 260,
150
103
  "wires": [
151
104
  [
105
+ "7f1816d008fa2b70",
152
106
  "b46481b92cb28e07"
153
107
  ]
154
108
  ]
@@ -156,23 +110,24 @@
156
110
  {
157
111
  "id": "f3c260d6c082e422",
158
112
  "type": "json",
159
- "z": "0d7c13f79e4a7aee",
160
- "name": "to JS",
113
+ "z": "ec796389903c1d0f",
114
+ "name": "JSON to JS object",
161
115
  "property": "payload",
162
116
  "action": "obj",
163
117
  "pretty": false,
164
- "x": 350,
165
- "y": 200,
118
+ "x": 230,
119
+ "y": 340,
166
120
  "wires": [
167
121
  [
168
- "81b18d7aad495891"
122
+ "86c429715975c781",
123
+ "8639805af947d0c3"
169
124
  ]
170
125
  ]
171
126
  },
172
127
  {
173
128
  "id": "e2a872db2d9086e6",
174
129
  "type": "sort",
175
- "z": "0d7c13f79e4a7aee",
130
+ "z": "ec796389903c1d0f",
176
131
  "name": "",
177
132
  "order": "ascending",
178
133
  "as_num": false,
@@ -183,7 +138,7 @@
183
138
  "seqKey": "payload",
184
139
  "seqKeyType": "msg",
185
140
  "x": 190,
186
- "y": 160,
141
+ "y": 260,
187
142
  "wires": [
188
143
  [
189
144
  "bebafe14f9cc467b"
@@ -191,11 +146,103 @@
191
146
  ]
192
147
  },
193
148
  {
194
- "id": "81b18d7aad495891",
149
+ "id": "3c563aca59369641",
150
+ "type": "function",
151
+ "z": "ec796389903c1d0f",
152
+ "name": "get config dir and filename extension",
153
+ "func": "const configDir = msg.configDir ?? null;\nif (typeof configDir === 'string' && configDir.trim().length > 0) {\n msg.configDir = configDir;\n}\nelse {\n msg.configDir = env.get('configDir');\n}\n\nconst configFileExt = msg.configFileExt ?? null;\nif (typeof configFileExt === 'string' && configFileExt.trim().length > 0) {\n msg.configFileExt = configFileExt;\n}\nelse {\n msg.configFileExt = env.get('configFileExt');\n}\n\nmsg.topic = 'configFiles';\nreturn msg;",
154
+ "outputs": 1,
155
+ "timeout": 0,
156
+ "noerr": 0,
157
+ "initialize": "",
158
+ "finalize": "",
159
+ "libs": [],
160
+ "x": 290,
161
+ "y": 40,
162
+ "wires": [
163
+ [
164
+ "7883192ade1cf939"
165
+ ]
166
+ ]
167
+ },
168
+ {
169
+ "id": "73c98834cb90c714",
170
+ "type": "function",
171
+ "z": "ec796389903c1d0f",
172
+ "name": "node status",
173
+ "func": "const count = Array.isArray(msg.payload) ? msg.payload.length : 0;\nlet errorMsg = null;\n\nlet statusText = '';\nif (count > 0) {\n statusText = `Found ${count} config files in '${msg.configDir}' with extension '${msg.configFileExt}'`;\n}\nelse {\n statusText = `No config files found in '${msg.configDir}' with extension '${msg.configFileExt}' -> using default config`;\n errorMsg = { payload: statusText };\n}\nconst statusMsg = { payload: { fill: \"blue\", shape: \"ring\", text: statusText } };\n\nmsg.configFiles = msg.payload;\n\nreturn [msg, statusMsg, errorMsg];\n",
174
+ "outputs": 3,
175
+ "timeout": 0,
176
+ "noerr": 0,
177
+ "initialize": "",
178
+ "finalize": "",
179
+ "libs": [],
180
+ "x": 210,
181
+ "y": 180,
182
+ "wires": [
183
+ [
184
+ "e2a872db2d9086e6"
185
+ ],
186
+ [],
187
+ [
188
+ "cdec17dcc5a199ab"
189
+ ]
190
+ ]
191
+ },
192
+ {
193
+ "id": "7883192ade1cf939",
194
+ "type": "function",
195
+ "z": "ec796389903c1d0f",
196
+ "name": "find files",
197
+ "func": "const dir = msg.configDir;\nconst ext = msg.configFileExt\n\nlet error = null;\n\n// ---------------------------------------------------------------------------------------\n\nasync function findFiles(dir, ext) {\n try {\n await fs.promises.access(dir); // throws if not accessible (e.g., doesn't exist)\n let filenames = await fs.promises.readdir(dir);\n filenames = filenames.filter(f => path.extname(f) === ext);\n\n const filepaths = filenames.map(f => path.join(dir, f));\n return filepaths;\n\n } catch (err) {\n error = `Directory not accessible: ${err.message}`;\n console.error('Directory not accessible:', err.message);\n return [];\n }\n}\n\n// ---------------------------------------------------------------------------------------\n\nnode.log(`Searching files in directory '${dir}' with filename extension '${ext}' ...`);\n\nconst filepaths = await findFiles(dir, ext);\n\nnode.log(`Files found:`);\n\nfor (let fp of filepaths) {\n node.log(` ${fp}`);\n}\n\nmsg.payload = filepaths;\nconst errorMsg = error ? { payload: error } : null;\nreturn [msg, errorMsg];\n",
198
+ "outputs": 2,
199
+ "timeout": 0,
200
+ "noerr": 0,
201
+ "initialize": "",
202
+ "finalize": "",
203
+ "libs": [
204
+ {
205
+ "var": "fs",
206
+ "module": "fs"
207
+ },
208
+ {
209
+ "var": "path",
210
+ "module": "path"
211
+ }
212
+ ],
213
+ "x": 200,
214
+ "y": 120,
215
+ "wires": [
216
+ [
217
+ "73c98834cb90c714"
218
+ ],
219
+ [
220
+ "aa017e7a5d0c867e"
221
+ ]
222
+ ]
223
+ },
224
+ {
225
+ "id": "cdec17dcc5a199ab",
195
226
  "type": "function",
196
- "z": "0d7c13f79e4a7aee",
227
+ "z": "ec796389903c1d0f",
228
+ "name": "throw warning",
229
+ "func": "node.warn(msg?.payload);\n",
230
+ "outputs": 0,
231
+ "timeout": 0,
232
+ "noerr": 0,
233
+ "initialize": "",
234
+ "finalize": "",
235
+ "libs": [],
236
+ "x": 420,
237
+ "y": 160,
238
+ "wires": []
239
+ },
240
+ {
241
+ "id": "86c429715975c781",
242
+ "type": "function",
243
+ "z": "ec796389903c1d0f",
197
244
  "name": "merge configs",
198
- "func": "// const _t = global.get('libTracer');\n// _t.init(node, msg, flow.get('enableTracer')); \n\nlet config = flow.get('mergedConfig');\nconfig = lodash.merge(config, msg.payload);\n// _t.trace(node, msg, 'config', lodash.cloneDeep(config));\n\nmsg.payload = config;\nreturn msg;\n",
245
+ "func": "// const _t = global.get('libTracer');\n// _t.init(node, msg, flow.get('enableTracer')); \n\nlet config = flow.get('mergedConfig');\n\nconfig = lodash.merge(config, msg.payload);\n// _t.trace(node, msg, 'config', lodash.cloneDeep(config));\n\n\n// Last config file read?\nif (msg.parts.index == msg.parts.count - 1) {\n msg.topic = 'config';\n msg.payload = config;\n delete msg.parts;\n delete msg.filename;\n\n node.log(`Merged ALL configs:\\n ${JSON.stringify(msg.payload, null, 2)}`);\n return msg;\n}\n",
199
246
  "outputs": 1,
200
247
  "timeout": 0,
201
248
  "noerr": 0,
@@ -208,49 +255,60 @@
208
255
  }
209
256
  ],
210
257
  "x": 220,
211
- "y": 240,
258
+ "y": 420,
212
259
  "wires": [
213
260
  []
214
261
  ]
215
262
  },
216
263
  {
217
- "id": "3c563aca59369641",
264
+ "id": "aa017e7a5d0c867e",
218
265
  "type": "function",
219
- "z": "0d7c13f79e4a7aee",
220
- "name": "get config dir and filename pattern",
221
- "func": "const configDir = msg.configDir ?? null;\nif (typeof configDir === 'string' && configDir.trim().length > 0) {\n msg.configDir = configDir;\n}\nelse {\n msg.configDir = env.get('configDir');\n}\n\nconst configFilePattern = msg.configFilePattern ?? null;\nif (typeof configFilePattern === 'string' && configFilePattern.trim().length > 0) {\n msg.configFilePattern = configFilePattern;\n}\nelse {\n msg.configFilePattern = env.get('configFilePattern');\n}\n\nmsg.topic = 'configFiles';\nreturn msg;",
266
+ "z": "ec796389903c1d0f",
267
+ "name": "throw error",
268
+ "func": "node.error(msg?.payload, msg);\n",
269
+ "outputs": 0,
270
+ "timeout": 0,
271
+ "noerr": 0,
272
+ "initialize": "",
273
+ "finalize": "",
274
+ "libs": [],
275
+ "x": 410,
276
+ "y": 120,
277
+ "wires": []
278
+ },
279
+ {
280
+ "id": "7f1816d008fa2b70",
281
+ "type": "function",
282
+ "z": "ec796389903c1d0f",
283
+ "name": "log",
284
+ "func": "node.log(`Reading file '${msg.payload}' ...`)\nreturn msg;",
222
285
  "outputs": 1,
223
286
  "timeout": 0,
224
287
  "noerr": 0,
225
288
  "initialize": "",
226
289
  "finalize": "",
227
290
  "libs": [],
228
- "x": 280,
229
- "y": 40,
291
+ "x": 570,
292
+ "y": 260,
230
293
  "wires": [
231
- [
232
- "2b5d6f663d02b82f"
233
- ]
294
+ []
234
295
  ]
235
296
  },
236
297
  {
237
- "id": "73c98834cb90c714",
298
+ "id": "8639805af947d0c3",
238
299
  "type": "function",
239
- "z": "0d7c13f79e4a7aee",
240
- "name": "status",
241
- "func": "const count = Array.isArray(msg.payload) ? msg.payload.length : 0;\n\nlet statusText = `No config files found in '${msg.configDir}' with pattern '${msg.configFilePattern}' -> using default config`;\nif (count > 0) {\n statusText = `Found ${count} config files in '${msg.configDir}' with pattern '${msg.configFilePattern}'`;\n}\nconst status = { fill: \"blue\", shape: \"ring\", text: statusText };\n\nreturn [ msg, { payload: status} ];",
242
- "outputs": 2,
300
+ "z": "ec796389903c1d0f",
301
+ "name": "log",
302
+ "func": "node.log(`Config read from '${msg.filename}':\\n${JSON.stringify(msg.payload, null, 2)}`)\nreturn msg;",
303
+ "outputs": 1,
243
304
  "timeout": 0,
244
305
  "noerr": 0,
245
306
  "initialize": "",
246
307
  "finalize": "",
247
308
  "libs": [],
248
- "x": 190,
249
- "y": 120,
309
+ "x": 570,
310
+ "y": 340,
250
311
  "wires": [
251
- [
252
- "e2a872db2d9086e6"
253
- ],
254
312
  []
255
313
  ]
256
314
  }