# Virtual/Objects in Node-RED

Virtual/Objects aka V/O is the Comonway metadata system for linking all type of objects from real to virtual.

V/O defined a metadata definition adapted for YAML, JSON, etc., data serialization standard. This serialization is adapted for all programming languages.

Actually, V/O development is in progress with a Golang database engine as a service and a metadata structure definition.

# Modbus TCP data to InfluxDB

A modbus TCP PLC collect objects data. All data are referred inside a 32-bits datatable collected in 16-bits through Node-RED, arranged and saved to InfluxDB by measurements and tags.

Node Red Vo Modbustcp To Influxdb

Requirements:

  • This tutorial use a YAML file available in path /workdir/nodered/config/VirtualObjects.yml

Node-RED nodes:

Node-RED JS libraries:

  • filteredVOForDatasource.js
  • modbusArrangeToInfluxDBFromVO.js
  • modbusPrepareRequestFromVO.js

Note: These nodes are available in Comongate Node-RED in import>library>vo-modbus-to-influxdb.json

# Nodes Configuration

  • Rate limit: Update the rate limit depending on the response speed of Modbus TCP PLC
  • Modbus Flex Getter: Define the modbus server only, the rest is defined inside V/O metadata
  • influxdb: Define influxdb server only, measurement is defined inside V/O metadata

# YAML file example

A complete file is available in config/

Note: Repeat the object vo as many as physical objects

# VIRTUAL OBJECTS
# Database definition
# version 0.1
virtual_objects:
  - vo:                                   # Virtual Object
      name: Huile_thermique               # Name
      tags:                               # Tags
        client: Cricad Energie SA         # Tag #1
        compteur_nom: Huile thermique     # Tag #2
        compteur_adresse: 21              # Tag #3
      items: [status, n_serie, puissance, debit, temp_arr, temp_ret, temp_delta, energie, error]
      datasources:                        # Datasources
        modbus_tcp:                       # Modbus datasource
          servername: T00                 # Modbus TCP server reference
          fc: 3                           # function code
          unitid: 14                      # unitid
          address: 10420                  # address 
          quantity: 20                    # quantity
          data:                           # dataset
            status: {pos: 1, format: U16, scale: 1}         #0 unsigned 16-bits data
            n_serie: {pos: 2, format: U32, scale: 1}        #1 unsigned 32-bits data
            puissance: {pos: 4, format: U32, scale: 1}      #2 unsigned 32-bits data
            debit: {pos: 7, format: U16, scale: 0.1}        #3 unsigned 16-bits data
            temp_arr: {pos: 9, format: U16, scale: 0.1}     #4 unsigned 16-bits data
            temp_ret: {pos: 11, format: U16, scale: 0.1}    #5 unsigned 16-bits data
            temp_delta: {pos: 13, format: U32, scale: 0.1}  #6 unsigned 32-bits data
            energie: {pos: 14, format: U32, scale: 1}       #7 unsigned 32-bits data
            error: {pos: 19, format: U16, scale: 1}         #8 unsigned 16-bits data
        influxdb:						# Influxdb datasource
          measurement: heat_energy_meter  # Measurement

# Installation

Copy content below to clipboard and paste it in Node-RED.

