UNPKG

homebridge-luxtronik2

Version:

Homebridge module to retrieve data from a luxtronik2 controller for heat pumps

223 lines (191 loc) 7.44 kB
// 'use strict'; let Service; let Characteristic; const path = require('node:path'); const packageFile = require('./package.json'); function translate(c) { const tools = require(path.join(__dirname, '/lib/tools.js')); const channellist = require(path.join(__dirname, '/lib/channellist.json')); const result = []; for (let i = 0; i < c.length; i++) { if (tools.isset(channellist[i])) { let value = c[i]; if (tools.isset(channellist[i].type)) { switch (channellist[i].type) { case 'fix1': { value /= 10; break; } case 'ip': { value = tools.int2ip(value); break; } case 'ignore': { continue; } case 'enum': { if (tools.isset(channellist[i].enum[c[i]])) value = channellist[i].enum[c[i]]; break; } default: { break; } } } result.push(value); } } return result; } function Luxtronik2(log, config) { this.log = log; const process = require('node:process'); const NODE_MAJOR_VERSION = process.versions.node.split('.')[0]; this.log.debug('NodeJS version is %s', process.versions.node); if (NODE_MAJOR_VERSION <= 16) { this.log.warn('WARNING: NodeJS version 16 is end of life 2023-09-11.'); this.log.warn('Visit nodejs.org for more details.'); } if (config.IP === undefined) { this.log.error('ERROR: your configuration is missing the parameter "IP"'); this.IP = '127.0.0.1'; } else { this.IP = config.IP; } if (config.Port === undefined) { this.log.error('ERROR: your configuration is missing the parameter "Port"'); this.Port = 8888; } else { this.Port = config.Port; } if (config.name === undefined) { this.log.error('ERROR: your configuration is missing the parameter "name"'); this.name = 'Unnamed Luxtronik2'; } else { this.name = config.name; } if (config.Channel === undefined) { this.log.error('ERROR: your configuration is missing the parameter "Channel"'); this.Channel = 5; } else { this.Channel = config.Channel; } this.log.debug('Config: IP is %s', this.IP); this.log.debug('Config: Port is %s', this.Port); this.log.debug('Config: name is %s', this.name); this.log.debug('Config: Channel is %s', this.Channel); this.CurrentTemperature = 0; this.counter = 1; this.temperatureDisplayUnits = Characteristic.TemperatureDisplayUnits.CELSIUS; this.firmwareRevision = packageFile.version; this.log.info('Luxtronik2 IP is %s', this.IP); this.log.info('Luxtronik2 Port is %s', this.Port); this.log.info('Luxtronik2 Name is %s', this.name); this.log.debug('Homebridge debug enabled'); this.temperatureService = new Service.TemperatureSensor(this.name); } Luxtronik2.prototype = { getTemperature: function (callback) { this.log.debug('getTemperature was called'); const net = require('node:net'); let temperature = -99; /* eslint unicorn/no-this-assignment: ["off"] */ const Channel = this.Channel; /* eslint prefer-destructuring: ["off"] */ const that = this; this.log.debug('host and port from config: ' + this.IP + ' ' + this.Port); const luxsock = net.connect({host: this.IP, port: this.Port}); this.log.debug('Going to connect'); luxsock.on('error', function (data) { that.log.error(data.toString()); }); luxsock.on('timeout', function () { that.log.warn('Connection timeout. Check network settings.'); }); luxsock.on('close', function () { that.log.debug('Connection to Luxtronik2 closed.'); }); luxsock.on('end', function () { that.log.debug('Connection to Luxtronik2 ended.'); }); luxsock.on('data', function (data) { that.log.debug('Connection to Luxtronik2 established. Requesting data by sending command.'); const {Buffer} = require('node:buffer'); const buf = Buffer.alloc(data.length); buf.write(data, 'binary'); that.log.debug('Buffer length is %s', buf.length); const confirm = buf.readUInt32BE(0); that.log.debug('Confirm message is %s', confirm); const offset = 8; if (offset + 4 > buf.length) { that.log.warn('Buffer does not have enough bytes. Exiting function without being able to update data.'); return; } if (confirm === 3004 && offset + 4 <= buf.length) { that.log.debug('Luxtronik2 confirmed command and the buffer byte count is good.'); const count = buf.readUInt32BE(offset); if (data.length === (count * 4) + 12) { let pos = 12; const calculated = new Int32Array(count); for (let i = 0; i < count; i++) { calculated[i] = buf.readInt32BE(pos); pos += 4; } that.log.debug('Data received: %s', calculated); const items = translate(calculated); that.log.debug('Itemized datalist: %s', items); that.log.debug('Plugin is reading Channel %s', Channel); temperature = items[Channel]; that.log.debug('Current temperature is: %s', temperature); callback(null, temperature); } } else { that.log.warn('Error: Luxtronik2 did not confirm command to send data.'); } luxsock.end(); }); luxsock.on('connect', function () { luxsock.setNoDelay(true); luxsock.setEncoding('binary'); const {Buffer} = require('node:buffer'); const buf = Buffer.alloc(4); buf.writeUInt32BE(3004, 0); luxsock.write(buf.toString('binary'), 'binary'); buf.writeUInt32BE(0, 0); luxsock.write(buf.toString('binary'), 'binary'); }); }, getCurrentTemperature(callback) { const counter = ++this.counter; this.log.debug('getCurrentTemperature: early callback with cached CurrentTemperature: %s (%d).', this.CurrentTemperature, counter); callback(null, this.CurrentTemperature); this.getTemperature((error, HomeKitState) => { this.CurrentTemperature = HomeKitState; this.temperatureService.getCharacteristic(Characteristic.CurrentTemperature).updateValue(this.CurrentTemperature); this.log.debug('getCurrentTemperature: update CurrentTemperature: %s (%d).', this.CurrentTemperature, counter); }); }, identify: function (callback) { this.log.info('Currently there is no way to help identify the Luxtronik2 device!'); callback(); }, getServices: function () { const informationService = new Service.AccessoryInformation(); informationService .setCharacteristic(Characteristic.Manufacturer, 'homebridge-luxtronik2') .setCharacteristic(Characteristic.Model, 'Luxtronik2') .setCharacteristic(Characteristic.FirmwareRevision, this.firmwareRevision) .setCharacteristic(Characteristic.SerialNumber, this.name); this.temperatureService .getCharacteristic(Characteristic.CurrentTemperature) .setProps({ minValue: Number.parseFloat('-50'), maxValue: Number.parseFloat('100'), }) .on('get', this.getCurrentTemperature.bind(this)); return [informationService, this.temperatureService]; }, }; module.exports = function (homebridge) { Service = homebridge.hap.Service; Characteristic = homebridge.hap.Characteristic; homebridge.registerAccessory('homebridge-luxtronik2', 'temperature', Luxtronik2); };