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 CHANGED
@@ -1,12 +1,12 @@
1
1
  ![Logo](admin/absolutehumidity.svg)
2
2
  # ioBroker.absolutehumidity
3
3
 
4
- [![NPM version](https://img.shields.io/npm/v/ioBroker.absolutehumidity.svg)](https://www.npmjs.com/package/ioBroker.absolutehumidity)
5
- [![Downloads](https://img.shields.io/npm/dm/ioBroker.absolutehumidity.svg)](https://www.npmjs.com/package/ioBroker.absolutehumidity)
4
+ [![NPM version](https://img.shields.io/npm/v/iobroker.absolutehumidity.svg)](https://www.npmjs.com/package/iobroker.absolutehumidity)
5
+ [![Downloads](https://img.shields.io/npm/dm/iobroker.absolutehumidity.svg)](https://www.npmjs.com/package/iobroker.absolutehumidity)
6
6
  ![Number of Installations](https://ioBroker.live/badges/absolutehumidity-installed.svg)
7
7
  ![Current version in stable repository](https://ioBroker.live/badges/absolutehumidity-stable.svg)
8
8
 
9
- [![NPM](https://nodei.co/npm/ioBroker.absolutehumidity.png?downloads=true)](https://nodei.co/npm/ioBroker.absolutehumidity/)
9
+ [![NPM](https://nodei.co/npm/iobroker.absolutehumidity.png?downloads=true)](https://nodei.co/npm/iobroker.absolutehumidity/)
10
10
 
11
11
  **Tests:** ![Test and Release](https://github.com/BenAhrdt/ioBroker.absolutehumidity/workflows/Test%20and%20Release/badge.svg)
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
@@ -2,49 +2,15 @@
2
2
  "i18n": true,
3
3
  "type": "panel",
4
4
  "items": {
5
- "_configurationInfo": {
6
- "type": "staticText",
7
- "text": "Configuration is done via the Device Manager tab on the left side.",
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.0",
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
  {
@@ -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
- STATE_DEW_POINT_TEMPERATURE,
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
- status: sourceValidation.valid
149
- ? {
150
- connection: 'connected',
151
- }
146
+ ...(sourceValidation.valid
147
+ ? {}
152
148
  : {
153
- connection: 'disconnected',
154
- warning: translate('Invalid source state'),
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, sourceValidation),
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, sourceValidation) {
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
- temperature: createStateInfoItem(
212
- translate('Temperature'),
213
- device.createTemperatureState || !sourceValidation.temperatureStateValid
214
- ? `${adapter.getDeviceObjectId(device.id)}.${STATE_TEMPERATURE}`
215
- : device.temperatureStateId,
216
- '°C',
217
- !device.createTemperatureState && sourceValidation.temperatureStateValid,
218
- STATE_ICONS.temperature,
219
- ),
220
- relativeHumidity: createStateInfoItem(
221
- translate('Relative humidity'),
222
- device.createRelativeHumidityState || !sourceValidation.relativeHumidityStateValid
223
- ? `${adapter.getDeviceObjectId(device.id)}.${STATE_RELATIVE_HUMIDITY}`
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} label
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 createStateInfoItem(label, oid, unit, foreign, labelIcon) {
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 {string} svg
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 createSvgIcon(svg) {
301
- return `data:image/svg+xml;base64,${Buffer.from(svg.replace(/\s+/g, ' ').trim()).toString('base64')}`;
245
+ function createMeasurementDisplayHtml(values, language) {
246
+ const escapeHtml = value =>
247
+ String(value).replace(/[&<>"']/g, character => {
248
+ switch (character) {
249
+ case '&':
250
+ return '&amp;';
251
+ case '<':
252
+ return '&lt;';
253
+ case '>':
254
+ return '&gt;';
255
+ case '"':
256
+ return '&quot;';
257
+ default:
258
+ return '&#39;';
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,
@@ -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.0",
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.1.1"
30
+ "@iobroker/dm-utils": "^3.2.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@alcalzone/release-script": "^5.2.1",
@@ -1,16 +0,0 @@
1
- {
2
- "i18n": true,
3
- "type": "panel",
4
- "items": {
5
- "deviceManager": {
6
- "type": "deviceManager",
7
- "smallCards": false,
8
- "newLine": true,
9
- "xs": 12,
10
- "sm": 12,
11
- "md": 12,
12
- "lg": 12,
13
- "xl": 12
14
- }
15
- }
16
- }