UNPKG

@andrei-tatar/node-red-contrib-nibetcp

Version:

Node-red integration for Nibe S series heatpumps using modbus over TCP

155 lines (154 loc) 6.72 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Modbus = void 0; const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const LABELS_BY_CODEID = new Map([ [3, 'READ_HOLDING'], [4, 'READ_INPUT'], [6, 'WRITE_SINGLE_REGISTER'], [16, 'WRITE_MULTIPLE_REGISTERS'], ]); const CODEID_BY_LABEL = new Map([...LABELS_BY_CODEID.entries()].map(([key, value]) => [value, key])); class Modbus { static get transactionId() { Modbus._transactionId++; if (Modbus._transactionId > 0xffff) { Modbus._transactionId = 0; } return Modbus._transactionId; } constructor(tcp) { this.tcp = tcp; this.packages$ = this.tcp.data$.pipe((0, operators_1.scan)(Modbus.handleMessage, { items: [], pending: Buffer.alloc(0) }), (0, operators_1.concatMap)(v => v.items), (0, operators_1.share)({ resetOnRefCountZero: true })); } static handleMessage(state, msg) { state.pending = state.pending ? Buffer.concat([state.pending, msg]) : msg; state.items = []; while (state.pending.length > 6) { const pkg_len = state.pending.readUInt16BE(4) + 6; if (state.pending.length < 8) return state; // safety precaution if (state.pending.length < pkg_len) return state; // not all data yet const data = state.pending.subarray(8, pkg_len); const pkgFunctionCode = state.pending.readUInt8(7); const functionCode = LABELS_BY_CODEID.get(pkgFunctionCode & 0x7F); if (!functionCode) return state; const pkg = { transactionId: state.pending.readUInt16BE(0), protocol: state.pending.readUInt16BE(2), length: pkg_len, unitId: state.pending.readUInt8(6), }; state.pending = state.pending.subarray(pkg_len); if (pkgFunctionCode & 0x80) { state.items.push(Object.assign(Object.assign({}, pkg), { functionCode, exception: data.readUInt8(0) })); return state; } switch (functionCode) { case 'READ_HOLDING': case 'READ_INPUT': const cnt = data.readUInt8(0); const readRegsPackage = { functionCode, data: data.subarray(1, 1 + cnt), }; state.items.push(Object.assign(Object.assign({}, pkg), readRegsPackage)); break; case 'WRITE_SINGLE_REGISTER': const writeReg = { functionCode, address: data.readUInt16BE(0), value: Buffer.from([data[2], data[3]]), }; state.items.push(Object.assign(Object.assign({}, pkg), writeReg)); break; case 'WRITE_MULTIPLE_REGISTERS': const from = data.readUInt16BE(0); const writeMany = { functionCode, from: from, to: data.readUInt16BE(2) + from - 1, }; state.items.push(Object.assign(Object.assign({}, pkg), writeMany)); break; } } return state; } readRegisters(kind, startAddress, count = 1, timeoutMsec = 1000) { const transactionId = Modbus.transactionId; const data = Buffer.alloc(4); data.writeUInt16BE(startAddress, 0); data.writeUInt16BE(count, 2); const tx = this.sendPacket(transactionId, kind, data).pipe((0, operators_1.ignoreElements)()); const rx = this.packages$.pipe((0, operators_1.filter)(p => p.transactionId === transactionId && p.functionCode === kind), (0, operators_1.timeout)(timeoutMsec), (0, operators_1.first)(), (0, operators_1.map)(v => { if ('exception' in v) { throw new Error(`Error while reading register ${startAddress}: (${v.functionCode} - ${v.exception})`); } if (v.functionCode !== kind) { //we already filter, this shouldn't happen throw new Error(); } return v; })); return (0, rxjs_1.merge)(rx, tx); } writeRegisters(startAddress, data, timeoutMsec = 1000) { if (data.length % 2 !== 0 || data.length === 0) { throw new Error(`Invalid data length ${data.length}`); } const transactionId = Modbus.transactionId; const writeCount = data.length / 2; let pckg; let writeType; if (writeCount === 1) { writeType = 'WRITE_SINGLE_REGISTER'; pckg = Buffer.alloc(2 + data.length); pckg.writeUInt16BE(startAddress, 0); data.copy(pckg, 2); } else { writeType = 'WRITE_MULTIPLE_REGISTERS'; pckg = Buffer.alloc(5 + data.length); let offset = 0; offset = pckg.writeUInt16BE(startAddress, offset); offset = pckg.writeUInt16BE(writeCount, offset); offset = pckg.writeUInt8(writeCount * 2, offset); data.copy(pckg, offset); } const tx = this.sendPacket(transactionId, writeType, pckg).pipe((0, operators_1.ignoreElements)()); const rx = this.packages$.pipe((0, operators_1.filter)(p => p.transactionId === transactionId && p.functionCode === writeType), (0, operators_1.timeout)(timeoutMsec), (0, operators_1.first)(), (0, operators_1.map)(v => { if ('exception' in v) { throw new Error(`Error while reading register ${startAddress}: (${v.functionCode} - ${v.exception})`); } if (v.functionCode !== writeType) { //we already filter, this shouldn't happen throw new Error(); } return v; })); return (0, rxjs_1.merge)(rx, tx); } sendPacket(transactionId, label, data) { const codeId = CODEID_BY_LABEL.get(label); if (!codeId) { throw new Error('Invalid code id while sending packet'); } const req = Buffer.alloc(8 + data.length); req.writeUInt16BE(transactionId, 0); req.writeUInt16BE(0, 2); //protocol req.writeUInt16BE(data.length + 2, 4); req.writeUInt8(0, 6); //unit id req.writeUInt8(codeId, 7); data.copy(req, 8); return this.tcp.send(req); } } exports.Modbus = Modbus; Modbus._transactionId = 0; ;