iobroker.acinfinity 0.7.1 → 0.7.3

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.
@@ -2,76 +2,38 @@
2
2
  "i18n": true,
3
3
  "type": "panel",
4
4
  "items": {
5
- "warning": {
6
- "type": "panel",
7
- "items": {
8
- "warningTitle": {
9
- "type": "staticText",
10
- "text": "WARNING / WARNUNG",
11
- "style": {
12
- "color": "red",
13
- "fontSize": "18px",
14
- "fontWeight": "bold",
15
- "textAlign": "center"
16
- },
17
- "sm": 12,
18
- "md": 12,
19
- "lg": 12,
20
- "xl": 12,
21
- "xs": 12
22
- },
23
- "warningText": {
24
- "type": "staticText",
25
- "text": "This adapter is in an early development stage. You use this adapter at your own risk. The author assumes no liability for any damage.<br><br>Dieser Adapter befindet sich in einem frühen Entwicklungsstadium. Sie verwenden diesen Adapter auf eigene Gefahr. Der Autor übernimmt keine Haftung für eventuelle Schäden.",
26
- "style": {
27
- "color": "red",
28
- "fontWeight": "bold",
29
- "textAlign": "center",
30
- "marginBottom": "20px"
31
- },
32
- "sm": 12,
33
- "md": 12,
34
- "lg": 12,
35
- "xl": 12,
36
- "xs": 12
37
- }
38
- }
5
+ "email": {
6
+ "type": "text",
7
+ "label": "AC Infinity Email",
8
+ "sm": 12,
9
+ "md": 8,
10
+ "lg": 6,
11
+ "xl": 6,
12
+ "xs": 12
39
13
  },
40
- "settings": {
41
- "type": "panel",
42
- "items": {
43
- "email": {
44
- "type": "text",
45
- "label": "AC Infinity E-Mail",
46
- "newLine": true,
47
- "sm": 12,
48
- "md": 12,
49
- "lg": 12,
50
- "xl": 12,
51
- "xs": 12
52
- },
53
- "password": {
54
- "type": "password",
55
- "label": "AC Infinity Password / Passwort",
56
- "newLine": true,
57
- "sm": 12,
58
- "md": 12,
59
- "lg": 12,
60
- "xl": 12,
61
- "xs": 12
62
- },
63
- "pollingInterval": {
64
- "type": "number",
65
- "label": "Polling Interval / Abfrageintervall (seconds)",
66
- "min": 5,
67
- "default": 30,
68
- "sm": 12,
69
- "md": 6,
70
- "lg": 4,
71
- "xl": 3,
72
- "xs": 12
73
- }
74
- }
14
+ "password": {
15
+ "type": "password",
16
+ "label": "AC Infinity Password",
17
+ "help": "Note: AC Infinity truncates passwords to 25 characters.",
18
+ "newLine": true,
19
+ "sm": 12,
20
+ "md": 8,
21
+ "lg": 6,
22
+ "xl": 6,
23
+ "xs": 12
24
+ },
25
+ "pollingInterval": {
26
+ "type": "number",
27
+ "label": "Polling Interval (seconds)",
28
+ "help": "Minimum polling interval is 5 seconds.",
29
+ "newLine": true,
30
+ "min": 5,
31
+ "default": 30,
32
+ "sm": 6,
33
+ "md": 4,
34
+ "lg": 3,
35
+ "xl": 2,
36
+ "xs": 6
75
37
  }
76
38
  }
77
- }
39
+ }
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "acinfinity",
4
- "version": "0.7.1",
4
+ "version": "0.7.3",
5
5
  "news": {
6
+ "0.7.3": {
7
+ "en": "Admin UI: remove warning box, clean up settings layout, fix help link",
8
+ "de": "Admin-UI: Warnhinweis entfernt, Einstellungslayout bereinigt, Hilfe-Link korrigiert",
9
+ "ru": "Admin UI: убран блок предупреждения, улучшен макет настроек, исправлена ссылка на справку",
10
+ "pt": "Admin UI: caixa de aviso removida, layout de configurações limpo, link de ajuda corrigido",
11
+ "nl": "Admin UI: waarschuwingsvak verwijderd, instellingen layout verbeterd, helplink gecorrigeerd",
12
+ "fr": "Admin UI: bloc d'avertissement supprimé, mise en page des paramètres améliorée, lien d'aide corrigé",
13
+ "it": "Admin UI: riquadro di avviso rimosso, layout impostazioni migliorato, link guida corretto",
14
+ "es": "Admin UI: cuadro de advertencia eliminado, diseño de configuración mejorado, enlace de ayuda corregido",
15
+ "pl": "Admin UI: usunięto pole ostrzeżenia, poprawiono układ ustawień, naprawiono link pomocy",
16
+ "uk": "Admin UI: видалено блок попередження, покращено макет налаштувань, виправлено посилання на довідку",
17
+ "zh-cn": "管理界面:移除警告框,优化设置布局,修复帮助链接"
18
+ },
19
+ "0.7.2": {
20
+ "en": "Code quality: migrate to ESLint 9 with @iobroker/eslint-config",
21
+ "de": "Codequalität: Migration auf ESLint 9 mit @iobroker/eslint-config",
22
+ "ru": "Качество кода: миграция на ESLint 9 с @iobroker/eslint-config",
23
+ "pt": "Qualidade do código: migração para ESLint 9 com @iobroker/eslint-config",
24
+ "nl": "Codekwaliteit: migratie naar ESLint 9 met @iobroker/eslint-config",
25
+ "fr": "Qualité du code: migration vers ESLint 9 avec @iobroker/eslint-config",
26
+ "it": "Qualità del codice: migrazione a ESLint 9 con @iobroker/eslint-config",
27
+ "es": "Calidad del código: migración a ESLint 9 con @iobroker/eslint-config",
28
+ "pl": "Jakość kodu: migracja do ESLint 9 z @iobroker/eslint-config",
29
+ "uk": "Якість коду: міграція на ESLint 9 з @iobroker/eslint-config",
30
+ "zh-cn": "代码质量:迁移到 ESLint 9 并使用 @iobroker/eslint-config"
31
+ },
6
32
  "0.7.1": {
7
33
  "en": "Add value ranges to state names for better readability",
8
34
  "de": "Wertebereiche zu Datenpunkt-Namen hinzugefügt für bessere Lesbarkeit",
@@ -137,7 +163,7 @@
137
163
  "icon": "acinfinity.png",
138
164
  "enabled": true,
139
165
  "extIcon": "https://raw.githubusercontent.com/raspilaurent/ioBroker.acinfinity/master/admin/acinfinity.png",
140
- "readme": "https://github.com/raspilaurent/ioBroker.acinfinity/blob/master/README.md",
166
+ "readme": "https://github.com/raspilaurent/ioBroker.acinfinity/blob/main/README.md",
141
167
  "loglevel": "info",
142
168
  "mode": "daemon",
143
169
  "type": "climate-control",
package/lib/client.js CHANGED
@@ -3,14 +3,15 @@
3
3
  * Handles all API communication with AC Infinity servers
4
4
  */
5
5
 
6
- "use strict";
6
+ 'use strict';
7
7
 
8
- const axios = require("axios");
9
- const { API_BASE_URL, API_ENDPOINTS } = require("./constants");
8
+ const axios = require('axios');
9
+ const { API_BASE_URL, API_ENDPOINTS } = require('./constants');
10
10
 
11
11
  class ACInfinityClient {
12
12
  /**
13
13
  * Creates a new AC Infinity API client
14
+ *
14
15
  * @param {string} email - AC Infinity account email
15
16
  * @param {string} password - AC Infinity account password
16
17
  * @param {object} log - Logger object
@@ -22,19 +23,20 @@ class ACInfinityClient {
22
23
  this.token = null;
23
24
  this.axiosInstance = axios.create({
24
25
  timeout: 30000,
25
- validateStatus: status => status >= 200 && status < 300
26
+ validateStatus: status => status >= 200 && status < 300,
26
27
  });
27
28
  }
28
29
 
29
30
  /**
30
31
  * Creates headers for API requests
32
+ *
31
33
  * @param {boolean} useAuthToken - Whether to include authentication token
32
34
  * @returns {object}
33
35
  */
34
36
  createHeaders(useAuthToken = false) {
35
37
  const headers = {
36
- "User-Agent": "ACController/1.8.5 (com.acinfinity.humiture; build:500; iOS 17.0.1) Alamofire/5.7.1",
37
- "Content-Type": "application/x-www-form-urlencoded; charset=utf-8"
38
+ 'User-Agent': 'ACController/1.8.5 (com.acinfinity.humiture; build:500; iOS 17.0.1) Alamofire/5.7.1',
39
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
38
40
  };
39
41
  if (useAuthToken && this.token) {
40
42
  headers.token = this.token;
@@ -44,6 +46,7 @@ class ACInfinityClient {
44
46
 
45
47
  /**
46
48
  * Performs API login and obtains authentication token
49
+ *
47
50
  * @returns {Promise<void>}
48
51
  */
49
52
  async login() {
@@ -52,21 +55,21 @@ class ACInfinityClient {
52
55
  const formData = `appEmail=${encodeURIComponent(this.email)}&appPasswordl=${encodeURIComponent(normalizedPassword)}`;
53
56
  this.log.debug(`Login attempt with: ${this.email}`);
54
57
 
55
- const response = await this.axiosInstance.post(
56
- `${API_BASE_URL}${API_ENDPOINTS.LOGIN}`,
57
- formData,
58
- { headers: this.createHeaders(false) }
59
- );
58
+ const response = await this.axiosInstance.post(`${API_BASE_URL}${API_ENDPOINTS.LOGIN}`, formData, {
59
+ headers: this.createHeaders(false),
60
+ });
60
61
 
61
62
  if (response.data && response.data.code === 200) {
62
63
  this.token = response.data.data.appId;
63
- this.log.debug("Login successful");
64
+ this.log.debug('Login successful');
64
65
  return;
65
66
  }
66
- throw new Error(`Login failed: ${response.data ? response.data.msg : "Unknown error"}`);
67
+ throw new Error(`Login failed: ${response.data ? response.data.msg : 'Unknown error'}`);
67
68
  } catch (error) {
68
69
  if (error.response) {
69
- throw new Error(`Login failed with status ${error.response.status}: ${error.response.data ? error.response.data.msg : "Unknown error"}`);
70
+ throw new Error(
71
+ `Login failed with status ${error.response.status}: ${error.response.data ? error.response.data.msg : 'Unknown error'}`,
72
+ );
70
73
  }
71
74
  throw new Error(`Login failed: ${error.message}`);
72
75
  }
@@ -74,6 +77,7 @@ class ACInfinityClient {
74
77
 
75
78
  /**
76
79
  * Checks if client is logged in
80
+ *
77
81
  * @returns {boolean}
78
82
  */
79
83
  isLoggedIn() {
@@ -82,6 +86,7 @@ class ACInfinityClient {
82
86
 
83
87
  /**
84
88
  * General API call with automatic re-login on 401
89
+ *
85
90
  * @param {string} endpoint
86
91
  * @param {object} data
87
92
  * @param {boolean} needsAuth
@@ -95,21 +100,19 @@ class ACInfinityClient {
95
100
  try {
96
101
  const formData = Object.entries(data)
97
102
  .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
98
- .join("&");
103
+ .join('&');
99
104
 
100
- const response = await this.axiosInstance.post(
101
- `${API_BASE_URL}${endpoint}`,
102
- formData,
103
- { headers: this.createHeaders(needsAuth) }
104
- );
105
+ const response = await this.axiosInstance.post(`${API_BASE_URL}${endpoint}`, formData, {
106
+ headers: this.createHeaders(needsAuth),
107
+ });
105
108
 
106
109
  if (response.data && response.data.code === 200) {
107
110
  return response.data.data || {};
108
111
  }
109
- throw new Error(`API call to ${endpoint} failed: ${response.data ? response.data.msg : "Unknown error"}`);
112
+ throw new Error(`API call to ${endpoint} failed: ${response.data ? response.data.msg : 'Unknown error'}`);
110
113
  } catch (error) {
111
114
  if (error.response && error.response.status === 401 && needsAuth) {
112
- this.log.info("Token expired, attempting to re-login");
115
+ this.log.info('Token expired, attempting to re-login');
113
116
  await this.login();
114
117
  return this.apiCall(endpoint, data, needsAuth);
115
118
  }
@@ -122,6 +125,7 @@ class ACInfinityClient {
122
125
 
123
126
  /**
124
127
  * Gets list of all AC Infinity devices
128
+ *
125
129
  * @returns {Promise<Array>}
126
130
  */
127
131
  async getDevicesList() {
@@ -130,35 +134,46 @@ class ACInfinityClient {
130
134
 
131
135
  /**
132
136
  * Gets mode settings for a specific device port
137
+ *
133
138
  * @param {string|number} deviceId
134
139
  * @param {number} portId
135
140
  * @returns {Promise<object>}
136
141
  */
137
142
  async getDeviceModeSettings(deviceId, portId) {
138
143
  this.log.debug(`Getting mode settings for deviceId=${deviceId}, portId=${portId}`);
139
- return this.apiCall(API_ENDPOINTS.DEVICE_MODE_SETTINGS, {
140
- devId: deviceId,
141
- port: portId
142
- }, true);
144
+ return this.apiCall(
145
+ API_ENDPOINTS.DEVICE_MODE_SETTINGS,
146
+ {
147
+ devId: deviceId,
148
+ port: portId,
149
+ },
150
+ true,
151
+ );
143
152
  }
144
153
 
145
154
  /**
146
155
  * Gets advanced settings for a device or port
156
+ *
147
157
  * @param {string|number} deviceId
148
158
  * @param {number} portId - 0 for controller-level settings
149
159
  * @returns {Promise<object>}
150
160
  */
151
161
  async getDeviceSettings(deviceId, portId) {
152
162
  this.log.debug(`Getting device settings for deviceId=${deviceId}, portId=${portId}`);
153
- return this.apiCall(API_ENDPOINTS.DEVICE_SETTINGS, {
154
- devId: deviceId,
155
- port: portId
156
- }, true);
163
+ return this.apiCall(
164
+ API_ENDPOINTS.DEVICE_SETTINGS,
165
+ {
166
+ devId: deviceId,
167
+ port: portId,
168
+ },
169
+ true,
170
+ );
157
171
  }
158
172
 
159
173
  /**
160
174
  * Sends a raw URL-encoded form payload to the addDevMode endpoint.
161
175
  * This is the single write path for all mode and speed changes.
176
+ *
162
177
  * @param {string} formData - URL-encoded form data string
163
178
  * @returns {Promise<boolean>}
164
179
  */
@@ -173,17 +188,17 @@ class ACInfinityClient {
173
188
  const response = await this.axiosInstance.post(
174
189
  `${API_BASE_URL}${API_ENDPOINTS.UPDATE_DEVICE_MODE}`,
175
190
  formData,
176
- { headers: this.createHeaders(true) }
191
+ { headers: this.createHeaders(true) },
177
192
  );
178
193
 
179
194
  if (response.data && response.data.code === 200) {
180
- this.log.debug("Mode update successful");
195
+ this.log.debug('Mode update successful');
181
196
  return true;
182
197
  }
183
- throw new Error(`Mode update failed: ${response.data ? response.data.msg : "Unknown error"}`);
198
+ throw new Error(`Mode update failed: ${response.data ? response.data.msg : 'Unknown error'}`);
184
199
  } catch (error) {
185
200
  if (error.response && error.response.status === 401) {
186
- this.log.info("Token expired, re-logging in");
201
+ this.log.info('Token expired, re-logging in');
187
202
  await this.login();
188
203
  return this.sendRawModeUpdate(formData);
189
204
  }
@@ -213,10 +228,10 @@ class ACInfinityClient {
213
228
 
214
229
  const settings = await this.getDeviceModeSettings(deviceId, portId);
215
230
  const modeSetid = settings.modeSetid;
216
- const vpdnums = (settings.vpdnums !== undefined && settings.vpdnums !== null) ? settings.vpdnums : 0;
231
+ const vpdnums = settings.vpdnums !== undefined && settings.vpdnums !== null ? settings.vpdnums : 0;
217
232
 
218
233
  // speak = active speed; 0 when mode is Off (atType=1)
219
- const speak = (atType === 2) ? onSpeed : 0;
234
+ const speak = atType === 2 ? onSpeed : 0;
220
235
 
221
236
  const formData = [
222
237
  `modeSetid=${encodeURIComponent(modeSetid)}`,
@@ -228,16 +243,19 @@ class ACInfinityClient {
228
243
  `speak=${encodeURIComponent(speak)}`,
229
244
  `curMode=${encodeURIComponent(atType)}`,
230
245
  `vpdstatus=0`,
231
- `vpdnums=${encodeURIComponent(vpdnums)}`
232
- ].join("&");
246
+ `vpdnums=${encodeURIComponent(vpdnums)}`,
247
+ ].join('&');
233
248
 
234
- this.log.info(`setDeviceMode: deviceId=${deviceId}, portId=${portId}, atType=${atType}, onSpeed=${onSpeed}, offSpeed=${offSpeed}`);
249
+ this.log.info(
250
+ `setDeviceMode: deviceId=${deviceId}, portId=${portId}, atType=${atType}, onSpeed=${onSpeed}, offSpeed=${offSpeed}`,
251
+ );
235
252
  return this.sendRawModeUpdate(formData);
236
253
  }
237
254
 
238
255
  /**
239
256
  * Updates specific mode settings parameters (timer/cycle/auto/vpd/schedule values).
240
257
  * Fetches current settings, applies overrides, and sends the full payload.
258
+ *
241
259
  * @param {string|number} deviceId
242
260
  * @param {number} portId
243
261
  * @param {Array<Array<string|number>>} keyValues - Array of [key, value] pairs to override
@@ -255,23 +273,27 @@ class ACInfinityClient {
255
273
  this.log.debug(`Current mode settings fetched for update`);
256
274
 
257
275
  // Remove fields that the API rejects in write calls
258
- const fieldsToRemove = ["devMacAddr", "ipcSetting", "devSetting"];
276
+ const fieldsToRemove = ['devMacAddr', 'ipcSetting', 'devSetting'];
259
277
  for (const field of fieldsToRemove) {
260
278
  delete settings[field];
261
279
  }
262
280
 
263
281
  // Ensure required fields exist
264
- if (!("vpdstatus" in settings)) settings.vpdstatus = 0;
265
- if (!("vpdnums" in settings)) settings.vpdnums = 0;
282
+ if (!('vpdstatus' in settings)) {
283
+ settings.vpdstatus = 0;
284
+ }
285
+ if (!('vpdnums' in settings)) {
286
+ settings.vpdnums = 0;
287
+ }
266
288
 
267
289
  settings.devId = parseInt(deviceIdStr);
268
- if ("modeSetid" in settings) {
290
+ if ('modeSetid' in settings) {
269
291
  settings.modeSetid = String(settings.modeSetid);
270
292
  }
271
293
 
272
294
  // Apply the requested overrides
273
295
  for (const [key, value] of keyValues) {
274
- settings[key] = (typeof value === "number") ? value : parseInt(String(value));
296
+ settings[key] = typeof value === 'number' ? value : parseInt(String(value));
275
297
  }
276
298
 
277
299
  // Replace any remaining null/undefined with 0
@@ -283,13 +305,14 @@ class ACInfinityClient {
283
305
 
284
306
  const formData = Object.entries(settings)
285
307
  .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
286
- .join("&");
308
+ .join('&');
287
309
 
288
310
  return this.sendRawModeUpdate(formData);
289
311
  }
290
312
 
291
313
  /**
292
314
  * Updates advanced (device/port-level) settings
315
+ *
293
316
  * @param {string|number} deviceId
294
317
  * @param {number} portId - 0 for controller-level settings
295
318
  * @param {string} deviceName - Device name (prevents API from resetting it to "None")
@@ -309,22 +332,29 @@ class ACInfinityClient {
309
332
  settings.devName = deviceName;
310
333
 
311
334
  const fieldsToRemove = [
312
- "setId", "devMacAddr", "portResistance", "devTimeZone",
313
- "sensorSetting", "sensorTransBuff", "subDeviceVersion",
314
- "secFucReportTime", "updateAllPort", "calibrationTime"
335
+ 'setId',
336
+ 'devMacAddr',
337
+ 'portResistance',
338
+ 'devTimeZone',
339
+ 'sensorSetting',
340
+ 'sensorTransBuff',
341
+ 'subDeviceVersion',
342
+ 'secFucReportTime',
343
+ 'updateAllPort',
344
+ 'calibrationTime',
315
345
  ];
316
346
  for (const field of fieldsToRemove) {
317
347
  delete settings[field];
318
348
  }
319
349
 
320
- const stringFields = ["sensorTransBuffStr", "sensorSettingStr", "portParamData", "paramSensors"];
350
+ const stringFields = ['sensorTransBuffStr', 'sensorSettingStr', 'portParamData', 'paramSensors'];
321
351
  for (const field of stringFields) {
322
352
  if (!(field in settings) || settings[field] === null) {
323
- settings[field] = "";
353
+ settings[field] = '';
324
354
  }
325
355
  }
326
356
 
327
- const defaultFields = ["sensorOneType", "isShare", "targetVpdSwitch", "sensorTwoType", "zoneSensorType"];
357
+ const defaultFields = ['sensorOneType', 'isShare', 'targetVpdSwitch', 'sensorTwoType', 'zoneSensorType'];
328
358
  for (const field of defaultFields) {
329
359
  if (!(field in settings)) {
330
360
  settings[field] = 0;
@@ -345,12 +375,12 @@ class ACInfinityClient {
345
375
 
346
376
  const formData = Object.entries(settings)
347
377
  .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
348
- .join("&");
378
+ .join('&');
349
379
 
350
380
  const response = await this.axiosInstance.post(
351
381
  `${API_BASE_URL}${API_ENDPOINTS.UPDATE_ADVANCED_SETTINGS}`,
352
382
  formData,
353
- { headers: this.createHeaders(true) }
383
+ { headers: this.createHeaders(true) },
354
384
  );
355
385
 
356
386
  if (response.data && response.data.code === 200) {
@@ -358,7 +388,7 @@ class ACInfinityClient {
358
388
  return;
359
389
  }
360
390
 
361
- throw new Error(`Failed to update advanced settings: ${response.data ? response.data.msg : "Unknown error"}`);
391
+ throw new Error(`Failed to update advanced settings: ${response.data ? response.data.msg : 'Unknown error'}`);
362
392
  }
363
393
  }
364
394