InfluxDB: input validation function for influxdb-out

This is a simple function node designed to validate data that you want to store in InfluxDB via the influxdb-out node from node-red-contrib-influxdb.

The structure of data for InfluxDB can be rather confusing. While influxdb-out makes it simpler, the instructions could be clearer. Because putting the wrong type of data or using the wrong structure when outputting can be hard to fix due to the way that InfluxDB works, it is much easier to validate things before they get there.

The flow comes with a bunch of test inputs to help you understand the data and potential issues (and they let me test the function).

Let me know if there are any errors or you would like something else added to the function.

[{"id":"b47174e3.35aef8","type":"group","z":"33700a61.0a0d46","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["1bb27551.b13a6b","7273324f.7f53ac","a000bc32.2dc19","2dbcfbbd.b61f44","4c6ac117.8bbe6","d16b6be1.138148","a13fc0ed.df4e","dc063c13.07848","e0d6c61d.faee48","91c03b02.b2c198","312b8552.1db4ea","b7c7c8cb.4003d8","79bbe9aa.3a2408","4025e7d.27e6c18","913cf8ef.65c9f8","54b3c063.c132b"],"x":54,"y":1159,"w":1132,"h":502},{"id":"1bb27551.b13a6b","type":"influxdb out","z":"33700a61.0a0d46","d":true,"g":"b47174e3.35aef8","influxdb":"c4e91fa4.30ca7","name":"","measurement":"measure1","precision":"s","retentionPolicy":"one_week","database":"database","precisionV18FluxV20":"ms","retentionPolicyV18Flux":"","org":"organisation","bucket":"bucket","x":1020,"y":1380,"wires":[]},{"id":"7273324f.7f53ac","type":"inject","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"},{"p":"measurement","v":"measure1","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"field1\":100,\"value\":101},{\"tagname1\":\"tag1val\"}]","payloadType":"json","x":730,"y":1360,"wires":[["1bb27551.b13a6b"]]},{"id":"a000bc32.2dc19","type":"function","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"Validate Input","func":"/**\n * Validate input suitable for the influxdb-out node.\n * We will ALWAYS use the \"array containing two objects\" payload\n * OR the singe Object payload (if no tags being used).\n * See the Description tab for more details.\n */\n\n// check measurement field is set - if not exit with error\nif ( ! msg.measurement ) {\n    node.error('msg.measurement is missing')\n    return\n}\n\nlet fields,tags\n\n// if payload is an object, assume that it contains fieldName:fieldValue pairs\nif ( msg.payload!== null && msg.payload.constructor.name === 'Object' ) {\n    fields = msg.payload\n} else if ( msg.payload!== null && msg.payload.constructor.name === 'Array' ) {\n    node.error('msg.payload cannot be an array. It must be an object containing fieldName:fieldValue pairs or a single value (which would be written to the `value` field name).')\n    return\n} else {\n    // Otherwise, we always use 'value' as the default field name\n    fields = {'value': msg.payload}\n}\n\nconst lstFields = Object.keys(fields)\n\n// check to make sure that there is a value field name - if not, continue but with a warning\nif ( ! lstFields.includes('value') ) {\n    // Lets us turn off the warning if we know what we are doing :-)\n    if ( msg.noValueField !== true )\n        node.warn('Default field name \"vaue\" not present, was that deliberate? Set msg.noValueField=true or use the `value` field name to avoid this msg')\n}\n\n// check to make sure that all field values are numeric - if not, exit with a warning\nlet allNumeric = true\nlstFields.forEach( key => {\n    // I use On/Off for simple switch values in MQTT but these are not useful\n    // in InfluxDB, so translate them to ON=1 and OFF=0 (ignoring case).\n    try {\n        if ( fields[key].toLowerCase() === 'on' ) fields[key] = 1\n        if ( fields[key].toLowerCase() === 'off' ) fields[key] = 0\n    } catch (e) {}\n\n    // then check to make sure the field is actually a number\n    if ( parseFloat(fields[key]) !== fields[key] ) {\n        node.error(`Field msg.payload.${key} is not numeric. Only use numbers for field values, text should go in tags.`)\n        allNumeric = false\n        return\n    }\n})\nif ( allNumeric === false ) {\n    return\n}\n\n// check to make sure that if msg.tags is present, it is an object - if not, exit with a warning\nif ( msg.tags ) {\n    if ( !(msg.tags!== null && msg.tags.constructor.name === 'Object') ) {\n        node.error('msg.tags is not an object - it must contain tagName:tagValue pairs')\n        return\n    }\n    tags = msg.tags\n}\n\n// Format the output to go to the InfluxDB out node\nif ( msg.tags ) {\n    msg.payload = [\n        fields,\n        tags,\n    ]\n} else {\n    msg.payload = fields\n}\n\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":580,"y":1400,"wires":[["1bb27551.b13a6b","2dbcfbbd.b61f44"]],"info":"This function validates msg input to ensure that it is suitable for the influxdb-out node.\n\nUse the following input msg schema:\n\n* `msg.payload`: **MUST** be an object containing fieldName:fieldValue pairs; OR a single value that will be written to the `value` field which is the default field name for InfluxDB.\n* `msg.measurement`: **MUST** be a valid measurement name for the database being written to.\n* `msg.tags`: Is _OPTIONAL_. If present, it **MUST** be an object containing tagName:tagValue pairs.\n\nWe will ALWAYS use the \"array containing two objects\" payload\nOR the singe Object payload (if no tags being used).\n\n## Tests\n* _SUCCESS_: \n  `msg={\"payload\":{\"fieldName1\":10,\"value\":15},\"tags\":{\"tagName1\":\"tagname1Value\"},\"measure\":\"myMeasure1\"}`\n* _SUCCESS_ (payload converted to `{\"value\":16}`): \n  `msg={\"payload\":16,\"tags\":{\"tagName1\":\"tagname1Value\"},\"measure\":\"myMeasure2\"}`\n* _SUCCESS_ (no tags): \n  `msg={\"payload\":16,\"measure\":\"myMeasure2\"}`\n* _SUCCESS_ (but warning issued, no value field): \n  `msg={\"payload\":{\"fieldName2\":17},\"tags\":{\"tagName1\":\"tagname1Value\"},\"measure\":\"myMeasure3\"}`\n* _SUCCESS_ (value \"On\" converted to 1): \n  `msg={\"payload\":\"On\",\"measure\":\"myMeasure4\"}`\n* _???_ (missing tags for a measure with tags): \n  `msg={\"payload\":\"On\",\"measure\":\"myMeasure1\"}`\n* _FAIL_: No `msg.measure`\n* _FAIL_: `msg.payload` is an array\n* _FAIL_: 1 or more fields have non-numeric values\n* _FAIL_: msg.tags exists but is not an object\n\n## Expected output formats\n```\nmsg.payload = [\n    objFields,\n    objTags\n]\n```\nOR (if no tags)\n```\nmsg.payload = {\n    \"fieldName1\":123,\n    \"fieldName2\":23,\n    \"value\":3,\n}\n```"},{"id":"2dbcfbbd.b61f44","type":"debug","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":895,"y":1420,"wires":[],"l":false},{"id":"4c6ac117.8bbe6","type":"inject","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"SUCCESS/Valid Input","props":[{"p":"topic","vt":"str"},{"p":"payload"},{"p":"measurement","v":"measure1","vt":"str"},{"p":"tags","v":"{\"tagname1\":\"tagname1Value\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"SUCCESS/Valid Input","payload":"{\"field1\":10,\"value\":15}","payloadType":"json","x":200,"y":1200,"wires":[["a000bc32.2dc19"]]},{"id":"d16b6be1.138148","type":"inject","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"FAIL/msg.measurement missing","props":[{"p":"payload"},{"p":"topic","vt":"str"},{"p":"tags","v":"{\"tagname1\":\"tagname1Value\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"FAIL/msg.measurement missing","payload":"{\"field1\":10,\"value\":15}","payloadType":"json","x":230,"y":1500,"wires":[["a000bc32.2dc19"]]},{"id":"a13fc0ed.df4e","type":"inject","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"SUCCESS/single value payload","props":[{"p":"topic","vt":"str"},{"p":"payload"},{"p":"measurement","v":"measure1","vt":"str"},{"p":"tags","v":"{\"tagname1\":\"tagname1Value\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"SUCCESS/single value payload","payload":"16","payloadType":"num","x":230,"y":1240,"wires":[["a000bc32.2dc19"]]},{"id":"dc063c13.07848","type":"inject","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"SUCCESS/no tags","props":[{"p":"topic","vt":"str"},{"p":"payload"},{"p":"measurement","v":"measure1","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"SUCCESS/no tags","payload":"16","payloadType":"num","x":190,"y":1280,"wires":[["a000bc32.2dc19"]]},{"id":"e0d6c61d.faee48","type":"inject","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"SUCCESS/no value field/warning","props":[{"p":"topic","vt":"str"},{"p":"payload"},{"p":"measurement","v":"measure1","vt":"str"},{"p":"tags","v":"{\"tagname1\":\"tagname1Value\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"SUCCESS/but warning issued, no value field","payload":"{\"fieldname2\":17}","payloadType":"json","x":230,"y":1320,"wires":[["a000bc32.2dc19"]]},{"id":"91c03b02.b2c198","type":"inject","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"SUCCESS/no value field/no warning","props":[{"p":"topic","vt":"str"},{"p":"payload"},{"p":"measurement","v":"measure1","vt":"str"},{"p":"tags","v":"{\"tagname1\":\"tagname1Value\"}","vt":"json"},{"p":"noValueField","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"SUCCESS/no value field, no warning","payload":"{\"fieldname2\":17}","payloadType":"json","x":240,"y":1360,"wires":[["a000bc32.2dc19"]]},{"id":"312b8552.1db4ea","type":"inject","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"SUCCESS/value \"On\" converted to 1","props":[{"p":"topic","vt":"str"},{"p":"payload"},{"p":"measurement","v":"measure1","vt":"str"},{"p":"tags","v":"{\"tagname1\":\"tagname1Value\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"SUCCESS/On converted to 1","payload":"On","payloadType":"str","x":240,"y":1400,"wires":[["a000bc32.2dc19"]]},{"id":"b7c7c8cb.4003d8","type":"inject","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"SUCCESS/value \"Off\" converted to 1","props":[{"p":"topic","vt":"str"},{"p":"payload"},{"p":"measurement","v":"measure1","vt":"str"},{"p":"tags","v":"{\"tagname1\":\"tagname1Value\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"SUCCESS/Off converted to 1","payload":"{\"fieldname3\":\"Off\",\"value\":\"ON\"}","payloadType":"json","x":240,"y":1440,"wires":[["a000bc32.2dc19"]]},{"id":"79bbe9aa.3a2408","type":"inject","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"FAIL/payload is an array","props":[{"p":"topic","vt":"str"},{"p":"payload"},{"p":"measurement","v":"measure1","vt":"str"},{"p":"tags","v":"{\"tagname1\":\"tagname1Value\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"FAIL/payload is an array","payload":"[10,15]","payloadType":"json","x":210,"y":1540,"wires":[["a000bc32.2dc19"]]},{"id":"4025e7d.27e6c18","type":"inject","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"FAIL/Field with non-numeric value","props":[{"p":"topic","vt":"str"},{"p":"payload"},{"p":"measurement","v":"measure1","vt":"str"},{"p":"tags","v":"{\"tagname1\":\"tagname1Value\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"FAIL/Field with non-numeric value","payload":"{\"field1\":\"hello\",\"value\":true}","payloadType":"json","x":240,"y":1580,"wires":[["a000bc32.2dc19"]]},{"id":"913cf8ef.65c9f8","type":"inject","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"FAIL/tags exists but not object","props":[{"p":"topic","vt":"str"},{"p":"payload"},{"p":"measurement","v":"measure1","vt":"str"},{"p":"tags","v":"{\"tagname1\":\"tagname1Value\"}","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"FAIL/msg.tags exists but is not an object","payload":"{\"field1\":10,\"value\":15}","payloadType":"json","x":220,"y":1620,"wires":[["a000bc32.2dc19"]]},{"id":"54b3c063.c132b","type":"comment","z":"33700a61.0a0d46","g":"b47174e3.35aef8","name":"Test validation function for influxdb-out","info":"","x":650,"y":1240,"wires":[]},{"id":"c4e91fa4.30ca7","type":"influxdb","hostname":"127.0.0.1","port":"8086","protocol":"http","database":"test","name":"","usetls":false,"tls":"","influxdbVersion":"1.x","url":"http://localhost:8086","rejectUnauthorized":true}]

Flow Info

Created 3 years, 7 months ago
Rating: 5 1

Actions

Rate:

Node Types

Core
  • comment (x1)
  • debug (x1)
  • function (x1)
  • inject (x12)
Other

Tags

  • influxdb
  • validation
  • testing
  • node-red-contrib-influxdb
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option