CM160 owl energy monitor from serial - mk2 condensed into one function

This flow queries the OWL energy monitor (mine is on COM8 on windows using the OWL USB drivers), and generates both a real-time gauge and a graph of data since the flow started.

This is an updates (simplified version) of the original, where all owl specific functionality is in one function node with 5 outputs:

Output 1: data to be sent back to the com port. Output 2: data for realtime display ('update' data plus per minute data who's time is > last 'update' data). Designed to not send the historical data; however it will if the flow is left running and the CM160 disconnected for any period. Output 3: per-minute values received who's time is > last 'update' data. Output 4: All 'per minute' data, including stored data at startup. (note: the CM160 will sent about 30 days worth of data if it has not seen a request for a timeout period of ??seconds). Output 5: any incoming data which was not recognised.

It uses dashboard for examples of display; displaying current KW, plus a chart of one day's usage.

Notably, this loads the chart using 'restore' - applying the OWL's timestamps as the X axis; - it gathers the stored values at startup (in the 'store' function node', storing 30 days worth of values. Then one day's worth of values is send to the chart at a maximum of 10 second intervals. A slider allows selection of historical days, and the total kwh for the day is calculated from the selected days data. This avoids chart point overload, browser crashes, etc. as well as allowing the historical time data to be used.

Credit to cm160server.py and other sources for data interpretation.

This is a work in progress, but hopefully will be of use to someone.

At first start, or start after a timeout, the OWL device will send a LOT of stored data. This will take a minute or two, and shows in the counts stats in the GUI. Only when this is over will the gauge fire up. During this time, the graph will show the historical data flowing in.

Usage (windows): Install the standard OWL software, but make sure the software is not running; we only need the usb drivers. Make sure you have node-red-dashboard (not the older contrib-ui). Import the below into your flow. Set the comport number in the flow correctly for your OWL device; make sure it takes the 250000 baud rate. See the ui at /ui

I assume this would work in linux; although there seem to be questions about selecting a baud rate of 250000 in std linux usb serial drivers. Reading tells me there may be modified drivers for the USB.

[{"id":"efa334e1.867128","type":"ui_gauge","z":"82482361.bfd2","name":"","group":"afbdecec.9f6f7","order":2,"width":0,"height":0,"gtype":"gage","title":"KW","label":"KW","format":"{{value | number:3}}kw","min":0,"max":10,"colors":["#00b500","#e6e600","#ca3838"],"x":710,"y":40,"wires":[]},{"id":"19c1f50c.3f12db","type":"change","z":"82482361.bfd2","name":"kw->payload","rules":[{"t":"set","p":"payload","pt":"msg","to":"kw","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":80,"wires":[["efa334e1.867128","7bee852b.2c000c"]]},{"id":"7bee852b.2c000c","type":"ui_text","z":"82482361.bfd2","group":"afbdecec.9f6f7","order":1,"width":0,"height":0,"name":"","label":"Last Time","format":"{{msg.orgtimestamp}}","layout":"row-spread","x":720,"y":80,"wires":[]},{"id":"6cf75224.8afa2c","type":"serial in","z":"82482361.bfd2","name":"","serial":"c6f4a4ce.53e7b8","x":50,"y":100,"wires":[["bc8ba7cd.d57eb8"]]},{"id":"e3124e24.19b09","type":"serial out","z":"82482361.bfd2","name":"","serial":"c6f4a4ce.53e7b8","x":370,"y":20,"wires":[]},{"id":"f7b6d4c7.e0b158","type":"ui_template","z":"82482361.bfd2","group":"41f1c9f5.cc8fb8","name":"Counts","order":2,"width":0,"height":0,"format":"<div>\n    <p>Ident: {{msg.payload.ident || ''}}</p>\n    <p>Wait: {{msg.payload.wait || ''}}</p>\n    <p>Realtime: {{msg.payload.realtime || ''}}</p>\n    <p>Stored: {{msg.payload.stored || ''}}</p>\n    <p>Storecount: {{msg.payload.storecount || ''}}</p>\n    <p>Converted: {{msg.payload.converted || ''}}</p>\n    <p>Unknown: {{msg.payload.unknown || ''}}</p>\n</div>","storeOutMessages":true,"fwdInMessages":true,"x":720,"y":300,"wires":[[]]},{"id":"27008198.26d69e","type":"ui_button","z":"82482361.bfd2","name":"","group":"41f1c9f5.cc8fb8","order":1,"width":0,"height":0,"label":"Reset","color":"","icon":"","payload":"","payloadType":"str","topic":"Reset","x":90,"y":340,"wires":[["c9d1f0a0.4068d"]]},{"id":"c9d1f0a0.4068d","type":"function","z":"82482361.bfd2","name":"Reset","func":"var count = {};\nflow.set('count', count);\nmsg.payload = count;\nreturn msg;","outputs":1,"noerr":0,"x":230,"y":340,"wires":[["89dc38fb.f6c658"]]},{"id":"d1b92a65.189f18","type":"ui_chart","z":"82482361.bfd2","name":"chart2","group":"41f1c9f5.cc8fb8","order":5,"width":0,"height":0,"label":"kw","chartType":"line","legend":"false","xformat":"%a %H:%M","interpolate":"linear","nodata":"","ymin":"","ymax":"","removeOlder":"24","removeOlderUnit":"3600","x":750,"y":420,"wires":[[],[]]},{"id":"bc8ba7cd.d57eb8","type":"function","z":"82482361.bfd2","name":"Process CM160","func":"// first output goes back to serial\n\n// second output carries update data and 'stored'\n// data more recent than last update data\n\n// third output is histroical data - lots at the start,\n// then every minute\n\n// fourth output is unknown messages\n\nvar counts = flow.get('count') || {};\n\nvar procmess = function(msg){\n    msg.year    = 2000 + msg.payload[1];\n    msg.month   = msg.payload[2] & 0xf;\n    msg.day     = msg.payload[3];\n    msg.hour    = msg.payload[4];\n    msg.minute  = msg.payload[5];\n    msg.cost    = msg.payload[6] + (msg.payload[7] << 8);\n    msg.cost    = msg.cost/100;\n    msg.amps    = (msg.payload[8] + (msg.payload[9] << 8))  *0.07;\n    msg.kw      = (msg.amps * 230 ) / 1000;\n    msg.orgtimestamp = new Date(msg.year, msg.month, msg.day, msg.hour, msg.minute, 0, 0);\n    msg.receivedat = new Date();\n}\n\nvar a = new Uint8Array(msg.payload);\n\n\n// if it is an update message\nif (a[0] == 0x51){\n    msg.realtime = true;\n    msg.type = 'update';\n    procmess(msg);\n    context.lastrealtime = msg;\n    counts.realtime = (counts.realtime || 0) + 1;\n    // send on update output\n    return[null, msg, null, null, null];\n}\n\n// if it is a stored message, or per minute message\nif (a[0] == 0x59){\n    msg.realtime = false;\n    msg.type = 'stored';\n    procmess(msg);\n    counts.stored = (counts.stored || 0) + 1;\n    \n    if (context.lastrealtime){\n        if (context.lastrealtime.orgtimestamp <= msg.orgtimestamp){\n            msg.realtime = true;\n            counts.converted = (counts.converted | 0) + 1;\n            console.log('Converted (last reatime:' + context.lastrealtime.orgtimestamp + \" this \" + msg.orgtimestamp + \")\");\n        } else {\n            counts.converted = (counts.converted | 0) + 0;\n            console.log('Not Converted (last reatime:' + context.lastrealtime.orgtimestamp + \" this \" + msg.orgtimestamp + \")\");\n        }\n    }\n\n    if (msg.realtime){    \n        // send on update, per-minute, and stored output\n        return[null, msg, msg, msg, null];\n    } else {\n        // send on per-minute, and stored output only\n        return[null, null, null, msg, null];\n    }\n}\n\n\n\n// if it is an information message \nif (a[0] == 0xA9){\n    a = new Uint8Array(msg.payload);\n    \n    // if a waiting message\n    if (a[4] == 0x57){\n        msg.type = 'W';\n        // send a5 on serial\n        msg.payload = Buffer([0xa5]);\n        counts.wait = (counts.wait || 0) + 1;\n        // send back to serial\n        return[msg, null, null, null, null];\n    }\n\n    // if an ident message\n    if (a[4] == 0x43){\n        msg.type = 'C';\n        // send 5a on serial\n        msg.payload = Buffer([0x5a]);\n        counts.ident = (counts.ident || 0) + 1;\n        // send back to serial\n        return[msg, null, null, null, null];\n    }\n    \n    msg.type = a[4];\n    counts.unknown = (counts.unknown || 0) + 1;\n    // send 'unknown' output\n    return[null, null, null, null, msg];\n}\n\n\n// we did not recognise it.\nmsg.here = true;\ncounts.unknown = (counts.unknown || 0) + 1;\n\n// send 'unknown' output\nreturn[null, null, null, null, msg];\n","outputs":"5","noerr":0,"x":200,"y":100,"wires":[["e3124e24.19b09"],["19c1f50c.3f12db"],[],["4c6f8302.5759cc"],[]]},{"id":"89dc38fb.f6c658","type":"function","z":"82482361.bfd2","name":"read flow counts","func":"msg.payload = flow.get('count');\nreturn msg;","outputs":1,"noerr":0,"x":490,"y":320,"wires":[["f7b6d4c7.e0b158"]]},{"id":"2de36d0f.e88eb2","type":"inject","z":"82482361.bfd2","name":"Trigger count display","topic":"","payload":"","payloadType":"date","repeat":"5","crontab":"","once":false,"x":140,"y":280,"wires":[["89dc38fb.f6c658"]]},{"id":"4c6f8302.5759cc","type":"function","z":"82482361.bfd2","name":"store","func":"var store = (flow.get('store') || []);\n\nvar maxtime = 1440; // 2 hours\nvar maxcount = 43200; // 30 days\n\nvar start = flow.get('daysback') * 1440 || 1440;\n\n\nvar out = 0;\nif (msg.topic != 'sendgraph'){\n\n    if (store.length > maxcount){\n        store.shift();\n        //console.log(\"dropping earliest\")\n    }\n\n\n    var point = []\n    point.push(msg.orgtimestamp.valueOf());\n    point.push(msg.kw);\n    store.push(point);\n    context.store = store;\n    \n    var counts = flow.get('count') || {};\n    counts.storecount = store.length;\n    flow.set('count', counts);\n    \n    flow.set('store', store);\n} else {\n    out = 1;\n}\n\npayload = {};\n\nvar count = maxtime;\nif (count > store.length)\n    count = store.length;\n    \nvar startpoint = start;\nif (startpoint > store.length)\n    startpoint = store.length\n\npayload.values = store.slice(store.length - startpoint, store.length - startpoint + maxtime);\npayload.key = 'Data';\n\nvar kwhtotal = 0;\nvar lasttime = payload.values[0][0];\nfor(var i = 1; i < payload.values.length; i++)\n{\n    kwhtotal = kwhtotal + \n        payload.values[i][1]*\n            (payload.values[i][0] - lasttime)/\n                (1000*60*60);\n    lasttime = payload.values[i][0];\n}\n\nmsg1 = {};\nmsg1.payload = kwhtotal;\n\nmsg.payload = [];\nmsg.payload.push(payload);\nmsg.topic = 'restore';\n\nif (out === 0){\n    return [msg, null, msg1];\n} else {\n    return [null, msg, msg1];\n}","outputs":"3","noerr":0,"x":430,"y":420,"wires":[["fcd94ee8.425f9"],["d1b92a65.189f18"],["85b8ace3.da3e6"]]},{"id":"fcd94ee8.425f9","type":"delay","z":"82482361.bfd2","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"10","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":580,"y":400,"wires":[["d1b92a65.189f18","1fdbb4c5.f26d4b"]]},{"id":"1fdbb4c5.f26d4b","type":"debug","z":"82482361.bfd2","name":"","active":false,"console":"false","complete":"payload","x":730,"y":340,"wires":[]},{"id":"8c78ad1e.c7a1f","type":"ui_slider","z":"82482361.bfd2","name":"","label":"days back","group":"41f1c9f5.cc8fb8","order":3,"width":0,"height":0,"passthru":true,"topic":"sendgraph","min":"1","max":"30","step":1,"x":90,"y":460,"wires":[["ad216883.01ba48","e3999d83.53c93"]]},{"id":"ad216883.01ba48","type":"function","z":"82482361.bfd2","name":"storedaysback","func":"flow.set('daysback', msg.payload);\nreturn msg;","outputs":1,"noerr":0,"x":260,"y":420,"wires":[["4c6f8302.5759cc"]]},{"id":"e3999d83.53c93","type":"ui_text","z":"82482361.bfd2","group":"41f1c9f5.cc8fb8","order":4,"width":0,"height":0,"name":"","label":"","format":"{{msg.payload}}","layout":"row-center","x":430,"y":480,"wires":[]},{"id":"85b8ace3.da3e6","type":"ui_text","z":"82482361.bfd2","group":"41f1c9f5.cc8fb8","order":6,"width":0,"height":0,"name":"","label":"Total kwhours:","format":"{{msg.payload}}","layout":"row-spread","x":720,"y":460,"wires":[]},{"id":"afbdecec.9f6f7","type":"ui_group","z":"","name":"Power Now","tab":"59ab55d3.a9255c","disp":true,"width":"6"},{"id":"c6f4a4ce.53e7b8","type":"serial-port","z":"","serialport":"com8","serialbaud":"250000","databits":"8","parity":"none","stopbits":"1","newline":"11","bin":"bin","out":"count","addchar":false},{"id":"41f1c9f5.cc8fb8","type":"ui_group","z":"","name":"Historical","tab":"59ab55d3.a9255c","disp":true,"width":"8"},{"id":"59ab55d3.a9255c","type":"ui_tab","z":"","name":"Home","icon":"dashboard"}]
btsimonh

Flow Info

created 9 months, 1 week ago

Node Types

Core
  • change (x1)
  • debug (x1)
  • delay (x1)
  • function (x5)
  • inject (x1)
  • serial in (x1)
  • serial out (x1)
  • serial-port (x1)
Other
  • ui_button (x1)
  • ui_chart (x1)
  • ui_gauge (x1)
  • ui_group (x2)
  • ui_slider (x1)
  • ui_tab (x1)
  • ui_template (x1)
  • ui_text (x3)

Tags

  • cm160
  • owl
  • energy
  • serial
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option