node-red-contrib-influxdb3 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.
- package/LICENSE +22 -0
- package/README.md +339 -0
- package/examples/basic-flow.json +170 -0
- package/examples/mqtt-to-influx.json +134 -0
- package/influxdb3.html +204 -0
- package/influxdb3.js +207 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
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.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# node-red-contrib-influxdb3
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/node-red-contrib-influxdb3)
|
|
4
|
+
[](https://www.npmjs.com/package/node-red-contrib-influxdb3)
|
|
5
|
+
[](https://flows.nodered.org/node/node-red-contrib-influxdb3)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
Node-RED nodes for writing data to InfluxDB v3.
|
|
9
|
+
|
|
10
|
+
This package provides Node-RED integration with InfluxDB v3 using the official [@influxdata/influxdb3-client](https://github.com/InfluxCommunity/influxdb3-js) JavaScript library.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
### From npm (when published)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd ~/.node-red
|
|
18
|
+
npm install node-red-contrib-influxdb3
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### From local directory
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cd ~/.node-red
|
|
25
|
+
npm install /path/to/node-red-contrib-influxdb3
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Development Installation
|
|
29
|
+
|
|
30
|
+
1. Clone this repository
|
|
31
|
+
2. Install dependencies:
|
|
32
|
+
```bash
|
|
33
|
+
npm install
|
|
34
|
+
```
|
|
35
|
+
3. Link to your Node-RED installation:
|
|
36
|
+
```bash
|
|
37
|
+
cd ~/.node-red
|
|
38
|
+
npm install /path/to/node-red-contrib-influxdb3
|
|
39
|
+
```
|
|
40
|
+
4. Restart Node-RED
|
|
41
|
+
|
|
42
|
+
## Nodes
|
|
43
|
+
|
|
44
|
+
This package includes two nodes:
|
|
45
|
+
|
|
46
|
+
### InfluxDB v3 Config Node
|
|
47
|
+
|
|
48
|
+
A configuration node that stores connection details for your InfluxDB v3 instance.
|
|
49
|
+
|
|
50
|
+
**Configuration:**
|
|
51
|
+
- **Name**: A friendly name for the connection
|
|
52
|
+
- **Host**: Your InfluxDB v3 host URL (e.g., `https://us-east-1-1.aws.cloud2.influxdata.com`)
|
|
53
|
+
- **Token**: Your InfluxDB v3 authentication token
|
|
54
|
+
- **Database**: The default database (bucket) name
|
|
55
|
+
|
|
56
|
+
### InfluxDB v3 Write Node
|
|
57
|
+
|
|
58
|
+
Writes data points to InfluxDB v3.
|
|
59
|
+
|
|
60
|
+
**Configuration:**
|
|
61
|
+
- **Connection**: Select an InfluxDB v3 config node
|
|
62
|
+
- **Name**: Optional node name
|
|
63
|
+
- **Measurement**: Default measurement name (can be overridden by `msg.measurement`)
|
|
64
|
+
- **Database**: Optional database override (uses connection default if not set)
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### Input Message Formats
|
|
69
|
+
|
|
70
|
+
The write node accepts data in multiple formats:
|
|
71
|
+
|
|
72
|
+
#### 1. Line Protocol String
|
|
73
|
+
|
|
74
|
+
Send data as a string in InfluxDB line protocol format:
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
msg.payload = "temperature,location=room1 value=21.5";
|
|
78
|
+
return msg;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### 2. Object with Fields and Tags
|
|
82
|
+
|
|
83
|
+
Send an object with explicit `fields` and `tags` properties:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
msg.measurement = "temperature";
|
|
87
|
+
msg.payload = {
|
|
88
|
+
fields: {
|
|
89
|
+
value: 21.5,
|
|
90
|
+
humidity: 65
|
|
91
|
+
},
|
|
92
|
+
tags: {
|
|
93
|
+
location: "room1",
|
|
94
|
+
sensor: "dht22"
|
|
95
|
+
},
|
|
96
|
+
timestamp: Date.now() // optional
|
|
97
|
+
};
|
|
98
|
+
return msg;
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### 3. Simplified Object Format
|
|
102
|
+
|
|
103
|
+
Send an object where all properties (except 'tags' and 'timestamp') are treated as fields:
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
msg.measurement = "environment";
|
|
107
|
+
msg.payload = {
|
|
108
|
+
temperature: 21.5,
|
|
109
|
+
humidity: 65,
|
|
110
|
+
pressure: 1013.25,
|
|
111
|
+
tags: {
|
|
112
|
+
room: "bedroom",
|
|
113
|
+
floor: "2"
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
return msg;
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Data Types
|
|
120
|
+
|
|
121
|
+
When using object format, the node automatically handles data types:
|
|
122
|
+
- **Numbers**:
|
|
123
|
+
- Integers are written as integer fields
|
|
124
|
+
- Floats are written as float fields
|
|
125
|
+
- **Booleans**: Written as boolean fields
|
|
126
|
+
- **Strings**: Written as string fields
|
|
127
|
+
- **Tags**: Always converted to strings
|
|
128
|
+
|
|
129
|
+
### Message Properties
|
|
130
|
+
|
|
131
|
+
The following message properties can be used to override node configuration:
|
|
132
|
+
|
|
133
|
+
- `msg.measurement` - Override the measurement name
|
|
134
|
+
- `msg.database` - Override the database name
|
|
135
|
+
- `msg.timestamp` - Set the timestamp for the data point (Date object or milliseconds)
|
|
136
|
+
|
|
137
|
+
## Examples
|
|
138
|
+
|
|
139
|
+
### Example 1: Temperature Sensor
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
// Function node
|
|
143
|
+
msg.measurement = "temperature";
|
|
144
|
+
msg.payload = {
|
|
145
|
+
value: 21.5,
|
|
146
|
+
tags: {
|
|
147
|
+
location: "living_room",
|
|
148
|
+
sensor_id: "temp_001"
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
return msg;
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Example 2: Multi-Sensor Data
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
// Function node
|
|
158
|
+
msg.measurement = "environment";
|
|
159
|
+
msg.payload = {
|
|
160
|
+
fields: {
|
|
161
|
+
temperature: 22.3,
|
|
162
|
+
humidity: 58,
|
|
163
|
+
co2: 412,
|
|
164
|
+
light: 850
|
|
165
|
+
},
|
|
166
|
+
tags: {
|
|
167
|
+
room: "office",
|
|
168
|
+
floor: "3",
|
|
169
|
+
building: "A"
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
return msg;
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Example 3: MQTT to InfluxDB
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
[MQTT In] --> [JSON Parse] --> [Function] --> [InfluxDB v3 Write]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Function node:
|
|
182
|
+
```javascript
|
|
183
|
+
// Parse MQTT topic for location
|
|
184
|
+
const location = msg.topic.split('/')[1];
|
|
185
|
+
|
|
186
|
+
msg.measurement = "sensor_data";
|
|
187
|
+
msg.payload = {
|
|
188
|
+
temperature: msg.payload.temp,
|
|
189
|
+
humidity: msg.payload.hum,
|
|
190
|
+
tags: {
|
|
191
|
+
location: location
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
return msg;
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Example 4: Using Line Protocol
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
// Function node - direct line protocol
|
|
201
|
+
const location = "warehouse";
|
|
202
|
+
const temp = 18.5;
|
|
203
|
+
const humidity = 72;
|
|
204
|
+
|
|
205
|
+
msg.payload = `climate,location=${location} temperature=${temp},humidity=${humidity}`;
|
|
206
|
+
return msg;
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Example 5: Multiple Databases
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
// Write to different databases based on data type
|
|
213
|
+
if (msg.payload.type === "critical") {
|
|
214
|
+
msg.database = "critical-events";
|
|
215
|
+
} else {
|
|
216
|
+
msg.database = "general-logs";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
msg.measurement = "events";
|
|
220
|
+
msg.payload = {
|
|
221
|
+
severity: msg.payload.severity,
|
|
222
|
+
message: msg.payload.msg,
|
|
223
|
+
tags: {
|
|
224
|
+
type: msg.payload.type
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
return msg;
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Sample Flow
|
|
231
|
+
|
|
232
|
+
Import this flow into Node-RED to get started:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
[
|
|
236
|
+
{
|
|
237
|
+
"id": "influxdb3-config-node",
|
|
238
|
+
"type": "influxdb3-config",
|
|
239
|
+
"name": "My InfluxDB v3",
|
|
240
|
+
"host": "https://us-east-1-1.aws.cloud2.influxdata.com",
|
|
241
|
+
"database": "my-database"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"id": "inject-node",
|
|
245
|
+
"type": "inject",
|
|
246
|
+
"name": "Generate Data",
|
|
247
|
+
"props": [{"p": "payload"}],
|
|
248
|
+
"repeat": "5",
|
|
249
|
+
"topic": "",
|
|
250
|
+
"payload": "",
|
|
251
|
+
"payloadType": "date",
|
|
252
|
+
"x": 140,
|
|
253
|
+
"y": 100,
|
|
254
|
+
"wires": [["function-node"]]
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"id": "function-node",
|
|
258
|
+
"type": "function",
|
|
259
|
+
"name": "Format Data",
|
|
260
|
+
"func": "msg.measurement = 'temperature';\nmsg.payload = {\n value: 20 + Math.random() * 10,\n tags: {\n location: 'office'\n }\n};\nreturn msg;",
|
|
261
|
+
"x": 320,
|
|
262
|
+
"y": 100,
|
|
263
|
+
"wires": [["influxdb3-write-node"]]
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"id": "influxdb3-write-node",
|
|
267
|
+
"type": "influxdb3-write",
|
|
268
|
+
"name": "Write to InfluxDB",
|
|
269
|
+
"influxdb": "influxdb3-config-node",
|
|
270
|
+
"measurement": "",
|
|
271
|
+
"database": "",
|
|
272
|
+
"x": 530,
|
|
273
|
+
"y": 100,
|
|
274
|
+
"wires": [["debug-node"]]
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"id": "debug-node",
|
|
278
|
+
"type": "debug",
|
|
279
|
+
"name": "Debug",
|
|
280
|
+
"x": 730,
|
|
281
|
+
"y": 100,
|
|
282
|
+
"wires": []
|
|
283
|
+
}
|
|
284
|
+
]
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Configuration with Environment Variables
|
|
288
|
+
|
|
289
|
+
You can use environment variables in the configuration node:
|
|
290
|
+
|
|
291
|
+
- `INFLUX_HOST` - InfluxDB v3 host URL
|
|
292
|
+
- `INFLUX_TOKEN` - Authentication token
|
|
293
|
+
- `INFLUX_DATABASE` - Default database name
|
|
294
|
+
|
|
295
|
+
Simply reference them in the Node-RED UI using `${INFLUX_HOST}` syntax (if using Node-RED environment variable substitution).
|
|
296
|
+
|
|
297
|
+
## Troubleshooting
|
|
298
|
+
|
|
299
|
+
### Connection Issues
|
|
300
|
+
|
|
301
|
+
- Verify your host URL is correct and includes `https://`
|
|
302
|
+
- Check that your token has write permissions for the database
|
|
303
|
+
- Ensure the database name exists in your InfluxDB v3 instance
|
|
304
|
+
|
|
305
|
+
### Data Not Appearing
|
|
306
|
+
|
|
307
|
+
- Check the node status - it should show "written" briefly after successful writes
|
|
308
|
+
- Verify at least one field is provided (InfluxDB requires at least one field)
|
|
309
|
+
- Check that field values are not null or undefined
|
|
310
|
+
|
|
311
|
+
### Error Messages
|
|
312
|
+
|
|
313
|
+
The node will display error status and log details to the Node-RED debug panel:
|
|
314
|
+
- **"no config"** - The InfluxDB v3 config node is not selected
|
|
315
|
+
- **"error"** - Check the debug panel for details
|
|
316
|
+
|
|
317
|
+
## Requirements
|
|
318
|
+
|
|
319
|
+
- Node-RED v2.0.0 or higher
|
|
320
|
+
- InfluxDB v3 instance (Cloud or Edge)
|
|
321
|
+
|
|
322
|
+
## License
|
|
323
|
+
|
|
324
|
+
MIT
|
|
325
|
+
|
|
326
|
+
## Author
|
|
327
|
+
|
|
328
|
+
Your Name
|
|
329
|
+
|
|
330
|
+
## Links
|
|
331
|
+
|
|
332
|
+
- [InfluxDB v3 JavaScript Client](https://github.com/InfluxCommunity/influxdb3-js)
|
|
333
|
+
- [InfluxDB v3 Documentation](https://docs.influxdata.com/influxdb/v3/)
|
|
334
|
+
- [Node-RED](https://nodered.org/)
|
|
335
|
+
|
|
336
|
+
## Contributing
|
|
337
|
+
|
|
338
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
339
|
+
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "example-flow",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "InfluxDB v3 Example",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "Example flow showing how to write data to InfluxDB v3"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "inject-temp",
|
|
11
|
+
"type": "inject",
|
|
12
|
+
"z": "example-flow",
|
|
13
|
+
"name": "Generate Temperature Data",
|
|
14
|
+
"props": [
|
|
15
|
+
{
|
|
16
|
+
"p": "payload"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"repeat": "5",
|
|
20
|
+
"crontab": "",
|
|
21
|
+
"once": false,
|
|
22
|
+
"onceDelay": 0.1,
|
|
23
|
+
"topic": "",
|
|
24
|
+
"payload": "",
|
|
25
|
+
"payloadType": "date",
|
|
26
|
+
"x": 180,
|
|
27
|
+
"y": 100,
|
|
28
|
+
"wires": [
|
|
29
|
+
[
|
|
30
|
+
"format-temp"
|
|
31
|
+
]
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "format-temp",
|
|
36
|
+
"type": "function",
|
|
37
|
+
"z": "example-flow",
|
|
38
|
+
"name": "Format Temperature",
|
|
39
|
+
"func": "// Generate random temperature between 18 and 28 degrees\nconst temp = 18 + Math.random() * 10;\nconst humidity = 40 + Math.random() * 30;\n\nmsg.measurement = 'environment';\nmsg.payload = {\n temperature: parseFloat(temp.toFixed(2)),\n humidity: parseFloat(humidity.toFixed(2)),\n tags: {\n location: 'office',\n sensor: 'dht22_01'\n }\n};\n\nreturn msg;",
|
|
40
|
+
"outputs": 1,
|
|
41
|
+
"noerr": 0,
|
|
42
|
+
"initialize": "",
|
|
43
|
+
"finalize": "",
|
|
44
|
+
"libs": [],
|
|
45
|
+
"x": 420,
|
|
46
|
+
"y": 100,
|
|
47
|
+
"wires": [
|
|
48
|
+
[
|
|
49
|
+
"write-influx",
|
|
50
|
+
"debug-payload"
|
|
51
|
+
]
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "write-influx",
|
|
56
|
+
"type": "influxdb3-write",
|
|
57
|
+
"z": "example-flow",
|
|
58
|
+
"influxdb": "influxdb3-config",
|
|
59
|
+
"name": "Write to InfluxDB v3",
|
|
60
|
+
"measurement": "",
|
|
61
|
+
"database": "",
|
|
62
|
+
"x": 660,
|
|
63
|
+
"y": 100,
|
|
64
|
+
"wires": [
|
|
65
|
+
[
|
|
66
|
+
"debug-result"
|
|
67
|
+
]
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "debug-payload",
|
|
72
|
+
"type": "debug",
|
|
73
|
+
"z": "example-flow",
|
|
74
|
+
"name": "Debug Payload",
|
|
75
|
+
"active": true,
|
|
76
|
+
"tosidebar": true,
|
|
77
|
+
"console": false,
|
|
78
|
+
"tostatus": false,
|
|
79
|
+
"complete": "payload",
|
|
80
|
+
"targetType": "msg",
|
|
81
|
+
"statusVal": "",
|
|
82
|
+
"statusType": "auto",
|
|
83
|
+
"x": 660,
|
|
84
|
+
"y": 60,
|
|
85
|
+
"wires": []
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": "debug-result",
|
|
89
|
+
"type": "debug",
|
|
90
|
+
"z": "example-flow",
|
|
91
|
+
"name": "Debug Result",
|
|
92
|
+
"active": true,
|
|
93
|
+
"tosidebar": true,
|
|
94
|
+
"console": false,
|
|
95
|
+
"tostatus": false,
|
|
96
|
+
"complete": "true",
|
|
97
|
+
"targetType": "full",
|
|
98
|
+
"statusVal": "",
|
|
99
|
+
"statusType": "auto",
|
|
100
|
+
"x": 870,
|
|
101
|
+
"y": 100,
|
|
102
|
+
"wires": []
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"id": "inject-line-protocol",
|
|
106
|
+
"type": "inject",
|
|
107
|
+
"z": "example-flow",
|
|
108
|
+
"name": "Line Protocol Example",
|
|
109
|
+
"props": [
|
|
110
|
+
{
|
|
111
|
+
"p": "payload"
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"repeat": "",
|
|
115
|
+
"crontab": "",
|
|
116
|
+
"once": false,
|
|
117
|
+
"onceDelay": 0.1,
|
|
118
|
+
"topic": "",
|
|
119
|
+
"payload": "weather,location=garden temperature=18.5,humidity=75,pressure=1013.25",
|
|
120
|
+
"payloadType": "str",
|
|
121
|
+
"x": 180,
|
|
122
|
+
"y": 200,
|
|
123
|
+
"wires": [
|
|
124
|
+
[
|
|
125
|
+
"write-influx-lp"
|
|
126
|
+
]
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"id": "write-influx-lp",
|
|
131
|
+
"type": "influxdb3-write",
|
|
132
|
+
"z": "example-flow",
|
|
133
|
+
"influxdb": "influxdb3-config",
|
|
134
|
+
"name": "Write Line Protocol",
|
|
135
|
+
"measurement": "",
|
|
136
|
+
"database": "",
|
|
137
|
+
"x": 450,
|
|
138
|
+
"y": 200,
|
|
139
|
+
"wires": [
|
|
140
|
+
[
|
|
141
|
+
"debug-lp"
|
|
142
|
+
]
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"id": "debug-lp",
|
|
147
|
+
"type": "debug",
|
|
148
|
+
"z": "example-flow",
|
|
149
|
+
"name": "Debug LP",
|
|
150
|
+
"active": true,
|
|
151
|
+
"tosidebar": true,
|
|
152
|
+
"console": false,
|
|
153
|
+
"tostatus": false,
|
|
154
|
+
"complete": "true",
|
|
155
|
+
"targetType": "full",
|
|
156
|
+
"statusVal": "",
|
|
157
|
+
"statusType": "auto",
|
|
158
|
+
"x": 660,
|
|
159
|
+
"y": 200,
|
|
160
|
+
"wires": []
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"id": "influxdb3-config",
|
|
164
|
+
"type": "influxdb3-config",
|
|
165
|
+
"name": "My InfluxDB v3",
|
|
166
|
+
"host": "https://us-east-1-1.aws.cloud2.influxdata.com",
|
|
167
|
+
"database": "my-database"
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "mqtt-flow",
|
|
4
|
+
"type": "tab",
|
|
5
|
+
"label": "MQTT to InfluxDB v3",
|
|
6
|
+
"disabled": false,
|
|
7
|
+
"info": "Example flow showing how to capture MQTT data and write to InfluxDB v3"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "mqtt-in",
|
|
11
|
+
"type": "mqtt in",
|
|
12
|
+
"z": "mqtt-flow",
|
|
13
|
+
"name": "MQTT Sensor Data",
|
|
14
|
+
"topic": "sensors/+/data",
|
|
15
|
+
"qos": "0",
|
|
16
|
+
"datatype": "json",
|
|
17
|
+
"broker": "mqtt-broker",
|
|
18
|
+
"nl": false,
|
|
19
|
+
"rap": false,
|
|
20
|
+
"inputs": 0,
|
|
21
|
+
"x": 140,
|
|
22
|
+
"y": 100,
|
|
23
|
+
"wires": [
|
|
24
|
+
[
|
|
25
|
+
"parse-mqtt"
|
|
26
|
+
]
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "parse-mqtt",
|
|
31
|
+
"type": "function",
|
|
32
|
+
"z": "mqtt-flow",
|
|
33
|
+
"name": "Parse MQTT Data",
|
|
34
|
+
"func": "// Extract sensor ID from topic (sensors/SENSOR_ID/data)\nconst topicParts = msg.topic.split('/');\nconst sensorId = topicParts[1];\n\n// Prepare data for InfluxDB\nmsg.measurement = 'sensor_readings';\nmsg.payload = {\n fields: {\n temperature: msg.payload.temperature,\n humidity: msg.payload.humidity,\n battery: msg.payload.battery\n },\n tags: {\n sensor_id: sensorId,\n location: msg.payload.location || 'unknown'\n }\n};\n\nreturn msg;",
|
|
35
|
+
"outputs": 1,
|
|
36
|
+
"noerr": 0,
|
|
37
|
+
"initialize": "",
|
|
38
|
+
"finalize": "",
|
|
39
|
+
"libs": [],
|
|
40
|
+
"x": 360,
|
|
41
|
+
"y": 100,
|
|
42
|
+
"wires": [
|
|
43
|
+
[
|
|
44
|
+
"influx-write",
|
|
45
|
+
"debug-mqtt"
|
|
46
|
+
]
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "influx-write",
|
|
51
|
+
"type": "influxdb3-write",
|
|
52
|
+
"z": "mqtt-flow",
|
|
53
|
+
"influxdb": "influxdb3-config",
|
|
54
|
+
"name": "Store in InfluxDB",
|
|
55
|
+
"measurement": "",
|
|
56
|
+
"database": "",
|
|
57
|
+
"x": 590,
|
|
58
|
+
"y": 100,
|
|
59
|
+
"wires": [
|
|
60
|
+
[
|
|
61
|
+
"debug-success"
|
|
62
|
+
]
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "debug-mqtt",
|
|
67
|
+
"type": "debug",
|
|
68
|
+
"z": "mqtt-flow",
|
|
69
|
+
"name": "MQTT Data",
|
|
70
|
+
"active": true,
|
|
71
|
+
"tosidebar": true,
|
|
72
|
+
"console": false,
|
|
73
|
+
"tostatus": false,
|
|
74
|
+
"complete": "payload",
|
|
75
|
+
"targetType": "msg",
|
|
76
|
+
"statusVal": "",
|
|
77
|
+
"statusType": "auto",
|
|
78
|
+
"x": 570,
|
|
79
|
+
"y": 60,
|
|
80
|
+
"wires": []
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "debug-success",
|
|
84
|
+
"type": "debug",
|
|
85
|
+
"z": "mqtt-flow",
|
|
86
|
+
"name": "Write Result",
|
|
87
|
+
"active": true,
|
|
88
|
+
"tosidebar": true,
|
|
89
|
+
"console": false,
|
|
90
|
+
"tostatus": false,
|
|
91
|
+
"complete": "true",
|
|
92
|
+
"targetType": "full",
|
|
93
|
+
"statusVal": "",
|
|
94
|
+
"statusType": "auto",
|
|
95
|
+
"x": 790,
|
|
96
|
+
"y": 100,
|
|
97
|
+
"wires": []
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": "mqtt-broker",
|
|
101
|
+
"type": "mqtt-broker",
|
|
102
|
+
"name": "Local MQTT Broker",
|
|
103
|
+
"broker": "localhost",
|
|
104
|
+
"port": "1883",
|
|
105
|
+
"clientid": "",
|
|
106
|
+
"autoConnect": true,
|
|
107
|
+
"usetls": false,
|
|
108
|
+
"protocolVersion": "4",
|
|
109
|
+
"keepalive": "60",
|
|
110
|
+
"cleansession": true,
|
|
111
|
+
"birthTopic": "",
|
|
112
|
+
"birthQos": "0",
|
|
113
|
+
"birthPayload": "",
|
|
114
|
+
"birthMsg": {},
|
|
115
|
+
"closeTopic": "",
|
|
116
|
+
"closeQos": "0",
|
|
117
|
+
"closePayload": "",
|
|
118
|
+
"closeMsg": {},
|
|
119
|
+
"willTopic": "",
|
|
120
|
+
"willQos": "0",
|
|
121
|
+
"willPayload": "",
|
|
122
|
+
"willMsg": {},
|
|
123
|
+
"userProps": "",
|
|
124
|
+
"sessionExpiry": ""
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": "influxdb3-config",
|
|
128
|
+
"type": "influxdb3-config",
|
|
129
|
+
"name": "My InfluxDB v3",
|
|
130
|
+
"host": "https://us-east-1-1.aws.cloud2.influxdata.com",
|
|
131
|
+
"database": "my-database"
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
|
package/influxdb3.html
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
InfluxDB v3 Configuration Node
|
|
3
|
+
-->
|
|
4
|
+
<script type="text/javascript">
|
|
5
|
+
RED.nodes.registerType('influxdb3-config', {
|
|
6
|
+
category: 'config',
|
|
7
|
+
defaults: {
|
|
8
|
+
name: { value: '' },
|
|
9
|
+
host: { value: '', required: true },
|
|
10
|
+
database: { value: '', required: true }
|
|
11
|
+
},
|
|
12
|
+
credentials: {
|
|
13
|
+
token: { type: 'password' }
|
|
14
|
+
},
|
|
15
|
+
label: function() {
|
|
16
|
+
return this.name || this.host || 'InfluxDB v3';
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<script type="text/html" data-template-name="influxdb3-config">
|
|
22
|
+
<div class="form-row">
|
|
23
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
24
|
+
<input type="text" id="node-config-input-name" placeholder="Connection name">
|
|
25
|
+
</div>
|
|
26
|
+
<div class="form-row">
|
|
27
|
+
<label for="node-config-input-host"><i class="fa fa-server"></i> Host</label>
|
|
28
|
+
<input type="text" id="node-config-input-host" placeholder="https://us-east-1-1.aws.cloud2.influxdata.com">
|
|
29
|
+
</div>
|
|
30
|
+
<div class="form-row">
|
|
31
|
+
<label for="node-config-input-token"><i class="fa fa-key"></i> Token</label>
|
|
32
|
+
<input type="password" id="node-config-input-token">
|
|
33
|
+
</div>
|
|
34
|
+
<div class="form-row">
|
|
35
|
+
<label for="node-config-input-database"><i class="fa fa-database"></i> Database</label>
|
|
36
|
+
<input type="text" id="node-config-input-database" placeholder="my-database">
|
|
37
|
+
</div>
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<script type="text/html" data-help-name="influxdb3-config">
|
|
41
|
+
<p>Configuration node for InfluxDB v3 connection.</p>
|
|
42
|
+
<h3>Details</h3>
|
|
43
|
+
<p>This configuration node stores the connection details for an InfluxDB v3 instance.</p>
|
|
44
|
+
<dl class="message-properties">
|
|
45
|
+
<dt>Name <span class="property-type">string</span></dt>
|
|
46
|
+
<dd>A friendly name for this connection</dd>
|
|
47
|
+
<dt>Host <span class="property-type">string</span></dt>
|
|
48
|
+
<dd>The InfluxDB v3 host URL (e.g., https://us-east-1-1.aws.cloud2.influxdata.com)</dd>
|
|
49
|
+
<dt>Token <span class="property-type">string</span></dt>
|
|
50
|
+
<dd>The authentication token for accessing InfluxDB v3</dd>
|
|
51
|
+
<dt>Database <span class="property-type">string</span></dt>
|
|
52
|
+
<dd>The default database (bucket) name to write to</dd>
|
|
53
|
+
</dl>
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<!--
|
|
57
|
+
InfluxDB v3 Write Node
|
|
58
|
+
-->
|
|
59
|
+
<script type="text/javascript">
|
|
60
|
+
RED.nodes.registerType('influxdb3-write', {
|
|
61
|
+
category: 'storage',
|
|
62
|
+
color: '#9ea8db',
|
|
63
|
+
defaults: {
|
|
64
|
+
influxdb: { type: 'influxdb3-config', required: true },
|
|
65
|
+
name: { value: '' },
|
|
66
|
+
measurement: { value: '' },
|
|
67
|
+
database: { value: '' }
|
|
68
|
+
},
|
|
69
|
+
inputs: 1,
|
|
70
|
+
outputs: 1,
|
|
71
|
+
icon: 'db.png',
|
|
72
|
+
label: function() {
|
|
73
|
+
return this.name || 'influxdb3 write';
|
|
74
|
+
},
|
|
75
|
+
labelStyle: function() {
|
|
76
|
+
return this.name ? 'node_label_italic' : '';
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<script type="text/html" data-template-name="influxdb3-write">
|
|
82
|
+
<div class="form-row">
|
|
83
|
+
<label for="node-input-influxdb"><i class="fa fa-server"></i> Connection</label>
|
|
84
|
+
<input type="text" id="node-input-influxdb">
|
|
85
|
+
</div>
|
|
86
|
+
<div class="form-row">
|
|
87
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
88
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
89
|
+
</div>
|
|
90
|
+
<div class="form-row">
|
|
91
|
+
<label for="node-input-measurement"><i class="fa fa-chart-line"></i> Measurement</label>
|
|
92
|
+
<input type="text" id="node-input-measurement" placeholder="Leave empty to use msg.measurement">
|
|
93
|
+
</div>
|
|
94
|
+
<div class="form-row">
|
|
95
|
+
<label for="node-input-database"><i class="fa fa-database"></i> Database</label>
|
|
96
|
+
<input type="text" id="node-input-database" placeholder="Override default database (optional)">
|
|
97
|
+
</div>
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<script type="text/html" data-help-name="influxdb3-write">
|
|
101
|
+
<p>Writes data to InfluxDB v3.</p>
|
|
102
|
+
|
|
103
|
+
<h3>Inputs</h3>
|
|
104
|
+
<dl class="message-properties">
|
|
105
|
+
<dt>payload <span class="property-type">string | object</span></dt>
|
|
106
|
+
<dd>The data to write. Can be either:
|
|
107
|
+
<ul>
|
|
108
|
+
<li>A string in line protocol format</li>
|
|
109
|
+
<li>An object with <code>fields</code> (required) and optionally <code>tags</code> and <code>timestamp</code></li>
|
|
110
|
+
</ul>
|
|
111
|
+
</dd>
|
|
112
|
+
<dt class="optional">measurement <span class="property-type">string</span></dt>
|
|
113
|
+
<dd>Override the measurement name configured in the node</dd>
|
|
114
|
+
<dt class="optional">database <span class="property-type">string</span></dt>
|
|
115
|
+
<dd>Override the database configured in the node or connection</dd>
|
|
116
|
+
<dt class="optional">timestamp <span class="property-type">Date | number</span></dt>
|
|
117
|
+
<dd>Timestamp for the data point (if not in payload)</dd>
|
|
118
|
+
</dl>
|
|
119
|
+
|
|
120
|
+
<h3>Outputs</h3>
|
|
121
|
+
<dl class="message-properties">
|
|
122
|
+
<dt>payload <span class="property-type">object</span></dt>
|
|
123
|
+
<dd>The original payload is passed through</dd>
|
|
124
|
+
</dl>
|
|
125
|
+
|
|
126
|
+
<h3>Details</h3>
|
|
127
|
+
<p>This node writes data to InfluxDB v3. The data can be provided in two formats:</p>
|
|
128
|
+
|
|
129
|
+
<h4>Line Protocol Format</h4>
|
|
130
|
+
<p>Send a string in line protocol format:</p>
|
|
131
|
+
<pre>msg.payload = "temperature,location=room1 value=21.5";</pre>
|
|
132
|
+
|
|
133
|
+
<h4>Object Format</h4>
|
|
134
|
+
<p>Send an object with fields and optionally tags:</p>
|
|
135
|
+
<pre>{
|
|
136
|
+
"fields": {
|
|
137
|
+
"temperature": 21.5,
|
|
138
|
+
"humidity": 65
|
|
139
|
+
},
|
|
140
|
+
"tags": {
|
|
141
|
+
"location": "room1",
|
|
142
|
+
"sensor": "dht22"
|
|
143
|
+
},
|
|
144
|
+
"timestamp": Date.now()
|
|
145
|
+
}</pre>
|
|
146
|
+
|
|
147
|
+
<h4>Simplified Object Format</h4>
|
|
148
|
+
<p>Send an object where all properties (except 'tags' and 'timestamp') are treated as fields:</p>
|
|
149
|
+
<pre>{
|
|
150
|
+
"temperature": 21.5,
|
|
151
|
+
"humidity": 65,
|
|
152
|
+
"tags": {
|
|
153
|
+
"location": "room1"
|
|
154
|
+
}
|
|
155
|
+
}</pre>
|
|
156
|
+
|
|
157
|
+
<h3>Configuration</h3>
|
|
158
|
+
<dl class="message-properties">
|
|
159
|
+
<dt>Connection</dt>
|
|
160
|
+
<dd>The InfluxDB v3 connection configuration</dd>
|
|
161
|
+
<dt>Measurement</dt>
|
|
162
|
+
<dd>The measurement name to use. Can be overridden by <code>msg.measurement</code></dd>
|
|
163
|
+
<dt>Database</dt>
|
|
164
|
+
<dd>Optional database override. If not set, uses the database from the connection config</dd>
|
|
165
|
+
</dl>
|
|
166
|
+
|
|
167
|
+
<h3>Examples</h3>
|
|
168
|
+
<h4>Example 1: Simple temperature reading</h4>
|
|
169
|
+
<pre>msg.measurement = "temperature";
|
|
170
|
+
msg.payload = {
|
|
171
|
+
"fields": {
|
|
172
|
+
"value": 21.5
|
|
173
|
+
},
|
|
174
|
+
"tags": {
|
|
175
|
+
"location": "living_room"
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
return msg;</pre>
|
|
179
|
+
|
|
180
|
+
<h4>Example 2: Multiple sensor values</h4>
|
|
181
|
+
<pre>msg.measurement = "environment";
|
|
182
|
+
msg.payload = {
|
|
183
|
+
"temperature": 21.5,
|
|
184
|
+
"humidity": 65,
|
|
185
|
+
"pressure": 1013.25,
|
|
186
|
+
"tags": {
|
|
187
|
+
"room": "bedroom",
|
|
188
|
+
"floor": "2"
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
return msg;</pre>
|
|
192
|
+
|
|
193
|
+
<h4>Example 3: Line protocol string</h4>
|
|
194
|
+
<pre>msg.payload = "weather,location=garden temperature=18.5,humidity=75";
|
|
195
|
+
return msg;</pre>
|
|
196
|
+
|
|
197
|
+
<h3>References</h3>
|
|
198
|
+
<ul>
|
|
199
|
+
<li><a href="https://github.com/InfluxCommunity/influxdb3-js">InfluxDB v3 JavaScript Client</a></li>
|
|
200
|
+
<li><a href="https://docs.influxdata.com/influxdb/v3/">InfluxDB v3 Documentation</a></li>
|
|
201
|
+
<li><a href="https://docs.influxdata.com/influxdb/v3/reference/syntax/line-protocol/">Line Protocol Reference</a></li>
|
|
202
|
+
</ul>
|
|
203
|
+
</script>
|
|
204
|
+
|
package/influxdb3.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfluxDB v3 nodes for Node-RED
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
module.exports = function(RED) {
|
|
6
|
+
const { InfluxDBClient, Point } = require('@influxdata/influxdb3-client');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configuration node to hold InfluxDB v3 connection details
|
|
10
|
+
*/
|
|
11
|
+
function InfluxDB3ConfigNode(config) {
|
|
12
|
+
RED.nodes.createNode(this, config);
|
|
13
|
+
|
|
14
|
+
this.host = config.host;
|
|
15
|
+
this.database = config.database;
|
|
16
|
+
this.name = config.name;
|
|
17
|
+
|
|
18
|
+
// Store token as a credential
|
|
19
|
+
this.token = this.credentials.token;
|
|
20
|
+
|
|
21
|
+
// Client instance (will be created on demand)
|
|
22
|
+
this.client = null;
|
|
23
|
+
|
|
24
|
+
// Get or create a client instance
|
|
25
|
+
this.getClient = function() {
|
|
26
|
+
if (!this.client) {
|
|
27
|
+
try {
|
|
28
|
+
this.client = new InfluxDBClient({
|
|
29
|
+
host: this.host,
|
|
30
|
+
token: this.token,
|
|
31
|
+
database: this.database
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error(`Failed to create InfluxDB client: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return this.client;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Clean up on close
|
|
41
|
+
this.on('close', function() {
|
|
42
|
+
if (this.client) {
|
|
43
|
+
try {
|
|
44
|
+
this.client.close();
|
|
45
|
+
this.client = null;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
// Ignore errors on close
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
RED.nodes.registerType('influxdb3-config', InfluxDB3ConfigNode, {
|
|
54
|
+
credentials: {
|
|
55
|
+
token: { type: 'password' }
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* InfluxDB v3 Write Node
|
|
61
|
+
*/
|
|
62
|
+
function InfluxDB3WriteNode(config) {
|
|
63
|
+
RED.nodes.createNode(this, config);
|
|
64
|
+
|
|
65
|
+
this.influxdb = RED.nodes.getNode(config.influxdb);
|
|
66
|
+
this.measurement = config.measurement;
|
|
67
|
+
this.database = config.database;
|
|
68
|
+
|
|
69
|
+
const node = this;
|
|
70
|
+
|
|
71
|
+
if (!this.influxdb) {
|
|
72
|
+
this.error('InfluxDB v3 config not set');
|
|
73
|
+
this.status({ fill: 'red', shape: 'dot', text: 'no config' });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Process incoming messages
|
|
78
|
+
node.on('input', async function(msg, send, done) {
|
|
79
|
+
// For Node-RED 0.x compatibility
|
|
80
|
+
send = send || function() { node.send.apply(node, arguments); };
|
|
81
|
+
done = done || function(err) {
|
|
82
|
+
if (err) {
|
|
83
|
+
node.error(err, msg);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const client = node.influxdb.getClient();
|
|
89
|
+
|
|
90
|
+
// Determine the database to use
|
|
91
|
+
const targetDatabase = msg.database || node.database || node.influxdb.database;
|
|
92
|
+
|
|
93
|
+
if (!targetDatabase) {
|
|
94
|
+
throw new Error('Database not specified');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let lineProtocol;
|
|
98
|
+
|
|
99
|
+
// Check if msg.payload is already in line protocol format
|
|
100
|
+
if (typeof msg.payload === 'string') {
|
|
101
|
+
lineProtocol = msg.payload;
|
|
102
|
+
} else if (msg.payload && typeof msg.payload === 'object') {
|
|
103
|
+
// Build line protocol from payload object
|
|
104
|
+
const measurement = msg.measurement || node.measurement;
|
|
105
|
+
|
|
106
|
+
if (!measurement) {
|
|
107
|
+
throw new Error('Measurement not specified');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const point = new Point(measurement);
|
|
111
|
+
|
|
112
|
+
// Add tags
|
|
113
|
+
if (msg.payload.tags && typeof msg.payload.tags === 'object') {
|
|
114
|
+
for (const [key, value] of Object.entries(msg.payload.tags)) {
|
|
115
|
+
if (value !== null && value !== undefined) {
|
|
116
|
+
point.setTag(key, String(value));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Add fields
|
|
122
|
+
if (msg.payload.fields && typeof msg.payload.fields === 'object') {
|
|
123
|
+
for (const [key, value] of Object.entries(msg.payload.fields)) {
|
|
124
|
+
if (value !== null && value !== undefined) {
|
|
125
|
+
if (typeof value === 'number') {
|
|
126
|
+
if (Number.isInteger(value)) {
|
|
127
|
+
point.setIntegerField(key, value);
|
|
128
|
+
} else {
|
|
129
|
+
point.setFloatField(key, value);
|
|
130
|
+
}
|
|
131
|
+
} else if (typeof value === 'boolean') {
|
|
132
|
+
point.setBooleanField(key, value);
|
|
133
|
+
} else {
|
|
134
|
+
point.setStringField(key, String(value));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
// If no 'fields' property, treat all non-tag properties as fields
|
|
140
|
+
for (const [key, value] of Object.entries(msg.payload)) {
|
|
141
|
+
if (key !== 'tags' && key !== 'timestamp' && value !== null && value !== undefined) {
|
|
142
|
+
if (typeof value === 'number') {
|
|
143
|
+
if (Number.isInteger(value)) {
|
|
144
|
+
point.setIntegerField(key, value);
|
|
145
|
+
} else {
|
|
146
|
+
point.setFloatField(key, value);
|
|
147
|
+
}
|
|
148
|
+
} else if (typeof value === 'boolean') {
|
|
149
|
+
point.setBooleanField(key, value);
|
|
150
|
+
} else if (typeof value !== 'object') {
|
|
151
|
+
point.setStringField(key, String(value));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Add timestamp if provided
|
|
158
|
+
if (msg.payload.timestamp) {
|
|
159
|
+
if (msg.payload.timestamp instanceof Date) {
|
|
160
|
+
point.setTimestamp(msg.payload.timestamp);
|
|
161
|
+
} else if (typeof msg.payload.timestamp === 'number') {
|
|
162
|
+
point.setTimestamp(new Date(msg.payload.timestamp));
|
|
163
|
+
}
|
|
164
|
+
} else if (msg.timestamp) {
|
|
165
|
+
if (msg.timestamp instanceof Date) {
|
|
166
|
+
point.setTimestamp(msg.timestamp);
|
|
167
|
+
} else if (typeof msg.timestamp === 'number') {
|
|
168
|
+
point.setTimestamp(new Date(msg.timestamp));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
lineProtocol = point.toLineProtocol();
|
|
173
|
+
|
|
174
|
+
if (!lineProtocol) {
|
|
175
|
+
throw new Error('No fields to write - at least one field is required');
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
throw new Error('Invalid payload format. Expected string (line protocol) or object with fields');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Write to InfluxDB
|
|
182
|
+
await client.write(lineProtocol, targetDatabase);
|
|
183
|
+
|
|
184
|
+
node.status({ fill: 'green', shape: 'dot', text: 'written' });
|
|
185
|
+
|
|
186
|
+
// Clear status after 3 seconds
|
|
187
|
+
setTimeout(() => {
|
|
188
|
+
node.status({});
|
|
189
|
+
}, 3000);
|
|
190
|
+
|
|
191
|
+
send(msg);
|
|
192
|
+
done();
|
|
193
|
+
|
|
194
|
+
} catch (error) {
|
|
195
|
+
node.status({ fill: 'red', shape: 'dot', text: 'error' });
|
|
196
|
+
done(error);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
node.on('close', function() {
|
|
201
|
+
node.status({});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
RED.nodes.registerType('influxdb3-write', InfluxDB3WriteNode);
|
|
206
|
+
};
|
|
207
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-red-contrib-influxdb3",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Node-RED nodes for InfluxDB v3 integration",
|
|
5
|
+
"main": "influxdb3.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"node-red",
|
|
11
|
+
"influxdb",
|
|
12
|
+
"influxdb3",
|
|
13
|
+
"time-series",
|
|
14
|
+
"database"
|
|
15
|
+
],
|
|
16
|
+
"author": "Stuart B",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/stuartb55/nodered-influxdb3.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/stuartb55/nodered-influxdb3#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/stuartb55/nodered-influxdb3/issues"
|
|
25
|
+
},
|
|
26
|
+
"node-red": {
|
|
27
|
+
"nodes": {
|
|
28
|
+
"influxdb3": "influxdb3.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@influxdata/influxdb3-client": "^1.4.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"node-red": ">=2.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|