iobroker.harvia-fenix 0.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 meistermopper <meister.mopper@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/meistermopper/ioBroker.harvia-fenix/main/admin/harvia.png" alt="Logo">
3
+ </p>
4
+ # ioBroker.harvia-fenix
5
+
6
+ [![NPM version](https://img.shields.io/npm/v/iobroker.harvia-fenix.svg)](https://www.npmjs.com/package/iobroker.harvia-fenix)
7
+ [![Downloads](https://img.shields.io/npm/dm/iobroker.harvia-fenix.svg)](https://www.npmjs.com/package/iobroker.harvia-fenix)
8
+ ![Number of Installations](https://iobroker.live/badges/harvia-fenix-installed.svg)
9
+ ![Current version in stable repository](https://iobroker.live/badges/harvia-fenix-stable.svg)
10
+
11
+ [![NPM](https://nodei.co/npm/iobroker.harvia-fenix.png?downloads=true)](https://nodei.co/npm/iobroker.harvia-fenix/)
12
+
13
+ **Tests:** ![Test and Release](https://github.com/meistermopper/ioBroker.harvia-fenix/workflows/Test%20and%20Release/badge.svg)
14
+
15
+ An ioBroker adapter to integrate and control your **Harvia Fenix** sauna control unit via the MyHarvia cloud infrastructure.
16
+
17
+ ---
18
+
19
+ ## ⚠️ CRITICAL SAFETY WARNING & DISCLAIMER / WICHTIGER SICHERHEITSHINWEIS
20
+
21
+ ### English
22
+ **Remote operation of a sauna heater is subject to strict safety regulations!** According to the European safety standard **EN 60335-2-53** in conjunction with **EN 60335-1**, fire protection measures are mandatory for remote control setups. The sauna cabin must be equipped with an approved door sensor or a safety switch-off system. This ensures that the heater cannot be started remotely or via a timer if a flammable object (e.g., a towel) has been left on or near the heater.
23
+
24
+ * **No Liability:** The developer of this adapter assumes absolutely no responsibility, warranty, or liability for any damages, fires, injuries, or legal issues resulting from the use or misconfiguration of this software. You operate this integration entirely at your own risk.
25
+
26
+ ### Deutsch
27
+ **Der Fernstart eines Saunaofens unterliegt strengen Sicherheitsbestimmungen!** Nach der europäischen Sicherheitsnorm **EN 60335-2-53** in Verbindung mit der **EN 60335-1** sind strenge Brandschutzvorgaben für den Fernzugriff vorgeschrieben. Die Saunakabine muss zwingend über einen funktionierenden Türsensor oder eine Sicherheitsabschaltung verfügen. Dadurch muss sichergestellt sein, dass der Ofen nicht aus der Ferne oder per Zeitschaltuhr startet, wenn sich brennbare Gegenstände (z. B. Handtücher) auf dem Ofen befinden.
28
+
29
+ * **Haftungsausschluss:** Der Entwickler dieses Adapters schließt jegliche Haftung, Gewährleistung oder Sicherheitsgarantien aus. Die Nutzung und Konfiguration dieser Software erfolgt vollständig auf eigene Gefahr und Verantwortung des Betreibers.
30
+
31
+ ---
32
+
33
+ ## Compatibility Note / Kompatibilität
34
+
35
+ * **Supported:** **Harvia Fenix** control units managed via the **MyHarvia 2** mobile application.
36
+ * **NOT Supported:** **Harvia Xenio** series (e.g., Xenio WiFi / CX001WIFI). The Xenio series relies on a legacy hardware ecosystem and uses the older *"MyHarvia for Xenio"* app, which is fundamentally incompatible with the API utilized by this adapter.
37
+
38
+ ---
39
+
40
+ ## Prerequisites / Voraussetzungen
41
+
42
+ To use this adapter, you need:
43
+ 1. A registered account within the official **MyHarvia 2** smartphone application.
44
+ 2. Your valid login credentials:
45
+ * **Email Address**
46
+ * **Password**
47
+
48
+ ---
49
+
50
+ ## Device Configuration & Multi-Device Support
51
+
52
+ ### Automatic Discovery
53
+ If you leave the **Device ID** field in the adapter settings empty, the adapter will automatically search for devices linked to your account upon startup. It will use the first device it finds as the active unit. The detected ID will be printed to the ioBroker log.
54
+
55
+ ### Manual Device ID
56
+ For most users with a single sauna, automatic discovery is sufficient. However, it is recommended to copy the detected ID from the log and paste it into the configuration to ensure a stable connection to the specific hardware.
57
+
58
+ *Note: Currently, the Device ID is not displayed anywhere within the MyHarvia 2 app interface.*
59
+
60
+ ### Multiple Saunas
61
+ If your MyHarvia account manages multiple control units (e.g., one at home and one in a vacation cottage):
62
+ 1. Create a separate instance of the adapter for each sauna (e.g., `harvia-fenix.0` and `harvia-fenix.1`).
63
+ 2. Manually enter the specific **Device ID** for each unit in its respective instance configuration.
64
+ This allows you to monitor and control both saunas independently with their own set of datapoints.
65
+
66
+ ---
67
+
68
+ ## Features & State Points / Datenpunkte
69
+
70
+ The adapter maps your sauna's cloud states into structured ioBroker datapoints under `harvia-fenix.0.*`.
71
+
72
+ ### Available Datapoints
73
+ | Datapoint | Type | Role | Access | Description |
74
+ |---|---|---|---|---|
75
+ | `online` | boolean | `indicator.reachable` | Read-only | Connection state of the control unit to the cloud. |
76
+ | `doorSafety` | boolean | `indicator.safety` | Read-only | Safety loop status (e.g., `true` if the door is secure / safe to run). |
77
+ | `errorMsg` | string | `text` | Read-only | Current error messages or status text from the heater. |
78
+ | `heatOn` | boolean | `switch.power` | Read/Write | Main toggle to switch the sauna heater ON (`true`) or OFF (`false`). |
79
+ | `heaterPower` | string / number | `value.power` | Read-only | *Note:* This object is provisioned by the MyHarvia API structure but is currently delivered as `0 W` (unpopulated). It appears to be reserved for future hardware or app updates. |
80
+ | `lightOn` | boolean | `switch.light` | Read/Write | Toggle to switch the integrated sauna lighting ON or OFF. |
81
+ | `panelTemp` | number | `value.temperature` | Read-only | The temperature reading measured at the physical control panel unit. |
82
+ | `remoteControl` | boolean | `indicator.state` | Read-only | Indicates if remote control authorization is currently active on the device. |
83
+ | `targetTemp` | number | `level.temperature` | Read/Write | Target temperature setpoint for the sauna cabin (e.g., `90 °C`). |
84
+ | `temp` | number | `value.temperature` | Read-only | The current ambient temperature inside the sauna cabin (e.g., `17 °C`). |
85
+ | `totalBathingHours` | number | `value.number` | Read-only | Total historical cumulative hours the sauna has been actively used (`h`). |
86
+ | `totalOperatingHours`| number | `value.number` | Read-only | Total system operational running hours (`h`). |
87
+ | `totalSessions` | number | `value` | Read-only | Counter for the total number of individual sauna heating sessions executed. |
88
+
89
+ ---
90
+
91
+ ## Changelog
92
+ ### **WORK IN PROGRESS**
93
+ * (meistermopper) initial release
94
+ * (meistermopper) Update comprehensive documentation, feature mapping, and legal safety declarations.
95
+
96
+ ## Trademarks / Markenhinweis
97
+ Harvia and MyHarvia 2 are registered trademarks of Harvia Group. This adapter is an independent, community-driven open-source project and is neither officially endorsed, sponsored, nor supported by Harvia.
98
+
99
+ ## License
100
+ MIT License - Copyright (c) 2026 meistermopper
Binary file
@@ -0,0 +1,10 @@
1
+ {
2
+ "Device ID": "Geräte-ID",
3
+ "Harvia Fenix Sauna Control": "MyHarvia 2 Saunasteuerung (Fenix)",
4
+ "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery). Note: The ID is not visible in the MyHarvia 2 app.": "Lass dieses Feld leer und starte den Adapter, um die ID automatisch im ioBroker-Log zu finden (Discovery). Hinweis: Die ID ist in der MyHarvia 2 App nicht sichtbar.",
5
+ "Login Settings": "Login-Einstellungen",
6
+ "Password": "Passwort",
7
+ "Polling Interval (seconds)": "Abfrageintervall (Sekunden)",
8
+ "Username / Email": "Benutzername / E-Mail",
9
+ "harvia-fenix adapter settings": "Adaptereinstellungen für MyHarvia 2"
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "Device ID": "Device ID",
3
+ "Harvia Fenix Sauna Control": "Harvia Fenix Sauna Control",
4
+ "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery).": "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery).",
5
+ "Login Settings": "Login Settings",
6
+ "Password": "Password",
7
+ "Polling Interval (seconds)": "Polling Interval (seconds)",
8
+ "Username / Email": "Username / Email",
9
+ "harvia-fenix adapter settings": "Harvia Fenix adapter settings"
10
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "Device ID": "ID del dispositivo",
3
+ "Harvia Fenix Sauna Control": "Control de sauna Harvia Fenix",
4
+ "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery).": "Deje este campo vacío e inicie el adaptador para encontrar la ID automáticamente en el registro de ioBroker (Discovery).",
5
+ "Login Settings": "Configuración de inicio de sesión",
6
+ "Password": "Contraseña",
7
+ "Polling Interval (seconds)": "Intervalo de sondeo (segundos)",
8
+ "Username / Email": "Nombre de usuario/correo electrónico",
9
+ "harvia-fenix adapter settings": "Ajustes del adaptador para harvia-fenix",
10
+ "option1": "opción1",
11
+ "option2": "opción2"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "Device ID": "ID de l'appareil",
3
+ "Harvia Fenix Sauna Control": "Contrôle du sauna Harvia MyHarvia (Xenio/Fenix)",
4
+ "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery).": "Laissez ce champ vide et démarrez l'adaptateur pour rechercher automatiquement l'ID dans le journal ioBroker (Découverte).",
5
+ "Login Settings": "Paramètres de connexion",
6
+ "Password": "Mot de passe",
7
+ "Polling Interval (seconds)": "Intervalle d'interrogation (secondes)",
8
+ "Username / Email": "Nom d'utilisateur / E-mail",
9
+ "harvia-fenix adapter settings": "Paramètres d'adaptateur pour Harvia MyHarvia (Fenix & Xenio)",
10
+ "option1": "option1",
11
+ "option2": "option2"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "Device ID": "ID del dispositivo",
3
+ "Harvia Fenix Sauna Control": "Controllo sauna Harvia MyHarvia (Xenio/Fenix)",
4
+ "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery).": "Lascia questo campo vuoto e avvia l'adattatore per trovare automaticamente l'ID nel registro di ioBroker (Discovery).",
5
+ "Login Settings": "Impostazioni di accesso",
6
+ "Password": "Password",
7
+ "Polling Interval (seconds)": "Intervallo di polling (secondi)",
8
+ "Username / Email": "Nome utente/e-mail",
9
+ "harvia-fenix adapter settings": "Impostazioni dell'adattatore per Harvia MyHarvia (Fenix & Xenio)",
10
+ "option1": "opzione1",
11
+ "option2": "opzione2"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "Device ID": "Apparaat-ID",
3
+ "Harvia Fenix Sauna Control": "Harvia Fenix ​​saunabediening",
4
+ "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery).": "Laat dit veld leeg en start de adapter om de ID automatisch te vinden in het ioBroker-logboek (Discovery).",
5
+ "Login Settings": "Login-instellingen",
6
+ "Password": "Wachtwoord",
7
+ "Polling Interval (seconds)": "Polling-interval (seconden)",
8
+ "Username / Email": "Gebruikersnaam / E-mailadres",
9
+ "harvia-fenix adapter settings": "Adapterinstellingen voor harvia-fenix",
10
+ "option1": "optie1",
11
+ "option2": "optie2"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "Device ID": "Identyfikator urządzenia",
3
+ "Harvia Fenix Sauna Control": "Sterowanie sauną Harvia MyHarvia (Xenio/Fenix)",
4
+ "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery).": "Pozostaw to pole puste i uruchom adapter, aby automatycznie odnalazł identyfikator w logu ioBroker (Discovery).",
5
+ "Login Settings": "Ustawienia logowania",
6
+ "Password": "Hasło",
7
+ "Polling Interval (seconds)": "Interwał odpytywania (sekundy)",
8
+ "Username / Email": "Nazwa użytkownika / e-mail",
9
+ "harvia-fenix adapter settings": "Ustawienia adaptera dla Harvia MyHarvia (Fenix & Xenio)",
10
+ "option1": "opcja 1",
11
+ "option2": "opcja 2"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "Device ID": "ID do dispositivo",
3
+ "Harvia Fenix Sauna Control": "Controle de Sauna Harvia Fenix",
4
+ "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery).": "Deixe este campo vazio e inicie o adaptador para encontrar o ID automaticamente no log do ioBroker (Discovery).",
5
+ "Login Settings": "Configurações de login",
6
+ "Password": "Senha",
7
+ "Polling Interval (seconds)": "Intervalo de pesquisa (segundos)",
8
+ "Username / Email": "Nome de usuário/e-mail",
9
+ "harvia-fenix adapter settings": "Configurações do adaptador para harvia-fenix",
10
+ "option1": "opção1",
11
+ "option2": "opção2"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "Device ID": "Идентификатор устройства",
3
+ "Harvia Fenix Sauna Control": "Управление сауной Harvia Fenix",
4
+ "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery).": "Оставьте это поле пустым и запустите адаптер, чтобы автоматически найти идентификатор в журнале ioBroker (Discovery).",
5
+ "Login Settings": "Настройки входа",
6
+ "Password": "Пароль",
7
+ "Polling Interval (seconds)": "Интервал опроса (секунды)",
8
+ "Username / Email": "Имя пользователя / адрес электронной почты",
9
+ "harvia-fenix adapter settings": "Настройки адаптера для harvia-fenix",
10
+ "option1": "вариант 1",
11
+ "option2": "вариант 2"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "Device ID": "ID пристрою",
3
+ "Harvia Fenix Sauna Control": "Керування сауною Harvia MyHarvia (Xenio/Fenix)",
4
+ "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery).": "Залиште це поле порожнім і запустіть адаптер, щоб автоматично знайти ідентифікатор у журналі ioBroker (Discovery).",
5
+ "Login Settings": "Налаштування входу",
6
+ "Password": "Пароль",
7
+ "Polling Interval (seconds)": "Інтервал опитування (секунди)",
8
+ "Username / Email": "Ім'я користувача/Електронна пошта",
9
+ "harvia-fenix adapter settings": "Налаштування адаптера для Harvia MyHarvia (Fenix & Xenio)",
10
+ "option1": "варіант1",
11
+ "option2": "варіант2"
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "Device ID": "设备ID",
3
+ "Harvia Fenix Sauna Control": "Harvia Fenix 桑拿控制",
4
+ "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery).": "将此字段留空并启动适配器以在 ioBroker 日志(发现)中自动查找 ID。",
5
+ "Login Settings": "登录设置",
6
+ "Password": "密码",
7
+ "Polling Interval (seconds)": "轮询间隔(秒)",
8
+ "Username / Email": "用户名/电子邮件",
9
+ "harvia-fenix adapter settings": "harvia-fenix的适配器设置",
10
+ "option1": "选项1",
11
+ "option2": "选项2"
12
+ }
@@ -0,0 +1,93 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <link type="text/css" rel="stylesheet" href="../../css/adapter.css"/>
5
+ <link type="text/css" rel="stylesheet" href="../../lib/css/materialize.css">
6
+
7
+ <script type="text/javascript" src="../../lib/js/jquery-3.2.1.min.js"></script>
8
+ <script type="text/javascript" src="../../lib/js/materialize.js"></script>
9
+ <script type="text/javascript" src="../../lib/js/framework.js"></script>
10
+ <script type="text/javascript" src="../../js/translate.js"></script>
11
+ <script type="text/javascript" src="words.js"></script>
12
+
13
+ <script type="text/javascript">
14
+ // This will be called by the admin adapter when the settings page is loaded
15
+ // biome-ignore lint/correctness/noUnusedVariables: called by ioBroker admin
16
+ function load(settings, onChange) {
17
+ // example: select elements with id=key and insert value from config
18
+ if (settings.username) {
19
+ $('#username').val(settings.username).trigger('change');
20
+ }
21
+ if (settings.password) {
22
+ $('#password').val(settings.password).trigger('change');
23
+ }
24
+ if (settings.deviceId) {
25
+ $('#deviceId').val(settings.deviceId).trigger('change');
26
+ }
27
+ if (settings.pollInterval) {
28
+ $('#pollInterval').val(settings.pollInterval).trigger('change');
29
+ }
30
+
31
+ M.updateTextFields(); // for material design if labels are not set
32
+ onChange(false); // no changes yet
33
+
34
+ // Signal to admin that configuration saved with changes
35
+ $('#username').on('change', onChange);
36
+ $('#password').on('change', onChange);
37
+ $('#deviceId').on('change', onChange);
38
+ $('#pollInterval').on('change', onChange);
39
+ }
40
+
41
+ // This will be called by the admin adapter when the user presses the save button
42
+ // biome-ignore lint/correctness/noUnusedVariables: called by ioBroker admin
43
+ function save(callback) {
44
+ // example: get each value from the corresponding element and store it in config
45
+ const config = {
46
+ username: $('#username').val(),
47
+ password: $('#password').val(),
48
+ deviceId: $('#deviceId').val(),
49
+ pollInterval: parseInt($('#pollInterval').val(), 10) || 60
50
+ };
51
+ callback(config);
52
+ }
53
+ </script>
54
+ </head>
55
+ <body>
56
+ <div class="m adapter-container">
57
+ <div class="row">
58
+ <div class="col s12 m4 l2">
59
+ <img src="harvia.png" class="adapter-icon" alt="Harvia Fenix Logo">
60
+ </div>
61
+ <div class="col s12 m8 l10">
62
+ <h5 class="translate" data-lang="harvia-fenix adapter settings">harvia-fenix adapter settings</h5>
63
+ </div>
64
+ </div>
65
+
66
+ <div class="row">
67
+ <div class="col s12 input-field">
68
+ <input type="text" id="username" class="value">
69
+ <label for="username" class="translate" data-lang="Username / Email">Username / Email</label>
70
+ </div>
71
+ </div>
72
+ <div class="row">
73
+ <div class="col s12 input-field">
74
+ <input type="password" id="password" class="value">
75
+ <label for="password" class="translate" data-lang="Password">Password</label>
76
+ </div>
77
+ </div>
78
+ <div class="row">
79
+ <div class="col s12 input-field">
80
+ <input type="text" id="deviceId" class="value">
81
+ <label for="deviceId" class="translate" data-lang="Device ID">Device ID</label>
82
+ <span class="helper-text translate" data-lang="Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery). Note: The ID is not visible in the MyHarvia 2 app.">Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery). Note: The ID is not visible in the MyHarvia 2 app.</span>
83
+ </div>
84
+ </div>
85
+ <div class="row">
86
+ <div class="col s12 input-field">
87
+ <input type="number" id="pollInterval" class="value">
88
+ <label for="pollInterval" class="translate" data-lang="Polling Interval (seconds)">Polling Interval (seconds)</label>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </body>
93
+ </html>
@@ -0,0 +1,35 @@
1
+ {
2
+ "type": "tabs",
3
+ "items": {
4
+ "main": {
5
+ "title": "Login Settings",
6
+ "type": "panel",
7
+ "items": {
8
+ "username": {
9
+ "type": "text",
10
+ "label": "Username / Email",
11
+ "sm": 6
12
+ },
13
+ "password": {
14
+ "type": "password",
15
+ "label": "Password",
16
+ "sm": 6
17
+ },
18
+ "deviceId": {
19
+ "type": "text",
20
+ "label": "Device ID",
21
+ "sm": 12,
22
+ "help": "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery)."
23
+ },
24
+ "pollInterval": {
25
+ "type": "number",
26
+ "label": "Polling Interval (seconds)",
27
+ "min": 30,
28
+ "max": 600,
29
+ "def": 60,
30
+ "sm": 6
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
package/admin/words.js ADDED
@@ -0,0 +1,29 @@
1
+ /*global systemDictionary:true */
2
+
3
+ systemDictionary = {
4
+ "harvia-fenix adapter settings": {
5
+ en: "Adapter settings for Harvia MyHarvia (Fenix)",
6
+ de: "Adaptereinstellungen für Harvia MyHarvia (Fenix)",
7
+ },
8
+ "Username / Email": {
9
+ en: "Username / Email",
10
+ de: "Benutzername / E-Mail",
11
+ },
12
+ Password: {
13
+ en: "Password",
14
+ de: "Passwort",
15
+ },
16
+ "Device ID": {
17
+ en: "Device ID",
18
+ de: "Geräte-ID",
19
+ },
20
+ "Polling Interval (seconds)": {
21
+ en: "Polling Interval (seconds)",
22
+ de: "Abfrageintervall (Sekunden)",
23
+ },
24
+ "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery). Note: The ID is not visible in the MyHarvia 2 app.":
25
+ {
26
+ en: "Leave this field empty and start the adapter to find the ID automatically in the ioBroker log (Discovery). Note: The ID is not visible in the MyHarvia 2 app.",
27
+ de: "Lass dieses Feld leer und starte den Adapter, um die ID automatisch im Log zu finden. Hinweis: Die ID ist in der MyHarvia 2 App nicht sichtbar.",
28
+ },
29
+ };
package/build/main.js ADDED
@@ -0,0 +1,622 @@
1
+ "use strict";
2
+ /*
3
+ * Created with @iobroker/create-adapter v3.1.5
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ // The adapter-core module gives you access to the core ioBroker functions
40
+ // you need to create an adapter
41
+ const utils = __importStar(require("@iobroker/adapter-core"));
42
+ const axios_1 = __importStar(require("axios"));
43
+ // Harvia API Constants
44
+ const CLIENT_ID = "24emhb2mm0v4sscqhbdev86b2v";
45
+ const PARTNER_ID = "ORG/prod:0:6656:0";
46
+ const MIN_TARGET_TEMP = 40; // Minimum allowed target temperature in C
47
+ const MAX_TARGET_TEMP = 110; // Maximum allowed target temperature in C
48
+ const LATENCY_MS = 5000;
49
+ class HarviaFenix extends utils.Adapter {
50
+ constructor(options = {}) {
51
+ super({
52
+ ...options,
53
+ name: "harvia-fenix",
54
+ });
55
+ this.idToken = "";
56
+ this.dataBaseUrl = "";
57
+ this.deviceBaseUrl = "";
58
+ this.authUrl = "";
59
+ this.activeDeviceId = "";
60
+ this.loginPromise = null;
61
+ this.isSendingCommand = false;
62
+ this.lastCommandTime = 0;
63
+ this.lastEventTime = {}; // For debouncing
64
+ this.on("ready", this.onReady.bind(this));
65
+ this.on("stateChange", this.onStateChange.bind(this));
66
+ // this.on('objectChange', this.onObjectChange.bind(this));
67
+ // this.on('message', this.onMessage.bind(this));
68
+ this.on("unload", this.onUnload.bind(this));
69
+ this.client = axios_1.default.create({
70
+ timeout: 20000,
71
+ headers: {
72
+ "User-Agent": "ioBroker.harvia-fenix/0.0.1",
73
+ },
74
+ });
75
+ }
76
+ /**
77
+ * Is called when databases are connected and adapter received configuration.
78
+ */
79
+ async onReady() {
80
+ // Reset status states
81
+ await this.setState("info.connection", false, true);
82
+ // Create necessary state objects
83
+ await this.ensureObjects();
84
+ // Subscribe to writable states
85
+ this.subscribeStates("heatOn");
86
+ this.subscribeStates("lightOn");
87
+ this.subscribeStates("targetTemp");
88
+ // CLEAN START: Reset all status values to 'false' on startup
89
+ await this.setState("online", false, true);
90
+ await this.setState("heatOn", false, true);
91
+ await this.setState("lightOn", false, true);
92
+ await this.setState("doorSafety", false, true);
93
+ await this.setState("remoteControl", false, true);
94
+ await this.setState("errorMsg", "", true);
95
+ // Start connection logic
96
+ await this.startCloudConnection();
97
+ }
98
+ async ensureObjects() {
99
+ const states = [
100
+ {
101
+ id: "online",
102
+ type: "boolean",
103
+ role: "indicator.reachable",
104
+ def: false,
105
+ },
106
+ { id: "heatOn", type: "boolean", role: "switch.power", def: false },
107
+ { id: "lightOn", type: "boolean", role: "switch.light", def: false },
108
+ {
109
+ id: "temp",
110
+ type: "number",
111
+ role: "value.temperature",
112
+ unit: "°C",
113
+ def: 0,
114
+ },
115
+ {
116
+ id: "targetTemp",
117
+ type: "number",
118
+ role: "level.temperature",
119
+ unit: "°C",
120
+ def: 90,
121
+ },
122
+ {
123
+ id: "heaterPower",
124
+ type: "number",
125
+ role: "value.power",
126
+ unit: "kW",
127
+ def: 0,
128
+ },
129
+ {
130
+ id: "doorSafety",
131
+ type: "boolean",
132
+ role: "indicator.safety",
133
+ def: false,
134
+ },
135
+ {
136
+ id: "remoteControl",
137
+ type: "boolean",
138
+ role: "indicator.state",
139
+ def: false,
140
+ },
141
+ { id: "errorMsg", type: "string", role: "text", def: "" },
142
+ {
143
+ id: "panelTemp",
144
+ type: "number",
145
+ role: "value.temperature",
146
+ unit: "°C",
147
+ def: 0,
148
+ },
149
+ {
150
+ id: "totalBathingHours",
151
+ type: "number",
152
+ role: "value.number",
153
+ unit: "h",
154
+ def: 0,
155
+ },
156
+ { id: "totalSessions", type: "number", role: "value", def: 0 },
157
+ // In original script it was totalHours, we keep totalOperatingHours
158
+ {
159
+ id: "totalOperatingHours",
160
+ type: "number",
161
+ role: "value",
162
+ unit: "h",
163
+ def: 0,
164
+ },
165
+ ];
166
+ for (const s of states) {
167
+ await this.setObjectNotExistsAsync(s.id, {
168
+ type: "state",
169
+ common: {
170
+ name: s.id,
171
+ type: s.type,
172
+ role: s.role,
173
+ unit: s.unit,
174
+ read: true,
175
+ write: true,
176
+ def: s.def,
177
+ },
178
+ native: {},
179
+ });
180
+ }
181
+ }
182
+ async fetchConfig() {
183
+ try {
184
+ const response = await this.client.get("https://api.harvia.io/endpoints");
185
+ this.log.debug(`Endpoints Response: ${JSON.stringify(response.data)}`);
186
+ const ep = response.data.endpoints.RestApi;
187
+ this.dataBaseUrl = ep.data.https;
188
+ this.deviceBaseUrl = ep.device.https;
189
+ this.authUrl = `${ep.generics.https}/auth/token`;
190
+ this.log.info(`API configuration loaded: Data=${this.dataBaseUrl}, Device=${this.deviceBaseUrl}`);
191
+ return true;
192
+ }
193
+ catch (err) {
194
+ this.log.error(`Error loading API configuration: ${err instanceof Error ? err.message : String(err)}`);
195
+ return false;
196
+ }
197
+ }
198
+ async login() {
199
+ if (this.loginPromise) {
200
+ return this.loginPromise;
201
+ }
202
+ this.loginPromise = this.performLogin();
203
+ try {
204
+ return await this.loginPromise;
205
+ }
206
+ finally {
207
+ this.loginPromise = null;
208
+ }
209
+ }
210
+ async performLogin() {
211
+ try {
212
+ if (!this.authUrl && !(await this.fetchConfig())) {
213
+ return false;
214
+ }
215
+ const response = await this.client.post(this.authUrl, {
216
+ username: this.config.username,
217
+ password: this.config.password,
218
+ client_id: CLIENT_ID,
219
+ });
220
+ this.idToken = response.data.idToken; // JWT-Token
221
+ await this.setState("info.connection", true, true);
222
+ return true;
223
+ }
224
+ catch (err) {
225
+ this.log.error(`Login failed: ${err instanceof Error ? err.message : String(err)}`);
226
+ await this.setState("info.connection", false, true);
227
+ return false;
228
+ }
229
+ }
230
+ async startCloudConnection() {
231
+ if (await this.login()) {
232
+ await this.discoverDevices();
233
+ void this.updateStatus(); // Start first poll
234
+ this.loginInterval = this.setInterval(() => void this.login(), 50 * 60 * 1000);
235
+ }
236
+ else {
237
+ this.log.warn("Initial login failed. Retrying in 5 minutes...");
238
+ this.updateInterval = this.setTimeout(() => this.startCloudConnection(), 5 * 60 * 1000);
239
+ }
240
+ }
241
+ async discoverDevices() {
242
+ var _a;
243
+ try {
244
+ if (!this.idToken || !this.deviceBaseUrl) {
245
+ return;
246
+ }
247
+ const baseUrl = this.deviceBaseUrl.replace(/\/$/, "");
248
+ // Try to retrieve the list of devices
249
+ const url = baseUrl.endsWith("/devices") ? baseUrl : `${baseUrl}/devices`;
250
+ this.log.info(`Searching for devices at: ${url}`);
251
+ const response = await this.client.get(url, {
252
+ headers: {
253
+ Authorization: `Bearer ${this.idToken}`,
254
+ "x-harvia-partner-id": PARTNER_ID,
255
+ },
256
+ });
257
+ const devices = response.data.devices || [];
258
+ if (devices.length > 0) {
259
+ this.log.info(`Harvia Cloud: ${devices.length} device(s) found.`);
260
+ for (const d of devices) {
261
+ const actualId = d.deviceId || d.id || d.name;
262
+ this.log.info(`Found device: ${d.name} (ID: ${actualId}, Type: ${(_a = d.type) !== null && _a !== void 0 ? _a : "Fenix"})`);
263
+ // Use the configured ID if available, otherwise fall back to discovered ID
264
+ if (!this.activeDeviceId && !this.config.deviceId && actualId) {
265
+ this.log.warn(`Device ID not set in adapter configuration. Using found ID: ${actualId}`);
266
+ this.activeDeviceId = actualId;
267
+ }
268
+ else if (this.config.deviceId &&
269
+ this.config.deviceId !== actualId) {
270
+ this.log.info(`Configured Device ID (${this.config.deviceId}) does not match found ID (${actualId}). Please check settings.`);
271
+ }
272
+ // Read static attributes directly at startup
273
+ if (Array.isArray(d.attr)) {
274
+ for (const a of d.attr) {
275
+ // Ensure value exists before parsing
276
+ if (a.value === undefined || a.value === null) {
277
+ continue;
278
+ }
279
+ switch (a.key) {
280
+ case "connected":
281
+ await this.setState("online", a.value === "true", true);
282
+ break;
283
+ case "stats.totalSessions.C1":
284
+ await this.setState("totalSessions", Math.round(Number.parseInt(a.value, 10)), true);
285
+ break;
286
+ case "stats.totalBathingHours.C1": // In script it was totalBathingHours
287
+ await this.setState("totalBathingHours", Math.round(Number.parseFloat(a.value) * 100) / 100, true);
288
+ break;
289
+ case "stats.totalOperatingHours.C1":
290
+ await this.setState("totalOperatingHours", Math.round(Number.parseFloat(a.value) * 100) / 100, true);
291
+ break;
292
+ case "BT_MAC":
293
+ this.log.debug(`Bluetooth MAC: ${a.value}`);
294
+ break;
295
+ }
296
+ }
297
+ }
298
+ }
299
+ }
300
+ else {
301
+ this.log.warn("Login successful, but no devices found in Harvia account.");
302
+ }
303
+ }
304
+ catch (err) {
305
+ this.log.error(`Error during device discovery: ${err instanceof Error ? err.message : String(err)}`);
306
+ }
307
+ }
308
+ async updateStatus() {
309
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
310
+ try {
311
+ if (!this.idToken || !this.dataBaseUrl) {
312
+ return;
313
+ }
314
+ const url = `${this.dataBaseUrl.replace(/\/$/, "")}/data/latest-data`; // Path from JS-script
315
+ const deviceId = this.activeDeviceId || this.config.deviceId;
316
+ this.log.debug(`Poll Status: ${url} (ID: ${deviceId})`);
317
+ const response = await this.client.get(url, {
318
+ params: { deviceId },
319
+ headers: {
320
+ // Headers from JS-script and successful calls
321
+ Accept: "application/json",
322
+ "x-harvia-app-id": CLIENT_ID,
323
+ "x-harvia-partner-id": PARTNER_ID,
324
+ Authorization: `Bearer ${this.idToken}`,
325
+ },
326
+ });
327
+ if (response.data) {
328
+ this.log.debug(`Poll Response: ${JSON.stringify(response.data)}`);
329
+ }
330
+ const responseData = response.data;
331
+ let p;
332
+ if (responseData && "data" in responseData && responseData.data) {
333
+ p = responseData.data;
334
+ }
335
+ else if (responseData &&
336
+ typeof responseData === "object" &&
337
+ !("data" in responseData)) {
338
+ p = responseData;
339
+ }
340
+ if (p) {
341
+ // LATENCY PROTECTION: If a command was sent less than LATENCY_MS ago,
342
+ // ignore this update to prevent UI jumping.
343
+ if (Date.now() - this.lastCommandTime < LATENCY_MS) {
344
+ this.log.debug(`Polling ignored due to latency protection (${LATENCY_MS}ms). Last command ${Date.now() - this.lastCommandTime}ms ago.`);
345
+ return;
346
+ }
347
+ // NEW DEBUG LOGGING FOR HEATON
348
+ if (p.online && p.heatOn !== undefined) {
349
+ const actualHeat = (_a = p.heatState) !== null && _a !== void 0 ? _a : p.heat;
350
+ const currentHeatOnState = (_b = (await this.getStateAsync("heatOn"))) === null || _b === void 0 ? void 0 : _b.val;
351
+ const isHeatingExpected = actualHeat === 1 || actualHeat === true || actualHeat === "on";
352
+ if (isHeatingExpected && !currentHeatOnState) {
353
+ this.log.warn(`Expected heatOn=true, but ioBroker state is false. Raw data: ${JSON.stringify(p.heatOn)}`);
354
+ }
355
+ else if (actualHeat === undefined) {
356
+ this.log.debug(`Heat status undefined in API response, but device is online. Raw data: ${JSON.stringify(p)}`);
357
+ }
358
+ }
359
+ // NORMALIZATION: Harvia uses 'temp' or 'temperature' depending on model.
360
+ const currentTemp = (_c = p.temperature) !== null && _c !== void 0 ? _c : p.temp;
361
+ if (currentTemp !== undefined) {
362
+ await this.setState("temp", Math.round(Number.parseFloat(currentTemp) * 10) / 10, true);
363
+ }
364
+ const pPanelTemp = (_d = p.panelTemp) !== null && _d !== void 0 ? _d : p.panelTemperature;
365
+ if (pPanelTemp !== undefined) {
366
+ await this.setState("panelTemp", Math.round(Number.parseFloat(pPanelTemp) * 10) / 10, true);
367
+ }
368
+ // Normalization of heater power (heaterPower vs power)
369
+ let currentPower = (_e = p.heaterPower) !== null && _e !== void 0 ? _e : p.power;
370
+ if (currentPower !== undefined) {
371
+ currentPower =
372
+ Math.round((Number.parseFloat(currentPower) / 1000) * 100) / 100;
373
+ await this.setState("heaterPower", currentPower, true);
374
+ }
375
+ if (p.totalBathingHours !== undefined) {
376
+ await this.setState("totalBathingHours", Math.round(Number.parseFloat(p.totalBathingHours) * 100) /
377
+ 100, true);
378
+ }
379
+ if (p.totalSessions !== undefined) {
380
+ await this.setState("totalSessions", Math.round(Number.parseInt(p.totalSessions, 10)), true);
381
+ }
382
+ if (p.totalHours !== undefined) {
383
+ await this.setState("totalOperatingHours", Math.round(Number.parseFloat(p.totalHours) * 100) / 100, true);
384
+ }
385
+ const tTemp = (_f = p.targetTemperature) !== null && _f !== void 0 ? _f : p.targetTemp;
386
+ if (tTemp !== undefined) {
387
+ await this.setState("targetTemp", typeof tTemp === "string" ? Number.parseFloat(tTemp) : tTemp, true);
388
+ }
389
+ // STATUS-FIX (Light/Heating): Robust check of state fields and base fields.
390
+ // Some cloud versions omit fields on 'off' or use alternative names.
391
+ const actualHeat = (_h = (_g = p.heatOn) !== null && _g !== void 0 ? _g : p.heatState) !== null && _h !== void 0 ? _h : p.heat;
392
+ const actualLight = (_k = (_j = p.lightOn) !== null && _j !== void 0 ? _j : p.lightState) !== null && _k !== void 0 ? _k : p.light;
393
+ // Conversion of 0/1 or "on"/"off" to boolean for ioBroker
394
+ if (actualHeat !== undefined && actualHeat !== null) {
395
+ await this.setState("heatOn", actualHeat === 1 || actualHeat === true || actualHeat === "on", true);
396
+ }
397
+ if (actualLight !== undefined && actualLight !== null) {
398
+ await this.setState("lightOn", actualLight === 1 || actualLight === true || actualLight === "on", true);
399
+ }
400
+ // Remote control readiness (safety chain acknowledged on panel?)
401
+ if (p.remoteControlState !== undefined) {
402
+ await this.setState("remoteControl", p.remoteControlState === 1, true);
403
+ }
404
+ await this.setState("doorSafety", p.doorSafetyState === 1, true); // 1 = Safe/Closed
405
+ await this.setState("online", true, true);
406
+ }
407
+ else {
408
+ this.log.warn(`Unexpected data structure during status poll: ${JSON.stringify(response.data)}`);
409
+ }
410
+ }
411
+ catch (err) {
412
+ if ((0, axios_1.isAxiosError)(err)) {
413
+ if (((_l = err.response) === null || _l === void 0 ? void 0 : _l.status) === 401) {
414
+ void this.login();
415
+ }
416
+ else {
417
+ this.log.error(`Status poll failed (${(_m = err.response) === null || _m === void 0 ? void 0 : _m.status}): ${err.message}. Response Data: ${JSON.stringify((_o = err.response) === null || _o === void 0 ? void 0 : _o.data)}`);
418
+ await this.setState("online", false, true);
419
+ }
420
+ }
421
+ else {
422
+ this.log.error(`Status poll failed: ${err instanceof Error ? err.message : String(err)}`);
423
+ await this.setState("online", false, true);
424
+ }
425
+ }
426
+ finally {
427
+ const interval = (this.config.pollInterval || 60) * 1000;
428
+ this.updateInterval = this.setTimeout(() => this.updateStatus(), interval);
429
+ }
430
+ }
431
+ async setSaunaState(stateName, value, isRetry = false) {
432
+ var _a, _b, _c;
433
+ if (!this.idToken || !this.deviceBaseUrl) {
434
+ return;
435
+ }
436
+ // Lock-Check: Only block if not an internal retry
437
+ if (this.isSendingCommand && !isRetry) {
438
+ return;
439
+ }
440
+ // RACE-CONDITION PROTECTION
441
+ const baseUrl = this.deviceBaseUrl.replace(/\/$/, "");
442
+ const devicesUrl = baseUrl.endsWith("/devices")
443
+ ? baseUrl
444
+ : `${baseUrl}/devices`;
445
+ this.isSendingCommand = true;
446
+ try {
447
+ const deviceId = this.activeDeviceId || this.config.deviceId;
448
+ if (stateName === "heatOn" || stateName === "lightOn") {
449
+ const commandType = stateName === "heatOn" ? "SAUNA" : "LIGHTS";
450
+ const stateStr = value ? "on" : "off";
451
+ const payload = {
452
+ deviceId,
453
+ cabin: { id: "C1" },
454
+ command: { type: commandType, state: stateStr },
455
+ };
456
+ const url = `${devicesUrl}/command`;
457
+ const resp = await this.client.post(url, payload, {
458
+ // Headers from JS-script and successful calls
459
+ headers: {
460
+ Authorization: `Bearer ${this.idToken.trim()}`, // .trim() from original JS-script
461
+ "Content-Type": "application/json",
462
+ "x-harvia-partner-id": PARTNER_ID,
463
+ "x-harvia-app-id": CLIENT_ID,
464
+ },
465
+ });
466
+ if ((_a = resp.data) === null || _a === void 0 ? void 0 : _a.handled) {
467
+ this.log.info(`${commandType} -> ${stateStr}`);
468
+ // CONFIRMATION: Set ack: true immediately to prevent UI "jumping"
469
+ await this.setState(stateName, !!value, true);
470
+ this.lastCommandTime = Date.now();
471
+ if (stateName === "heatOn") {
472
+ await this.setState("errorMsg", "", true);
473
+ }
474
+ }
475
+ else {
476
+ const reason = resp.data ? resp.data.failureReason : "Unknown";
477
+ this.log.warn(`Cloud rejected command: ${reason}`);
478
+ await this.setState("errorMsg", `Cloud error: ${reason}`, true);
479
+ }
480
+ }
481
+ else if (stateName === "targetTemp") {
482
+ const payload = {
483
+ deviceId,
484
+ cabin: { id: "C1" },
485
+ temperature: Number.parseFloat(value),
486
+ };
487
+ const url = `${devicesUrl}/target`;
488
+ await this.client.patch(url, payload, {
489
+ headers: {
490
+ Authorization: `Bearer ${this.idToken.trim()}`,
491
+ "Content-Type": "application/json",
492
+ "x-harvia-partner-id": PARTNER_ID,
493
+ "x-harvia-app-id": CLIENT_ID,
494
+ },
495
+ });
496
+ this.log.info(`Target temperature -> ${value}°C`);
497
+ // Immediate confirmation in ioBroker
498
+ await this.setState("targetTemp", Number.parseFloat(value), true);
499
+ this.lastCommandTime = Date.now();
500
+ }
501
+ }
502
+ catch (err) {
503
+ let detail;
504
+ if ((0, axios_1.isAxiosError)(err) && ((_b = err.response) === null || _b === void 0 ? void 0 : _b.data)) {
505
+ detail = JSON.stringify(err.response.data);
506
+ }
507
+ else if (err instanceof Error) {
508
+ detail = err.message;
509
+ }
510
+ else {
511
+ detail = String(err);
512
+ }
513
+ // "Device unavailable" is a cloud lock effect during rapid clicking.
514
+ // Log as debug to keep the info log clean.
515
+ if (detail.includes("Device unavailable")) {
516
+ this.log.debug("Cloud lock: Device busy, command discarded.");
517
+ }
518
+ else {
519
+ this.log.error(`Control error: ${detail}`);
520
+ const msg = err instanceof Error ? err.message : String(err);
521
+ await this.setState("errorMsg", `Error: ${msg}`, true);
522
+ }
523
+ // RE-LOGIN LOGIC: If token became invalid during runtime
524
+ // Automatic re-login on expired token (HTTP 401)
525
+ if ((0, axios_1.isAxiosError)(err) && ((_c = err.response) === null || _c === void 0 ? void 0 : _c.status) === 401) {
526
+ this.log.warn("Token expired during control, triggering re-login...");
527
+ this.isSendingCommand = false; // Briefly release lock for login
528
+ if (await this.login()) {
529
+ // Repeat command once after successful login
530
+ await this.setSaunaState(stateName, value, true);
531
+ }
532
+ }
533
+ }
534
+ finally {
535
+ this.isSendingCommand = false;
536
+ }
537
+ }
538
+ /**
539
+ * Is called when adapter shuts down - callback has to be called under any circumstances!
540
+ *
541
+ * @param callback - Callback to be called after shutdown logic
542
+ */
543
+ onUnload(callback) {
544
+ try {
545
+ if (this.updateInterval) {
546
+ this.clearTimeout(this.updateInterval);
547
+ }
548
+ if (this.loginInterval) {
549
+ this.clearInterval(this.loginInterval);
550
+ }
551
+ callback();
552
+ }
553
+ catch {
554
+ callback();
555
+ }
556
+ }
557
+ // Internal helper function for debouncing ioBroker events (Race Condition protection)
558
+ shouldProcess(id) {
559
+ const now = Date.now();
560
+ if (this.lastEventTime[id] && now - this.lastEventTime[id] < 1500) {
561
+ return false; // Ignore events within 1500ms (VIS bouncing)
562
+ }
563
+ this.lastEventTime[id] = now;
564
+ return true;
565
+ }
566
+ async onStateChange(id, state) {
567
+ var _a;
568
+ if (state && !state.ack) {
569
+ const stateId = id.split(".").pop();
570
+ if (!stateId) {
571
+ return;
572
+ }
573
+ if (stateId === "heatOn") {
574
+ if (!this.shouldProcess(id)) {
575
+ return;
576
+ }
577
+ // Ensure boolean conversion (VIS often sends strings)
578
+ const val = state.val === true || state.val === "true" || state.val === 1;
579
+ const isRemoteReady = (_a = (await this.getStateAsync("remoteControl"))) === null || _a === void 0 ? void 0 : _a.val;
580
+ if (val && !isRemoteReady) {
581
+ this.log.warn("Remote start not ready!");
582
+ await this.setState("heatOn", false, true);
583
+ await this.setState("errorMsg", "Remote start not ready at panel!", true);
584
+ }
585
+ else {
586
+ await this.setSaunaState("heatOn", val);
587
+ }
588
+ }
589
+ else if (stateId === "lightOn" || stateId === "targetTemp") {
590
+ if (!this.shouldProcess(id)) {
591
+ return;
592
+ }
593
+ // Ensure type conversion
594
+ let val = state.val;
595
+ if (stateId === "targetTemp") {
596
+ val = Number.parseFloat(state.val);
597
+ if (Number.isNaN(val) ||
598
+ val < MIN_TARGET_TEMP ||
599
+ val > MAX_TARGET_TEMP) {
600
+ this.log.warn(`Invalid target temperature (${state.val}°C) received. Allowed range: ${MIN_TARGET_TEMP}-${MAX_TARGET_TEMP}°C. Resetting to default (${MAX_TARGET_TEMP}°C).`);
601
+ await this.setState("targetTemp", MAX_TARGET_TEMP, true); // Reset to default or max
602
+ await this.setState("errorMsg", `Invalid target temperature: ${state.val}°C`, true);
603
+ return;
604
+ }
605
+ }
606
+ else {
607
+ val = state.val === true || state.val === "true" || state.val === 1;
608
+ }
609
+ await this.setSaunaState(stateId, val);
610
+ }
611
+ }
612
+ }
613
+ }
614
+ if (require.main !== module) {
615
+ // Export the constructor in compact mode
616
+ module.exports = (options) => new HarviaFenix(options);
617
+ }
618
+ else {
619
+ // otherwise start the instance directly
620
+ (() => new HarviaFenix())();
621
+ }
622
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ {
2
+ "common": {
3
+ "name": "harvia-fenix",
4
+ "version": "0.0.1",
5
+ "news": {
6
+ "0.0.1": {
7
+ "en": "initial release",
8
+ "de": "Erstveröffentlichung",
9
+ "ru": "Начальная версия",
10
+ "pt": "lançamento inicial",
11
+ "nl": "Eerste uitgave",
12
+ "fr": "Première version",
13
+ "it": "Versione iniziale",
14
+ "es": "Versión inicial",
15
+ "pl": "Pierwsze wydanie",
16
+ "uk": "Початкова версія",
17
+ "zh-cn": "首次出版"
18
+ }
19
+ },
20
+ "titleLang": {
21
+ "en": "Harvia-Fenix Sauna Control",
22
+ "de": "Harvia-Fenix Saunasteuerung",
23
+ "ru": "Управление сауной Harvia-Fenix",
24
+ "pt": "Controle de Sauna Harvia-Fenix",
25
+ "nl": "Harvia-Fenix ​​saunabediening",
26
+ "fr": "Contrôle du sauna HarviaFenix",
27
+ "it": "Controllo sauna Harvia-Fenix",
28
+ "es": "Control de sauna Harvia-Fenix",
29
+ "pl": "Sterowanie sauną Harvia-Fenix",
30
+ "uk": "Керування сауною Harvia-Fenix",
31
+ "zh-cn": "Harvia MyHarvia 桑拿控制"
32
+ },
33
+ "desc": {
34
+ "en": "Control Harvia Fenix sauna units via MyHarvia cloud",
35
+ "de": "Steuerung von Harvia Fenix Sauna-Steuerungen über die MyHarvia Cloud",
36
+ "ru": "Управление саунами Harvia Fenix через облако MyHarvia",
37
+ "pt": "Controle as unidades de sauna Harvia Fenix via nuvem MyHarvia",
38
+ "nl": "Bestuur Harvia Fenix-sauna-units via de MyHarvia-cloud",
39
+ "fr": "Contrôlez les saunas Harvia Fenix via le cloud MyHarvia",
40
+ "it": "Controlla le unità sauna Harvia Fenix tramite il cloud MyHarvia",
41
+ "es": "Controle las unidades de sauna Harvia Fenix a través de la nube MyHarvia",
42
+ "pl": "Sterowanie saunami Harvia Fenix za pomocą chmury MyHarvia",
43
+ "uk": "Керування саунами Harvia Fenix через хмару MyHarvia",
44
+ "zh-cn": "通过 MyHarvia 云端控制 Harvia Fenix 桑拿房"
45
+ },
46
+ "authors": ["meistermopper <meister.mopper@gmail.com>"],
47
+ "keywords": ["harvia", "fenix", "sauna", "myharvia", "cloud"],
48
+ "licenseInformation": {
49
+ "type": "free",
50
+ "license": "MIT"
51
+ },
52
+ "platform": "Javascript/Node.js",
53
+ "icon": "harvia.png",
54
+ "enabled": true,
55
+ "extIcon": "https://raw.githubusercontent.com/meistermopper/ioBroker.harvia-fenix/main/admin/harvia.png",
56
+ "readme": "https://github.com/meistermopper/ioBroker.harvia-fenix/blob/main/README.md",
57
+ "loglevel": "info",
58
+ "tier": 3,
59
+ "mode": "daemon",
60
+ "type": "household",
61
+ "compact": true,
62
+ "connectionType": "cloud",
63
+ "dataSource": "poll",
64
+ "adminUI": {
65
+ "config": "json"
66
+ },
67
+ "dependencies": [
68
+ {
69
+ "js-controller": ">=6.0.11"
70
+ }
71
+ ],
72
+ "protectedNative": ["password"],
73
+ "encryptedNative": ["password"],
74
+ "globalDependencies": [
75
+ {
76
+ "admin": ">=7.0.23"
77
+ }
78
+ ]
79
+ },
80
+ "native": {
81
+ "username": "",
82
+ "password": "",
83
+ "deviceId": "",
84
+ "pollInterval": 60
85
+ },
86
+ "objects": [],
87
+ "instanceObjects": [
88
+ {
89
+ "_id": "info",
90
+ "type": "channel",
91
+ "common": {
92
+ "name": "Information"
93
+ },
94
+ "native": {}
95
+ },
96
+ {
97
+ "_id": "info.connection",
98
+ "type": "state",
99
+ "common": {
100
+ "role": "indicator.connected",
101
+ "name": "Device or service connected",
102
+ "type": "boolean",
103
+ "read": true,
104
+ "write": false,
105
+ "def": false
106
+ },
107
+ "native": {}
108
+ }
109
+ ]
110
+ }
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "iobroker.harvia-fenix",
3
+ "version": "0.0.1",
4
+ "description": "ioBroker adapter for Harvia Fenix Sauna Control",
5
+ "type": "commonjs",
6
+ "author": {
7
+ "name": "Thomas",
8
+ "email": "meister.mopper@gmail.com"
9
+ },
10
+ "homepage": "https://github.com/meistermopper/ioBroker.harvia-fenix",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "ioBroker",
14
+ "Harvia",
15
+ "Fenix",
16
+ "Sauna"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/meistermopper/ioBroker.harvia-fenix.git"
21
+ },
22
+ "engines": {
23
+ "node": ">=18"
24
+ },
25
+ "dependencies": {
26
+ "@iobroker/adapter-core": "^3.2.2",
27
+ "axios": "^1.7.9"
28
+ },
29
+ "devDependencies": {
30
+ "@biomejs/biome": "^2.4.16",
31
+ "@iobroker/testing": "^5.1.0",
32
+ "@tsconfig/node20": "^20.1.4",
33
+ "@types/chai": "^4.3.20",
34
+ "@types/mocha": "^10.0.10",
35
+ "@types/node": "^20.17.10",
36
+ "chai": "^4.5.0",
37
+ "mocha": "^10.7.3",
38
+ "rimraf": "^6.1.3",
39
+ "source-map-support": "^0.5.21",
40
+ "ts-node": "^10.9.2",
41
+ "typescript": "~5.7.3",
42
+ "husky": "^9.1.7",
43
+ "lint-staged": "^15.2.11"
44
+ },
45
+ "main": "build/main.js",
46
+ "files": [
47
+ "admin",
48
+ "build",
49
+ "io-package.json",
50
+ "LICENSE"
51
+ ],
52
+ "scripts": {
53
+ "test:ts": "node --require ts-node/register node_modules/mocha/bin/mocha --config test/mocharc.build.json",
54
+ "test:package": "mocha test/package --exit",
55
+ "test:unit": "mocha test/unit --exit",
56
+ "test:integration": "mocha test/integration --exit",
57
+ "test": "npm run test:ts && npm run test:package",
58
+ "check": "biome check .",
59
+ "format": "biome format --write .",
60
+ "build": "rimraf build && tsc -p tsconfig.build.json",
61
+ "watch": "tsc -p tsconfig.build.json --watch",
62
+ "lint": "biome check .",
63
+ "release": "npx iobroker release --yes",
64
+ "prepare": "husky"
65
+ },
66
+ "bugs": "https://github.com/meistermopper/ioBroker.harvia-fenix/issues",
67
+ "readmeFilename": "README.md",
68
+ "allowScripts": {
69
+ "@biomejs/biome@2.4.16": true,
70
+ "esbuild@0.25.12": true
71
+ },
72
+ "overrides": {
73
+ "debug": "^4.3.1",
74
+ "diff": "^5.0.1",
75
+ "esbuild": "^0.25.12",
76
+ "glob": "^11.0.0",
77
+ "js-yaml": "^3.14.2",
78
+ "minimatch": "^3.1.4",
79
+ "nanoid": "^3.3.8",
80
+ "serialize-javascript": "^7.0.5"
81
+ },
82
+ "lint-staged": {
83
+ "*.{ts,js,json}": "biome check --write --no-errors-on-unmatched",
84
+ "**/*.{ts,js,json}": "biome check --write --no-errors-on-unmatched"
85
+ }
86
+ }