[{"id":"69e74a84.f06be4","type":"modbus-flex-getter","z":"f17b2099.15a2e","name":"","showStatusActivities":false,"showErrors":false,"server":"a6d7cd5c.42f59","useIOFile":false,"ioFile":"","useIOForPayload":true,"x":990,"y":240,"wires":[["823572c0.2df94","3be16aea.e9e5e6"],[]]},{"id":"14148712.f75c19","type":"function","z":"f17b2099.15a2e","name":"Modbus request from V/O","func":"// Modbus request prepare from V/O\nmsg.payload = { \n    'value': msg.payload.vo, \n    'fc': msg.payload.vo.datasources.modbus_tcp.fc, \n    'unitid': msg.payload.vo.datasources.modbus_tcp.unitid, \n    'address': msg.payload.vo.datasources.modbus_tcp.address, \n    'quantity': msg.payload.vo.datasources.modbus_tcp.quantity\n}\nreturn msg;","outputs":1,"noerr":0,"x":770,"y":240,"wires":[["69e74a84.f06be4"]]},{"id":"20f1433b.ef8ffc","type":"bigsplitter","z":"f17b2099.15a2e","name":"","property":"payload","x":380,"y":240,"wires":[["c5818915.ef96e8"],[]]},{"id":"9c744cfa.14c61","type":"function","z":"f17b2099.15a2e","name":"Filtered V/O to payload","func":"// filterVOForDatasource filter V/O array for the destination datasource\nfunction filterVOForDatasource(element) {\n    if (element.vo.datasources[dsType]) {\n        if (element.vo.datasources[dsType].servername == dsName) {\n            return element;\n        }\n    }\n}\n\nvar meters = msg.payload.virtual_objects\n\n/*****************************/\n/* Datasource definition     */\n/*****************************/\nvar dsType = \"modbus_tcp\"\nvar dsName = \"T00\"\n/*****************************/\n\nvar filteredMeters = meters.filter(filterVOForDatasource);\n\nreturn {payload: filteredMeters}","outputs":1,"noerr":0,"x":200,"y":240,"wires":[["20f1433b.ef8ffc"]]},{"id":"4be0d19f.1978e","type":"debug","z":"f17b2099.15a2e","name":"data","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":470,"y":420,"wires":[]},{"id":"c5818915.ef96e8","type":"delay","z":"f17b2099.15a2e","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"100","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":560,"y":240,"wires":[["14148712.f75c19"]]},{"id":"72f8e408.9f321c","type":"comment","z":"f17b2099.15a2e","name":"Collect Heatmeters via Modbus TCP","info":"","x":180,"y":200,"wires":[]},{"id":"c4003b46.46fb38","type":"function","z":"f17b2099.15a2e","name":"Arrange Modbus payload with V/O","func":"/*\n * Translate array of value for saving realtime data to InfluxDB measurement\n * with help of Comonway Virtual/Objects metadata\n */\n// Initialization\ntags = {};\nitems = {};\n\n// Virtual/Objects used metadata\ntag =  msg.input.payload.value.tags;\nitem = msg.input.payload.value.items;\ndata = msg.input.payload.value.datasources.modbus_tcp.data;\n\n// Populate tags\nfor (var i in tag) {\n    tags[i] = tag[i]\n}\n\n// Populate items\nfor (var i in item) {\n    if (data[item[i]]) {\n        switch (data[item[i]].format) {\n            case \"U16\":\n                items[item[i]] = msg.payload[data[item[i]].pos] * data[item[i]].scale;\n                break;\n            case \"U32\":\n                items[item[i]] = ((msg.payload[data[item[i]].pos] << 16) + msg.payload[data[item[i]].pos + 1]) * data[item[i]].scale;\n                break;\n        }   \n    }\n}\n\nmsg.payload = [items, tags];\n\n// Define measurement name\nmsg.measurement = msg.input.payload.value.datasources.influxdb.measurement\n\nreturn msg;","outputs":1,"noerr":0,"x":240,"y":380,"wires":[["4be0d19f.1978e","1ca7e067.27f"]]},{"id":"823572c0.2df94","type":"link out","z":"f17b2099.15a2e","name":"CollectEnergyMeters","links":["e26ec2f8.e7505"],"x":1135,"y":240,"wires":[]},{"id":"e26ec2f8.e7505","type":"link in","z":"f17b2099.15a2e","name":"","links":["823572c0.2df94"],"x":60,"y":380,"wires":[["c4003b46.46fb38"]]},{"id":"1ca7e067.27f","type":"influxdb out","z":"f17b2099.15a2e","influxdb":"9838e9c4.cf5248","name":"","measurement":"","precision":"","retentionPolicy":"","x":520,"y":380,"wires":[]},{"id":"8f7de12c.a64c2","type":"yaml","z":"f17b2099.15a2e","property":"payload","name":"","x":450,"y":100,"wires":[["4af78a7a.7cd3b4","6ccebb55.a72d84"]]},{"id":"294641a.fc341be","type":"file in","z":"f17b2099.15a2e","name":"V/O datasource","filename":"/workdir/config/VirtualObjects.yml","format":"utf8","chunk":false,"sendError":false,"x":280,"y":100,"wires":[["8f7de12c.a64c2"]]},{"id":"a3def7e5.a8d9d8","type":"inject","z":"f17b2099.15a2e","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":100,"wires":[["294641a.fc341be"]]},{"id":"6ccebb55.a72d84","type":"debug","z":"f17b2099.15a2e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":610,"y":140,"wires":[]},{"id":"5bad7371.30fc6c","type":"comment","z":"f17b2099.15a2e","name":"Load Energy metering configuration","info":"","x":180,"y":60,"wires":[]},{"id":"2374d846.e4d3b8","type":"comment","z":"f17b2099.15a2e","name":"Arrange and save to database","info":"","x":160,"y":340,"wires":[]},{"id":"4af78a7a.7cd3b4","type":"link out","z":"f17b2099.15a2e","name":"LoadConfigEnergyMeters","links":["37283705.7af558"],"x":555,"y":100,"wires":[]},{"id":"37283705.7af558","type":"link in","z":"f17b2099.15a2e","name":"","links":["4af78a7a.7cd3b4"],"x":55,"y":240,"wires":[["9c744cfa.14c61"]]},{"id":"3be16aea.e9e5e6","type":"debug","z":"f17b2099.15a2e","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1190,"y":280,"wires":[]},{"id":"a6d7cd5c.42f59","type":"modbus-client","z":"","name":"T00","clienttype":"tcp","bufferCommands":true,"stateLogEnabled":false,"tcpHost":"192.168.0.14","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","unit_id":"14","commandDelay":"1","clientTimeout":"1000","reconnectTimeout":"2000"},{"id":"9838e9c4.cf5248","type":"influxdb","z":"","hostname":"influxdb","port":"8086","protocol":"http","database":"monitor","name":"","usetls":false,"tls":""}]