iobroker.absolutehumidity 0.0.1 → 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 +4 -93
- 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 +133 -107
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-

|
|
2
2
|
# ioBroker.absolutehumidity
|
|
3
3
|
|
|
4
4
|
[](https://www.npmjs.com/package/iobroker.absolutehumidity)
|
|
@@ -14,104 +14,15 @@
|
|
|
14
14
|
|
|
15
15
|
build absolute humidity from actual temperature and relative humidity
|
|
16
16
|
|
|
17
|
-
## Developer manual
|
|
18
|
-
This section is intended for the developer. It can be deleted later.
|
|
19
|
-
|
|
20
|
-
### DISCLAIMER
|
|
21
|
-
|
|
22
|
-
Please make sure that you consider copyrights and trademarks when you use names or logos of a company and add a disclaimer to your README.
|
|
23
|
-
You can check other adapters for examples or ask in the developer community. Using a name or logo of a company without permission may cause legal problems for you.
|
|
24
|
-
|
|
25
|
-
### Getting started
|
|
26
|
-
|
|
27
|
-
You are almost done, only a few steps left:
|
|
28
|
-
1. Create a new repository on GitHub with the name `ioBroker.absolutehumidity`
|
|
29
|
-
1. Initialize the current folder as a new git repository:
|
|
30
|
-
```bash
|
|
31
|
-
git init -b main
|
|
32
|
-
git add .
|
|
33
|
-
git commit -m "Initial commit"
|
|
34
|
-
```
|
|
35
|
-
1. Link your local repository with the one on GitHub:
|
|
36
|
-
```bash
|
|
37
|
-
git remote add origin https://github.com/BenAhrdt/ioBroker.absolutehumidity
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
1. Push all files to the GitHub repo:
|
|
41
|
-
```bash
|
|
42
|
-
git push origin main
|
|
43
|
-
```
|
|
44
|
-
1. Add a new secret under https://github.com/BenAhrdt/ioBroker.absolutehumidity/settings/secrets. It must be named `AUTO_MERGE_TOKEN` and contain a personal access token with push access to the repository, e.g. yours. You can create a new token under https://github.com/settings/tokens.
|
|
45
|
-
|
|
46
|
-
1. Head over to [main.js](main.js) and start programming!
|
|
47
|
-
|
|
48
|
-
### Best Practices
|
|
49
|
-
We've collected some [best practices](https://github.com/ioBroker/ioBroker.repositories#development-and-coding-best-practices) regarding ioBroker development and coding in general. If you're new to ioBroker or Node.js, you should
|
|
50
|
-
check them out. If you're already experienced, you should also take a look at them - you might learn something new :)
|
|
51
|
-
|
|
52
|
-
### State Roles
|
|
53
|
-
When creating state objects, it is important to use the correct role for the state. The role defines how the state should be interpreted by visualizations and other adapters. For a list of available roles and their meanings, please refer to the [state roles documentation](https://www.iobroker.net/#en/documentation/dev/stateroles.md).
|
|
54
|
-
|
|
55
|
-
**Important:** Do not invent your own custom role names. If you need a role that is not part of the official list, please contact the ioBroker developer community for guidance and discussion about adding new roles.
|
|
56
|
-
|
|
57
|
-
### Scripts in `package.json`
|
|
58
|
-
Several npm scripts are predefined for your convenience. You can run them using `npm run <scriptname>`
|
|
59
|
-
| Script name | Description |
|
|
60
|
-
|-------------|-------------|
|
|
61
|
-
| `test:js` | Executes the tests you defined in `*.test.js` files. |
|
|
62
|
-
| `test:package` | Ensures your `package.json` and `io-package.json` are valid. |
|
|
63
|
-
| `test:integration` | Tests the adapter startup with an actual instance of ioBroker. |
|
|
64
|
-
| `test` | Performs a minimal test run on package files and your tests. |
|
|
65
|
-
| `check` | Performs a type-check on your code (without compiling anything). |
|
|
66
|
-
| `lint` | Runs `ESLint` to check your code for formatting errors and potential bugs. |
|
|
67
|
-
| `translate` | Translates texts in your adapter to all required languages, see [`@iobroker/adapter-dev`](https://github.com/ioBroker/adapter-dev#manage-translations) for more details. |
|
|
68
|
-
| `release` | Creates a new release, see [`@alcalzone/release-script`](https://github.com/AlCalzone/release-script#usage) for more details. |
|
|
69
|
-
|
|
70
|
-
### Writing tests
|
|
71
|
-
When done right, testing code is invaluable, because it gives you the
|
|
72
|
-
confidence to change your code while knowing exactly if and when
|
|
73
|
-
something breaks. A good read on the topic of test-driven development
|
|
74
|
-
is https://hackernoon.com/introduction-to-test-driven-development-tdd-61a13bc92d92.
|
|
75
|
-
Although writing tests before the code might seem strange at first, but it has very
|
|
76
|
-
clear upsides.
|
|
77
|
-
|
|
78
|
-
The template provides you with basic tests for the adapter startup and package files.
|
|
79
|
-
It is recommended that you add your own tests into the mix.
|
|
80
|
-
|
|
81
|
-
### Publishing the adapter
|
|
82
|
-
Using GitHub Actions, you can enable automatic releases on npm whenever you push a new git tag that matches the form
|
|
83
|
-
`v<major>.<minor>.<patch>`. We **strongly recommend** that you do. The necessary steps are described in `.github/workflows/test-and-release.yml`.
|
|
84
|
-
|
|
85
|
-
Since you installed the release script, you can create a new
|
|
86
|
-
release simply by calling:
|
|
87
|
-
```bash
|
|
88
|
-
npm run release
|
|
89
|
-
```
|
|
90
|
-
Additional command line options for the release script are explained in the
|
|
91
|
-
[release-script documentation](https://github.com/AlCalzone/release-script#command-line).
|
|
92
|
-
|
|
93
|
-
To get your adapter released in ioBroker, please refer to the documentation
|
|
94
|
-
of [ioBroker.repositories](https://github.com/ioBroker/ioBroker.repositories#requirements-for-adapter-to-get-added-to-the-latest-repository).
|
|
95
|
-
|
|
96
|
-
### Test the adapter manually with dev-server
|
|
97
|
-
Since you set up `dev-server`, you can use it to run, test and debug your adapter.
|
|
98
|
-
|
|
99
|
-
You may start `dev-server` by calling from your dev directory:
|
|
100
|
-
```bash
|
|
101
|
-
dev-server watch
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
The ioBroker.admin interface will then be available at http://localhost:undefined/
|
|
105
|
-
|
|
106
|
-
Please refer to the [`dev-server` documentation](https://github.com/ioBroker/dev-server#command-line) for more details.
|
|
107
|
-
|
|
108
17
|
## Changelog
|
|
109
18
|
<!--
|
|
110
19
|
Placeholder for the next version (at the beginning of the line):
|
|
111
20
|
### **WORK IN PROGRESS**
|
|
112
21
|
-->
|
|
22
|
+
### 0.0.3 (2026-07-05)
|
|
23
|
+
* (BenAhrdt) Add new features and icons to deviceManager
|
|
113
24
|
|
|
114
|
-
###
|
|
25
|
+
### 0.0.2 (2026-07-05)
|
|
115
26
|
* (BenAhrdt) initial release
|
|
116
27
|
|
|
117
28
|
## License
|
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,109 +1,135 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
2
|
+
"common": {
|
|
3
|
+
"name": "absolutehumidity",
|
|
4
|
+
"version": "0.0.3",
|
|
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
|
+
},
|
|
19
|
+
"0.0.2": {
|
|
20
|
+
"en": "initial release",
|
|
21
|
+
"de": "Erstveröffentlichung",
|
|
22
|
+
"ru": "первоначальный релиз",
|
|
23
|
+
"pt": "lançamento inicial",
|
|
24
|
+
"nl": "eerste release",
|
|
25
|
+
"fr": "version initiale",
|
|
26
|
+
"it": "rilascio iniziale",
|
|
27
|
+
"es": "lanzamiento inicial",
|
|
28
|
+
"pl": "wydanie początkowe",
|
|
29
|
+
"uk": "початковий випуск",
|
|
30
|
+
"zh-cn": "初始版本"
|
|
31
|
+
},
|
|
32
|
+
"0.0.1": {
|
|
33
|
+
"en": "initial release",
|
|
34
|
+
"de": "Erstveröffentlichung",
|
|
35
|
+
"ru": "Начальная версия",
|
|
36
|
+
"pt": "lançamento inicial",
|
|
37
|
+
"nl": "Eerste uitgave",
|
|
38
|
+
"fr": "Première version",
|
|
39
|
+
"it": "Versione iniziale",
|
|
40
|
+
"es": "Versión inicial",
|
|
41
|
+
"pl": "Pierwsze wydanie",
|
|
42
|
+
"uk": "Початкова версія",
|
|
43
|
+
"zh-cn": "首次出版"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"titleLang": {
|
|
47
|
+
"en": "Absolute Humidity",
|
|
48
|
+
"de": "Absolute Luftfeuchtigkeit",
|
|
49
|
+
"ru": "Абсолютная влажность",
|
|
50
|
+
"pt": "Umidade Absoluta",
|
|
51
|
+
"nl": "Absolute vochtigheid",
|
|
52
|
+
"fr": "Humidité absolue",
|
|
53
|
+
"it": "Umidità assoluta",
|
|
54
|
+
"es": "Humedad absoluta",
|
|
55
|
+
"pl": "Wilgotność bezwzględna",
|
|
56
|
+
"uk": "Абсолютна вологість",
|
|
57
|
+
"zh-cn": "绝对湿度"
|
|
58
|
+
},
|
|
59
|
+
"desc": {
|
|
60
|
+
"en": "build absolute humidity from actual temperature and relative humidity",
|
|
61
|
+
"de": "Ermitteln Sie die absolute Luftfeuchtigkeit aus der tatsächlichen Temperatur und der relativen Luftfeuchtigkeit",
|
|
62
|
+
"ru": "построить абсолютную влажность на основе фактической температуры и относительной влажности",
|
|
63
|
+
"pt": "construir umidade absoluta a partir da temperatura real e umidade relativa",
|
|
64
|
+
"nl": "bouw de absolute vochtigheid op uit de werkelijke temperatuur en de relatieve vochtigheid",
|
|
65
|
+
"fr": "construire l'humidité absolue à partir de la température réelle et de l'humidité relative",
|
|
66
|
+
"it": "costruire l'umidità assoluta dalla temperatura effettiva e dall'umidità relativa",
|
|
67
|
+
"es": "construir humedad absoluta a partir de la temperatura real y la humedad relativa",
|
|
68
|
+
"pl": "zbuduj wilgotność bezwzględną na podstawie rzeczywistej temperatury i wilgotności względnej",
|
|
69
|
+
"uk": "побудувати абсолютну вологість з фактичної температури та відносної вологості",
|
|
70
|
+
"zh-cn": "根据实际温度和相对湿度建立绝对湿度"
|
|
71
|
+
},
|
|
72
|
+
"authors": [
|
|
73
|
+
"BenAhrdt <github@ben-schmidt.net>"
|
|
74
|
+
],
|
|
75
|
+
"keywords": [
|
|
76
|
+
"absolute Humidity",
|
|
77
|
+
"dewpoint temperature"
|
|
78
|
+
],
|
|
79
|
+
"licenseInformation": {
|
|
80
|
+
"type": "free",
|
|
81
|
+
"license": "MIT"
|
|
82
|
+
},
|
|
83
|
+
"platform": "Javascript/Node.js",
|
|
84
|
+
"icon": "absolutehumidity.svg",
|
|
85
|
+
"enabled": true,
|
|
86
|
+
"extIcon": "https://raw.githubusercontent.com/BenAhrdt/ioBroker.absolutehumidity/main/admin/absolutehumidity.svg",
|
|
87
|
+
"readme": "https://github.com/BenAhrdt/ioBroker.absolutehumidity/blob/main/README.md",
|
|
88
|
+
"loglevel": "info",
|
|
89
|
+
"tier": 3,
|
|
90
|
+
"mode": "daemon",
|
|
91
|
+
"type": "weather",
|
|
92
|
+
"compact": true,
|
|
93
|
+
"connectionType": "local",
|
|
94
|
+
"dataSource": "push",
|
|
95
|
+
"supportedMessages": {
|
|
96
|
+
"deviceManager": true
|
|
97
|
+
},
|
|
98
|
+
"adminUI": {
|
|
99
|
+
"config": "json",
|
|
100
|
+
"tab": "json"
|
|
101
|
+
},
|
|
102
|
+
"adminTab": {
|
|
103
|
+
"singleton": true,
|
|
104
|
+
"name": {
|
|
105
|
+
"en": "Absolute Humidity",
|
|
106
|
+
"de": "Absolute Luftfeuchtigkeit",
|
|
107
|
+
"ru": "Абсолютная влажность",
|
|
108
|
+
"pt": "Umidade Absoluta",
|
|
109
|
+
"nl": "Absolute vochtigheid",
|
|
110
|
+
"fr": "Humidité absolue",
|
|
111
|
+
"it": "Umidità assoluta",
|
|
112
|
+
"es": "Humedad absoluta",
|
|
113
|
+
"pl": "Wilgotność bezwzględna",
|
|
114
|
+
"uk": "Абсолютна вологість",
|
|
115
|
+
"zh-cn": "绝对湿度"
|
|
116
|
+
},
|
|
117
|
+
"link": "TabConfig.json"
|
|
118
|
+
},
|
|
119
|
+
"dependencies": [
|
|
120
|
+
{
|
|
121
|
+
"js-controller": ">=6.0.11"
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
"globalDependencies": [
|
|
125
|
+
{
|
|
126
|
+
"admin": ">=7.0.23"
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
"native": {
|
|
131
|
+
"devices": []
|
|
132
|
+
},
|
|
133
|
+
"objects": [],
|
|
134
|
+
"instanceObjects": []
|
|
109
135
|
}
|
|
@@ -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;
|