@andrei-tatar/node-red-contrib-nibetcp
Version:
Node-red integration for Nibe S series heatpumps using modbus over TCP
142 lines (141 loc) • 6.35 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;