@nagisa~/node-red-dsmr
Version:
A node to give some structure to a stream of DSMR data
96 lines (95 loc) • 5.28 kB
JavaScript
;
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;