UNPKG

@nagisa~/node-red-dsmr

Version:

A node to give some structure to a stream of DSMR data

96 lines (95 loc) 5.28 kB
"use strict"; var crc_1 = require("crc"); var init = function (RED) { RED.nodes.registerType("dsmr", function (props) { var _this = this; RED.nodes.createNode(this, props); this.buffer = ""; var cosemSplitPattern = /[()]{1,2}/; this.on("input", function (msg, send, done) { var payload = RED.util.getMessageProperty(msg, props.property); if (payload instanceof Array) { payload = Uint8Array.from(payload, function (n) { return n > 0xFF ? 0 : n; }); } if (payload instanceof String) { _this.buffer += payload; } else if (payload instanceof Uint8Array) { _this.buffer += new TextDecoder('ascii').decode(payload); } else { return done(Error("DSMR node can only handle strings or arrays of bytes")); } ; // We've pretty much did all the work to get the data so far, so might as well give it // a shot at parsing this and pushing it along, even if the buffer now exceeds the // maximum configured length. var truncate_buffer = true; if (_this.buffer.length >= props.maximumTelegram) { _this.warn("buffer at ".concat(_this.buffer.length, " bytes exceeds the limit, but trying to parse it at least once anyway")); } var previous_length; do { previous_length = _this.buffer.length; var _a = _this.buffer.split("\r\n!"), telegram = _a[0], telegram_tail = _a[1], remainders = _a.slice(2); if (telegram_tail === undefined) { // CRC not available yet. if (_this.buffer.length >= props.maximumTelegram) { _this.warn("did not receive a well formed telegram within ".concat(props.maximumTelegram, " bytes of payload")); _this.buffer = ""; } return done(); } // Update the buffer with the remainder we're not looking at. From this point on // the control flow is particularly simple -- we can simply `continue` to move on // to the "next" potential telegram. var _b = telegram_tail.split("\r\n"), crc = _b[0], crc_tail = _b.slice(1); if (crc_tail.length != 0) remainders.unshift(crc_tail.join("\r\n")); _this.buffer = remainders.join("\r\n!"); if (crc.length != 4) continue; var parsed_crc = parseInt("F" + crc, 16); // Make sure all digits in the original string were consumed by parseInt if ((parsed_crc & 0xF0000) != 0xF0000) continue; parsed_crc &= 0xFFFF; var computed_crc = (0, crc_1.crc16)(telegram); computed_crc = (0, crc_1.crc16)("\r\n!", computed_crc); if (computed_crc != parsed_crc) { _this.warn("CRC mismatch: computed=".concat(computed_crc.toString(16), ", parsed=").concat(parsed_crc.toString(16))); continue; } var _c = telegram.split("\r\n"), identifier = _c[0], empty = _c[1], cosem_objects = _c.slice(2); var structured_payload = { vendor: identifier.slice(1, 4), identification: identifier.slice(5), }; for (var _i = 0, cosem_objects_1 = cosem_objects; _i < cosem_objects_1.length; _i++) { var obj = cosem_objects_1[_i]; // COSEM objects can be one of these shapes: // ID '(' VALUE ('*' UNIT)? ')' // ID '(' TIMESTAMP ')(' VALUE ('*' UNIT)? ')' // ID '(' COUNT ')(' ID ')(' TIMESTAMP ')(' VALUE ... // // For the last one I'm not quite sure if there are COUNT TIMESTAMP-VALUE pairs // or just the most recent and the oldest one. The specification is extremely // ambiguous on that point and my meter does not send any log objects // // Anyhow, we just don't try to interpret these at all, just like we don't // attempt to prescribe any meaning to the OBIS codes. Doing this part should // be reasonably straightforward to anybody with a function node and the get an // opportunity to shape the data in precisely the way that suits them best. var _d = obj.split(cosemSplitPattern).filter(function (n) { return n; }), obis = _d[0], value = _d.slice(1); structured_payload[obis] = value; } RED.util.setMessageProperty(msg, props.property, structured_payload); send(msg); // Keep parsing telegrams out of the buffer while for as long as doing so makes // progress. } while (_this.buffer.length != previous_length); return done(); }); }); }; module.exports = init;