├── .gitignore
├── CHANGELOG.md
├── README.md
├── assets
├── screenshotQuery.png
└── screenshotWrite.png
├── package.json
└── src
├── main.html
├── main.js
├── query.html
├── query.js
├── write.html
└── write.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log
3 | node_modules/
4 | yarn.lock
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 1.0.4 (2021-01-25)
4 |
5 | - 🌟 Update influxdb-v2 to 1.0.0, escaping special characters in measurement, tags and fields (see https://docs.influxdata.com/influxdb/v2.0/reference/syntax/line-protocol/#special-characters)
6 |
7 |
8 | ## 1.0.3 (2020-10-12)
9 |
10 | - 🌟 Add support of strings in fields values
11 |
12 |
13 | ## 1.0.2 (2020-05-12)
14 |
15 | - 👩⚖️ Add license informations
16 |
17 |
18 | ## 1.0.1 (2020-05-11)
19 |
20 | - Bump to resolve a problem with NPM
21 |
22 |
23 | ## 1.0.0 (2020-05-11)
24 |
25 | - 🎉 First version
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # node-red-contrib-stackhero-influxdb-v2
2 |
3 | [Node-RED](https://nodered.org) node to read and write to an InfluxDB v2 database.
4 |
5 | **Remember: if you like it, please star it! 🥰**
6 |
7 | Official repository: [https://github.com/stackhero-io/node-red-contrib-stackhero-influxdb-v2](https://github.com/stackhero-io/node-red-contrib-stackhero-influxdb-v2)
8 |
9 |
10 | ## Sponsors
11 |
12 | `node-red-contrib-stackhero-influxdb-v2` is developed by [Stackhero](https://www.stackhero.io/).
13 | If you are looking for powerful managed services, like InfluxDB, you should seriously consider Stackhero 🤓
14 |
15 |
16 | ## Usage
17 |
18 | We have 2 nodes here. A `write` node, to send data to InfluxDB and a `query` node, to retrieve data using the Flux language.
19 |
20 |
21 | ### Write data
22 |
23 | The write node requires a payload object like this one:
24 | ```javascript
25 | msg.payload = {
26 | // You bucket
27 | // Optional (it can be defined in the node credentials settings)
28 | bucket: 'myBucket',
29 |
30 | // Precision of timestamp
31 | // Optional
32 | // Can be `ns` (nanoseconds),
33 | // `us` (microseconds),
34 | // `ms` (milliseconds),
35 | // `s` (seconds).
36 | // The default is `ns`
37 | // Note: if you set the `timestamp` field to `Date.now()`, you have to set the `precision` to `ms`
38 | precision: 'ms',
39 |
40 | // Data to send to InfluxDB
41 | // Can be an array of objects or only one object
42 | data: [
43 | {
44 | measurement: 'machinerySensor',
45 |
46 | tags: {
47 | deviceId: 'gyh43',
48 | hardwareVersion: '1.0.2',
49 | softwareVersion: '2.5.1',
50 | location: 'factory-1'
51 | },
52 |
53 | fields: {
54 | temperature: 12,
55 | humidity: 46,
56 | vibrations: 18,
57 | batteryVoltage: 3.6
58 | },
59 |
60 | timestamp: Date.now()
61 | },
62 |
63 | // More data can be send here, simply re add an object
64 | // { ... },
65 | ]
66 | };
67 |
68 | return msg;
69 | ```
70 |
71 | 
72 |
73 |
74 | ### Query data
75 |
76 | The query node requires a topic string containing a Flux query.
77 | ```javascript
78 | msg.topic = 'from(bucket: "myBucket") |> range(start: -1h)';
79 | return msg;
80 | ```
81 |
82 | You can write multiple lines queries like this:
83 | ```javascript
84 | msg.topic = [
85 | 'from(bucket: "myBucket")',
86 | ' |> range(start: -1d, stop: now)',
87 | ' |> filter(fn: (r) => r._measurement == "machinerySensor")',
88 | ' |> filter(fn: (r) => r._field == "vibrations")',
89 | ' |> aggregateWindow(every: 1h, fn: mean)',
90 | ' |> yield(name: "mean")',
91 |
92 | 'from(bucket: "myBucket")',
93 | ' |> range(start: -1d, stop: now)',
94 | ' |> filter(fn: (r) => r._measurement == "machinerySensor")',
95 | ' |> filter(fn: (r) => r._field == "vibrations")',
96 | ' |> aggregateWindow(every: 1h, fn: max)',
97 | ' |> yield(name: "max")',
98 | ].join('\n');
99 |
100 | return msg;
101 | ```
102 |
103 | 
104 |
105 |
--------------------------------------------------------------------------------
/assets/screenshotQuery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stackhero-io/node-red-contrib-stackhero-influxdb-v2/2b73d529c6b5fd6ddece3c85cbef752de15ac211/assets/screenshotQuery.png
--------------------------------------------------------------------------------
/assets/screenshotWrite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stackhero-io/node-red-contrib-stackhero-influxdb-v2/2b73d529c6b5fd6ddece3c85cbef752de15ac211/assets/screenshotWrite.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-red-contrib-stackhero-influxdb-v2",
3 | "version": "1.0.4",
4 | "description": "A Node-RED node to connect to an InfluxDB v2 database.",
5 | "dependencies": {
6 | "influxdb-v2": "1.0.0"
7 | },
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/stackhero-io/node-red-contrib-stackhero-influxdb-v2"
11 | },
12 | "keywords": [
13 | "node-red",
14 | "stackhero",
15 | "influxdb",
16 | "influxdb-v2"
17 | ],
18 | "node-red": {
19 | "nodes": {
20 | "stackhero-influxdb-v2-main": "src/main.js",
21 | "stackhero-influxdb-v2-query": "src/query.js",
22 | "stackhero-influxdb-v2-write": "src/write.js"
23 | }
24 | },
25 | "author": {
26 | "name": "Stackhero",
27 | "url": "https://www.stackhero.io/"
28 | },
29 | "license" : "AGPL-3.0"
30 | }
31 |
--------------------------------------------------------------------------------
/src/main.html:
--------------------------------------------------------------------------------
1 |
32 |
33 |
73 |
74 |
75 |
81 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | module.exports = (RED) => {
2 | 'use strict';
3 |
4 | const Influxdb = require('influxdb-v2');
5 |
6 | function InfluxDbV2Node(config) {
7 | RED.nodes.createNode(this, config);
8 |
9 | this.connection = async () => {
10 | this.influxdb = new Influxdb({
11 | host: config.host,
12 | port: config.port,
13 | protocol: config.tls ? 'https' : 'http',
14 | token: this.credentials.token
15 | });
16 | };
17 |
18 | this.query = async query => {
19 | await this.connection();
20 | return await this.influxdb.query(
21 | { org: this.credentials.organization },
22 | { query }
23 | );
24 | };
25 |
26 | this.write = async ({ bucket = this.credentials.bucket, precision = 'ns', data }) => {
27 | await this.connection();
28 | return await this.influxdb.write(
29 | {
30 | org: this.credentials.organization,
31 | bucket,
32 | precision
33 | },
34 | data
35 | );
36 | };
37 |
38 | this.on('close', async done => {
39 | if (this.stateTimeout) {
40 | clearTimeout(this.stateTimeout);
41 | }
42 | done();
43 | });
44 | }
45 | RED.nodes.registerType(
46 | 'Stackhero-InfluxDB-v2-Server',
47 | InfluxDbV2Node,
48 | {
49 | credentials: {
50 | token: { type: 'text' },
51 | organization: { type: 'text' },
52 | bucket: { type: 'text' }
53 | }
54 | }
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/src/query.html:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
74 |
75 |
--------------------------------------------------------------------------------
/src/query.js:
--------------------------------------------------------------------------------
1 | module.exports = (RED) => {
2 | 'use strict';
3 |
4 | function InfluxDbV2NodeQuery(config) {
5 | RED.nodes.createNode(this, config);
6 | this.serverConfig = RED.nodes.getNode(config.server);
7 |
8 | if (!this.serverConfig) {
9 | this.error('InfluxDB database not configured');
10 | return;
11 | }
12 |
13 | this.setState = (code, info) => {
14 | if (code === 'doing') {
15 | this.status({ fill: 'grey', shape: 'ring', text: 'doing...' });
16 | }
17 | else if (code === 'error') {
18 | this.status({ fill: 'red', shape: 'ring', text: info });
19 | }
20 | else if (code === 'done') {
21 | this.status({ fill: 'blue', shape: 'dot', text: 'done' });
22 | }
23 | else {
24 | this.status({});
25 | }
26 | };
27 |
28 | this.on('input', async msg => {
29 | if (!msg.topic) {
30 | this.error('msg.topic should be a string containing the Flux query.');
31 | return;
32 | }
33 |
34 |
35 | clearTimeout(this.stateTimeout);
36 | try {
37 | this.setState('doing');
38 | const result = await this.serverConfig
39 | .query(msg.topic);
40 | msg.payload = result;
41 | this.send(msg);
42 | this.setState('done');
43 | this.stateTimeout = setTimeout(
44 | () => this.setState(),
45 | 2 * 1000
46 | );
47 | }
48 | catch (error) {
49 | this.error(error, msg);
50 | this.setState('error', error.toString());
51 | }
52 | });
53 |
54 | this.on('close', async () => {
55 | clearTimeout(this.stateTimeout);
56 | this.serverConfig.removeAllListeners();
57 | this.setState();
58 | });
59 | }
60 | RED.nodes.registerType('Stackhero-InfluxDB-v2-query', InfluxDbV2NodeQuery);
61 | };
62 |
--------------------------------------------------------------------------------
/src/write.html:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
92 |
93 |
--------------------------------------------------------------------------------
/src/write.js:
--------------------------------------------------------------------------------
1 | module.exports = (RED) => {
2 | 'use strict';
3 |
4 | function InfluxDbV2NodeWrite(config) {
5 | RED.nodes.createNode(this, config);
6 | this.serverConfig = RED.nodes.getNode(config.server);
7 |
8 | if (!this.serverConfig) {
9 | this.error('InfluxDB database not configured');
10 | return;
11 | }
12 |
13 | this.setState = (code, info) => {
14 | if (code === 'doing') {
15 | this.status({ fill: 'grey', shape: 'ring', text: 'doing...' });
16 | }
17 | else if (code === 'error') {
18 | this.status({ fill: 'red', shape: 'ring', text: info });
19 | }
20 | else if (code === 'done') {
21 | this.status({ fill: 'blue', shape: 'dot', text: 'done' });
22 | }
23 | else {
24 | this.status({});
25 | }
26 | };
27 |
28 | this.serverConfig.on('stateWrite', (code, info) => this.setState(code, info));
29 |
30 | this.on('input', async msg => {
31 | if (!msg.payload) {
32 | this.error('msg.payload should be an object containing the data argument, as an object or an array.');
33 | return;
34 | }
35 |
36 | if (!Array.isArray(msg.payload.data)) {
37 | msg.payload.data = [ msg.payload.data ];
38 | }
39 |
40 | clearTimeout(this.stateTimeout);
41 | try {
42 | this.setState('doing');
43 | const { bucket, precision, data } = msg.payload;
44 | const result = await this.serverConfig
45 | .write({ bucket, precision, data });
46 | msg.payload = result;
47 | this.send(msg);
48 |
49 | this.setState('done');
50 | this.stateTimeout = setTimeout(
51 | () => this.setState(),
52 | 2 * 1000
53 | );
54 | }
55 | catch (error) {
56 | this.error(error, msg);
57 | this.setState('error', error.toString());
58 | }
59 | });
60 |
61 | this.on('close', async () => {
62 | clearTimeout(this.stateTimeout);
63 | this.serverConfig.removeAllListeners();
64 | this.setState();
65 | });
66 | }
67 | RED.nodes.registerType('Stackhero-InfluxDB-v2-write', InfluxDbV2NodeWrite);
68 | };
69 |
--------------------------------------------------------------------------------