iobroker.absolutehumidity 0.1.0 → 0.1.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/README.md +9 -9
- package/admin/jsonConfig.json +4 -38
- package/io-package.json +28 -46
- package/lib/modules/constants.js +2 -0
- package/lib/modules/deviceManager.js +88 -87
- package/lib/modules/i18n.js +13 -0
- package/main.js +46 -1
- package/package.json +2 -2
- package/admin/jsonTab.json5 +0 -16
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|

|
|
2
2
|
# ioBroker.absolutehumidity
|
|
3
3
|
|
|
4
|
-
[](https://www.npmjs.com/package/iobroker.absolutehumidity)
|
|
5
|
+
[](https://www.npmjs.com/package/iobroker.absolutehumidity)
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
[](https://nodei.co/npm/iobroker.absolutehumidity/)
|
|
10
10
|
|
|
11
11
|
**Tests:** 
|
|
12
12
|
|
|
@@ -35,6 +35,12 @@ use different Magnus, Tetens, Sonntag, Bolton or Buck coefficient sets.
|
|
|
35
35
|
Placeholder for the next version (at the beginning of the line):
|
|
36
36
|
### **WORK IN PROGRESS**
|
|
37
37
|
-->
|
|
38
|
+
### 0.1.2 (2026-09-20)
|
|
39
|
+
* Keep the Device Manager in the adapter configuration instead of a separate Admin tab. Render the four live measurements through one shared HTML row template per card; Admin's read-only numeric state control otherwise adds a progress indicator for percent and bounded states.
|
|
40
|
+
|
|
41
|
+
### 0.1.1 (2026-08-07)
|
|
42
|
+
* (BenAhrdt) update dm-utils to 3.2.0
|
|
43
|
+
|
|
38
44
|
### 0.1.0 (2026-07-07)
|
|
39
45
|
* (BenAhrdt) Ready for add to ioBroker latest repo
|
|
40
46
|
|
|
@@ -45,12 +51,6 @@ use different Magnus, Tetens, Sonntag, Bolton or Buck coefficient sets.
|
|
|
45
51
|
* (BenAhrdt) Id will change in case of renameing device
|
|
46
52
|
* (BenAhrdt) Add second manual calculation for compare
|
|
47
53
|
|
|
48
|
-
### 0.0.4 (2026-07-05)
|
|
49
|
-
* (BenAhrdt) Build Config in adapter own device object
|
|
50
|
-
|
|
51
|
-
### 0.0.3 (2026-07-05)
|
|
52
|
-
* (BenAhrdt) Add new features and icons to deviceManager
|
|
53
|
-
|
|
54
54
|
[Older changes can be found there](CHANGELOG_OLD.md)
|
|
55
55
|
|
|
56
56
|
## License
|
package/admin/jsonConfig.json
CHANGED
|
@@ -2,49 +2,15 @@
|
|
|
2
2
|
"i18n": true,
|
|
3
3
|
"type": "panel",
|
|
4
4
|
"items": {
|
|
5
|
-
"
|
|
6
|
-
"type": "
|
|
7
|
-
"
|
|
5
|
+
"_deviceManager": {
|
|
6
|
+
"type": "deviceManager",
|
|
7
|
+
"smallCards": false,
|
|
8
8
|
"newLine": true,
|
|
9
9
|
"xs": 12,
|
|
10
10
|
"sm": 12,
|
|
11
11
|
"md": 12,
|
|
12
12
|
"lg": 12,
|
|
13
|
-
"xl": 12
|
|
14
|
-
"style": {
|
|
15
|
-
"fontSize": 16,
|
|
16
|
-
"lineHeight": 1.5
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"_configurationLink": {
|
|
20
|
-
"type": "staticLink",
|
|
21
|
-
"label": "Open adapter tab",
|
|
22
|
-
"href": "#tab-absolutehumidity",
|
|
23
|
-
"target": "_self",
|
|
24
|
-
"button": true,
|
|
25
|
-
"variant": "contained",
|
|
26
|
-
"color": "primary",
|
|
27
|
-
"newLine": true,
|
|
28
|
-
"xs": 12,
|
|
29
|
-
"sm": 12,
|
|
30
|
-
"md": 8,
|
|
31
|
-
"lg": 6,
|
|
32
|
-
"xl": 6,
|
|
33
|
-
"controlStyle": {
|
|
34
|
-
"backgroundColor": "#0f4c81",
|
|
35
|
-
"color": "#ffffff"
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
"_previewCards": {
|
|
39
|
-
"type": "staticText",
|
|
40
|
-
"format": "html",
|
|
41
|
-
"text": "<div style='display:flex;justify-content:center;gap:32px;flex-wrap:wrap;margin-top:24px;font-family:Roboto,Arial,sans-serif;'><div data-ah-card='outdoor' style='width:300px;min-height:404px;background:#d3d3d3;border-radius:4px;overflow:hidden;box-shadow:0 2px 4px rgba(0,0,0,.25);color:#000;'><div style='height:60px;background:#21867a;color:#fff;display:flex;align-items:center;padding:0 18px;gap:18px;font-weight:700;font-size:15px;'><img src='adapter/absolutehumidity/absolutehumidity.svg' alt='' style='width:34px;height:34px;object-fit:contain;' onload='(async img=>{if(window.ahPreviewReady)return;window.ahPreviewReady=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=>{try{const r=await fetch(\"adapter/absolutehumidity/i18n/\"+l+\".json\");return r.ok?r.json():null;}catch(e){return null;}};const dict=(await load(lang))||(await load(\"en\"))||{};const text=(k,f)=>dict[k]||f||k;const fill=(s,v)=>Object.entries(v).reduce((r,e)=>r.replaceAll(\"{\"+e[0]+\"}\",e[1]),s);window.ahPreviewUpdate=()=>{document.querySelectorAll(\"[data-ah-card]\").forEach(c=>{const t=Number(c.querySelector(\"[data-ah-temp]\").value),h=Number(c.querySelector(\"[data-ah-rh]\").value),abs=c.querySelector(\"[data-ah-abs]\"),dew=c.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,a=(216.7*e)/(273.15+t);abs.textContent=a.toFixed(2);abs.dataset.value=String(a);}else{abs.textContent=\"\";abs.dataset.value=\"\";}if(Number.isFinite(t)&&Number.isFinite(h)&&h>0&&h<=100){const x=Math.log(h/100)+17.62*t/(243.12+t);dew.textContent=(243.12*x/(17.62-x)).toFixed(2);}else dew.textContent=\"\";});const out=document.querySelector(\"[data-ah-card=outdoor]\"),inn=document.querySelector(\"[data-ah-card=indoor]\");const outAbs=Number(out.querySelector(\"[data-ah-abs]\").dataset.value),inAbs=Number(inn.querySelector(\"[data-ah-abs]\").dataset.value);out.querySelector(\"[data-ah-summary]\").textContent=fill(text(\"Outdoor ventilation reference\"),{absoluteHumidity:Number.isFinite(outAbs)?outAbs.toFixed(2):\"\"});const diff=inAbs-outAbs;const key=diff>0?\"Ventilation recommended because indoor is higher\":\"Ventilation not recommended because indoor is not higher\";inn.querySelector(\"[data-ah-summary]\").textContent=fill(text(key),{absoluteHumidity:Number.isFinite(inAbs)?inAbs.toFixed(2):\"\",outdoorAbsoluteHumidity:Number.isFinite(outAbs)?outAbs.toFixed(2):\"\",difference:Number.isFinite(diff)?Math.abs(diff).toFixed(2):\"\"});};document.querySelectorAll(\"[data-ah-i18n]\").forEach(e=>{e.textContent=text(e.dataset.ahI18n,e.textContent);});window.ahPreviewUpdate();})(this)'><span data-ah-i18n='Outdoor'>Outdoor</span></div><div style='padding:18px 16px 20px;box-sizing:border-box;'><div style='height:24px;margin-bottom:22px;display:flex;align-items:center;'><i class='material-icons' aria-hidden='true' style='color:#00a51a;font-size:22px;line-height:22px;'>link</i></div><div style='display:grid;grid-template-columns:28px 1fr auto;gap:18px 8px;align-items:center;font-size:16px;'><svg viewBox='0 0 24 24' width='24' height='24' aria-hidden='true'><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'/><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'/></svg><span><span data-ah-i18n='Temperature'>Temperature</span>:</span><span style='white-space:nowrap;'><input data-ah-temp type='number' value='16' step='0.1' style='width:72px;background:#fff;border:1px solid #ccc;border-radius:3px;padding:4px 6px;text-align:right;color:#000;font-size:14px;' oninput='event.stopPropagation();window.ahPreviewUpdate&&window.ahPreviewUpdate();'> <span style='font-size:14px;'>°C</span></span><svg viewBox='0 0 24 24' width='24' height='24' aria-hidden='true'><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'/><text x='12' y='16'text-anchor='middle'font-family='Arial, sans-serif'font-size='7'font-weight='700'fill='#fff'>%</text></svg><span><span data-ah-i18n='Relative humidity'>Relative humidity</span>:</span><span style='white-space:nowrap;'><input data-ah-rh type='number' value='60' 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;font-size:14px;' oninput='event.stopPropagation();window.ahPreviewUpdate&&window.ahPreviewUpdate();'> <span style='font-size:14px;'>%</span></span><svg viewBox='0 0 24 24' width='24' height='24' aria-hidden='true'><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'/><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'/></svg><span><span data-ah-i18n='Absolute humidity'>Absolute humidity</span>:</span><span style='text-align:right;white-space:nowrap;'><span data-ah-abs>8.17</span> <span style='font-size:14px;'>g/m³</span></span><svg viewBox='0 0 24 24' width='24' height='24' aria-hidden='true'><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'/><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'/><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'/></svg><span><span data-ah-i18n='Dew point temperature'>Dew point temperature</span>:</span><span style='text-align:right;white-space:nowrap;'><span data-ah-dew>8.23</span> <span style='font-size:14px;'>°C</span></span></div><p style='margin:18px 0 0;font-size:14px;line-height:1.35;'><span data-ah-summary>Outdoor reference: Absolute humidity is 8.17 g/m³. This value is used for indoor ventilation recommendations.</span></p></div></div><div data-ah-card='indoor' style='width:300px;min-height:404px;background:#d3d3d3;border-radius:4px;overflow:hidden;box-shadow:0 2px 4px rgba(0,0,0,.25);color:#000;'><div style='height:60px;background:#0f4c81;color:#fff;display:flex;align-items:center;padding:0 18px;gap:18px;font-weight:700;font-size:15px;'><img src='adapter/absolutehumidity/absolutehumidity.svg' alt='' style='width:34px;height:34px;object-fit:contain;'><span data-ah-i18n='Indoor'>Indoor</span></div><div style='padding:18px 16px 20px;box-sizing:border-box;'><div style='height:24px;margin-bottom:22px;display:flex;align-items:center;'><i class='material-icons' aria-hidden='true' style='color:#00a51a;font-size:22px;line-height:22px;'>link</i></div><div style='display:grid;grid-template-columns:28px 1fr auto;gap:18px 8px;align-items:center;font-size:16px;'><svg viewBox='0 0 24 24' width='24' height='24'><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'/><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'/></svg><span><span data-ah-i18n='Temperature'>Temperature</span>:</span><span style='white-space:nowrap;'><input data-ah-temp type='number' value='22' step='0.1' style='width:72px;background:#fff;border:1px solid #ccc;border-radius:3px;padding:4px 6px;text-align:right;color:#000;font-size:14px;' oninput='event.stopPropagation();window.ahPreviewUpdate&&window.ahPreviewUpdate();'> <span style='font-size:14px;'>°C</span></span><svg viewBox='0 0 24 24' width='24' height='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'/><text x='12' y='16' text-anchor='middle' font-family='Arial,sans-serif' font-size='7' font-weight='700' fill='#fff'>%</text></svg><span><span data-ah-i18n='Relative humidity'>Relative humidity</span>:</span><span style='white-space:nowrap;'><input 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;font-size:14px;' oninput='event.stopPropagation();window.ahPreviewUpdate&&window.ahPreviewUpdate();'> <span style='font-size:14px;'>%</span></span><svg viewBox='0 0 24 24' width='24' height='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'/><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'/></svg><span><span data-ah-i18n='Absolute humidity'>Absolute humidity</span>:</span><span style='text-align:right;white-space:nowrap;'><span data-ah-abs>9.70</span> <span style='font-size:14px;'>g/m³</span></span><svg viewBox='0 0 24 24' width='24' height='24'><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'/><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'/><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'/></svg><span><span data-ah-i18n='Dew point temperature'>Dew point temperature</span>:</span><span style='text-align:right;white-space:nowrap;'><span data-ah-dew>11.09</span> <span style='font-size:14px;'>°C</span></span></div><p style='margin:18px 0 0;font-size:14px;line-height:1.35;'><span data-ah-summary>Ventilation could be useful: Indoor absolute humidity is 9.70 g/m³, outdoor is 8.17 g/m³. Indoor air is 1.53 g/m³ more humid.</span></p></div></div></div>",
|
|
42
|
-
"newLine": true,
|
|
43
|
-
"xs": 12,
|
|
44
|
-
"sm": 12,
|
|
45
|
-
"md": 8,
|
|
46
|
-
"lg": 6,
|
|
47
|
-
"xl": 6
|
|
13
|
+
"xl": 12
|
|
48
14
|
}
|
|
49
15
|
}
|
|
50
16
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "absolutehumidity",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.1.2": {
|
|
7
|
+
"en": "Keep the Device Manager in the adapter configuration instead of a separate Admin tab. Render the four live measurements through one shared HTML row template per card; Admin's read-only numeric state control otherwise adds a progress indicator for percent and bounded states.",
|
|
8
|
+
"de": "Behalten Sie den Gerätemanager in der Adapterkonfiguration statt im Admin-Tab. Rendern Sie die vier Live-Messungen über eine gemeinsame HTML-Zeilenvorlage pro Karte; Admins schreibgeschützte numerische Statusanzeige fügt sonst Werte in Prozent und Grenzen hinzu.",
|
|
9
|
+
"ru": "Храните диспетчер устройств в конфигурации адаптера, а не на отдельной вкладке «Администратор». Выполните визуализацию четырех измерений в реальном времени с помощью одного шаблона строк HTML на каждой карточке; в остальном числовой элемент управления состоянием, доступный только для чтения, добавляет индикатор выполнения измерений в процентах и ограниченных состояниях.",
|
|
10
|
+
"pt": "Configure o Device Manager no adaptador em vez de uma guia Admin separada. Faça as quatro medições ao vivo usando um modelo de linha HTML compartilhado por cartão; caso contrário, o Admin adiciona um indicador de progresso para porcentagem e estados limitados.",
|
|
11
|
+
"nl": "Houd de apparaatbeheerder in de adapterconfiguratie in plaats van op een apart tabblad Admin. Geef de vier actuele metingen weer via één gedeeld HTML-rijsjabloon per kaart; de numerieke statuscontrole van de beheerder voegt anders een voortgangsindicator toe voor percentages en begrensde staten.",
|
|
12
|
+
"fr": "Conservez le Device Manager dans la configuration de l'adaptateur au lieu d'un onglet Admin séparé. Effectuez les quatre mesures en direct via un modèle de ligne HTML partagé par carte ; le contrôle numérique en lecture seule d'Admin ajoute un indicateur de progression pour le pourcentage et les états bornés.",
|
|
13
|
+
"it": "Mantieni Device Manager nell'adattatore anziché in una scheda Admin separata. Renderizza le quattro misure in tempo reale con un modello di riga HTML condiviso per scheda; il controllo numerico di sola lettura di Admin aggiunge la percentuale e gli stati limitati.",
|
|
14
|
+
"es": "Mantenga el Device Manager en la configuración del adaptador y no en otra pestaña Admin. Renderice las cuatro mediciones en vivo a través de una plantilla de filas HTML compartida por tarjeta; el control de estado numérico del administrador añade un indicador de progreso para los estados porcentuales y acotados.",
|
|
15
|
+
"pl": "Zachowaj Menedżera urządzeń w konfiguracji adaptera zamiast osobnej karty Administrator. Renderuj cztery pomiary na żywo za pomocą jednego współdzielonego szablonu wiersza HTML na kartę; Kontrola stanu numerycznego tylko do odczytu przez administratora dodaje wskaźnik postępu dla stanów procentowych i ograniczonych.",
|
|
16
|
+
"uk": "Зберігайте Диспетчер пристроїв в конфігурації адаптера замість окремої вкладки Admin. Відтворюйте чотири вимірювання в реальному часі за допомогою одного спільного шаблону рядка HTML на картку; в іншому випадку, числовий контроль стану адміністратора, призначений лише для читання, додає індикатор прогресу для відсотків та обмежених станів.",
|
|
17
|
+
"zh-cn": "将设备管理器保留在适配器配置中,而不是单独的 “管理” 选项卡。通过每张卡片的一个共享 HTML 行模板呈现四次实时测量结果;管理员的只读数字状态控件还会添加百分比和边界状态的进度指示器。"
|
|
18
|
+
},
|
|
19
|
+
"0.1.1": {
|
|
20
|
+
"en": "update dm-utils to 3.2.0",
|
|
21
|
+
"de": "update dm-utils auf 3.2.0",
|
|
22
|
+
"ru": "обновление dm-utils до 3.2.0",
|
|
23
|
+
"pt": "atualizar dm-utils para 3.2.0",
|
|
24
|
+
"nl": "update dm-utils naar 3.2.0",
|
|
25
|
+
"fr": "mettre à jour dm-utils à 3.2.0",
|
|
26
|
+
"it": "aggiornamento dm-utils a 3.2.0",
|
|
27
|
+
"es": "actualización dm-utils a 3.2.0",
|
|
28
|
+
"pl": "update dm- utils to 3.2.0",
|
|
29
|
+
"uk": "оновлення dm-utils до 3.2.0",
|
|
30
|
+
"zh-cn": "更新 dm- utils 到 3. 2. 0"
|
|
31
|
+
},
|
|
6
32
|
"0.1.0": {
|
|
7
33
|
"en": "Ready for add to ioBroker latest repo",
|
|
8
34
|
"de": "Bereit zum Hinzufügen zum neuesten ioBroker-Repository",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "Dodaj nowe funkcje i ikony do DeviceManager",
|
|
68
94
|
"uk": "Додавання нових функцій та піктограм до DeviceManager",
|
|
69
95
|
"zh-cn": "向设备管理器添加新功能和图标"
|
|
70
|
-
},
|
|
71
|
-
"0.0.2": {
|
|
72
|
-
"en": "initial release",
|
|
73
|
-
"de": "Erstveröffentlichung",
|
|
74
|
-
"ru": "первоначальный релиз",
|
|
75
|
-
"pt": "lançamento inicial",
|
|
76
|
-
"nl": "eerste release",
|
|
77
|
-
"fr": "version initiale",
|
|
78
|
-
"it": "rilascio iniziale",
|
|
79
|
-
"es": "lanzamiento inicial",
|
|
80
|
-
"pl": "wydanie początkowe",
|
|
81
|
-
"uk": "початковий випуск",
|
|
82
|
-
"zh-cn": "初始版本"
|
|
83
|
-
},
|
|
84
|
-
"0.0.1": {
|
|
85
|
-
"en": "initial release",
|
|
86
|
-
"de": "Erstveröffentlichung",
|
|
87
|
-
"ru": "Начальная версия",
|
|
88
|
-
"pt": "lançamento inicial",
|
|
89
|
-
"nl": "Eerste uitgave",
|
|
90
|
-
"fr": "Première version",
|
|
91
|
-
"it": "Versione iniziale",
|
|
92
|
-
"es": "Versión inicial",
|
|
93
|
-
"pl": "Pierwsze wydanie",
|
|
94
|
-
"uk": "Початкова версія",
|
|
95
|
-
"zh-cn": "首次出版"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
|
@@ -148,25 +148,7 @@
|
|
|
148
148
|
"deviceManager": true
|
|
149
149
|
},
|
|
150
150
|
"adminUI": {
|
|
151
|
-
"config": "json"
|
|
152
|
-
"tab": "json"
|
|
153
|
-
},
|
|
154
|
-
"adminTab": {
|
|
155
|
-
"singleton": true,
|
|
156
|
-
"name": {
|
|
157
|
-
"en": "Absolute Humidity",
|
|
158
|
-
"de": "Absolute Luftfeuchtigkeit",
|
|
159
|
-
"ru": "Абсолютная влажность",
|
|
160
|
-
"pt": "Umidade Absoluta",
|
|
161
|
-
"nl": "Absolute vochtigheid",
|
|
162
|
-
"fr": "Humidité absolue",
|
|
163
|
-
"it": "Umidità assoluta",
|
|
164
|
-
"es": "Humedad absoluta",
|
|
165
|
-
"pl": "Wilgotność bezwzględna",
|
|
166
|
-
"uk": "Абсолютна вологість",
|
|
167
|
-
"zh-cn": "绝对湿度"
|
|
168
|
-
},
|
|
169
|
-
"link": "jsonTab.json5"
|
|
151
|
+
"config": "json"
|
|
170
152
|
},
|
|
171
153
|
"dependencies": [
|
|
172
154
|
{
|
package/lib/modules/constants.js
CHANGED
|
@@ -5,6 +5,7 @@ const STATE_TEMPERATURE = 'temperature';
|
|
|
5
5
|
const STATE_RELATIVE_HUMIDITY = 'relativeHumidity';
|
|
6
6
|
const STATE_ABSOLUTE_HUMIDITY = 'absoluteHumidity';
|
|
7
7
|
const STATE_DEW_POINT_TEMPERATURE = 'dewPointTemperature';
|
|
8
|
+
const STATE_DEVICE_MANAGER_HTML = 'deviceManagerHtml';
|
|
8
9
|
const CARD_HEADER_BACKGROUND = '#0f4c81';
|
|
9
10
|
const OUTDOOR_CARD_HEADER_BACKGROUND = '#21867a';
|
|
10
11
|
const ADAPTER_ICON = 'adapter/absolutehumidity/absolutehumidity.svg';
|
|
@@ -16,6 +17,7 @@ module.exports = {
|
|
|
16
17
|
OUTDOOR_CARD_HEADER_BACKGROUND,
|
|
17
18
|
STATE_ABSOLUTE_HUMIDITY,
|
|
18
19
|
STATE_DEW_POINT_TEMPERATURE,
|
|
20
|
+
STATE_DEVICE_MANAGER_HTML,
|
|
19
21
|
STATE_RELATIVE_HUMIDITY,
|
|
20
22
|
STATE_TEMPERATURE,
|
|
21
23
|
};
|
|
@@ -7,11 +7,9 @@ const {
|
|
|
7
7
|
CARD_HEADER_BACKGROUND,
|
|
8
8
|
OUTDOOR_CARD_HEADER_BACKGROUND,
|
|
9
9
|
STATE_ABSOLUTE_HUMIDITY,
|
|
10
|
-
|
|
11
|
-
STATE_RELATIVE_HUMIDITY,
|
|
12
|
-
STATE_TEMPERATURE,
|
|
10
|
+
STATE_DEVICE_MANAGER_HTML,
|
|
13
11
|
} = require('./constants');
|
|
14
|
-
const { translate } = require('./i18n');
|
|
12
|
+
const { translate, translateToLanguage } = require('./i18n');
|
|
15
13
|
|
|
16
14
|
const STATE_ICONS = {
|
|
17
15
|
temperature: createSvgIcon(`
|
|
@@ -145,14 +143,14 @@ function createDeviceInfo(adapter, device, sourceValidation) {
|
|
|
145
143
|
name: device.name,
|
|
146
144
|
icon: ADAPTER_ICON,
|
|
147
145
|
backgroundColor: device.isOutdoor ? OUTDOOR_CARD_HEADER_BACKGROUND : CARD_HEADER_BACKGROUND,
|
|
148
|
-
|
|
149
|
-
? {
|
|
150
|
-
connection: 'connected',
|
|
151
|
-
}
|
|
146
|
+
...(sourceValidation.valid
|
|
147
|
+
? {}
|
|
152
148
|
: {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
149
|
+
status: {
|
|
150
|
+
connection: 'disconnected',
|
|
151
|
+
warning: translate('Invalid source state'),
|
|
152
|
+
},
|
|
153
|
+
}),
|
|
156
154
|
actions: [
|
|
157
155
|
{
|
|
158
156
|
id: 'editDevice',
|
|
@@ -188,17 +186,16 @@ function createDeviceInfo(adapter, device, sourceValidation) {
|
|
|
188
186
|
},
|
|
189
187
|
},
|
|
190
188
|
],
|
|
191
|
-
customInfo: createDeviceCustomInfo(adapter, device
|
|
189
|
+
customInfo: createDeviceCustomInfo(adapter, device),
|
|
192
190
|
};
|
|
193
191
|
}
|
|
194
192
|
|
|
195
193
|
/**
|
|
196
194
|
* @param {any} adapter
|
|
197
195
|
* @param {Record<string, any>} device
|
|
198
|
-
* @param {{ valid: boolean; temperatureStateValid: boolean; relativeHumidityStateValid: boolean }} sourceValidation
|
|
199
196
|
* @returns {import('@iobroker/dm-utils').DeviceDetails<string>} Device manager custom information.
|
|
200
197
|
*/
|
|
201
|
-
function createDeviceCustomInfo(adapter, device
|
|
198
|
+
function createDeviceCustomInfo(adapter, device) {
|
|
202
199
|
return {
|
|
203
200
|
id: device.id,
|
|
204
201
|
schema: {
|
|
@@ -208,51 +205,19 @@ function createDeviceCustomInfo(adapter, device, sourceValidation) {
|
|
|
208
205
|
paddingTop: 0,
|
|
209
206
|
},
|
|
210
207
|
items: {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
: device.relativeHumidityStateId,
|
|
225
|
-
'%',
|
|
226
|
-
!device.createRelativeHumidityState && sourceValidation.relativeHumidityStateValid,
|
|
227
|
-
STATE_ICONS.relativeHumidity,
|
|
228
|
-
),
|
|
229
|
-
absoluteHumidity: createStateInfoItem(
|
|
230
|
-
translate('Absolute humidity'),
|
|
231
|
-
`${adapter.getDeviceObjectId(device.id)}.${STATE_ABSOLUTE_HUMIDITY}`,
|
|
232
|
-
'g/m³',
|
|
233
|
-
false,
|
|
234
|
-
STATE_ICONS.absoluteHumidity,
|
|
235
|
-
),
|
|
236
|
-
dewPointTemperature: createStateInfoItem(
|
|
237
|
-
translate('Dew point temperature'),
|
|
238
|
-
`${adapter.getDeviceObjectId(device.id)}.${STATE_DEW_POINT_TEMPERATURE}`,
|
|
239
|
-
'°C',
|
|
240
|
-
false,
|
|
241
|
-
STATE_ICONS.dewPointTemperature,
|
|
242
|
-
),
|
|
243
|
-
spacer: {
|
|
244
|
-
type: 'staticText',
|
|
245
|
-
text: '',
|
|
246
|
-
newLine: true,
|
|
247
|
-
xs: 12,
|
|
248
|
-
sm: 12,
|
|
249
|
-
md: 12,
|
|
250
|
-
lg: 12,
|
|
251
|
-
xl: 12,
|
|
252
|
-
},
|
|
253
|
-
spacer2: {
|
|
254
|
-
type: 'staticText',
|
|
255
|
-
text: '',
|
|
208
|
+
measurements: {
|
|
209
|
+
type: 'state',
|
|
210
|
+
label: '',
|
|
211
|
+
oid: `${adapter.getDeviceObjectId(device.id)}.${STATE_DEVICE_MANAGER_HTML}`,
|
|
212
|
+
control: 'html',
|
|
213
|
+
readOnly: true,
|
|
214
|
+
size: 'normal',
|
|
215
|
+
narrow: false,
|
|
216
|
+
style: {
|
|
217
|
+
width: '100%',
|
|
218
|
+
minHeight: 200,
|
|
219
|
+
boxSizing: 'border-box',
|
|
220
|
+
},
|
|
256
221
|
newLine: true,
|
|
257
222
|
xs: 12,
|
|
258
223
|
sm: 12,
|
|
@@ -266,39 +231,74 @@ function createDeviceCustomInfo(adapter, device, sourceValidation) {
|
|
|
266
231
|
}
|
|
267
232
|
|
|
268
233
|
/**
|
|
269
|
-
* @param {string}
|
|
270
|
-
* @param {string} oid
|
|
271
|
-
* @param {string} unit
|
|
272
|
-
* @param {boolean} foreign
|
|
273
|
-
* @param {string} labelIcon
|
|
274
|
-
* @returns {import('@iobroker/dm-utils').ConfigItemAny} State display item.
|
|
234
|
+
* @param {string} svg
|
|
275
235
|
*/
|
|
276
|
-
function
|
|
277
|
-
return {
|
|
278
|
-
type: 'state',
|
|
279
|
-
label,
|
|
280
|
-
labelIcon,
|
|
281
|
-
oid,
|
|
282
|
-
foreign,
|
|
283
|
-
control: 'text',
|
|
284
|
-
readOnly: true,
|
|
285
|
-
unit,
|
|
286
|
-
narrow: false,
|
|
287
|
-
addColon: true,
|
|
288
|
-
newLine: true,
|
|
289
|
-
xs: 12,
|
|
290
|
-
sm: 12,
|
|
291
|
-
md: 12,
|
|
292
|
-
lg: 12,
|
|
293
|
-
xl: 12,
|
|
294
|
-
};
|
|
236
|
+
function createSvgIcon(svg) {
|
|
237
|
+
return `data:image/svg+xml;base64,${Buffer.from(svg.replace(/\s+/g, ' ').trim()).toString('base64')}`;
|
|
295
238
|
}
|
|
296
239
|
|
|
297
240
|
/**
|
|
298
|
-
* @param {
|
|
241
|
+
* @param {{ absoluteHumidity: number | null; relativeHumidity: number | null; temperature: number | null; dewPointTemperature: number | null }} values
|
|
242
|
+
* @param {string} language
|
|
243
|
+
* @returns {string} Safe HTML for the four live Device Manager measurements.
|
|
299
244
|
*/
|
|
300
|
-
function
|
|
301
|
-
|
|
245
|
+
function createMeasurementDisplayHtml(values, language) {
|
|
246
|
+
const escapeHtml = value =>
|
|
247
|
+
String(value).replace(/[&<>"']/g, character => {
|
|
248
|
+
switch (character) {
|
|
249
|
+
case '&':
|
|
250
|
+
return '&';
|
|
251
|
+
case '<':
|
|
252
|
+
return '<';
|
|
253
|
+
case '>':
|
|
254
|
+
return '>';
|
|
255
|
+
case '"':
|
|
256
|
+
return '"';
|
|
257
|
+
default:
|
|
258
|
+
return ''';
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
const rows = [
|
|
262
|
+
{
|
|
263
|
+
label: translateToLanguage('Absolute humidity', language),
|
|
264
|
+
icon: STATE_ICONS.absoluteHumidity,
|
|
265
|
+
value: values.absoluteHumidity,
|
|
266
|
+
unit: 'g/m³',
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
label: translateToLanguage('Relative humidity', language),
|
|
270
|
+
icon: STATE_ICONS.relativeHumidity,
|
|
271
|
+
value: values.relativeHumidity,
|
|
272
|
+
unit: '%',
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
label: translateToLanguage('Temperature', language),
|
|
276
|
+
icon: STATE_ICONS.temperature,
|
|
277
|
+
value: values.temperature,
|
|
278
|
+
unit: '°C',
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
label: translateToLanguage('Dew point temperature', language),
|
|
282
|
+
icon: STATE_ICONS.dewPointTemperature,
|
|
283
|
+
value: values.dewPointTemperature,
|
|
284
|
+
unit: '°C',
|
|
285
|
+
},
|
|
286
|
+
];
|
|
287
|
+
|
|
288
|
+
const rowHtml = rows
|
|
289
|
+
.map(row => {
|
|
290
|
+
const value = Number.isFinite(row.value) ? row.value : '—';
|
|
291
|
+
|
|
292
|
+
return `<span style="display:grid;grid-template-columns:24px minmax(0,1fr) max-content 34px;column-gap:3px;align-items:center;height:44px;min-height:44px;box-sizing:border-box;">
|
|
293
|
+
<img src="${escapeHtml(row.icon)}" alt="" style="display:block;width:24px;height:24px;object-fit:contain;">
|
|
294
|
+
<span style="min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:16px;font-weight:400;line-height:1.2;">${escapeHtml(row.label)}:</span>
|
|
295
|
+
<span style="font-size:20px;font-variant-numeric:tabular-nums;line-height:1.2;white-space:nowrap;">${escapeHtml(value)}</span>
|
|
296
|
+
<span style="width:34px;min-width:34px;box-sizing:border-box;color:rgba(0,0,0,.7);font-size:14px;line-height:1.2;white-space:nowrap;text-align:right;">${escapeHtml(row.unit)}</span>
|
|
297
|
+
</span>`;
|
|
298
|
+
})
|
|
299
|
+
.join('');
|
|
300
|
+
|
|
301
|
+
return `<span style="display:flex;flex-direction:column;row-gap:8px;width:264px;max-width:100%;box-sizing:border-box;font-family:Roboto,Arial,sans-serif;">${rowHtml}</span>`;
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
/**
|
|
@@ -452,6 +452,7 @@ async function readAbsoluteHumidityValue(adapter, device) {
|
|
|
452
452
|
|
|
453
453
|
module.exports = {
|
|
454
454
|
AbsoluteHumidityDeviceManagement,
|
|
455
|
+
createMeasurementDisplayHtml,
|
|
455
456
|
createDeviceInfo,
|
|
456
457
|
getDevicesSortedByAbsoluteHumidity,
|
|
457
458
|
getDeviceFormSchema,
|
package/lib/modules/i18n.js
CHANGED
|
@@ -36,7 +36,20 @@ function translate(text) {
|
|
|
36
36
|
return I18N[text] || text;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function translateToLanguage(text, language) {
|
|
40
|
+
const translation = translate(text);
|
|
41
|
+
|
|
42
|
+
if (typeof translation === 'string') {
|
|
43
|
+
return translation;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const languageCode = typeof language === 'string' ? language.toLowerCase() : 'en';
|
|
47
|
+
|
|
48
|
+
return translation[languageCode] || translation.en || text;
|
|
49
|
+
}
|
|
50
|
+
|
|
39
51
|
module.exports = {
|
|
40
52
|
I18N,
|
|
41
53
|
translate,
|
|
54
|
+
translateToLanguage,
|
|
42
55
|
};
|
package/main.js
CHANGED
|
@@ -8,11 +8,12 @@ const { calculateAbsoluteHumidity, calculateDewPointTemperature } = require('./l
|
|
|
8
8
|
const {
|
|
9
9
|
DEVICE_ROOT,
|
|
10
10
|
STATE_ABSOLUTE_HUMIDITY,
|
|
11
|
+
STATE_DEVICE_MANAGER_HTML,
|
|
11
12
|
STATE_DEW_POINT_TEMPERATURE,
|
|
12
13
|
STATE_RELATIVE_HUMIDITY,
|
|
13
14
|
STATE_TEMPERATURE,
|
|
14
15
|
} = require('./lib/modules/constants');
|
|
15
|
-
const { AbsoluteHumidityDeviceManagement } = require('./lib/modules/deviceManager');
|
|
16
|
+
const { AbsoluteHumidityDeviceManagement, createMeasurementDisplayHtml } = require('./lib/modules/deviceManager');
|
|
16
17
|
const {
|
|
17
18
|
createUniqueDeviceId,
|
|
18
19
|
createUniqueIdFromBase,
|
|
@@ -34,6 +35,7 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
34
35
|
|
|
35
36
|
this.deviceManagement = null;
|
|
36
37
|
this.devices = [];
|
|
38
|
+
this.deviceManagerLanguage = 'en';
|
|
37
39
|
this.subscribedSourceIds = new Set();
|
|
38
40
|
|
|
39
41
|
this.on('ready', this.onReady.bind(this));
|
|
@@ -47,6 +49,8 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
47
49
|
* Is called when databases are connected and adapter received configuration.
|
|
48
50
|
*/
|
|
49
51
|
async onReady() {
|
|
52
|
+
const systemConfig = await this.getForeignObjectAsync('system.config');
|
|
53
|
+
this.deviceManagerLanguage = systemConfig?.common?.language || 'en';
|
|
50
54
|
this.deviceManagement = new AbsoluteHumidityDeviceManagement(this);
|
|
51
55
|
await this.ensureInfoStates();
|
|
52
56
|
await this.ensureDeviceRootObject();
|
|
@@ -456,9 +460,27 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
456
460
|
} else {
|
|
457
461
|
await this.deleteObjectIfExists(`${this.getDeviceObjectId(device.id)}.${STATE_RELATIVE_HUMIDITY}`);
|
|
458
462
|
}
|
|
463
|
+
await this.ensureDeviceManagerHtmlState(device);
|
|
459
464
|
await this.deleteObjectIfExists(`${this.getDeviceObjectId(device.id)}.sources`, { recursive: true });
|
|
460
465
|
}
|
|
461
466
|
|
|
467
|
+
/**
|
|
468
|
+
* @param {Record<string, any>} device
|
|
469
|
+
*/
|
|
470
|
+
async ensureDeviceManagerHtmlState(device) {
|
|
471
|
+
await this.setObjectNotExistsAsync(`${this.getDeviceObjectId(device.id)}.${STATE_DEVICE_MANAGER_HTML}`, {
|
|
472
|
+
type: 'state',
|
|
473
|
+
common: {
|
|
474
|
+
name: 'Device Manager display',
|
|
475
|
+
type: 'string',
|
|
476
|
+
role: 'text',
|
|
477
|
+
read: true,
|
|
478
|
+
write: false,
|
|
479
|
+
},
|
|
480
|
+
native: {},
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
462
484
|
/**
|
|
463
485
|
* @param {Record<string, any>} device
|
|
464
486
|
* @param {string} stateName
|
|
@@ -633,6 +655,12 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
633
655
|
async updateDeviceValues(device) {
|
|
634
656
|
if (!(await this.hasValidDeviceSourceIds(device))) {
|
|
635
657
|
this.log.warn(`Skipping device "${device.name}" because it has unusable source state IDs`);
|
|
658
|
+
await this.updateDeviceManagerHtml(device, {
|
|
659
|
+
absoluteHumidity: null,
|
|
660
|
+
relativeHumidity: null,
|
|
661
|
+
temperature: null,
|
|
662
|
+
dewPointTemperature: null,
|
|
663
|
+
});
|
|
636
664
|
return;
|
|
637
665
|
}
|
|
638
666
|
|
|
@@ -664,6 +692,23 @@ class Absolutehumidity extends utils.Adapter {
|
|
|
664
692
|
val: dewPointTemperature,
|
|
665
693
|
ack: true,
|
|
666
694
|
});
|
|
695
|
+
await this.updateDeviceManagerHtml(device, {
|
|
696
|
+
absoluteHumidity,
|
|
697
|
+
relativeHumidity,
|
|
698
|
+
temperature,
|
|
699
|
+
dewPointTemperature,
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* @param {Record<string, any>} device
|
|
705
|
+
* @param {{ absoluteHumidity: number | null; relativeHumidity: number | null; temperature: number | null; dewPointTemperature: number | null }} values
|
|
706
|
+
*/
|
|
707
|
+
async updateDeviceManagerHtml(device, values) {
|
|
708
|
+
await this.setStateChangedAsync(`${this.getDeviceObjectId(device.id)}.${STATE_DEVICE_MANAGER_HTML}`, {
|
|
709
|
+
val: createMeasurementDisplayHtml(values, this.deviceManagerLanguage),
|
|
710
|
+
ack: true,
|
|
711
|
+
});
|
|
667
712
|
}
|
|
668
713
|
|
|
669
714
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.absolutehumidity",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "build absolute humidity from actual temperature and relative humidity",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "BenAhrdt",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@iobroker/adapter-core": "^3.4.1",
|
|
30
|
-
"@iobroker/dm-utils": "^3.
|
|
30
|
+
"@iobroker/dm-utils": "^3.2.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@alcalzone/release-script": "^5.2.1",
|
package/admin/jsonTab.json5
DELETED