iobroker.absolutehumidity 0.0.3 → 0.0.4
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/README.md +22 -1
- package/io-package.json +27 -5
- package/lib/modules/deviceManager.js +7 -0
- package/main.js +147 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,17 +14,38 @@
|
|
|
14
14
|
|
|
15
15
|
build absolute humidity from actual temperature and relative humidity
|
|
16
16
|
|
|
17
|
+
## Calculation method
|
|
18
|
+
|
|
19
|
+
The adapter uses empirical Magnus approximation formulas to calculate the
|
|
20
|
+
saturation vapor pressure from temperature and relative humidity.
|
|
21
|
+
|
|
22
|
+
* Absolute humidity is calculated from the saturation vapor pressure using the
|
|
23
|
+
Magnus/Bolton approximation and the ideal gas law. The result is returned in
|
|
24
|
+
g/m³.
|
|
25
|
+
* Dew point temperature is calculated using the Magnus formula with Sonntag
|
|
26
|
+
coefficients. The result is returned in °C.
|
|
27
|
+
|
|
28
|
+
Small deviations from online tables are expected because different tables often
|
|
29
|
+
use different Magnus, Tetens, Sonntag, Bolton or Buck coefficient sets.
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
17
33
|
## Changelog
|
|
18
34
|
<!--
|
|
19
35
|
Placeholder for the next version (at the beginning of the line):
|
|
20
36
|
### **WORK IN PROGRESS**
|
|
21
37
|
-->
|
|
38
|
+
### 0.0.4 (2026-07-05)
|
|
39
|
+
* (BenAhrdt) Build Config in adapter own device object
|
|
40
|
+
|
|
22
41
|
### 0.0.3 (2026-07-05)
|
|
23
42
|
* (BenAhrdt) Add new features and icons to deviceManager
|
|
24
43
|
|
|
25
44
|
### 0.0.2 (2026-07-05)
|
|
26
45
|
* (BenAhrdt) initial release
|
|
27
46
|
|
|
47
|
+
[Older changes can be found there](CHANGELOG_OLD.md)
|
|
48
|
+
|
|
28
49
|
## License
|
|
29
50
|
MIT License
|
|
30
51
|
|
|
@@ -46,4 +67,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
46
67
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
47
68
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
48
69
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
49
|
-
SOFTWARE.
|
|
70
|
+
SOFTWARE.
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "absolutehumidity",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.0.4": {
|
|
7
|
+
"en": "Build Config in adapter own device object",
|
|
8
|
+
"de": "Konfiguration im eigenen Geräteobjekt des Adapters erstellen",
|
|
9
|
+
"ru": "Создайте конфигурацию в собственном объекте устройства адаптера.",
|
|
10
|
+
"pt": "Configuração de construção no objeto de dispositivo próprio do adaptador",
|
|
11
|
+
"nl": "Bouw de configuratie in het eigen apparaatobject van de adapter.",
|
|
12
|
+
"fr": "Configurer l'objet périphérique propre à l'adaptateur",
|
|
13
|
+
"it": "Costruisci la configurazione nell'oggetto dispositivo dell'adattatore",
|
|
14
|
+
"es": "Construir la configuración en el objeto de dispositivo propio del adaptador",
|
|
15
|
+
"pl": "Konfiguracja kompilacji w obiekcie własnego urządzenia adaptera",
|
|
16
|
+
"uk": "Збірка конфігурації у власному об'єкті пристрою адаптера",
|
|
17
|
+
"zh-cn": "适配器自身设备对象中的构建配置"
|
|
18
|
+
},
|
|
6
19
|
"0.0.3": {
|
|
7
20
|
"en": "Add new features and icons to deviceManager",
|
|
8
21
|
"de": "Füge dem Gerätemanager neue Funktionen und Symbole hinzu.",
|
|
@@ -127,9 +140,18 @@
|
|
|
127
140
|
}
|
|
128
141
|
]
|
|
129
142
|
},
|
|
130
|
-
"native": {
|
|
131
|
-
"devices": []
|
|
132
|
-
},
|
|
143
|
+
"native": {},
|
|
133
144
|
"objects": [],
|
|
134
|
-
"instanceObjects": [
|
|
145
|
+
"instanceObjects": [
|
|
146
|
+
{
|
|
147
|
+
"_id": "devices",
|
|
148
|
+
"type": "folder",
|
|
149
|
+
"common": {
|
|
150
|
+
"name": "Devices"
|
|
151
|
+
},
|
|
152
|
+
"native": {
|
|
153
|
+
"devices": []
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
]
|
|
135
157
|
}
|
|
@@ -40,6 +40,11 @@ const STATE_ICONS = {
|
|
|
40
40
|
`),
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
const TEMPERATURE_STATE_FILTER =
|
|
44
|
+
"const unit = String(obj.common?.unit || '').trim().toLowerCase().replace(/\\s+/g, ''); return obj.type === 'state' && obj.common?.type === 'number' && (unit === '' || unit === '°c' || unit === 'c' || unit === '°f' || unit === 'f');";
|
|
45
|
+
const RELATIVE_HUMIDITY_STATE_FILTER =
|
|
46
|
+
"const unit = String(obj.common?.unit || '').trim().toLowerCase().replace(/\\s+/g, ''); return obj.type === 'state' && obj.common?.type === 'number' && (unit === '' || unit === '%');";
|
|
47
|
+
|
|
43
48
|
class AbsoluteHumidityDeviceManagement extends DeviceManagement {
|
|
44
49
|
/**
|
|
45
50
|
* @param {import('../../main')} adapter
|
|
@@ -322,6 +327,7 @@ function getDeviceFormSchema() {
|
|
|
322
327
|
type: 'number',
|
|
323
328
|
},
|
|
324
329
|
},
|
|
330
|
+
filterFunc: TEMPERATURE_STATE_FILTER,
|
|
325
331
|
},
|
|
326
332
|
createTemperatureState: {
|
|
327
333
|
type: 'checkbox',
|
|
@@ -342,6 +348,7 @@ function getDeviceFormSchema() {
|
|
|
342
348
|
type: 'number',
|
|
343
349
|
},
|
|
344
350
|
},
|
|
351
|
+
filterFunc: RELATIVE_HUMIDITY_STATE_FILTER,
|
|
345
352
|
},
|
|
346
353
|
createRelativeHumidityState: {
|
|
347
354
|
type: 'checkbox',
|
package/main.js
CHANGED
|
@@ -27,6 +27,7 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
this.deviceManagement = null;
|
|
30
|
+
this.devices = [];
|
|
30
31
|
this.subscribedSourceIds = new Set();
|
|
31
32
|
|
|
32
33
|
this.on('ready', this.onReady.bind(this));
|
|
@@ -42,6 +43,8 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
42
43
|
async onReady() {
|
|
43
44
|
this.deviceManagement = new AbsoluteHumidityDeviceManagement(this);
|
|
44
45
|
await this.ensureInfoStates();
|
|
46
|
+
await this.ensureDeviceRootObject();
|
|
47
|
+
await this.loadDevices();
|
|
45
48
|
await this.migrateLegacyDeviceIds();
|
|
46
49
|
await this.rebuildAllDevices();
|
|
47
50
|
await this.refreshSubscriptions();
|
|
@@ -52,7 +55,7 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
52
55
|
* @returns {Array<Record<string, any>>}
|
|
53
56
|
*/
|
|
54
57
|
getConfiguredDevices() {
|
|
55
|
-
return
|
|
58
|
+
return this.devices;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
/**
|
|
@@ -71,7 +74,6 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
71
74
|
const device = this.normalizeDeviceFormData(data, createUniqueDeviceId(data.name, devices));
|
|
72
75
|
const updatedDevices = [...devices, device];
|
|
73
76
|
|
|
74
|
-
await this.validateDeviceSourceIds(device);
|
|
75
77
|
await this.ensureDeviceObjects(device);
|
|
76
78
|
await this.updateDeviceValues(device);
|
|
77
79
|
await this.refreshSubscriptions(updatedDevices);
|
|
@@ -88,8 +90,6 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
88
90
|
const device = this.normalizeDeviceFormData(data, id);
|
|
89
91
|
const updatedDevices = devices.map(existingDevice => (existingDevice.id === id ? device : existingDevice));
|
|
90
92
|
|
|
91
|
-
await this.validateDeviceSourceIds(device);
|
|
92
|
-
|
|
93
93
|
if (oldDevice) {
|
|
94
94
|
await this.deleteObsoleteDeviceStates(oldDevice, device);
|
|
95
95
|
}
|
|
@@ -161,8 +161,8 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
161
161
|
* @param {Record<string, any>} device
|
|
162
162
|
*/
|
|
163
163
|
async getDeviceSourceValidation(device) {
|
|
164
|
-
const temperatureStateValid = await this.
|
|
165
|
-
const relativeHumidityStateValid = await this.
|
|
164
|
+
const temperatureStateValid = await this.isUsableTemperatureStateId(device.temperatureStateId);
|
|
165
|
+
const relativeHumidityStateValid = await this.isUsableRelativeHumidityStateId(device.relativeHumidityStateId);
|
|
166
166
|
|
|
167
167
|
return {
|
|
168
168
|
valid: temperatureStateValid && relativeHumidityStateValid,
|
|
@@ -184,26 +184,122 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
184
184
|
* @returns {Promise<boolean>}
|
|
185
185
|
*/
|
|
186
186
|
async isUsableSourceStateId(stateId) {
|
|
187
|
+
const object = await this.getUsableSourceStateObject(stateId);
|
|
188
|
+
|
|
189
|
+
return object?.type === 'state' && object.common?.type === 'number';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @param {unknown} stateId
|
|
194
|
+
* @returns {Promise<boolean>}
|
|
195
|
+
*/
|
|
196
|
+
async isUsableTemperatureStateId(stateId) {
|
|
197
|
+
const object = await this.getUsableSourceStateObject(stateId);
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
object?.type === 'state' &&
|
|
201
|
+
object.common?.type === 'number' &&
|
|
202
|
+
this.isSupportedTemperatureUnit(object.common.unit)
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @param {unknown} stateId
|
|
208
|
+
* @returns {Promise<boolean>}
|
|
209
|
+
*/
|
|
210
|
+
async isUsableRelativeHumidityStateId(stateId) {
|
|
211
|
+
const object = await this.getUsableSourceStateObject(stateId);
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
object?.type === 'state' &&
|
|
215
|
+
object.common?.type === 'number' &&
|
|
216
|
+
this.isSupportedRelativeHumidityUnit(object.common.unit)
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @param {unknown} stateId
|
|
222
|
+
*/
|
|
223
|
+
async getUsableSourceStateObject(stateId) {
|
|
187
224
|
if (!this.isValidForeignStateId(stateId)) {
|
|
188
|
-
return
|
|
225
|
+
return null;
|
|
189
226
|
}
|
|
190
227
|
|
|
191
|
-
let object;
|
|
192
|
-
|
|
193
228
|
try {
|
|
194
|
-
|
|
229
|
+
return await this.getForeignObjectAsync(stateId);
|
|
195
230
|
} catch (error) {
|
|
196
231
|
this.log.warn(`Cannot validate state ID "${stateId}": ${error.message}`);
|
|
197
|
-
return
|
|
232
|
+
return null;
|
|
198
233
|
}
|
|
234
|
+
}
|
|
199
235
|
|
|
200
|
-
|
|
236
|
+
/**
|
|
237
|
+
* @param {unknown} unit
|
|
238
|
+
*/
|
|
239
|
+
isSupportedTemperatureUnit(unit) {
|
|
240
|
+
const normalizedUnit = this.normalizeUnit(unit);
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
normalizedUnit === '' || normalizedUnit === '°c' || normalizedUnit === 'c' || this.isFahrenheitUnit(unit)
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @param {unknown} unit
|
|
249
|
+
*/
|
|
250
|
+
isSupportedRelativeHumidityUnit(unit) {
|
|
251
|
+
const normalizedUnit = this.normalizeUnit(unit);
|
|
252
|
+
|
|
253
|
+
return normalizedUnit === '' || normalizedUnit === '%';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* @param {unknown} unit
|
|
258
|
+
*/
|
|
259
|
+
isFahrenheitUnit(unit) {
|
|
260
|
+
const normalizedUnit = this.normalizeUnit(unit);
|
|
261
|
+
|
|
262
|
+
return normalizedUnit === '°f' || normalizedUnit === 'f';
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @param {unknown} unit
|
|
267
|
+
*/
|
|
268
|
+
normalizeUnit(unit) {
|
|
269
|
+
return typeof unit === 'string' ? unit.trim().toLowerCase().replace(/\s+/g, '') : '';
|
|
201
270
|
}
|
|
202
271
|
|
|
203
272
|
/**
|
|
204
273
|
* @param {Record<string, any>[]} devices
|
|
205
274
|
*/
|
|
206
275
|
async saveDevices(devices) {
|
|
276
|
+
await this.extendObjectAsync(DEVICE_ROOT, {
|
|
277
|
+
native: {
|
|
278
|
+
devices,
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
this.devices = devices;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async loadDevices() {
|
|
285
|
+
const legacyDevices = Array.isArray(this.config.devices) ? this.config.devices : [];
|
|
286
|
+
const hasLegacyDevicesConfig = Object.prototype.hasOwnProperty.call(this.config, 'devices');
|
|
287
|
+
|
|
288
|
+
if (legacyDevices.length) {
|
|
289
|
+
await this.saveDevices(legacyDevices);
|
|
290
|
+
await this.deleteLegacyConfiguredDevices();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const deviceRootObject = await this.getObjectAsync(DEVICE_ROOT);
|
|
295
|
+
this.devices = Array.isArray(deviceRootObject?.native?.devices) ? deviceRootObject.native.devices : [];
|
|
296
|
+
|
|
297
|
+
if (hasLegacyDevicesConfig) {
|
|
298
|
+
await this.deleteLegacyConfiguredDevices();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async deleteLegacyConfiguredDevices() {
|
|
207
303
|
const adapterObjectId = `system.adapter.${this.namespace}`;
|
|
208
304
|
const adapterObject = await this.getForeignObjectAsync(adapterObjectId);
|
|
209
305
|
|
|
@@ -211,13 +307,15 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
211
307
|
throw new Error(`Could not find adapter object ${adapterObjectId}`);
|
|
212
308
|
}
|
|
213
309
|
|
|
214
|
-
adapterObject.native
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
310
|
+
if (!Object.prototype.hasOwnProperty.call(adapterObject.native || {}, 'devices')) {
|
|
311
|
+
delete this.config.devices;
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
delete adapterObject.native.devices;
|
|
218
316
|
|
|
219
317
|
await this.setForeignObjectAsync(adapterObjectId, adapterObject);
|
|
220
|
-
this.config.devices
|
|
318
|
+
delete this.config.devices;
|
|
221
319
|
}
|
|
222
320
|
|
|
223
321
|
async rebuildAllDevices() {
|
|
@@ -281,6 +379,18 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
281
379
|
});
|
|
282
380
|
}
|
|
283
381
|
|
|
382
|
+
async ensureDeviceRootObject() {
|
|
383
|
+
await this.setObjectNotExistsAsync(DEVICE_ROOT, {
|
|
384
|
+
type: 'folder',
|
|
385
|
+
common: {
|
|
386
|
+
name: 'Devices',
|
|
387
|
+
},
|
|
388
|
+
native: {
|
|
389
|
+
devices: [],
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
284
394
|
/**
|
|
285
395
|
* @param {Record<string, any>} device
|
|
286
396
|
*/
|
|
@@ -499,7 +609,7 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
499
609
|
return;
|
|
500
610
|
}
|
|
501
611
|
|
|
502
|
-
const temperature = await this.
|
|
612
|
+
const temperature = await this.readTemperatureStateAsCelsius(device.temperatureStateId);
|
|
503
613
|
const relativeHumidity = await this.readNumberState(device.relativeHumidityStateId);
|
|
504
614
|
const absoluteHumidity = calculateAbsoluteHumidity(temperature, relativeHumidity);
|
|
505
615
|
const dewPointTemperature = calculateDewPointTemperature(temperature, relativeHumidity);
|
|
@@ -529,6 +639,25 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
529
639
|
});
|
|
530
640
|
}
|
|
531
641
|
|
|
642
|
+
/**
|
|
643
|
+
* @param {string} stateId
|
|
644
|
+
*/
|
|
645
|
+
async readTemperatureStateAsCelsius(stateId) {
|
|
646
|
+
const temperature = await this.readNumberState(stateId);
|
|
647
|
+
|
|
648
|
+
if (temperature === null) {
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const object = await this.getUsableSourceStateObject(stateId);
|
|
653
|
+
|
|
654
|
+
if (this.isFahrenheitUnit(object?.common?.unit)) {
|
|
655
|
+
return ((temperature - 32) * 5) / 9;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return temperature;
|
|
659
|
+
}
|
|
660
|
+
|
|
532
661
|
/**
|
|
533
662
|
* @param {string} stateId
|
|
534
663
|
*/
|