-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
231 lines (201 loc) · 7.68 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
// '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'));
// translate dword to data type
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: {
// nothing
break;}
}
}
result.push(value); // push to array
}
}
return result;
} // function translate(c)
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;
this.log.debug('Config: IP is %s', 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;
this.log.debug('Config: Port is %s', 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;
this.log.debug('Config: name is %s', 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: Channel is %s', config.Channel);
}
this.CurrentTemperature = 0; // Initial value for early response
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());
// stop();
});
/* handle timeout */
luxsock.on('timeout', function () {
that.log.warn('Connection timeout. Check network settings.');
// stop();
});
/* handle close */
luxsock.on('close', function () {
that.log.debug('Connection to Luxtronik2 closed.');
// stop();
});
/* handle end */
luxsock.on('end', function () {
that.log.debug('Connection to Luxtronik2 ended.');
// stop();
});
/* receive data */
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');
/* luxtronik must confirm command */
that.log.debug('Buffer length is %s', buf.length);
const confirm = buf.readUInt32BE(0);
that.log.debug('Confirm message is %s', confirm);
/* is 0 if data is unchanged since last request */
const offset = 8;
if (offset + 4 > buf.length) {
// buffer does not have enough bytes to read uint32 at offset
that.log.warn('Buffer does not have enough bytes. Exiting function without being able to update data.');
}
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.');
// stop();
}
luxsock.end();
});
// connected => get values
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) { // Wrapper for service call to early return
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) { /* unicorn/no-anonymous-default-export: ["off"] */
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
homebridge.registerAccessory('homebridge-luxtronik2', 'temperature', Luxtronik2);
};