iobroker.acinfinity 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/io-package.json +14 -1
- package/lib/client.js +84 -54
- package/lib/constants.js +96 -108
- package/lib/creators/deviceCreator.js +97 -91
- package/lib/creators/portCreator.js +238 -225
- package/lib/creators/stateCreator.js +27 -21
- package/lib/dataModels.js +25 -20
- package/lib/handlers/deviceSettingsHandler.js +55 -38
- package/lib/handlers/portModeHandler.js +72 -71
- package/lib/handlers/portSettingsHandler.js +95 -68
- package/lib/stateManager.js +62 -42
- package/lib/updaters/deviceUpdater.js +133 -96
- package/lib/updaters/portUpdater.js +256 -207
- package/main.js +102 -77
- package/package.json +4 -4
package/main.js
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ioBroker AC Infinity Adapter
|
|
3
3
|
* Adapter to control AC Infinity devices via ioBroker
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* Based on the Home Assistant integration by dalinicus
|
|
6
6
|
* https://github.com/dalinicus/homeassistant-acinfinity
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
'use strict';
|
|
10
10
|
|
|
11
|
-
const utils = require(
|
|
12
|
-
const ACInfinityClient = require(
|
|
13
|
-
const StateManager = require(
|
|
14
|
-
const { DEFAULT_POLLING_INTERVAL, MINIMUM_POLLING_INTERVAL } = require(
|
|
11
|
+
const utils = require('@iobroker/adapter-core');
|
|
12
|
+
const ACInfinityClient = require('./lib/client');
|
|
13
|
+
const StateManager = require('./lib/stateManager');
|
|
14
|
+
const { DEFAULT_POLLING_INTERVAL, MINIMUM_POLLING_INTERVAL } = require('./lib/constants');
|
|
15
15
|
|
|
16
16
|
class ACInfinity extends utils.Adapter {
|
|
17
17
|
/**
|
|
18
|
-
* @param {Partial<utils.AdapterOptions>} [options
|
|
18
|
+
* @param {Partial<utils.AdapterOptions>} [options]
|
|
19
19
|
*/
|
|
20
20
|
constructor(options) {
|
|
21
21
|
super({
|
|
22
22
|
...options,
|
|
23
|
-
name:
|
|
23
|
+
name: 'acinfinity',
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
this.on(
|
|
27
|
-
this.on(
|
|
28
|
-
this.on(
|
|
26
|
+
this.on('ready', this.onReady.bind(this));
|
|
27
|
+
this.on('stateChange', this.onStateChange.bind(this));
|
|
28
|
+
this.on('unload', this.onUnload.bind(this));
|
|
29
29
|
|
|
30
30
|
this.client = null;
|
|
31
31
|
this.stateManager = null;
|
|
@@ -38,20 +38,22 @@ class ACInfinity extends utils.Adapter {
|
|
|
38
38
|
*/
|
|
39
39
|
async onReady() {
|
|
40
40
|
// Initialize adapter
|
|
41
|
-
this.log.info(
|
|
41
|
+
this.log.info('Starting AC Infinity adapter');
|
|
42
42
|
|
|
43
43
|
// Display a warning message in the log
|
|
44
|
-
this.log.warn(
|
|
45
|
-
|
|
44
|
+
this.log.warn(
|
|
45
|
+
'⚠️ WARNING: BETA VERSION ⚠️ - This adapter is in an early development stage. Use at your own risk!',
|
|
46
|
+
);
|
|
47
|
+
|
|
46
48
|
// Display a toast notification in the admin UI if possible
|
|
47
49
|
try {
|
|
48
|
-
this.sendTo(
|
|
49
|
-
message:
|
|
50
|
-
type:
|
|
51
|
-
duration: 15000
|
|
50
|
+
this.sendTo('system.adapter.admin.0', 'toast', {
|
|
51
|
+
message: 'AC Infinity Adapter: BETA Version - Use at your own risk!',
|
|
52
|
+
type: 'warning',
|
|
53
|
+
duration: 15000,
|
|
52
54
|
});
|
|
53
55
|
} catch (e) {
|
|
54
|
-
this.log.debug(
|
|
56
|
+
this.log.debug(`Could not send toast notification: ${e.message}`);
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
// Get adapter configuration
|
|
@@ -59,39 +61,39 @@ class ACInfinity extends utils.Adapter {
|
|
|
59
61
|
const password = this.config.password;
|
|
60
62
|
const pollingInterval = Math.max(
|
|
61
63
|
this.config.pollingInterval || DEFAULT_POLLING_INTERVAL,
|
|
62
|
-
MINIMUM_POLLING_INTERVAL
|
|
64
|
+
MINIMUM_POLLING_INTERVAL,
|
|
63
65
|
);
|
|
64
66
|
|
|
65
67
|
if (!email || !password) {
|
|
66
|
-
this.log.error(
|
|
68
|
+
this.log.error('Missing login credentials. Please configure in adapter settings.');
|
|
67
69
|
return;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
// Set up connection indicator state
|
|
71
|
-
await this.setStateAsync(
|
|
73
|
+
await this.setStateAsync('info.connection', { val: false, ack: true });
|
|
72
74
|
|
|
73
75
|
try {
|
|
74
76
|
// Initialize API client
|
|
75
77
|
this.client = new ACInfinityClient(email, password, this.log);
|
|
76
78
|
this.stateManager = new StateManager(this);
|
|
77
|
-
|
|
79
|
+
|
|
78
80
|
// Wichtig: Den Client an den StateManager weitergeben
|
|
79
81
|
this.stateManager.setClient(this.client);
|
|
80
82
|
|
|
81
83
|
// Attempt to login
|
|
82
84
|
this.log.info(`Logging in with email: ${email}`);
|
|
83
85
|
await this.client.login();
|
|
84
|
-
|
|
86
|
+
|
|
85
87
|
// If we got here, login was successful
|
|
86
|
-
this.log.info(
|
|
88
|
+
this.log.info('Login successful');
|
|
87
89
|
this.isConnected = true;
|
|
88
|
-
await this.setStateAsync(
|
|
90
|
+
await this.setStateAsync('info.connection', { val: true, ack: true });
|
|
89
91
|
|
|
90
92
|
// Store token information
|
|
91
|
-
await this.setStateAsync(
|
|
93
|
+
await this.setStateAsync('info.token', { val: this.client.token, ack: true });
|
|
92
94
|
|
|
93
95
|
// Abonniere alle Zustände
|
|
94
|
-
this.subscribeStates(
|
|
96
|
+
this.subscribeStates('*');
|
|
95
97
|
this.log.debug("Abonniere alle Zustände mit subscribeStates('*')");
|
|
96
98
|
|
|
97
99
|
// Initialize adapter by fetching devices and setting up states
|
|
@@ -104,11 +106,11 @@ class ACInfinity extends utils.Adapter {
|
|
|
104
106
|
await this.updateDeviceData();
|
|
105
107
|
} catch (error) {
|
|
106
108
|
this.log.error(`Error during polling update: ${error.message}`);
|
|
107
|
-
if (error.message.includes(
|
|
108
|
-
this.log.info(
|
|
109
|
+
if (error.message.includes('unauthorized') || error.message.includes('auth')) {
|
|
110
|
+
this.log.info('Authentication error detected, attempting to re-login');
|
|
109
111
|
try {
|
|
110
112
|
await this.client.login();
|
|
111
|
-
this.log.info(
|
|
113
|
+
this.log.info('Re-login successful');
|
|
112
114
|
} catch (loginError) {
|
|
113
115
|
this.log.error(`Failed to re-login: ${loginError.message}`);
|
|
114
116
|
}
|
|
@@ -117,7 +119,7 @@ class ACInfinity extends utils.Adapter {
|
|
|
117
119
|
}, pollingInterval * 1000);
|
|
118
120
|
} catch (error) {
|
|
119
121
|
this.log.error(`Initialization error: ${error.message}`);
|
|
120
|
-
await this.setStateAsync(
|
|
122
|
+
await this.setStateAsync('info.connection', { val: false, ack: true });
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
125
|
|
|
@@ -125,17 +127,17 @@ class ACInfinity extends utils.Adapter {
|
|
|
125
127
|
* Initialize adapter by fetching devices and setting up states
|
|
126
128
|
*/
|
|
127
129
|
async initializeAdapter() {
|
|
128
|
-
this.log.info(
|
|
129
|
-
|
|
130
|
+
this.log.info('Initializing adapter and fetching devices');
|
|
131
|
+
|
|
130
132
|
try {
|
|
131
133
|
// Fetch all devices
|
|
132
134
|
const devices = await this.client.getDevicesList();
|
|
133
|
-
|
|
135
|
+
|
|
134
136
|
// Debug log - check structure
|
|
135
137
|
if (devices && devices.length > 0) {
|
|
136
138
|
this.log.debug(`First device sample: ${JSON.stringify(devices[0]).substring(0, 1000)}...`);
|
|
137
139
|
}
|
|
138
|
-
|
|
140
|
+
|
|
139
141
|
this.log.info(`Found ${devices.length} devices`);
|
|
140
142
|
|
|
141
143
|
// Create device information in state tree
|
|
@@ -143,8 +145,8 @@ class ACInfinity extends utils.Adapter {
|
|
|
143
145
|
|
|
144
146
|
// Perform initial data update
|
|
145
147
|
await this.updateDeviceData();
|
|
146
|
-
|
|
147
|
-
this.log.info(
|
|
148
|
+
|
|
149
|
+
this.log.info('Adapter initialization completed successfully');
|
|
148
150
|
} catch (error) {
|
|
149
151
|
this.log.error(`Failed to initialize adapter: ${error.message}`);
|
|
150
152
|
throw error;
|
|
@@ -156,64 +158,76 @@ class ACInfinity extends utils.Adapter {
|
|
|
156
158
|
*/
|
|
157
159
|
async updateDeviceData() {
|
|
158
160
|
if (!this.isConnected || !this.client) {
|
|
159
|
-
this.log.debug(
|
|
161
|
+
this.log.debug('Not connected, skipping update');
|
|
160
162
|
return;
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
this.log.debug(
|
|
165
|
+
this.log.debug('Updating device data');
|
|
164
166
|
try {
|
|
165
167
|
// Get latest device data
|
|
166
168
|
const devices = await this.client.getDevicesList();
|
|
167
|
-
|
|
169
|
+
|
|
168
170
|
this.log.debug(`Fetched ${devices.length} devices for update`);
|
|
169
|
-
|
|
171
|
+
|
|
170
172
|
// Update states for all devices
|
|
171
173
|
for (const device of devices) {
|
|
172
174
|
this.log.debug(`Updating device ${device.devId} (${device.devName})`);
|
|
173
|
-
|
|
175
|
+
|
|
174
176
|
// Debug logging for important values
|
|
175
|
-
if (typeof device.temperature !==
|
|
176
|
-
this.log.debug(
|
|
177
|
+
if (typeof device.temperature !== 'undefined') {
|
|
178
|
+
this.log.debug(
|
|
179
|
+
`Device ${device.devId} temperature: ${device.temperature} (raw), ${device.temperature / 100} (converted)`,
|
|
180
|
+
);
|
|
177
181
|
}
|
|
178
|
-
if (typeof device.humidity !==
|
|
179
|
-
this.log.debug(
|
|
182
|
+
if (typeof device.humidity !== 'undefined') {
|
|
183
|
+
this.log.debug(
|
|
184
|
+
`Device ${device.devId} humidity: ${device.humidity} (raw), ${device.humidity / 100} (converted)`,
|
|
185
|
+
);
|
|
180
186
|
}
|
|
181
|
-
if (typeof device.vpdnums !==
|
|
182
|
-
this.log.debug(
|
|
187
|
+
if (typeof device.vpdnums !== 'undefined') {
|
|
188
|
+
this.log.debug(
|
|
189
|
+
`Device ${device.devId} vpd: ${device.vpdnums} (raw), ${device.vpdnums / 100} (converted)`,
|
|
190
|
+
);
|
|
183
191
|
}
|
|
184
|
-
|
|
192
|
+
|
|
185
193
|
await this.stateManager.updateDeviceData(device);
|
|
186
|
-
|
|
194
|
+
|
|
187
195
|
// Fetch and update port settings for each device
|
|
188
196
|
if (device.deviceInfo && Array.isArray(device.deviceInfo.ports)) {
|
|
189
197
|
for (const port of device.deviceInfo.ports) {
|
|
190
198
|
const portId = port.port;
|
|
191
199
|
this.log.debug(`Fetching settings for device ${device.devId}, port ${portId}`);
|
|
192
|
-
|
|
200
|
+
|
|
193
201
|
try {
|
|
194
202
|
const portSettings = await this.client.getDeviceModeSettings(device.devId, portId);
|
|
195
203
|
await this.stateManager.updatePortSettings(device.devId, portId, portSettings);
|
|
196
204
|
} catch (portError) {
|
|
197
|
-
this.log.warn(
|
|
205
|
+
this.log.warn(
|
|
206
|
+
`Error fetching port mode settings for device ${device.devId}, port ${portId}: ${portError.message}`,
|
|
207
|
+
);
|
|
198
208
|
}
|
|
199
|
-
|
|
209
|
+
|
|
200
210
|
try {
|
|
201
211
|
const advancedSettings = await this.client.getDeviceSettings(device.devId, portId);
|
|
202
212
|
await this.stateManager.updateAdvancedSettings(device.devId, portId, advancedSettings);
|
|
203
213
|
} catch (advError) {
|
|
204
|
-
this.log.warn(
|
|
214
|
+
this.log.warn(
|
|
215
|
+
`Error fetching advanced settings for device ${device.devId}, port ${portId}: ${advError.message}`,
|
|
216
|
+
);
|
|
205
217
|
}
|
|
206
218
|
}
|
|
207
219
|
} else {
|
|
208
220
|
this.log.warn(`No ports found for device ${device.devId}`);
|
|
209
221
|
}
|
|
210
|
-
|
|
222
|
+
|
|
211
223
|
// Fetch and update advanced settings for controller
|
|
212
224
|
try {
|
|
213
225
|
const controllerSettings = await this.client.getDeviceSettings(device.devId, 0);
|
|
214
226
|
await this.stateManager.updateAdvancedSettings(device.devId, 0, controllerSettings);
|
|
215
227
|
} catch (ctrlError) {
|
|
216
|
-
this.log.warn(
|
|
228
|
+
this.log.warn(
|
|
229
|
+
`Error fetching controller settings for device ${device.devId}: ${ctrlError.message}`,
|
|
230
|
+
);
|
|
217
231
|
}
|
|
218
232
|
}
|
|
219
233
|
} catch (error) {
|
|
@@ -224,6 +238,7 @@ class ACInfinity extends utils.Adapter {
|
|
|
224
238
|
|
|
225
239
|
/**
|
|
226
240
|
* Is called when adapter shuts down - callback has to be called under any circumstances!
|
|
241
|
+
*
|
|
227
242
|
* @param {() => void} callback
|
|
228
243
|
*/
|
|
229
244
|
onUnload(callback) {
|
|
@@ -233,23 +248,26 @@ class ACInfinity extends utils.Adapter {
|
|
|
233
248
|
clearInterval(this.pollingInterval);
|
|
234
249
|
this.pollingInterval = null;
|
|
235
250
|
}
|
|
236
|
-
|
|
237
|
-
this.log.info(
|
|
251
|
+
|
|
252
|
+
this.log.info('AC Infinity adapter shutting down');
|
|
238
253
|
this.isConnected = false;
|
|
239
254
|
callback();
|
|
240
|
-
} catch
|
|
255
|
+
} catch {
|
|
241
256
|
callback();
|
|
242
257
|
}
|
|
243
258
|
}
|
|
244
259
|
|
|
245
260
|
/**
|
|
246
261
|
* Is called if a subscribed state changes
|
|
262
|
+
*
|
|
247
263
|
* @param {string} id
|
|
248
264
|
* @param {ioBroker.State | null | undefined} state
|
|
249
265
|
*/
|
|
250
266
|
async onStateChange(id, state) {
|
|
251
267
|
// Nur für Debug-Zwecke, kann später entfernt oder als debug-Level geloggt werden
|
|
252
|
-
this.log.debug(
|
|
268
|
+
this.log.debug(
|
|
269
|
+
`State change detected: ${id} = ${state ? state.val : 'null'}, ack = ${state ? state.ack : 'null'}`,
|
|
270
|
+
);
|
|
253
271
|
|
|
254
272
|
// Standard-Verarbeitung
|
|
255
273
|
if (!state) {
|
|
@@ -267,59 +285,66 @@ class ACInfinity extends utils.Adapter {
|
|
|
267
285
|
try {
|
|
268
286
|
// Überprüfen, ob der Client und StateManager initialisiert wurden
|
|
269
287
|
if (!this.client || !this.stateManager) {
|
|
270
|
-
this.log.error(
|
|
271
|
-
|
|
288
|
+
this.log.error(
|
|
289
|
+
`Adapter not fully initialized. Client: ${!!this.client}, StateManager: ${!!this.stateManager}`,
|
|
290
|
+
);
|
|
291
|
+
throw new Error('Adapter is not fully initialized');
|
|
272
292
|
}
|
|
273
293
|
|
|
274
294
|
// Überprüfen, ob wir angemeldet sind
|
|
275
295
|
if (!this.isConnected || !this.client.isLoggedIn()) {
|
|
276
|
-
this.log.info(
|
|
296
|
+
this.log.info(
|
|
297
|
+
`Not logged in, trying to log in again. isConnected: ${this.isConnected}, isLoggedIn: ${this.client ? this.client.isLoggedIn() : 'client is null'}`,
|
|
298
|
+
);
|
|
277
299
|
try {
|
|
278
300
|
await this.client.login();
|
|
279
301
|
this.log.info(`Re-login successful. Token: ${this.client.token}`);
|
|
280
302
|
this.isConnected = true;
|
|
281
|
-
await this.setStateAsync(
|
|
303
|
+
await this.setStateAsync('info.connection', { val: true, ack: true });
|
|
282
304
|
} catch (loginError) {
|
|
283
305
|
this.log.error(`Error during re-login: ${loginError.message}`);
|
|
284
306
|
this.isConnected = false;
|
|
285
|
-
await this.setStateAsync(
|
|
286
|
-
throw new Error(
|
|
307
|
+
await this.setStateAsync('info.connection', { val: false, ack: true });
|
|
308
|
+
throw new Error('Login failed, state change cannot be processed');
|
|
287
309
|
}
|
|
288
310
|
}
|
|
289
311
|
|
|
290
312
|
// Parse ID, um zu prüfen, um welche Art von State es sich handelt
|
|
291
|
-
const idParts = id.split(
|
|
313
|
+
const idParts = id.split('.');
|
|
292
314
|
this.log.debug(`ID parts: ${JSON.stringify(idParts)}`);
|
|
293
315
|
|
|
294
316
|
// Lasse den StateManager die Änderung verarbeiten
|
|
295
317
|
this.log.debug(`Forwarding state change to StateManager: ${id} = ${state.val}`);
|
|
296
318
|
await this.stateManager.handleStateChange(id, state);
|
|
297
319
|
this.log.debug(`StateManager processed state change: ${id}`);
|
|
298
|
-
|
|
299
320
|
} catch (error) {
|
|
300
321
|
this.log.error(`Error processing state change: ${error.message}`);
|
|
301
322
|
if (error.stack) {
|
|
302
323
|
this.log.debug(`Stack trace: ${error.stack}`);
|
|
303
324
|
}
|
|
304
|
-
|
|
325
|
+
|
|
305
326
|
// Bei Kommunikationsfehlern Verbindungsstatus aktualisieren
|
|
306
|
-
if (
|
|
307
|
-
error.message.includes(
|
|
327
|
+
if (
|
|
328
|
+
error.message.includes('network') ||
|
|
329
|
+
error.message.includes('timeout') ||
|
|
330
|
+
error.message.includes('connection') ||
|
|
331
|
+
error.message.includes('connect')
|
|
332
|
+
) {
|
|
308
333
|
this.isConnected = false;
|
|
309
|
-
await this.setStateAsync(
|
|
334
|
+
await this.setStateAsync('info.connection', { val: false, ack: true });
|
|
310
335
|
}
|
|
311
336
|
}
|
|
312
337
|
}
|
|
313
338
|
}
|
|
314
339
|
|
|
315
|
-
// @ts-
|
|
340
|
+
// @ts-expect-error parent is a valid property on module
|
|
316
341
|
if (module.parent) {
|
|
317
342
|
// Export the constructor in compact mode
|
|
318
343
|
/**
|
|
319
|
-
* @param {Partial<utils.AdapterOptions>} [options
|
|
344
|
+
* @param {Partial<utils.AdapterOptions>} [options]
|
|
320
345
|
*/
|
|
321
|
-
module.exports =
|
|
346
|
+
module.exports = options => new ACInfinity(options);
|
|
322
347
|
} else {
|
|
323
348
|
// otherwise start the instance directly
|
|
324
349
|
new ACInfinity();
|
|
325
|
-
}
|
|
350
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.acinfinity",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Monitor and control AC Infinity devices",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "laurent Standard",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"axios": "^1.5.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
+
"@iobroker/eslint-config": "^2.2.0",
|
|
30
31
|
"@iobroker/testing": "^5.2.2",
|
|
31
32
|
"@types/node": "^18.16.18",
|
|
32
|
-
"eslint": "^8.44.0",
|
|
33
33
|
"typescript": "~5.0.4"
|
|
34
34
|
},
|
|
35
35
|
"main": "main.js",
|
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
"test:integration": "mocha test/integration --exit",
|
|
48
48
|
"test": "npm run test:js && npm run test:package",
|
|
49
49
|
"check": "tsc --noEmit -p tsconfig.check.json",
|
|
50
|
-
"lint": "eslint .",
|
|
50
|
+
"lint": "eslint -c eslint.config.mjs .",
|
|
51
51
|
"build": "npm run lint && npm test"
|
|
52
52
|
},
|
|
53
53
|
"bugs": {
|
|
54
54
|
"url": "https://github.com/raspilaurent/ioBroker.acinfinity/issues"
|
|
55
55
|
},
|
|
56
56
|
"readmeFilename": "README.md"
|
|
57
|
-
}
|
|
57
|
+
}
|