UNPKG

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

Version:

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

133 lines (132 loc) 6.19 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Nibe = void 0; const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const modbus_1 = require("./modbus"); const registers_1 = require("./registers"); const tcp_1 = require("./tcp"); const DEFAULT_TIMEOUT = 1000; class Nibe { static createTcp(address, registerFilePath, logger) { const nibe = tcp_1.Tcp .create(address, logger === null || logger === void 0 ? void 0 : logger.scope('tcp')) .pipe((0, operators_1.map)(x => new modbus_1.Modbus(x)), (0, operators_1.map)(m => new Nibe(m, registerFilePath))); return nibe; } constructor(modbus, registerFile) { this.modbus = modbus; this.registerFile = registerFile; this.registers$ = (0, rxjs_1.defer)(() => __awaiter(this, void 0, void 0, function* () { return yield (0, registers_1.loadAllRegisters)(this.registerFile); })).pipe((0, operators_1.shareReplay)(1)); } readRegister(labelOrAddress, timeoutMsec) { return this.registers$.pipe((0, operators_1.first)(), (0, operators_1.map)(registers => { const reg = this.findRegister(labelOrAddress, registers); if (!reg) { throw new Error(`Could not find register '${labelOrAddress}'`); } return reg; }), (0, operators_1.switchMap)(reg => this.readInternal(reg, timeoutMsec).pipe((0, operators_1.retry)({ count: 3 })))); } writeRegister(labelOrAddress, value, force, timeoutMsec) { return this.registers$.pipe((0, operators_1.first)(), (0, operators_1.map)(registers => { const reg = this.findRegister(labelOrAddress, registers); if (!reg) { throw new Error(`Could not find register '${labelOrAddress}'`); } if (reg.type !== 'MODBUS_HOLDING_REGISTER') { throw new Error(`Readonly register '${labelOrAddress}'`); } return reg; }), (0, operators_1.switchMap)(reg => this.writeInteral(reg, value, force, timeoutMsec).pipe((0, operators_1.retry)({ count: 3 }))), (0, operators_1.ignoreElements)()); } findRegister(labelOrAddress, list) { const address = typeof labelOrAddress === 'number' ? labelOrAddress : parseInt(labelOrAddress, 10); if (!isNaN(address) && address >= 30000) { return list.find(r => r.address === address); } const match = list.filter(r => r.label === labelOrAddress); match.sort((a, b) => a.type.localeCompare(b.type)); return match[0]; } writeInteral(register, value, force = false, timeoutMsec = DEFAULT_TIMEOUT) { const raw = value * register.divisionFactor; if (!force && (register.minValue !== null && raw < register.minValue || register.maxValue !== null && raw > register.maxValue)) { throw new Error(`Invalid value for register ${register.address}: ${raw}`); } const data = this.writeValue(register.size, raw); return this.modbus.writeRegisters(register.address % 10000, data, timeoutMsec); } readInternal(register, timeoutMsec = DEFAULT_TIMEOUT) { const registerCount = register.size === 's32' || register.size === 'u32' ? 2 : 1; const readKind = register.type === 'MODBUS_HOLDING_REGISTER' ? 'READ_HOLDING' : 'READ_INPUT'; return this.modbus.readRegisters(readKind, register.address % 10000, registerCount, timeoutMsec).pipe((0, operators_1.map)(v => { const rawValue = this.readValue(v.data, register.size); const value = { value: rawValue / register.divisionFactor, label: register.label, unit: register.unit, address: register.address, formatted: `${rawValue / register.divisionFactor}${register.unit}`, rawValue, raw: Buffer.from(v.data), }; return value; })); } writeValue(size, value) { let data; switch (size) { case 'u8': case 'u16': data = Buffer.alloc(2); data.writeUInt16BE(value, 0); break; case 's8': case 's16': data = Buffer.alloc(2); data.writeInt16BE(value, 0); break; case 'u32': case 's32': data = Buffer.alloc(4); data.writeUint16BE((value >> 16) & 0xFFFF, 0); data.writeUint16BE(value & 0xFFFF, 2); break; default: throw new Error(`Register size not supported '${size}'`); } return data; } readValue(data, size) { switch (size) { case 'u8': return data.readUInt16BE(0); case 's8': return data.readInt16BE(0); case 'u16': return data.readUInt16BE(0); case 's16': return data.readInt16BE(0); case 'u32': const p1 = data.readUint16BE(0); const p2 = data.readUint16BE(2); return (p2 << 16) + p1; case 's32': const p1s = data.readUint16BE(0); const p2s = data.readUint16BE(2); return (p2s << 16) + p1s; default: throw new Error(`Register size not supported '${size}'`); } } } exports.Nibe = Nibe;