@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
JavaScript
;
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;