iobroker.fairland 0.2.1
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/LICENSE +23 -0
- package/README.md +219 -0
- package/THIRD_PARTY_NOTICES.md +37 -0
- package/admin/fairland.png +0 -0
- package/admin/i18n/de/translations.json +9 -0
- package/admin/i18n/en/translations.json +9 -0
- package/admin/i18n/es/translations.json +9 -0
- package/admin/i18n/fr/translations.json +9 -0
- package/admin/i18n/it/translations.json +9 -0
- package/admin/i18n/nl/translations.json +9 -0
- package/admin/i18n/pl/translations.json +9 -0
- package/admin/i18n/pt/translations.json +9 -0
- package/admin/i18n/ru/translations.json +9 -0
- package/admin/i18n/uk/translations.json +9 -0
- package/admin/i18n/zh-cn/translations.json +9 -0
- package/admin/jsonConfig.json +59 -0
- package/build/dpUtils.js +186 -0
- package/build/fairlandApi.js +190 -0
- package/build/main.js +757 -0
- package/build/mappings.js +427 -0
- package/build/types.js +2 -0
- package/io-package.json +228 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dude2k
|
|
4
|
+
Portions derived from ha-fairland:
|
|
5
|
+
Copyright (c) 2025 @siedi
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# ioBroker Fairland Adapter
|
|
2
|
+
|
|
3
|
+
Unofficial ioBroker adapter for Fairland pool heat pumps and pool pumps that
|
|
4
|
+
use the Fairland **iGarden** cloud API.
|
|
5
|
+
|
|
6
|
+
This adapter talks directly to the iGarden cloud. It does not use Tuya and it
|
|
7
|
+
does not support Fairland devices paired through the SmartPool app.
|
|
8
|
+
|
|
9
|
+
## Supported devices
|
|
10
|
+
|
|
11
|
+
- Fairland pool heat pumps on the iGarden platform
|
|
12
|
+
- Fairland Inverflow Plus pool pumps on the iGarden platform
|
|
13
|
+
- OEM-rebadged iGarden devices, for example Madimack pool pumps
|
|
14
|
+
|
|
15
|
+
The adapter currently knows the device categories `heatPump` and `waterPump`.
|
|
16
|
+
Unknown categories are logged and skipped.
|
|
17
|
+
|
|
18
|
+
This project is not affiliated with, endorsed by, or supported by Fairland,
|
|
19
|
+
Home Assistant, ioBroker, or the upstream ha-fairland project maintainers.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
This adapter is not published to npm or the official ioBroker repository yet.
|
|
24
|
+
Installation instructions will be added after the adapter is published.
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- Node.js 22 or newer
|
|
29
|
+
- ioBroker js-controller 6.0.11 or newer
|
|
30
|
+
- ioBroker Admin 7.8.23 or newer
|
|
31
|
+
|
|
32
|
+
For local development:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm run build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Additional development commands:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm run lint
|
|
42
|
+
npm run translate
|
|
43
|
+
npm run release
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
The instance configuration contains:
|
|
49
|
+
|
|
50
|
+
- `iGarden account e-mail`: account name used in the iGarden app
|
|
51
|
+
- `iGarden password`: account password
|
|
52
|
+
- `Scan interval`: polling interval in seconds, minimum 10 seconds
|
|
53
|
+
- `Courtyard ID`: optional. Leave empty to use the first courtyard returned by
|
|
54
|
+
the cloud. If the account has several courtyards, the adapter logs all IDs.
|
|
55
|
+
- `Create raw dpId states`: optional diagnostic states under
|
|
56
|
+
`devices.<device>.raw.dp_<id>`
|
|
57
|
+
|
|
58
|
+
The adapter automatically detects the correct regional API server:
|
|
59
|
+
|
|
60
|
+
- EU: `api-eu.fairlandiot.com`
|
|
61
|
+
- US: `api-us.fairlandiot.com`
|
|
62
|
+
- CN: `api-cn.fairlandiot.com`
|
|
63
|
+
- HK: `api-hk.fairlandiot.com`
|
|
64
|
+
|
|
65
|
+
## Important iGarden limitation
|
|
66
|
+
|
|
67
|
+
The iGarden cloud usually allows only one active session per account. If the
|
|
68
|
+
adapter is logged in, the iGarden mobile app may show the device as offline, and
|
|
69
|
+
the reverse can also happen.
|
|
70
|
+
|
|
71
|
+
Recommended workaround: create a second iGarden account, share the device to
|
|
72
|
+
that account in the iGarden app, and configure ioBroker with the second account.
|
|
73
|
+
|
|
74
|
+
## State structure
|
|
75
|
+
|
|
76
|
+
Devices are created below:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
fairland.0.devices.<deviceId>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Common states:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
info.name
|
|
86
|
+
info.category
|
|
87
|
+
info.version
|
|
88
|
+
power
|
|
89
|
+
mode
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Heat pump states include:
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
temperature.current
|
|
96
|
+
temperature.target
|
|
97
|
+
temperature.outlet
|
|
98
|
+
temperature.ambient
|
|
99
|
+
power.current
|
|
100
|
+
presetMode
|
|
101
|
+
hvac.action
|
|
102
|
+
config.*
|
|
103
|
+
diagnostic.*
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Water pump states include:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
pump.speedSetpoint
|
|
110
|
+
pump.runningRate
|
|
111
|
+
pump.backwashDuration
|
|
112
|
+
pump.backwashCountdown
|
|
113
|
+
power.current
|
|
114
|
+
energy.consumption
|
|
115
|
+
mode
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Writable states are mapped back to the correct Fairland `dpId`. The adapter keeps
|
|
119
|
+
optimistic values for a short period after writes because the iGarden cloud can
|
|
120
|
+
take a few seconds to report newly written values back.
|
|
121
|
+
|
|
122
|
+
## Development notes
|
|
123
|
+
|
|
124
|
+
The implementation is a TypeScript port of the Home Assistant Fairland/iGarden
|
|
125
|
+
integration logic:
|
|
126
|
+
|
|
127
|
+
- cloud login and automatic regional server detection
|
|
128
|
+
- courtyard and device discovery
|
|
129
|
+
- category-specific `dpId` mappings
|
|
130
|
+
- scale and unit parsing from `dpProperty`
|
|
131
|
+
- optimistic write handling
|
|
132
|
+
|
|
133
|
+
Build:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npm run build
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The compiled adapter entry point is `build/main.js`.
|
|
140
|
+
|
|
141
|
+
## Attribution
|
|
142
|
+
|
|
143
|
+
This adapter is derived from the MIT-licensed Home Assistant Fairland
|
|
144
|
+
integration by @siedi:
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
https://github.com/siedi/ha-fairland
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The original project license notice is preserved in `LICENSE`, and additional
|
|
151
|
+
third-party notices are listed in `THIRD_PARTY_NOTICES.md`.
|
|
152
|
+
|
|
153
|
+
## Changelog
|
|
154
|
+
|
|
155
|
+
### 0.2.1
|
|
156
|
+
|
|
157
|
+
- Skip the npm deploy job until npm publishing is explicitly enabled for the repository.
|
|
158
|
+
|
|
159
|
+
### 0.2.0
|
|
160
|
+
|
|
161
|
+
- Added Dependabot update configuration and Dependabot auto-merge workflow.
|
|
162
|
+
- Added Node.js 22 TypeScript base configuration.
|
|
163
|
+
- Raised the minimum ioBroker Admin requirement to 7.8.23.
|
|
164
|
+
|
|
165
|
+
### 0.1.8
|
|
166
|
+
|
|
167
|
+
- Updated TypeScript to 6.0.3.
|
|
168
|
+
- Adjusted the TypeScript configuration for TypeScript 6.
|
|
169
|
+
- Added `CHANGELOG_OLD.md` for older changelog entries.
|
|
170
|
+
|
|
171
|
+
### 0.1.7
|
|
172
|
+
|
|
173
|
+
- Aligned Node.js type definitions with the supported Node.js 22 runtime.
|
|
174
|
+
|
|
175
|
+
### 0.1.6
|
|
176
|
+
|
|
177
|
+
- Completed admin UI i18n files for all standard ioBroker languages.
|
|
178
|
+
|
|
179
|
+
### 0.1.5
|
|
180
|
+
|
|
181
|
+
- Added the standard GitHub Actions test and release workflow.
|
|
182
|
+
- Added ioBroker development tooling for linting, translations, and releases.
|
|
183
|
+
- Replaced plain timers with ioBroker adapter timers or native abort timeouts.
|
|
184
|
+
- Removed direct GitHub installation instructions for repository checks.
|
|
185
|
+
|
|
186
|
+
### 0.1.4
|
|
187
|
+
|
|
188
|
+
- Added an adapter icon.
|
|
189
|
+
- Completed `io-package.json` translations for repository checks.
|
|
190
|
+
|
|
191
|
+
### 0.1.3
|
|
192
|
+
|
|
193
|
+
- Raised the minimum Node.js version to 22.
|
|
194
|
+
- Added `@iobroker/testing` as a development dependency.
|
|
195
|
+
- Updated package keywords for ioBroker repository checks.
|
|
196
|
+
|
|
197
|
+
### 0.1.2
|
|
198
|
+
|
|
199
|
+
- Fixed `diagnostic.powerDisplayStatus` state type for boolean Fairland API values.
|
|
200
|
+
|
|
201
|
+
### 0.1.1
|
|
202
|
+
|
|
203
|
+
- Fixed ioBroker package schema for GitHub installation.
|
|
204
|
+
- Added upstream license attribution and third-party notices.
|
|
205
|
+
|
|
206
|
+
### 0.1.0
|
|
207
|
+
|
|
208
|
+
- Initial ioBroker port of the Fairland iGarden integration.
|
|
209
|
+
|
|
210
|
+
Older changelog entries may be moved to [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT.
|
|
215
|
+
|
|
216
|
+
Copyright (c) 2026 dude2k.
|
|
217
|
+
Portions derived from ha-fairland: Copyright (c) 2025 @siedi.
|
|
218
|
+
|
|
219
|
+
See `LICENSE` for details.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
## ha-fairland
|
|
4
|
+
|
|
5
|
+
This ioBroker adapter is derived from the MIT-licensed Home Assistant Fairland
|
|
6
|
+
integration:
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
Project: ha-fairland
|
|
10
|
+
Repository: https://github.com/siedi/ha-fairland
|
|
11
|
+
Copyright (c) 2025 @siedi
|
|
12
|
+
License: MIT
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The upstream project provides Fairland/iGarden cloud API behavior, regional
|
|
16
|
+
server detection, device category handling, data point mappings, scale/unit
|
|
17
|
+
handling, and optimistic write behavior that were ported and adapted for
|
|
18
|
+
ioBroker.
|
|
19
|
+
|
|
20
|
+
## Runtime dependencies
|
|
21
|
+
|
|
22
|
+
Direct runtime dependency:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
@iobroker/adapter-core
|
|
26
|
+
License: MIT
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Development dependencies:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
typescript
|
|
33
|
+
License: Apache-2.0
|
|
34
|
+
|
|
35
|
+
@types/node
|
|
36
|
+
License: MIT
|
|
37
|
+
```
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"iGarden account e-mail": "iGarden Account-E-Mail",
|
|
3
|
+
"iGarden password": "iGarden Passwort",
|
|
4
|
+
"Scan interval in seconds": "Abfrageintervall in Sekunden",
|
|
5
|
+
"Courtyard ID (optional)": "Courtyard-ID (optional)",
|
|
6
|
+
"Create raw dpId states": "Raw-dpId-States erstellen",
|
|
7
|
+
"Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.": "Leer lassen, um den ersten von der iGarden-Cloud gelieferten Hof zu verwenden. Bei mehreren Hoefen die gewuenschte ID aus dem Adapter-Log eintragen.",
|
|
8
|
+
"Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics.": "Erstellt Raw-States unter devices.<device>.raw.dp_<id>. Schreibbare Raw-States folgen dem Cloud-dpMode und sind fuer Diagnosen hilfreich."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"iGarden account e-mail": "iGarden account e-mail",
|
|
3
|
+
"iGarden password": "iGarden password",
|
|
4
|
+
"Scan interval in seconds": "Scan interval in seconds",
|
|
5
|
+
"Courtyard ID (optional)": "Courtyard ID (optional)",
|
|
6
|
+
"Create raw dpId states": "Create raw dpId states",
|
|
7
|
+
"Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.": "Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.",
|
|
8
|
+
"Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics.": "Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"iGarden account e-mail": "Correo electronico de la cuenta iGarden",
|
|
3
|
+
"iGarden password": "Contrasena de iGarden",
|
|
4
|
+
"Scan interval in seconds": "Intervalo de consulta en segundos",
|
|
5
|
+
"Courtyard ID (optional)": "ID del patio (opcional)",
|
|
6
|
+
"Create raw dpId states": "Crear estados dpId sin procesar",
|
|
7
|
+
"Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.": "Dejelo vacio para usar el primer patio devuelto por la nube iGarden. Si tiene varios patios, introduzca el ID deseado desde el registro del adaptador.",
|
|
8
|
+
"Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics.": "Crea estados sin procesar bajo devices.<device>.raw.dp_<id>. Los estados sin procesar escribibles siguen el dpMode de la nube y son utiles para diagnosticos."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"iGarden account e-mail": "E-mail du compte iGarden",
|
|
3
|
+
"iGarden password": "Mot de passe iGarden",
|
|
4
|
+
"Scan interval in seconds": "Intervalle d'interrogation en secondes",
|
|
5
|
+
"Courtyard ID (optional)": "ID de cour (facultatif)",
|
|
6
|
+
"Create raw dpId states": "Creer les etats dpId bruts",
|
|
7
|
+
"Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.": "Laissez vide pour utiliser la premiere cour renvoyee par le cloud iGarden. Si vous avez plusieurs cours, indiquez l'ID souhaite depuis le journal de l'adaptateur.",
|
|
8
|
+
"Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics.": "Cree des etats bruts sous devices.<device>.raw.dp_<id>. Les etats bruts modifiables suivent le dpMode du cloud et sont utiles pour le diagnostic."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"iGarden account e-mail": "E-mail dell'account iGarden",
|
|
3
|
+
"iGarden password": "Password iGarden",
|
|
4
|
+
"Scan interval in seconds": "Intervallo di scansione in secondi",
|
|
5
|
+
"Courtyard ID (optional)": "ID cortile (opzionale)",
|
|
6
|
+
"Create raw dpId states": "Crea stati dpId grezzi",
|
|
7
|
+
"Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.": "Lasciare vuoto per usare il primo cortile restituito dal cloud iGarden. Se ci sono piu cortili, impostare l'ID desiderato dal log dell'adapter.",
|
|
8
|
+
"Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics.": "Crea stati grezzi sotto devices.<device>.raw.dp_<id>. Gli stati grezzi scrivibili seguono il dpMode del cloud e sono utili per la diagnostica."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"iGarden account e-mail": "E-mail van iGarden-account",
|
|
3
|
+
"iGarden password": "iGarden-wachtwoord",
|
|
4
|
+
"Scan interval in seconds": "Scaninterval in seconden",
|
|
5
|
+
"Courtyard ID (optional)": "Binnenplaats-ID (optioneel)",
|
|
6
|
+
"Create raw dpId states": "Ruwe dpId-statussen maken",
|
|
7
|
+
"Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.": "Laat leeg om de eerste binnenplaats te gebruiken die door de iGarden-cloud wordt teruggegeven. Als u meerdere binnenplaatsen hebt, stelt u de gewenste ID uit het adapterlog in.",
|
|
8
|
+
"Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics.": "Maakt ruwe statussen onder devices.<device>.raw.dp_<id>. Schrijfbare ruwe statussen volgen de cloud-dpMode en zijn nuttig voor diagnose."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"iGarden account e-mail": "E-mail konta iGarden",
|
|
3
|
+
"iGarden password": "Haslo iGarden",
|
|
4
|
+
"Scan interval in seconds": "Interwal odpytywania w sekundach",
|
|
5
|
+
"Courtyard ID (optional)": "ID dziedzinca (opcjonalnie)",
|
|
6
|
+
"Create raw dpId states": "Utworz surowe stany dpId",
|
|
7
|
+
"Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.": "Pozostaw puste, aby uzyc pierwszego dziedzinca zwroconego przez chmure iGarden. Jesli masz kilka dziedzincow, ustaw wybrane ID z logu adaptera.",
|
|
8
|
+
"Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics.": "Tworzy surowe stany pod devices.<device>.raw.dp_<id>. Zapisywalne surowe stany sa zgodne z cloud dpMode i sa przydatne do diagnostyki."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"iGarden account e-mail": "E-mail da conta iGarden",
|
|
3
|
+
"iGarden password": "Senha do iGarden",
|
|
4
|
+
"Scan interval in seconds": "Intervalo de consulta em segundos",
|
|
5
|
+
"Courtyard ID (optional)": "ID do patio (opcional)",
|
|
6
|
+
"Create raw dpId states": "Criar estados dpId brutos",
|
|
7
|
+
"Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.": "Deixe vazio para usar o primeiro patio retornado pela nuvem iGarden. Se houver varios patios, defina o ID desejado a partir do log do adaptador.",
|
|
8
|
+
"Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics.": "Cria estados brutos em devices.<device>.raw.dp_<id>. Estados brutos gravaveis seguem o dpMode da nuvem e sao uteis para diagnosticos."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"iGarden account e-mail": "E-mail uchetnoy zapisi iGarden",
|
|
3
|
+
"iGarden password": "Parol iGarden",
|
|
4
|
+
"Scan interval in seconds": "Interval oprosa v sekundah",
|
|
5
|
+
"Courtyard ID (optional)": "ID dvora (neobyazatelno)",
|
|
6
|
+
"Create raw dpId states": "Sozdavat syrye sostoyaniya dpId",
|
|
7
|
+
"Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.": "Ostavte pustym, chtoby ispolzovat pervyy dvor, vozvrashchennyy oblakom iGarden. Esli dvorov neskolko, ukazhite nuzhnyy ID iz zhurnala adaptera.",
|
|
8
|
+
"Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics.": "Sozdaet syrye sostoyaniya v devices.<device>.raw.dp_<id>. Dostupnye dlya zapisi syrye sostoyaniya sleduyut oblachnomu dpMode i polezny dlya diagnostiki."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"iGarden account e-mail": "E-mail oblikovoho zapysu iGarden",
|
|
3
|
+
"iGarden password": "Parol iGarden",
|
|
4
|
+
"Scan interval in seconds": "Interval opytuvannia v sekundakh",
|
|
5
|
+
"Courtyard ID (optional)": "ID dvoru (neoboviazkovo)",
|
|
6
|
+
"Create raw dpId states": "Stvoriuvaty syri stany dpId",
|
|
7
|
+
"Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.": "Zalyshte porozhnim, shchob vykorystaty pershyi dvir, povernanyi khmaroiu iGarden. Yakshcho dvoriv kilka, vstanovit potribnyi ID z zhurnalu adaptera.",
|
|
8
|
+
"Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics.": "Stvoriuie syri stany u devices.<device>.raw.dp_<id>. Zapysuvani syri stany vidpovidaiut khmarnomu dpMode i korysni dlia diahnostyky."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"iGarden account e-mail": "iGarden 账户电子邮件",
|
|
3
|
+
"iGarden password": "iGarden 密码",
|
|
4
|
+
"Scan interval in seconds": "扫描间隔(秒)",
|
|
5
|
+
"Courtyard ID (optional)": "庭院 ID(可选)",
|
|
6
|
+
"Create raw dpId states": "创建原始 dpId 状态",
|
|
7
|
+
"Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.": "留空则使用 iGarden 云返回的第一个庭院。如果有多个庭院,请从适配器日志中设置所需的 ID。",
|
|
8
|
+
"Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics.": "在 devices.<device>.raw.dp_<id> 下创建原始状态。可写原始状态遵循云端 dpMode,可用于诊断。"
|
|
9
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "panel",
|
|
3
|
+
"i18n": true,
|
|
4
|
+
"items": {
|
|
5
|
+
"accountName": {
|
|
6
|
+
"type": "text",
|
|
7
|
+
"label": "iGarden account e-mail",
|
|
8
|
+
"xs": 12,
|
|
9
|
+
"sm": 6,
|
|
10
|
+
"md": 6,
|
|
11
|
+
"lg": 6,
|
|
12
|
+
"xl": 6,
|
|
13
|
+
"newLine": true
|
|
14
|
+
},
|
|
15
|
+
"password": {
|
|
16
|
+
"type": "password",
|
|
17
|
+
"label": "iGarden password",
|
|
18
|
+
"xs": 12,
|
|
19
|
+
"sm": 6,
|
|
20
|
+
"md": 6,
|
|
21
|
+
"lg": 6,
|
|
22
|
+
"xl": 6
|
|
23
|
+
},
|
|
24
|
+
"scanInterval": {
|
|
25
|
+
"type": "number",
|
|
26
|
+
"label": "Scan interval in seconds",
|
|
27
|
+
"min": 10,
|
|
28
|
+
"max": 3600,
|
|
29
|
+
"xs": 12,
|
|
30
|
+
"sm": 6,
|
|
31
|
+
"md": 6,
|
|
32
|
+
"lg": 6,
|
|
33
|
+
"xl": 6,
|
|
34
|
+
"default": 30,
|
|
35
|
+
"newLine": true
|
|
36
|
+
},
|
|
37
|
+
"courtyardId": {
|
|
38
|
+
"type": "text",
|
|
39
|
+
"label": "Courtyard ID (optional)",
|
|
40
|
+
"help": "Leave empty to use the first courtyard returned by the iGarden cloud. If you have several courtyards, set the desired ID from the adapter log.",
|
|
41
|
+
"xs": 12,
|
|
42
|
+
"sm": 6,
|
|
43
|
+
"md": 6,
|
|
44
|
+
"lg": 6,
|
|
45
|
+
"xl": 6
|
|
46
|
+
},
|
|
47
|
+
"createRawStates": {
|
|
48
|
+
"type": "checkbox",
|
|
49
|
+
"label": "Create raw dpId states",
|
|
50
|
+
"help": "Creates raw states under devices.<device>.raw.dp_<id>. Writable raw states follow the cloud dpMode and are useful for diagnostics.",
|
|
51
|
+
"xs": 12,
|
|
52
|
+
"sm": 6,
|
|
53
|
+
"md": 6,
|
|
54
|
+
"lg": 6,
|
|
55
|
+
"xl": 6,
|
|
56
|
+
"newLine": true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
package/build/dpUtils.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseDpProperty = parseDpProperty;
|
|
4
|
+
exports.getDpScale = getDpScale;
|
|
5
|
+
exports.scaleRead = scaleRead;
|
|
6
|
+
exports.scaleWrite = scaleWrite;
|
|
7
|
+
exports.applyDpProperty = applyDpProperty;
|
|
8
|
+
exports.parseEnumOptions = parseEnumOptions;
|
|
9
|
+
exports.toStatesObject = toStatesObject;
|
|
10
|
+
exports.invertEnum = invertEnum;
|
|
11
|
+
exports.sanitizeObjectId = sanitizeObjectId;
|
|
12
|
+
exports.toStateValue = toStateValue;
|
|
13
|
+
exports.inferStateType = inferStateType;
|
|
14
|
+
exports.coerceStateValue = coerceStateValue;
|
|
15
|
+
exports.valuesMatch = valuesMatch;
|
|
16
|
+
const TIME_UNIT_MAP = {
|
|
17
|
+
s: 's',
|
|
18
|
+
min: 'min',
|
|
19
|
+
};
|
|
20
|
+
function parseDpProperty(dp) {
|
|
21
|
+
if (typeof dp.dpProperty !== 'string' || dp.dpProperty.trim() === '') {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(dp.dpProperty);
|
|
26
|
+
return isRecord(parsed) ? parsed : {};
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function getDpScale(dp, fallback = 0) {
|
|
33
|
+
const prop = parseDpProperty(dp);
|
|
34
|
+
const scale = prop.scale;
|
|
35
|
+
if (typeof scale === 'number' && Number.isFinite(scale)) {
|
|
36
|
+
return scale;
|
|
37
|
+
}
|
|
38
|
+
if (typeof scale === 'string' && scale.trim() !== '') {
|
|
39
|
+
const parsed = Number(scale);
|
|
40
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
41
|
+
}
|
|
42
|
+
return fallback;
|
|
43
|
+
}
|
|
44
|
+
function scaleRead(value, scale) {
|
|
45
|
+
if (value === null || value === undefined) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
if (scale > 0 && typeof value === 'number') {
|
|
49
|
+
return value / 10 ** scale;
|
|
50
|
+
}
|
|
51
|
+
return toStateValue(value);
|
|
52
|
+
}
|
|
53
|
+
function scaleWrite(value, scale) {
|
|
54
|
+
if (value === null || value === undefined) {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
if (scale > 0 && typeof value === 'number') {
|
|
58
|
+
return Math.round(value * 10 ** scale);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
function applyDpProperty(definition, dp) {
|
|
63
|
+
const prop = parseDpProperty(dp);
|
|
64
|
+
const next = { ...definition };
|
|
65
|
+
if (definition.useDpScale) {
|
|
66
|
+
next.scale = getDpScale(dp, definition.scale ?? 0);
|
|
67
|
+
}
|
|
68
|
+
if (definition.useDpRange) {
|
|
69
|
+
if (prop.min !== undefined) {
|
|
70
|
+
const min = Number(prop.min);
|
|
71
|
+
if (Number.isFinite(min)) {
|
|
72
|
+
next.min = min;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (prop.max !== undefined) {
|
|
76
|
+
const max = Number(prop.max);
|
|
77
|
+
if (Number.isFinite(max)) {
|
|
78
|
+
next.max = max;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (prop.step !== undefined) {
|
|
82
|
+
const step = Number(prop.step);
|
|
83
|
+
if (Number.isFinite(step) && step > 0) {
|
|
84
|
+
next.step = step;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (definition.useDpTimeUnit && typeof prop.unit === 'string' && TIME_UNIT_MAP[prop.unit]) {
|
|
89
|
+
next.unit = TIME_UNIT_MAP[prop.unit];
|
|
90
|
+
}
|
|
91
|
+
return next;
|
|
92
|
+
}
|
|
93
|
+
function parseEnumOptions(dp, fallback, labelToOption) {
|
|
94
|
+
const prop = parseDpProperty(dp);
|
|
95
|
+
const options = {};
|
|
96
|
+
for (const [raw, label] of Object.entries(prop)) {
|
|
97
|
+
const value = Number(raw);
|
|
98
|
+
if (!Number.isInteger(value)) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const text = String(label);
|
|
102
|
+
options[value] = labelToOption[text] ?? slugify(text);
|
|
103
|
+
}
|
|
104
|
+
return Object.keys(options).length > 0 ? options : { ...fallback };
|
|
105
|
+
}
|
|
106
|
+
function toStatesObject(options) {
|
|
107
|
+
return Object.fromEntries(Object.values(options).map(option => [option, humanize(option)]));
|
|
108
|
+
}
|
|
109
|
+
function invertEnum(options) {
|
|
110
|
+
return Object.fromEntries(Object.entries(options).map(([raw, option]) => [option, Number(raw)]));
|
|
111
|
+
}
|
|
112
|
+
function sanitizeObjectId(value) {
|
|
113
|
+
const sanitized = String(value)
|
|
114
|
+
.trim()
|
|
115
|
+
.replace(/[.\s]+/g, '_')
|
|
116
|
+
.replace(/[^A-Za-z0-9_-]/g, '_')
|
|
117
|
+
.replace(/_+/g, '_')
|
|
118
|
+
.replace(/^_+|_+$/g, '');
|
|
119
|
+
return sanitized || 'unknown';
|
|
120
|
+
}
|
|
121
|
+
function toStateValue(value) {
|
|
122
|
+
if (value === null || value === undefined) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
return JSON.stringify(value);
|
|
129
|
+
}
|
|
130
|
+
function inferStateType(value) {
|
|
131
|
+
if (typeof value === 'boolean') {
|
|
132
|
+
return 'boolean';
|
|
133
|
+
}
|
|
134
|
+
if (typeof value === 'number') {
|
|
135
|
+
return 'number';
|
|
136
|
+
}
|
|
137
|
+
if (typeof value === 'string') {
|
|
138
|
+
return 'string';
|
|
139
|
+
}
|
|
140
|
+
return 'mixed';
|
|
141
|
+
}
|
|
142
|
+
function coerceStateValue(value, type) {
|
|
143
|
+
if (value === undefined || value === null || type === 'mixed' || type === undefined) {
|
|
144
|
+
return value ?? null;
|
|
145
|
+
}
|
|
146
|
+
if (type === 'boolean') {
|
|
147
|
+
if (typeof value === 'boolean') {
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
if (typeof value === 'number') {
|
|
151
|
+
return value !== 0;
|
|
152
|
+
}
|
|
153
|
+
return ['true', '1', 'on', 'yes'].includes(value.toLowerCase());
|
|
154
|
+
}
|
|
155
|
+
if (type === 'number') {
|
|
156
|
+
const parsed = Number(value);
|
|
157
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
158
|
+
}
|
|
159
|
+
return String(value);
|
|
160
|
+
}
|
|
161
|
+
function valuesMatch(a, b) {
|
|
162
|
+
const aNumber = Number(a);
|
|
163
|
+
const bNumber = Number(b);
|
|
164
|
+
if (Number.isFinite(aNumber) && Number.isFinite(bNumber)) {
|
|
165
|
+
return aNumber === bNumber;
|
|
166
|
+
}
|
|
167
|
+
return String(a) === String(b);
|
|
168
|
+
}
|
|
169
|
+
function slugify(value) {
|
|
170
|
+
const slug = value
|
|
171
|
+
.trim()
|
|
172
|
+
.toLowerCase()
|
|
173
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
174
|
+
.replace(/^_+|_+$/g, '');
|
|
175
|
+
return slug || 'unknown';
|
|
176
|
+
}
|
|
177
|
+
function humanize(value) {
|
|
178
|
+
return value
|
|
179
|
+
.split('_')
|
|
180
|
+
.filter(Boolean)
|
|
181
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
182
|
+
.join(' ');
|
|
183
|
+
}
|
|
184
|
+
function isRecord(value) {
|
|
185
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
186
|
+
}
|