iobroker.absolutehumidity 0.0.2 → 0.0.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.
- package/README.md +3 -0
- package/admin/i18n/de.json +1 -0
- package/admin/i18n/en.json +1 -0
- package/admin/i18n/es.json +1 -0
- package/admin/i18n/fr.json +1 -0
- package/admin/i18n/it.json +1 -0
- package/admin/i18n/nl.json +1 -0
- package/admin/i18n/pl.json +1 -0
- package/admin/i18n/pt.json +1 -0
- package/admin/i18n/ru.json +1 -0
- package/admin/i18n/uk.json +1 -0
- package/admin/i18n/zh-cn.json +1 -0
- package/admin/jsonConfig.json +1 -1
- package/io-package.json +14 -1
- package/lib/modules/deviceManager.js +61 -11
- package/lib/modules/i18n.js +30 -197
- package/main.js +135 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,9 @@ build absolute humidity from actual temperature and relative humidity
|
|
|
19
19
|
Placeholder for the next version (at the beginning of the line):
|
|
20
20
|
### **WORK IN PROGRESS**
|
|
21
21
|
-->
|
|
22
|
+
### 0.0.3 (2026-07-05)
|
|
23
|
+
* (BenAhrdt) Add new features and icons to deviceManager
|
|
24
|
+
|
|
22
25
|
### 0.0.2 (2026-07-05)
|
|
23
26
|
* (BenAhrdt) initial release
|
|
24
27
|
|
package/admin/i18n/de.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Delete device": "Gerät löschen",
|
|
12
12
|
"Delete this device?": "Dieses Gerät löschen?",
|
|
13
13
|
"Device not found": "Gerät nicht gefunden",
|
|
14
|
+
"Invalid source state": "Ungültiger Quell-State",
|
|
14
15
|
"Name": "Name",
|
|
15
16
|
"Temperature": "Temperatur",
|
|
16
17
|
"Temperature state": "Temperatur-State",
|
package/admin/i18n/en.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Delete device": "Delete device",
|
|
12
12
|
"Delete this device?": "Delete this device?",
|
|
13
13
|
"Device not found": "Device not found",
|
|
14
|
+
"Invalid source state": "Invalid source state",
|
|
14
15
|
"Name": "Name",
|
|
15
16
|
"Temperature": "Temperature",
|
|
16
17
|
"Temperature state": "Temperature state",
|
package/admin/i18n/es.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Delete device": "Eliminar dispositivo",
|
|
12
12
|
"Delete this device?": "¿Eliminar este dispositivo?",
|
|
13
13
|
"Device not found": "Dispositivo no encontrado",
|
|
14
|
+
"Invalid source state": "Estado de origen no válido",
|
|
14
15
|
"Name": "Nombre",
|
|
15
16
|
"Temperature": "Temperatura",
|
|
16
17
|
"Temperature state": "Estado de temperatura",
|
package/admin/i18n/fr.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Delete device": "Supprimer l'appareil",
|
|
12
12
|
"Delete this device?": "Supprimer cet appareil ?",
|
|
13
13
|
"Device not found": "Appareil introuvable",
|
|
14
|
+
"Invalid source state": "État source invalide",
|
|
14
15
|
"Name": "Nom",
|
|
15
16
|
"Temperature": "Température",
|
|
16
17
|
"Temperature state": "État de température",
|
package/admin/i18n/it.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Delete device": "Elimina dispositivo",
|
|
12
12
|
"Delete this device?": "Eliminare questo dispositivo?",
|
|
13
13
|
"Device not found": "Dispositivo non trovato",
|
|
14
|
+
"Invalid source state": "Stato sorgente non valido",
|
|
14
15
|
"Name": "Nome",
|
|
15
16
|
"Temperature": "Temperatura",
|
|
16
17
|
"Temperature state": "Stato temperatura",
|
package/admin/i18n/nl.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Delete device": "Apparaat verwijderen",
|
|
12
12
|
"Delete this device?": "Dit apparaat verwijderen?",
|
|
13
13
|
"Device not found": "Apparaat niet gevonden",
|
|
14
|
+
"Invalid source state": "Ongeldige bronstate",
|
|
14
15
|
"Name": "Naam",
|
|
15
16
|
"Temperature": "Temperatuur",
|
|
16
17
|
"Temperature state": "Temperatuurstate",
|
package/admin/i18n/pl.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Delete device": "Usuń urządzenie",
|
|
12
12
|
"Delete this device?": "Usunąć to urządzenie?",
|
|
13
13
|
"Device not found": "Nie znaleziono urządzenia",
|
|
14
|
+
"Invalid source state": "Nieprawidłowy stan źródłowy",
|
|
14
15
|
"Name": "Nazwa",
|
|
15
16
|
"Temperature": "Temperatura",
|
|
16
17
|
"Temperature state": "Stan temperatury",
|
package/admin/i18n/pt.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Delete device": "Excluir dispositivo",
|
|
12
12
|
"Delete this device?": "Excluir este dispositivo?",
|
|
13
13
|
"Device not found": "Dispositivo não encontrado",
|
|
14
|
+
"Invalid source state": "Estado de origem inválido",
|
|
14
15
|
"Name": "Nome",
|
|
15
16
|
"Temperature": "Temperatura",
|
|
16
17
|
"Temperature state": "Estado de temperatura",
|
package/admin/i18n/ru.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Delete device": "Удалить устройство",
|
|
12
12
|
"Delete this device?": "Удалить это устройство?",
|
|
13
13
|
"Device not found": "Устройство не найдено",
|
|
14
|
+
"Invalid source state": "Недопустимое исходное состояние",
|
|
14
15
|
"Name": "Имя",
|
|
15
16
|
"Temperature": "Температура",
|
|
16
17
|
"Temperature state": "Состояние температуры",
|
package/admin/i18n/uk.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"Delete device": "Видалити пристрій",
|
|
12
12
|
"Delete this device?": "Видалити цей пристрій?",
|
|
13
13
|
"Device not found": "Пристрій не знайдено",
|
|
14
|
+
"Invalid source state": "Недійсний вихідний стан",
|
|
14
15
|
"Name": "Ім'я",
|
|
15
16
|
"Temperature": "Температура",
|
|
16
17
|
"Temperature state": "Стан температури",
|
package/admin/i18n/zh-cn.json
CHANGED
package/admin/jsonConfig.json
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"_previewCard": {
|
|
39
39
|
"type": "staticText",
|
|
40
40
|
"format": "html",
|
|
41
|
-
"text": "<div class='ah-preview-card' style='width:300px;background:#d3d3d3;border-radius:4px;overflow:hidden;box-shadow:0 2px 4px rgba(0,0,0,.25);margin-top:24px;color:#000;font-family:Roboto,Arial,sans-serif;'><div style='height:60px;background:#0f4c81;color:#fff;display:flex;align-items:center;padding:0 18px;gap:18px;font-weight:700;'><img src='adapter/absolutehumidity/absolutehumidity.svg' alt='' style='width:34px;height:34px;object-fit:contain;'><span>
|
|
41
|
+
"text": "<div class='ah-preview-card' style='width:300px;background:#d3d3d3;border-radius:4px;overflow:hidden;box-shadow:0 2px 4px rgba(0,0,0,.25);margin-top:24px;color:#000;font-family:Roboto,Arial,sans-serif;'><div style='height:60px;background:#0f4c81;color:#fff;display:flex;align-items:center;padding:0 18px;gap:18px;font-weight:700;'><img src='adapter/absolutehumidity/absolutehumidity.svg' alt='' style='width:34px;height:34px;object-fit:contain;' onload='(async img=>{const c=img.closest(\".ah-preview-card\");if(!c||c.dataset.ahI18nDone)return;c.dataset.ahI18nDone=\"1\";const raw=String(window.systemLang||window.adminLanguage||navigator.language||\"en\").toLowerCase();const lang=raw.startsWith(\"zh\")?\"zh-cn\":raw.slice(0,2);const load=async l=>{const r=await fetch(\"adapter/absolutehumidity/i18n/\"+l+\".json\");return r.ok?r.json():null;};try{const d=(await load(lang))||(await load(\"en\"))||{};c.querySelectorAll(\"[data-ah-i18n]\").forEach(e=>{const k=e.dataset.ahI18n;e.textContent=d[k]||k;});}catch(e){}})(this)'><span data-ah-i18n='Manual calculation'>Manual calculation</span></div><div style='padding:18px 16px 20px;'><div style='color:#00a51a;font-size:20px;margin-bottom:18px;'>↔</div><div style='display:grid;grid-template-columns:1fr auto;gap:14px 16px;align-items:center;'><label for='ah-preview-temp'><span data-ah-i18n='Temperature'>Temperature</span>:</label><span><input id='ah-preview-temp' data-ah-temp type='number' value='21' step='0.1' style='width:72px;background:#fff;border:1px solid #ccc;border-radius:3px;padding:4px 6px;text-align:right;color:#000;' onchange='event.stopPropagation();event.stopImmediatePropagation();' oninput='event.stopPropagation();event.stopImmediatePropagation();const r=this.closest(\".ah-preview-card\"),t=Number(r.querySelector(\"[data-ah-temp]\").value),h=Number(r.querySelector(\"[data-ah-rh]\").value),abs=r.querySelector(\"[data-ah-abs]\"),dew=r.querySelector(\"[data-ah-dew]\");if(Number.isFinite(t)&&Number.isFinite(h)&&h>=0&&h<=100){const es=6.112*Math.exp((17.67*t)/(t+243.5)),e=h/100*es;abs.textContent=(Math.round(((216.7*e)/(273.15+t))*100)/100).toFixed(2);}else abs.textContent=\"\";if(Number.isFinite(t)&&Number.isFinite(h)&&h>0&&h<=100){const a=Math.log(h/100)+(17.62*t)/(243.12+t);dew.textContent=(Math.round(((243.12*a)/(17.62-a))*100)/100).toFixed(2);}else dew.textContent=\"\";'> <span style='color:#555;font-size:12px;'>°C</span></span><label for='ah-preview-rh'><span data-ah-i18n='Relative humidity'>Relative humidity</span>:</label><span><input id='ah-preview-rh' data-ah-rh type='number' value='50' min='0' max='100' step='1' style='width:72px;background:#fff;border:1px solid #ccc;border-radius:3px;padding:4px 6px;text-align:right;color:#000;' onchange='event.stopPropagation();event.stopImmediatePropagation();' oninput='event.stopPropagation();event.stopImmediatePropagation();const r=this.closest(\".ah-preview-card\"),t=Number(r.querySelector(\"[data-ah-temp]\").value),h=Number(r.querySelector(\"[data-ah-rh]\").value),abs=r.querySelector(\"[data-ah-abs]\"),dew=r.querySelector(\"[data-ah-dew]\");if(Number.isFinite(t)&&Number.isFinite(h)&&h>=0&&h<=100){const es=6.112*Math.exp((17.67*t)/(t+243.5)),e=h/100*es;abs.textContent=(Math.round(((216.7*e)/(273.15+t))*100)/100).toFixed(2);}else abs.textContent=\"\";if(Number.isFinite(t)&&Number.isFinite(h)&&h>0&&h<=100){const a=Math.log(h/100)+(17.62*t)/(243.12+t);dew.textContent=(Math.round(((243.12*a)/(17.62-a))*100)/100).toFixed(2);}else dew.textContent=\"\";'> <span style='color:#555;font-size:12px;'>%</span></span><span><span data-ah-i18n='Absolute humidity'>Absolute humidity</span>:</span><span style='text-align:right;'><span data-ah-abs>9.16</span> <span style='color:#555;font-size:12px;'>g/m³</span></span><span><span data-ah-i18n='Dew point temperature'>Dew point temperature</span>:</span><span style='text-align:right;'><span data-ah-dew>10.18</span> <span style='color:#555;font-size:12px;'>°C</span></span></div><div style='height:34px;'></div></div></div>",
|
|
42
42
|
"newLine": true,
|
|
43
43
|
"xs": 12,
|
|
44
44
|
"sm": 12,
|
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.3",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.0.3": {
|
|
7
|
+
"en": "Add new features and icons to deviceManager",
|
|
8
|
+
"de": "Füge dem Gerätemanager neue Funktionen und Symbole hinzu.",
|
|
9
|
+
"ru": "Добавьте новые функции и значки в deviceManager.",
|
|
10
|
+
"pt": "Adicionar novos recursos e ícones ao gerenciador de dispositivos",
|
|
11
|
+
"nl": "Voeg nieuwe functies en pictogrammen toe aan DeviceManager.",
|
|
12
|
+
"fr": "Ajouter de nouvelles fonctionnalités et icônes à deviceManager",
|
|
13
|
+
"it": "Aggiungi nuove funzionalità e icone a DeviceManager",
|
|
14
|
+
"es": "Agregar nuevas funciones e íconos al Administrador de dispositivos",
|
|
15
|
+
"pl": "Dodaj nowe funkcje i ikony do DeviceManager",
|
|
16
|
+
"uk": "Додавання нових функцій та піктограм до DeviceManager",
|
|
17
|
+
"zh-cn": "向设备管理器添加新功能和图标"
|
|
18
|
+
},
|
|
6
19
|
"0.0.2": {
|
|
7
20
|
"en": "initial release",
|
|
8
21
|
"de": "Erstveröffentlichung",
|
|
@@ -12,6 +12,34 @@ const {
|
|
|
12
12
|
} = require('./constants');
|
|
13
13
|
const { translate } = require('./i18n');
|
|
14
14
|
|
|
15
|
+
const STATE_ICONS = {
|
|
16
|
+
temperature: createSvgIcon(`
|
|
17
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
18
|
+
<path fill="none" stroke="#1976d2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M14 14.76V5a4 4 0 0 0-8 0v9.76a6 6 0 1 0 8 0Z"/>
|
|
19
|
+
<path fill="#1976d2" d="M10 17.5a2.5 2.5 0 1 1-2-2.45V8a2 2 0 0 1 4 0v7.05a2.5 2.5 0 0 1-2 2.45Z"/>
|
|
20
|
+
</svg>
|
|
21
|
+
`),
|
|
22
|
+
relativeHumidity: createSvgIcon(`
|
|
23
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
24
|
+
<path fill="#03a9f4" d="M12 2.7 6.6 9.3A8.2 8.2 0 0 0 5 14a7 7 0 0 0 14 0 8.2 8.2 0 0 0-1.6-4.7L12 2.7Z"/>
|
|
25
|
+
<text x="12" y="16" text-anchor="middle" font-family="Arial, sans-serif" font-size="7" font-weight="700" fill="#fff">%</text>
|
|
26
|
+
</svg>
|
|
27
|
+
`),
|
|
28
|
+
absoluteHumidity: createSvgIcon(`
|
|
29
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
30
|
+
<path fill="#03a9f4" d="M12 2.7 6.6 9.3A8.2 8.2 0 0 0 5 14a7 7 0 0 0 14 0 8.2 8.2 0 0 0-1.6-4.7L12 2.7Z"/>
|
|
31
|
+
<path fill="rgba(255,255,255,.7)" d="M8.4 14.1c.3 2.1 1.6 3.3 3.4 3.7-2.8.3-5-1.2-5.4-3.6-.2-1.2.2-2.4 1.1-3.5-.1 1.2.2 2.4.9 3.4Z"/>
|
|
32
|
+
</svg>
|
|
33
|
+
`),
|
|
34
|
+
dewPointTemperature: createSvgIcon(`
|
|
35
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
36
|
+
<path fill="none" stroke="#1976d2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M10 13.9V5a3 3 0 0 0-6 0v8.9a5 5 0 1 0 6 0Z"/>
|
|
37
|
+
<path fill="#1976d2" d="M7 17.6a2.1 2.1 0 0 1-1-4V8a1 1 0 0 1 2 0v5.6a2.1 2.1 0 0 1-1 4Z"/>
|
|
38
|
+
<path fill="#03a9f4" d="M17 6.4 13.7 10a5 5 0 0 0-1.2 3.2 4.5 4.5 0 0 0 9 0 5 5 0 0 0-1.2-3.2L17 6.4Z"/>
|
|
39
|
+
</svg>
|
|
40
|
+
`),
|
|
41
|
+
};
|
|
42
|
+
|
|
15
43
|
class AbsoluteHumidityDeviceManagement extends DeviceManagement {
|
|
16
44
|
/**
|
|
17
45
|
* @param {import('../../main')} adapter
|
|
@@ -67,13 +95,15 @@ class AbsoluteHumidityDeviceManagement extends DeviceManagement {
|
|
|
67
95
|
/**
|
|
68
96
|
* @param {import('@iobroker/dm-utils').DeviceLoadContext<string>} context
|
|
69
97
|
*/
|
|
70
|
-
loadDevices(context) {
|
|
98
|
+
async loadDevices(context) {
|
|
71
99
|
const devices = this.adapter.getConfiguredDevices();
|
|
72
100
|
|
|
73
101
|
context.setTotalDevices(devices.length);
|
|
74
102
|
|
|
75
103
|
for (const device of devices) {
|
|
76
|
-
|
|
104
|
+
const sourceValidation = await this.adapter.getDeviceSourceValidation(device);
|
|
105
|
+
|
|
106
|
+
context.addDevice(createDeviceInfo(this.adapter, device, sourceValidation));
|
|
77
107
|
}
|
|
78
108
|
}
|
|
79
109
|
}
|
|
@@ -81,14 +111,20 @@ class AbsoluteHumidityDeviceManagement extends DeviceManagement {
|
|
|
81
111
|
/**
|
|
82
112
|
* @param {import('../../main')} adapter
|
|
83
113
|
* @param {Record<string, any>} device
|
|
114
|
+
* @param {{ valid: boolean; temperatureStateValid: boolean; relativeHumidityStateValid: boolean }} sourceValidation
|
|
84
115
|
*/
|
|
85
|
-
function createDeviceInfo(adapter, device) {
|
|
116
|
+
function createDeviceInfo(adapter, device, sourceValidation) {
|
|
86
117
|
return {
|
|
87
118
|
id: device.id,
|
|
88
119
|
name: device.name,
|
|
89
120
|
icon: ADAPTER_ICON,
|
|
90
121
|
backgroundColor: CARD_HEADER_BACKGROUND,
|
|
91
|
-
status:
|
|
122
|
+
status: sourceValidation.valid
|
|
123
|
+
? 'connected'
|
|
124
|
+
: {
|
|
125
|
+
connection: 'disconnected',
|
|
126
|
+
warning: translate('Invalid source state'),
|
|
127
|
+
},
|
|
92
128
|
actions: [
|
|
93
129
|
{
|
|
94
130
|
id: 'editDevice',
|
|
@@ -124,15 +160,16 @@ function createDeviceInfo(adapter, device) {
|
|
|
124
160
|
},
|
|
125
161
|
},
|
|
126
162
|
],
|
|
127
|
-
customInfo: createDeviceCustomInfo(adapter, device),
|
|
163
|
+
customInfo: createDeviceCustomInfo(adapter, device, sourceValidation),
|
|
128
164
|
};
|
|
129
165
|
}
|
|
130
166
|
|
|
131
167
|
/**
|
|
132
168
|
* @param {import('../../main')} adapter
|
|
133
169
|
* @param {Record<string, any>} device
|
|
170
|
+
* @param {{ valid: boolean; temperatureStateValid: boolean; relativeHumidityStateValid: boolean }} sourceValidation
|
|
134
171
|
*/
|
|
135
|
-
function createDeviceCustomInfo(adapter, device) {
|
|
172
|
+
function createDeviceCustomInfo(adapter, device, sourceValidation) {
|
|
136
173
|
return {
|
|
137
174
|
id: device.id,
|
|
138
175
|
schema: {
|
|
@@ -144,31 +181,35 @@ function createDeviceCustomInfo(adapter, device) {
|
|
|
144
181
|
items: {
|
|
145
182
|
temperature: createStateInfoItem(
|
|
146
183
|
translate('Temperature'),
|
|
147
|
-
device.createTemperatureState
|
|
184
|
+
device.createTemperatureState || !sourceValidation.temperatureStateValid
|
|
148
185
|
? `${adapter.getDeviceObjectId(device.id)}.${STATE_TEMPERATURE}`
|
|
149
186
|
: device.temperatureStateId,
|
|
150
187
|
'°C',
|
|
151
|
-
!device.createTemperatureState,
|
|
188
|
+
!device.createTemperatureState && sourceValidation.temperatureStateValid,
|
|
189
|
+
STATE_ICONS.temperature,
|
|
152
190
|
),
|
|
153
191
|
relativeHumidity: createStateInfoItem(
|
|
154
192
|
translate('Relative humidity'),
|
|
155
|
-
device.createRelativeHumidityState
|
|
193
|
+
device.createRelativeHumidityState || !sourceValidation.relativeHumidityStateValid
|
|
156
194
|
? `${adapter.getDeviceObjectId(device.id)}.${STATE_RELATIVE_HUMIDITY}`
|
|
157
195
|
: device.relativeHumidityStateId,
|
|
158
196
|
'%',
|
|
159
|
-
!device.createRelativeHumidityState,
|
|
197
|
+
!device.createRelativeHumidityState && sourceValidation.relativeHumidityStateValid,
|
|
198
|
+
STATE_ICONS.relativeHumidity,
|
|
160
199
|
),
|
|
161
200
|
absoluteHumidity: createStateInfoItem(
|
|
162
201
|
translate('Absolute humidity'),
|
|
163
202
|
`${adapter.getDeviceObjectId(device.id)}.${STATE_ABSOLUTE_HUMIDITY}`,
|
|
164
203
|
'g/m³',
|
|
165
204
|
false,
|
|
205
|
+
STATE_ICONS.absoluteHumidity,
|
|
166
206
|
),
|
|
167
207
|
dewPointTemperature: createStateInfoItem(
|
|
168
208
|
translate('Dew point temperature'),
|
|
169
209
|
`${adapter.getDeviceObjectId(device.id)}.${STATE_DEW_POINT_TEMPERATURE}`,
|
|
170
210
|
'°C',
|
|
171
211
|
false,
|
|
212
|
+
STATE_ICONS.dewPointTemperature,
|
|
172
213
|
),
|
|
173
214
|
spacer: {
|
|
174
215
|
type: 'staticText',
|
|
@@ -200,11 +241,13 @@ function createDeviceCustomInfo(adapter, device) {
|
|
|
200
241
|
* @param {string} oid
|
|
201
242
|
* @param {string} unit
|
|
202
243
|
* @param {boolean} foreign
|
|
244
|
+
* @param {string} labelIcon
|
|
203
245
|
*/
|
|
204
|
-
function createStateInfoItem(label, oid, unit, foreign) {
|
|
246
|
+
function createStateInfoItem(label, oid, unit, foreign, labelIcon) {
|
|
205
247
|
return {
|
|
206
248
|
type: 'state',
|
|
207
249
|
label,
|
|
250
|
+
labelIcon,
|
|
208
251
|
oid,
|
|
209
252
|
foreign,
|
|
210
253
|
control: 'text',
|
|
@@ -221,6 +264,13 @@ function createStateInfoItem(label, oid, unit, foreign) {
|
|
|
221
264
|
};
|
|
222
265
|
}
|
|
223
266
|
|
|
267
|
+
/**
|
|
268
|
+
* @param {string} svg
|
|
269
|
+
*/
|
|
270
|
+
function createSvgIcon(svg) {
|
|
271
|
+
return `data:image/svg+xml;base64,${Buffer.from(svg.replace(/\s+/g, ' ').trim()).toString('base64')}`;
|
|
272
|
+
}
|
|
273
|
+
|
|
224
274
|
/**
|
|
225
275
|
* @param {import('@iobroker/dm-utils').ActionContext} context
|
|
226
276
|
* @param {Record<string, any> | undefined} device
|
package/lib/modules/i18n.js
CHANGED
|
@@ -1,203 +1,36 @@
|
|
|
1
1
|
/* eslint-disable jsdoc/require-jsdoc */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const
|
|
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
|
-
ru: 'Редактировать устройство',
|
|
35
|
-
pt: 'Editar dispositivo',
|
|
36
|
-
nl: 'Apparaat bewerken',
|
|
37
|
-
fr: "Modifier l'appareil",
|
|
38
|
-
it: 'Modifica dispositivo',
|
|
39
|
-
es: 'Editar dispositivo',
|
|
40
|
-
pl: 'Edytuj urządzenie',
|
|
41
|
-
uk: 'Редагувати пристрій',
|
|
42
|
-
'zh-cn': '编辑设备',
|
|
43
|
-
},
|
|
44
|
-
'Delete device': {
|
|
45
|
-
en: 'Delete device',
|
|
46
|
-
de: 'Gerät löschen',
|
|
47
|
-
ru: 'Удалить устройство',
|
|
48
|
-
pt: 'Excluir dispositivo',
|
|
49
|
-
nl: 'Apparaat verwijderen',
|
|
50
|
-
fr: "Supprimer l'appareil",
|
|
51
|
-
it: 'Elimina dispositivo',
|
|
52
|
-
es: 'Eliminar dispositivo',
|
|
53
|
-
pl: 'Usuń urządzenie',
|
|
54
|
-
uk: 'Видалити пристрій',
|
|
55
|
-
'zh-cn': '删除设备',
|
|
56
|
-
},
|
|
57
|
-
'Delete this device?': {
|
|
58
|
-
en: 'Delete this device?',
|
|
59
|
-
de: 'Dieses Gerät löschen?',
|
|
60
|
-
ru: 'Удалить это устройство?',
|
|
61
|
-
pt: 'Excluir este dispositivo?',
|
|
62
|
-
nl: 'Dit apparaat verwijderen?',
|
|
63
|
-
fr: 'Supprimer cet appareil ?',
|
|
64
|
-
it: 'Eliminare questo dispositivo?',
|
|
65
|
-
es: '¿Eliminar este dispositivo?',
|
|
66
|
-
pl: 'Usunąć to urządzenie?',
|
|
67
|
-
uk: 'Видалити цей пристрій?',
|
|
68
|
-
'zh-cn': '删除此设备?',
|
|
69
|
-
},
|
|
70
|
-
'Device not found': {
|
|
71
|
-
en: 'Device not found',
|
|
72
|
-
de: 'Gerät nicht gefunden',
|
|
73
|
-
ru: 'Устройство не найдено',
|
|
74
|
-
pt: 'Dispositivo não encontrado',
|
|
75
|
-
nl: 'Apparaat niet gevonden',
|
|
76
|
-
fr: 'Appareil introuvable',
|
|
77
|
-
it: 'Dispositivo non trovato',
|
|
78
|
-
es: 'Dispositivo no encontrado',
|
|
79
|
-
pl: 'Nie znaleziono urządzenia',
|
|
80
|
-
uk: 'Пристрій не знайдено',
|
|
81
|
-
'zh-cn': '未找到设备',
|
|
82
|
-
},
|
|
83
|
-
Name: {
|
|
84
|
-
en: 'Name',
|
|
85
|
-
de: 'Name',
|
|
86
|
-
ru: 'Имя',
|
|
87
|
-
pt: 'Nome',
|
|
88
|
-
nl: 'Naam',
|
|
89
|
-
fr: 'Nom',
|
|
90
|
-
it: 'Nome',
|
|
91
|
-
es: 'Nombre',
|
|
92
|
-
pl: 'Nazwa',
|
|
93
|
-
uk: "Ім'я",
|
|
94
|
-
'zh-cn': '名称',
|
|
95
|
-
},
|
|
96
|
-
Temperature: {
|
|
97
|
-
en: 'Temperature',
|
|
98
|
-
de: 'Temperatur',
|
|
99
|
-
ru: 'Температура',
|
|
100
|
-
pt: 'Temperatura',
|
|
101
|
-
nl: 'Temperatuur',
|
|
102
|
-
fr: 'Température',
|
|
103
|
-
it: 'Temperatura',
|
|
104
|
-
es: 'Temperatura',
|
|
105
|
-
pl: 'Temperatura',
|
|
106
|
-
uk: 'Температура',
|
|
107
|
-
'zh-cn': '温度',
|
|
108
|
-
},
|
|
109
|
-
'Temperature state': {
|
|
110
|
-
en: 'Temperature state',
|
|
111
|
-
de: 'Temperatur-State',
|
|
112
|
-
ru: 'Состояние температуры',
|
|
113
|
-
pt: 'Estado de temperatura',
|
|
114
|
-
nl: 'Temperatuurstate',
|
|
115
|
-
fr: 'État de température',
|
|
116
|
-
it: 'Stato temperatura',
|
|
117
|
-
es: 'Estado de temperatura',
|
|
118
|
-
pl: 'Stan temperatury',
|
|
119
|
-
uk: 'Стан температури',
|
|
120
|
-
'zh-cn': '温度状态',
|
|
121
|
-
},
|
|
122
|
-
'Create temperature state in adapter': {
|
|
123
|
-
en: 'Create temperature state in adapter',
|
|
124
|
-
de: 'Temperatur-State im Adapter anlegen',
|
|
125
|
-
ru: 'Создать состояние температуры в адаптере',
|
|
126
|
-
pt: 'Criar estado de temperatura no adaptador',
|
|
127
|
-
nl: 'Temperatuurstate in adapter aanmaken',
|
|
128
|
-
fr: "Créer l'état de température dans l'adaptateur",
|
|
129
|
-
it: "Crea stato temperatura nell'adattatore",
|
|
130
|
-
es: 'Crear estado de temperatura en el adaptador',
|
|
131
|
-
pl: 'Utwórz stan temperatury w adapterze',
|
|
132
|
-
uk: 'Створити стан температури в адаптері',
|
|
133
|
-
'zh-cn': '在适配器中创建温度状态',
|
|
134
|
-
},
|
|
135
|
-
'Relative humidity': {
|
|
136
|
-
en: 'Relative humidity',
|
|
137
|
-
de: 'Relative Luftfeuchte',
|
|
138
|
-
ru: 'Относительная влажность',
|
|
139
|
-
pt: 'Umidade relativa',
|
|
140
|
-
nl: 'Relatieve luchtvochtigheid',
|
|
141
|
-
fr: 'Humidité relative',
|
|
142
|
-
it: 'Umidità relativa',
|
|
143
|
-
es: 'Humedad relativa',
|
|
144
|
-
pl: 'Wilgotność względna',
|
|
145
|
-
uk: 'Відносна вологість',
|
|
146
|
-
'zh-cn': '相对湿度',
|
|
147
|
-
},
|
|
148
|
-
'Relative humidity state': {
|
|
149
|
-
en: 'Relative humidity state',
|
|
150
|
-
de: 'State für relative Luftfeuchte',
|
|
151
|
-
ru: 'Состояние относительной влажности',
|
|
152
|
-
pt: 'Estado de umidade relativa',
|
|
153
|
-
nl: 'Relatieve-luchtvochtigheidstate',
|
|
154
|
-
fr: "État d'humidité relative",
|
|
155
|
-
it: 'Stato umidità relativa',
|
|
156
|
-
es: 'Estado de humedad relativa',
|
|
157
|
-
pl: 'Stan wilgotności względnej',
|
|
158
|
-
uk: 'Стан відносної вологості',
|
|
159
|
-
'zh-cn': '相对湿度状态',
|
|
160
|
-
},
|
|
161
|
-
'Create relative humidity state in adapter': {
|
|
162
|
-
en: 'Create relative humidity state in adapter',
|
|
163
|
-
de: 'State für relative Luftfeuchte im Adapter anlegen',
|
|
164
|
-
ru: 'Создать состояние относительной влажности в адаптере',
|
|
165
|
-
pt: 'Criar estado de umidade relativa no adaptador',
|
|
166
|
-
nl: 'Relatieve-luchtvochtigheidstate in adapter aanmaken',
|
|
167
|
-
fr: "Créer l'état d'humidité relative dans l'adaptateur",
|
|
168
|
-
it: "Crea stato umidità relativa nell'adattatore",
|
|
169
|
-
es: 'Crear estado de humedad relativa en el adaptador',
|
|
170
|
-
pl: 'Utwórz stan wilgotności względnej w adapterze',
|
|
171
|
-
uk: 'Створити стан відносної вологості в адаптері',
|
|
172
|
-
'zh-cn': '在适配器中创建相对湿度状态',
|
|
173
|
-
},
|
|
174
|
-
'Absolute humidity': {
|
|
175
|
-
en: 'Absolute humidity',
|
|
176
|
-
de: 'Absolute Luftfeuchte',
|
|
177
|
-
ru: 'Абсолютная влажность',
|
|
178
|
-
pt: 'Umidade absoluta',
|
|
179
|
-
nl: 'Absolute luchtvochtigheid',
|
|
180
|
-
fr: 'Humidité absolue',
|
|
181
|
-
it: 'Umidità assoluta',
|
|
182
|
-
es: 'Humedad absoluta',
|
|
183
|
-
pl: 'Wilgotność bezwzględna',
|
|
184
|
-
uk: 'Абсолютна вологість',
|
|
185
|
-
'zh-cn': '绝对湿度',
|
|
186
|
-
},
|
|
187
|
-
'Dew point temperature': {
|
|
188
|
-
en: 'Dew point temperature',
|
|
189
|
-
de: 'Taupunkttemperatur',
|
|
190
|
-
ru: 'Температура точки росы',
|
|
191
|
-
pt: 'Temperatura do ponto de orvalho',
|
|
192
|
-
nl: 'Dauwpunttemperatuur',
|
|
193
|
-
fr: 'Température du point de rosée',
|
|
194
|
-
it: 'Temperatura del punto di rugiada',
|
|
195
|
-
es: 'Temperatura del punto de rocío',
|
|
196
|
-
pl: 'Temperatura punktu rosy',
|
|
197
|
-
uk: 'Температура точки роси',
|
|
198
|
-
'zh-cn': '露点温度',
|
|
199
|
-
},
|
|
200
|
-
};
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const ADMIN_I18N_DIR = path.join(__dirname, '..', '..', 'admin', 'i18n');
|
|
8
|
+
const LANGUAGES = ['en', 'de', 'ru', 'pt', 'nl', 'fr', 'it', 'es', 'pl', 'uk', 'zh-cn'];
|
|
9
|
+
const I18N = loadAdminTranslations();
|
|
10
|
+
|
|
11
|
+
function loadAdminTranslations() {
|
|
12
|
+
const translations = {};
|
|
13
|
+
|
|
14
|
+
for (const language of LANGUAGES) {
|
|
15
|
+
const filePath = path.join(ADMIN_I18N_DIR, `${language}.json`);
|
|
16
|
+
const languageTranslations = readLanguageTranslations(filePath);
|
|
17
|
+
|
|
18
|
+
for (const [key, value] of Object.entries(languageTranslations)) {
|
|
19
|
+
translations[key] = translations[key] || {};
|
|
20
|
+
translations[key][language] = value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return translations;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readLanguageTranslations(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
30
|
+
} catch {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
201
34
|
|
|
202
35
|
function translate(text) {
|
|
203
36
|
return I18N[text] || text;
|
package/main.js
CHANGED
|
@@ -69,11 +69,13 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
69
69
|
async addDevice(data) {
|
|
70
70
|
const devices = this.getConfiguredDevices();
|
|
71
71
|
const device = this.normalizeDeviceFormData(data, createUniqueDeviceId(data.name, devices));
|
|
72
|
+
const updatedDevices = [...devices, device];
|
|
72
73
|
|
|
73
|
-
await this.
|
|
74
|
+
await this.validateDeviceSourceIds(device);
|
|
74
75
|
await this.ensureDeviceObjects(device);
|
|
75
|
-
await this.refreshSubscriptions();
|
|
76
76
|
await this.updateDeviceValues(device);
|
|
77
|
+
await this.refreshSubscriptions(updatedDevices);
|
|
78
|
+
await this.saveDevices(updatedDevices);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
/**
|
|
@@ -84,16 +86,18 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
84
86
|
const oldDevice = this.getDeviceById(id);
|
|
85
87
|
const devices = this.getConfiguredDevices();
|
|
86
88
|
const device = this.normalizeDeviceFormData(data, id);
|
|
89
|
+
const updatedDevices = devices.map(existingDevice => (existingDevice.id === id ? device : existingDevice));
|
|
87
90
|
|
|
88
|
-
await this.
|
|
91
|
+
await this.validateDeviceSourceIds(device);
|
|
89
92
|
|
|
90
93
|
if (oldDevice) {
|
|
91
94
|
await this.deleteObsoleteDeviceStates(oldDevice, device);
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
await this.ensureDeviceObjects(device);
|
|
95
|
-
await this.refreshSubscriptions();
|
|
96
98
|
await this.updateDeviceValues(device);
|
|
99
|
+
await this.refreshSubscriptions(updatedDevices);
|
|
100
|
+
await this.saveDevices(updatedDevices);
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
/**
|
|
@@ -101,10 +105,11 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
101
105
|
*/
|
|
102
106
|
async deleteDevice(id) {
|
|
103
107
|
const devices = this.getConfiguredDevices();
|
|
108
|
+
const updatedDevices = devices.filter(device => device.id !== id);
|
|
104
109
|
|
|
105
|
-
await this.saveDevices(devices.filter(device => device.id !== id));
|
|
106
110
|
await this.deleteObjectIfExists(this.getDeviceObjectId(id), { recursive: true });
|
|
107
|
-
await this.refreshSubscriptions();
|
|
111
|
+
await this.refreshSubscriptions(updatedDevices);
|
|
112
|
+
await this.saveDevices(updatedDevices);
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
/**
|
|
@@ -122,6 +127,79 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
122
127
|
};
|
|
123
128
|
}
|
|
124
129
|
|
|
130
|
+
/**
|
|
131
|
+
* @param {Record<string, any>} device
|
|
132
|
+
*/
|
|
133
|
+
async validateDeviceSourceIds(device) {
|
|
134
|
+
await this.validateForeignStateId(device.temperatureStateId, 'Temperature state');
|
|
135
|
+
await this.validateForeignStateId(device.relativeHumidityStateId, 'Relative humidity state');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {Record<string, any>} device
|
|
140
|
+
* @returns {Promise<boolean>}
|
|
141
|
+
*/
|
|
142
|
+
async hasValidDeviceSourceIds(device) {
|
|
143
|
+
const sourceValidation = await this.getDeviceSourceValidation(device);
|
|
144
|
+
|
|
145
|
+
return sourceValidation.valid;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @param {unknown} stateId
|
|
150
|
+
* @param {string} label
|
|
151
|
+
*/
|
|
152
|
+
async validateForeignStateId(stateId, label) {
|
|
153
|
+
if (await this.isUsableSourceStateId(stateId)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
throw new Error(`${label} "${stateId}" is not a valid number state ID`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param {Record<string, any>} device
|
|
162
|
+
*/
|
|
163
|
+
async getDeviceSourceValidation(device) {
|
|
164
|
+
const temperatureStateValid = await this.isUsableSourceStateId(device.temperatureStateId);
|
|
165
|
+
const relativeHumidityStateValid = await this.isUsableSourceStateId(device.relativeHumidityStateId);
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
valid: temperatureStateValid && relativeHumidityStateValid,
|
|
169
|
+
temperatureStateValid,
|
|
170
|
+
relativeHumidityStateValid,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @param {unknown} stateId
|
|
176
|
+
* @returns {stateId is string}
|
|
177
|
+
*/
|
|
178
|
+
isValidForeignStateId(stateId) {
|
|
179
|
+
return typeof stateId === 'string' && stateId.trim() !== '' && !stateId.endsWith('.');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @param {unknown} stateId
|
|
184
|
+
* @returns {Promise<boolean>}
|
|
185
|
+
*/
|
|
186
|
+
async isUsableSourceStateId(stateId) {
|
|
187
|
+
if (!this.isValidForeignStateId(stateId)) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let object;
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
object = await this.getForeignObjectAsync(stateId);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this.log.warn(`Cannot validate state ID "${stateId}": ${error.message}`);
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return object?.type === 'state' && object.common?.type === 'number';
|
|
201
|
+
}
|
|
202
|
+
|
|
125
203
|
/**
|
|
126
204
|
* @param {Record<string, any>[]} devices
|
|
127
205
|
*/
|
|
@@ -182,11 +260,15 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
182
260
|
return;
|
|
183
261
|
}
|
|
184
262
|
|
|
185
|
-
|
|
263
|
+
for (const device of migratedDevices) {
|
|
264
|
+
await this.ensureDeviceObjects(device);
|
|
265
|
+
}
|
|
186
266
|
|
|
187
267
|
for (const oldId of oldIdsToDelete) {
|
|
188
268
|
await this.deleteObjectIfExists(this.getDeviceObjectId(oldId), { recursive: true });
|
|
189
269
|
}
|
|
270
|
+
|
|
271
|
+
await this.saveDevices(migratedDevices);
|
|
190
272
|
}
|
|
191
273
|
|
|
192
274
|
async ensureInfoStates() {
|
|
@@ -314,20 +396,42 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
314
396
|
}
|
|
315
397
|
}
|
|
316
398
|
|
|
317
|
-
|
|
399
|
+
/**
|
|
400
|
+
* @param {Array<Record<string, any>>} [devices]
|
|
401
|
+
*/
|
|
402
|
+
async refreshSubscriptions(devices = this.getConfiguredDevices()) {
|
|
318
403
|
for (const stateId of this.subscribedSourceIds) {
|
|
319
404
|
this.unsubscribeForeignStates(stateId);
|
|
320
405
|
}
|
|
321
406
|
|
|
322
407
|
this.subscribedSourceIds.clear();
|
|
323
408
|
|
|
324
|
-
for (const device of
|
|
325
|
-
this.
|
|
326
|
-
|
|
409
|
+
for (const device of devices) {
|
|
410
|
+
const sourceValidation = await this.getDeviceSourceValidation(device);
|
|
411
|
+
|
|
412
|
+
if (sourceValidation.temperatureStateValid) {
|
|
413
|
+
this.subscribedSourceIds.add(device.temperatureStateId);
|
|
414
|
+
} else {
|
|
415
|
+
this.log.warn(
|
|
416
|
+
`Ignoring unusable temperature state ID for device "${device.name}": ${device.temperatureStateId}`,
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (sourceValidation.relativeHumidityStateValid) {
|
|
421
|
+
this.subscribedSourceIds.add(device.relativeHumidityStateId);
|
|
422
|
+
} else {
|
|
423
|
+
this.log.warn(
|
|
424
|
+
`Ignoring unusable relative humidity state ID for device "${device.name}": ${device.relativeHumidityStateId}`,
|
|
425
|
+
);
|
|
426
|
+
}
|
|
327
427
|
}
|
|
328
428
|
|
|
329
429
|
for (const stateId of this.subscribedSourceIds) {
|
|
330
|
-
|
|
430
|
+
try {
|
|
431
|
+
this.subscribeForeignStates(stateId);
|
|
432
|
+
} catch (error) {
|
|
433
|
+
this.log.warn(`Cannot subscribe state ID "${stateId}": ${error.message}`);
|
|
434
|
+
}
|
|
331
435
|
}
|
|
332
436
|
}
|
|
333
437
|
|
|
@@ -390,6 +494,11 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
390
494
|
* @param {Record<string, any>} device
|
|
391
495
|
*/
|
|
392
496
|
async updateDeviceValues(device) {
|
|
497
|
+
if (!(await this.hasValidDeviceSourceIds(device))) {
|
|
498
|
+
this.log.warn(`Skipping device "${device.name}" because it has unusable source state IDs`);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
393
502
|
const temperature = await this.readNumberState(device.temperatureStateId);
|
|
394
503
|
const relativeHumidity = await this.readNumberState(device.relativeHumidityStateId);
|
|
395
504
|
const absoluteHumidity = calculateAbsoluteHumidity(temperature, relativeHumidity);
|
|
@@ -424,7 +533,20 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
424
533
|
* @param {string} stateId
|
|
425
534
|
*/
|
|
426
535
|
async readNumberState(stateId) {
|
|
427
|
-
|
|
536
|
+
if (!(await this.isUsableSourceStateId(stateId))) {
|
|
537
|
+
this.log.warn(`Cannot read unusable state ID: ${stateId}`);
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
let state;
|
|
542
|
+
|
|
543
|
+
try {
|
|
544
|
+
state = await this.getForeignStateAsync(stateId);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
this.log.warn(`Cannot read state ID "${stateId}": ${error.message}`);
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
|
|
428
550
|
const value = Number(state?.val);
|
|
429
551
|
|
|
430
552
|
return Number.isFinite(value) ? value : null;
|