iobroker.foxesscloud 0.1.0

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 skvarel <skvarel@inventwo.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,106 @@
1
+ ![Logo](admin/foxesscloud-logo.png)
2
+
3
+ ## ioBroker adapter for FoxESS Cloud
4
+
5
+ ![Number of Installations](https://iobroker.live/badges/foxesscloud-installed.svg) ![Current version in stable repository](https://iobroker.live/badges/foxesscloud-stable.svg)
6
+ [![NPM version](https://img.shields.io/npm/v/iobroker.foxesscloud.svg)](https://www.npmjs.com/package/iobroker.foxesscloud)
7
+ [![Downloads](https://img.shields.io/npm/dm/iobroker.foxesscloud.svg)](https://www.npmjs.com/package/iobroker.foxesscloud)
8
+
9
+ [![Paypal Donation](https://img.shields.io/badge/paypal-donate%20|%20spenden-green.svg)](https://www.paypal.com/donate/?hosted_button_id=7W6M3TFZ4W9LW)
10
+
11
+ ## What this adapter does
12
+
13
+ Retrieves real-time data from FoxESS Cloud API for solar inverters (e.g., used in Enpal systems) and exposes ioBroker states for home automation:
14
+
15
+ - Monitor solar power production in real-time
16
+ - Track battery state of charge (SoC)
17
+ - Analyze grid consumption and feed-in power
18
+ - Automate based on power generation
19
+ - Visualize energy flows in ioBroker dashboards
20
+
21
+ ## Features
22
+
23
+ ### Power Values
24
+ - **`pvPower`**: Current PV power generation (kW)
25
+ - **`generationPower`**: Total generation/output power (kW)
26
+ - **`load`**: Current load/consumption power (kW)
27
+ - **`gridConsumption`**: Power imported from grid (kW)
28
+ - **`feedinPower`**: Power exported to grid (kW)
29
+
30
+ ### Battery
31
+ - **`soc`**: Battery state of charge (%)
32
+ - **`batCharge`**: Battery charging power (kW)
33
+ - **`batDischarge`**: Battery discharging power (kW)
34
+
35
+ ### Connection Status
36
+ - **`info.connection`**: Connection status in**1440 calls per day**. With an interval of 60 seconds, this limit is perfectly utilized (1440 minutes = 24 hours).
37
+
38
+ ## Data Points
39
+
40
+ The adapter creates the following data points:
41
+
42
+ - `foxesscloud.0.pvPower` - PV Power (kW)
43
+ - `foxesscloud.0.generationPower` - Generation Power/Output (kW)
44
+ - `foxesscloud.0.soc` - Battery State of Charge (%)
45
+ - `foxesscloud.0.load` - Load Power (kW)
46
+ - `foxesscloud.0.gridConsumption` - Grid Consumption/Import (kW)
47
+ - `foxesscloud.0.feedinPower` - Feed-in/Export Power (kW)
48
+ - `foxesscloud.0.batCharge` - Battery Charge Power (kW)
49
+ - `foxesscloud.0.batDischarge` - Battery Discharge Power (kW)
50
+ - `foxesscloud.0.info.connection` - Connection status
51
+
52
+ ## Installation
53
+
54
+ 1. Install the adapter from the ioBroker admin interface
55
+ 2. Create a new instance
56
+ 3. Configure:
57
+ - **API Token**: Your API key from the FoxESS Cloud portal
58
+ - **Serial Number (SN)**: The serial number of your inverter
59
+ - **Update Interval**: Data refresh interval in seconds (default: 60, minimum: 60)
60
+ 4. Save and start the instance
61
+
62
+ ### How to get your API credentials
63
+
64
+ 1. Log in to [FoxESS Cloud](https://www.foxesscloud.com)
65
+ 2. Go to your profile/settings
66
+ 3. Generate an API key (token)
67
+ 4. Find your inverter serial number in the device list
68
+
69
+ ## Privacy & Data Handling
70
+
71
+ - This adapter only reads data from **FoxESS Cloud API** using your personal API token
72
+ - Your API token is stored encrypted in the ioBroker database
73
+
74
+ ## Changelog
75
+ <!--
76
+ ### **WORK IN PROGRESS**
77
+ -->
78
+ ### 0.1.0 (2026-01-22)
79
+ - Initial release
80
+
81
+ ## Older changes
82
+ - [CHANGELOG_OLD.md](CHANGELOG_OLD.md)
83
+
84
+ ## License
85
+
86
+ MIT License
87
+
88
+ Copyright (c) 2026 skvarel <skvarel@inventwo.com>
89
+
90
+ Permission is hereby granted, free of charge, to any person obtaining a copy
91
+ of this software and associated documentation files (the "Software"), to deal
92
+ in the Software without restriction, including without limitation the rights
93
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
94
+ copies of the Software, and to permit persons to whom the Software is
95
+ furnished to do so, subject to the following conditions:
96
+
97
+ The above copyright notice and this permission notice shall be included in all
98
+ copies or substantial portions of the Software.
99
+
100
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
101
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
102
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
103
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
104
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
105
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
106
+ SOFTWARE.
@@ -0,0 +1,15 @@
1
+ // This file extends the AdapterConfig type from "@types/iobroker"
2
+
3
+ // Augment the globally declared type ioBroker.AdapterConfig
4
+ declare global {
5
+ namespace ioBroker {
6
+ interface AdapterConfig {
7
+ token: string;
8
+ sn: string;
9
+ interval: number;
10
+ }
11
+ }
12
+ }
13
+
14
+ // this is required so the above AdapterConfig is found by TypeScript / type checking
15
+ export {};
Binary file
Binary file
@@ -0,0 +1,9 @@
1
+ {
2
+ "API Token": "API-Token",
3
+ "API-Key aus der FoxESS Cloud": "API-Schlüssel vom FoxESS Cloud-Portal",
4
+ "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Calls/Tag)": "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Aufrufe/Tag)",
5
+ "FoxESS Cloud API Configuration": "FoxESS Cloud API-Konfiguration",
6
+ "Serial Number (SN)": "Seriennummer (SN)",
7
+ "Seriennummer vom Inverter": "Seriennummer des Wechselrichters",
8
+ "Update Interval (seconds)": "Aktualisierungsintervall (Sekunden)"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "FoxESS Cloud API Configuration": "FoxESS Cloud API Configuration",
3
+ "API Token": "API Token",
4
+ "API-Key aus der FoxESS Cloud": "API key from the FoxESS Cloud portal",
5
+ "Serial Number (SN)": "Serial Number (SN)",
6
+ "Seriennummer vom Inverter": "Serial number of the inverter",
7
+ "Update Interval (seconds)": "Update Interval (seconds)",
8
+ "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Calls/Tag)": "Update interval in seconds (Recommended: 60, Minimum: 60, API limit: 1440 calls/day)"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "API Token": "Ficha API",
3
+ "API-Key aus der FoxESS Cloud": "Clave API del portal FoxESS Cloud",
4
+ "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Calls/Tag)": "Intervalo de actualización en segundos (recomendado: 60, mínimo: 60, límite de API: 1440 llamadas/día)",
5
+ "FoxESS Cloud API Configuration": "Configuración de la API de la nube FoxESS",
6
+ "Serial Number (SN)": "Número de serie (SN)",
7
+ "Seriennummer vom Inverter": "Número de serie del inversor",
8
+ "Update Interval (seconds)": "Intervalo de actualización (segundos)"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "API Token": "Jeton API",
3
+ "API-Key aus der FoxESS Cloud": "Clé API du portail FoxESS Cloud",
4
+ "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Calls/Tag)": "Intervalle de mise à jour en secondes (Recommandé : 60, Minimum : 60, Limite API : 1 440 appels/jour)",
5
+ "FoxESS Cloud API Configuration": "Configuration de l'API cloud FoxESS",
6
+ "Serial Number (SN)": "Numéro de série (SN)",
7
+ "Seriennummer vom Inverter": "Numéro de série de l'onduleur",
8
+ "Update Interval (seconds)": "Intervalle de mise à jour (secondes)"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "API Token": "Token API",
3
+ "API-Key aus der FoxESS Cloud": "Chiave API dal portale FoxESS Cloud",
4
+ "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Calls/Tag)": "Intervallo di aggiornamento in secondi (consigliato: 60, minimo: 60, limite API: 1440 chiamate/giorno)",
5
+ "FoxESS Cloud API Configuration": "Configurazione API FoxESS Cloud",
6
+ "Serial Number (SN)": "Numero di serie (SN)",
7
+ "Seriennummer vom Inverter": "Numero di serie dell'inverter",
8
+ "Update Interval (seconds)": "Intervallo di aggiornamento (secondi)"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "API Token": "API-token",
3
+ "API-Key aus der FoxESS Cloud": "API-sleutel van het FoxESS Cloud-portaal",
4
+ "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Calls/Tag)": "Update-interval in seconden (aanbevolen: 60, minimum: 60, API-limiet: 1440 oproepen/dag)",
5
+ "FoxESS Cloud API Configuration": "FoxESS Cloud API-configuratie",
6
+ "Serial Number (SN)": "Serienummer (SN)",
7
+ "Seriennummer vom Inverter": "Serienummer van de omvormer",
8
+ "Update Interval (seconds)": "Update-interval (seconden)"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "API Token": "Token API",
3
+ "API-Key aus der FoxESS Cloud": "Klucz API z portalu FoxESS Cloud",
4
+ "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Calls/Tag)": "Interwał aktualizacji w sekundach (zalecany: 60, minimalnie: 60, limit API: 1440 połączeń/dzień)",
5
+ "FoxESS Cloud API Configuration": "Konfiguracja API chmury FoxESS",
6
+ "Serial Number (SN)": "Numer seryjny (SN)",
7
+ "Seriennummer vom Inverter": "Numer seryjny falownika",
8
+ "Update Interval (seconds)": "Interwał aktualizacji (sekundy)"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "API Token": "Token de API",
3
+ "API-Key aus der FoxESS Cloud": "Chave API do portal FoxESS Cloud",
4
+ "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Calls/Tag)": "Intervalo de atualização em segundos (recomendado: 60, mínimo: 60, limite de API: 1.440 chamadas/dia)",
5
+ "FoxESS Cloud API Configuration": "Configuração da API de nuvem FoxESS",
6
+ "Serial Number (SN)": "Número de série (SN)",
7
+ "Seriennummer vom Inverter": "Número de série do inversor",
8
+ "Update Interval (seconds)": "Intervalo de atualização (segundos)"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "API Token": "API-токен",
3
+ "API-Key aus der FoxESS Cloud": "API-ключ от портала FoxESS Cloud",
4
+ "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Calls/Tag)": "Интервал обновления в секундах (рекомендуется: 60, минимум: 60, лимит API: 1440 вызовов в день)",
5
+ "FoxESS Cloud API Configuration": "Конфигурация облачного API FoxESS",
6
+ "Serial Number (SN)": "Серийный номер (SN)",
7
+ "Seriennummer vom Inverter": "Серийный номер инвертора",
8
+ "Update Interval (seconds)": "Интервал обновления (секунды)"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "API Token": "Маркер API",
3
+ "API-Key aus der FoxESS Cloud": "Ключ API з порталу FoxESS Cloud",
4
+ "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Calls/Tag)": "Інтервал оновлення в секундах (рекомендовано: 60, мінімум: 60, обмеження API: 1440 викликів/день)",
5
+ "FoxESS Cloud API Configuration": "Конфігурація FoxESS Cloud API",
6
+ "Serial Number (SN)": "Серійний номер (SN)",
7
+ "Seriennummer vom Inverter": "Серійний номер інвертора",
8
+ "Update Interval (seconds)": "Інтервал оновлення (секунди)"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "API Token": "API令牌",
3
+ "API-Key aus der FoxESS Cloud": "来自 FoxESS Cloud 门户的 API 密钥",
4
+ "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Calls/Tag)": "更新间隔(以秒为单位)(建议:60,最低:60,API 限制:1440 次调用/天)",
5
+ "FoxESS Cloud API Configuration": "FoxESS云API配置",
6
+ "Serial Number (SN)": "序列号 (SN)",
7
+ "Seriennummer vom Inverter": "逆变器序列号",
8
+ "Update Interval (seconds)": "更新间隔(秒)"
9
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "i18n": true,
3
+ "type": "panel",
4
+ "items": {
5
+ "_info": {
6
+ "type": "header",
7
+ "text": "FoxESS Cloud API Configuration",
8
+ "size": 3
9
+ },
10
+ "token": {
11
+ "type": "password",
12
+ "label": "API Token",
13
+ "xs": 12,
14
+ "sm": 12,
15
+ "md": 12,
16
+ "lg": 12,
17
+ "xl": 12,
18
+ "help": "API-Key aus der FoxESS Cloud"
19
+ },
20
+ "sn": {
21
+ "type": "text",
22
+ "label": "Serial Number (SN)",
23
+ "xs": 12,
24
+ "sm": 12,
25
+ "md": 12,
26
+ "lg": 12,
27
+ "xl": 12,
28
+ "help": "Seriennummer vom Inverter"
29
+ },
30
+ "interval": {
31
+ "type": "number",
32
+ "label": "Update Interval (seconds)",
33
+ "min": 60,
34
+ "max": 3600,
35
+ "default": 60,
36
+ "xs": 12,
37
+ "sm": 12,
38
+ "md": 6,
39
+ "lg": 6,
40
+ "xl": 6,
41
+ "help": "Aktualisierungsintervall in Sekunden (Empfohlen: 60, Minimum: 60, API-Limit: 1440 Calls/Tag)"
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,117 @@
1
+ {
2
+ "common": {
3
+ "name": "foxesscloud",
4
+ "version": "0.1.0",
5
+ "news": {
6
+ "0.1.0": {
7
+ "en": "Initial release",
8
+ "de": "Erstveröffentlichung",
9
+ "ru": "Первоначальный выпуск",
10
+ "pt": "Lançamento inicial",
11
+ "nl": "Eerste release",
12
+ "fr": "Version initiale",
13
+ "it": "Rilascio iniziale",
14
+ "es": "Lanzamiento inicial",
15
+ "pl": "Pierwsze wydanie",
16
+ "uk": "Початковий випуск",
17
+ "zh-cn": "初次发布"
18
+ }
19
+ },
20
+ "titleLang": {
21
+ "en": "FoxESS Cloud",
22
+ "de": "FoxESS Cloud",
23
+ "ru": "Облако FoxESS",
24
+ "pt": "Nuvem FoxESS",
25
+ "nl": "FoxESS-wolk",
26
+ "fr": "Nuage FoxESS",
27
+ "it": "FoxESSNuvola",
28
+ "es": "Nube FoxESS",
29
+ "pl": "Chmura FoxESS",
30
+ "uk": "Хмара FoxESS",
31
+ "zh-cn": "福克斯云"
32
+ },
33
+ "desc": {
34
+ "en": "Adapter to retrieve data from FoxESS Cloud API",
35
+ "de": "Adapter zum Abrufen von Daten aus der FoxESS Cloud API",
36
+ "ru": "Адаптер для получения данных из FoxESS Cloud API",
37
+ "pt": "Adaptador para recuperar dados da API FoxESS Cloud",
38
+ "nl": "Adapter om gegevens op te halen uit FoxESS Cloud API",
39
+ "fr": "Adaptateur pour récupérer les données de l'API FoxESS Cloud",
40
+ "it": "Adattatore per recuperare i dati dall'API FoxESS Cloud",
41
+ "es": "Adaptador para recuperar datos de FoxESS Cloud API",
42
+ "pl": "Adapter do pobierania danych z FoxESS Cloud API",
43
+ "uk": "Адаптер для отримання даних з FoxESS Cloud API",
44
+ "zh-cn": "Adapter to retrieve data from FoxESS Cloud API"
45
+ },
46
+ "authors": [
47
+ "skvarel <skvarel@inventwo.com>"
48
+ ],
49
+ "keywords": [
50
+ "FoxESS",
51
+ "Enpal",
52
+ "Solar",
53
+ "PV",
54
+ "Battery"
55
+ ],
56
+ "licenseInformation": {
57
+ "license": "MIT",
58
+ "type": "free"
59
+ },
60
+ "tier": 3,
61
+ "platform": "Javascript/Node.js",
62
+ "icon": "foxesscloud.png",
63
+ "enabled": true,
64
+ "extIcon": "https://raw.githubusercontent.com/inventwo/ioBroker.foxesscloud/main/admin/foxesscloud.png",
65
+ "readme": "https://github.com/inventwo/ioBroker.foxesscloud/blob/main/README.md",
66
+ "loglevel": "info",
67
+ "mode": "daemon",
68
+ "type": "energy",
69
+ "compact": true,
70
+ "connectionType": "cloud",
71
+ "dataSource": "poll",
72
+ "adminUI": {
73
+ "config": "json"
74
+ },
75
+ "dependencies": [
76
+ {
77
+ "js-controller": ">=6.0.11"
78
+ }
79
+ ],
80
+ "globalDependencies": [
81
+ {
82
+ "admin": ">=7.6.17"
83
+ }
84
+ ]
85
+ },
86
+ "protectedNative": ["token"],
87
+ "encryptedNative": ["token"],
88
+ "native": {
89
+ "token": "",
90
+ "sn": "",
91
+ "interval": 60
92
+ },
93
+ "objects": [],
94
+ "instanceObjects": [
95
+ {
96
+ "_id": "info",
97
+ "type": "channel",
98
+ "common": {
99
+ "name": "Information"
100
+ },
101
+ "native": {}
102
+ },
103
+ {
104
+ "_id": "info.connection",
105
+ "type": "state",
106
+ "common": {
107
+ "role": "indicator.connected",
108
+ "name": "Device or service connected",
109
+ "type": "boolean",
110
+ "read": true,
111
+ "write": false,
112
+ "def": false
113
+ },
114
+ "native": {}
115
+ }
116
+ ]
117
+ }
package/main.js ADDED
@@ -0,0 +1,373 @@
1
+ 'use strict';
2
+
3
+ /*
4
+ * Created with @iobroker/create-adapter v2.1.1
5
+ */
6
+
7
+ const utils = require('@iobroker/adapter-core');
8
+ const https = require('https');
9
+ const crypto = require('crypto');
10
+
11
+ class Foxesscloud extends utils.Adapter {
12
+ /**
13
+ * @param {Partial<utils.AdapterOptions>} [options] - Adapter options
14
+ */
15
+ constructor(options) {
16
+ super({
17
+ ...options,
18
+ name: 'foxesscloud',
19
+ });
20
+ this.on('ready', this.onReady.bind(this));
21
+ this.on('unload', this.onUnload.bind(this));
22
+
23
+ this.updateInterval = null;
24
+ }
25
+
26
+ /**
27
+ * Is called when databases are connected and adapter received configuration.
28
+ */
29
+ async onReady() {
30
+ // Initialize your adapter here
31
+
32
+ // Reset the connection indicator during startup
33
+ this.setState('info.connection', false, true);
34
+
35
+ // Check configuration
36
+ if (!this.config.token) {
37
+ this.log.error('API Token is not configured!');
38
+ return;
39
+ }
40
+
41
+ if (!this.config.sn) {
42
+ this.log.error('Serial Number (SN) is not configured!');
43
+ return;
44
+ }
45
+
46
+ // Create states
47
+ await this.createStates();
48
+
49
+ // Get interval from config (default 60 seconds, minimum 60 seconds)
50
+ const intervalSeconds = Math.max(60, this.config.interval || 60);
51
+ this.log.info(`Update interval set to ${intervalSeconds} seconds`);
52
+
53
+ // Start periodic data retrieval
54
+ this.getData(); // First call immediately
55
+ this.updateInterval = setInterval(() => {
56
+ this.getData();
57
+ }, intervalSeconds * 1000);
58
+ }
59
+
60
+ /**
61
+ * Create all necessary states
62
+ */
63
+ async createStates() {
64
+ await this.setObjectNotExistsAsync('pvPower', {
65
+ type: 'state',
66
+ common: {
67
+ name: {
68
+ en: 'PV Power',
69
+ de: 'PV-Leistung',
70
+ },
71
+ type: 'number',
72
+ role: 'value.power',
73
+ unit: 'kW',
74
+ read: true,
75
+ write: false,
76
+ },
77
+ native: {},
78
+ });
79
+
80
+ await this.setObjectNotExistsAsync('generationPower', {
81
+ type: 'state',
82
+ common: {
83
+ name: {
84
+ en: 'Generation Power (Output)',
85
+ de: 'Erzeugungsleistung (Ausgang)',
86
+ },
87
+ type: 'number',
88
+ role: 'value.power',
89
+ unit: 'kW',
90
+ read: true,
91
+ write: false,
92
+ },
93
+ native: {},
94
+ });
95
+
96
+ await this.setObjectNotExistsAsync('soc', {
97
+ type: 'state',
98
+ common: {
99
+ name: {
100
+ en: 'Battery State of Charge',
101
+ de: 'Batterie-Ladezustand',
102
+ },
103
+ type: 'number',
104
+ role: 'value.battery',
105
+ unit: '%',
106
+ read: true,
107
+ write: false,
108
+ },
109
+ native: {},
110
+ });
111
+
112
+ await this.setObjectNotExistsAsync('load', {
113
+ type: 'state',
114
+ common: {
115
+ name: {
116
+ en: 'Load Power',
117
+ de: 'Verbrauchsleistung',
118
+ },
119
+ type: 'number',
120
+ role: 'value.power',
121
+ unit: 'kW',
122
+ read: true,
123
+ write: false,
124
+ },
125
+ native: {},
126
+ });
127
+
128
+ await this.setObjectNotExistsAsync('gridConsumption', {
129
+ type: 'state',
130
+ common: {
131
+ name: {
132
+ en: 'Grid Consumption Power (Importing)',
133
+ de: 'Netzbezugsleistung (Import)',
134
+ },
135
+ type: 'number',
136
+ role: 'value.power',
137
+ unit: 'kW',
138
+ read: true,
139
+ write: false,
140
+ },
141
+ native: {},
142
+ });
143
+
144
+ await this.setObjectNotExistsAsync('feedinPower', {
145
+ type: 'state',
146
+ common: {
147
+ name: {
148
+ en: 'Feed-in Power (Exporting)',
149
+ de: 'Einspeiseleistung (Export)',
150
+ },
151
+ type: 'number',
152
+ role: 'value.power',
153
+ unit: 'kW',
154
+ read: true,
155
+ write: false,
156
+ },
157
+ native: {},
158
+ });
159
+
160
+ await this.setObjectNotExistsAsync('batCharge', {
161
+ type: 'state',
162
+ common: {
163
+ name: {
164
+ en: 'Battery Charge Power',
165
+ de: 'Batterie-Ladeleistung',
166
+ },
167
+ type: 'number',
168
+ role: 'value.power',
169
+ unit: 'kW',
170
+ read: true,
171
+ write: false,
172
+ },
173
+ native: {},
174
+ });
175
+
176
+ await this.setObjectNotExistsAsync('batDischarge', {
177
+ type: 'state',
178
+ common: {
179
+ name: {
180
+ en: 'Battery Discharge Power',
181
+ de: 'Batterie-Entladeleistung',
182
+ },
183
+ type: 'number',
184
+ role: 'value.power',
185
+ unit: 'kW',
186
+ read: true,
187
+ write: false,
188
+ },
189
+ native: {},
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Get data from FoxESS Cloud API
195
+ */
196
+ async getData() {
197
+ try {
198
+ const data = JSON.stringify({
199
+ sn: this.config.sn,
200
+ variables: [
201
+ 'pvPower',
202
+ 'generationPower',
203
+ 'SoC',
204
+ 'loadsPower',
205
+ 'gridConsumptionPower',
206
+ 'feedinPower',
207
+ 'batChargePower',
208
+ 'batDischargePower',
209
+ ],
210
+ });
211
+
212
+ const path = '/op/v0/device/real/query';
213
+ const milliseconds = new Date().getTime();
214
+
215
+ // WICHTIG: Die Signatur verwendet buchstäbliche Zeichen "\\r\\n", NICHT echte Zeilenumbrüche!
216
+ const signatureString = `${path}\\r\\n${this.config.token}\\r\\n${milliseconds}`;
217
+ const signature = crypto.createHash('md5').update(signatureString).digest('hex');
218
+
219
+ const options = {
220
+ headers: {
221
+ 'Content-Type': 'application/json',
222
+ token: this.config.token,
223
+ timestamp: milliseconds,
224
+ signature: signature,
225
+ lang: 'en',
226
+ },
227
+ hostname: 'www.foxesscloud.com',
228
+ method: 'POST',
229
+ path: path,
230
+ port: 443,
231
+ };
232
+
233
+ const request = https.request(options, response => {
234
+ response.setEncoding('utf8');
235
+
236
+ let responseData = '';
237
+
238
+ response.on('data', chunk => {
239
+ responseData += chunk;
240
+ });
241
+
242
+ response.on('end', () => {
243
+ try {
244
+ if (!responseData) {
245
+ this.log.error('No data received from API');
246
+ this.setState('info.connection', false, true);
247
+ return;
248
+ }
249
+
250
+ const json = JSON.parse(responseData);
251
+
252
+ // Check if API response is valid
253
+ if (!json.result || !json.result[0] || !json.result[0].datas) {
254
+ this.log.error('Invalid API response - missing result/datas structure');
255
+ this.log.error(`API response: ${JSON.stringify(json, null, 2)}`);
256
+ if (json.errno !== undefined) {
257
+ this.log.error(`API error code: ${json.errno}`);
258
+ }
259
+ if (json.msg !== undefined) {
260
+ this.log.error(`API error message: ${json.msg}`);
261
+ }
262
+ this.setState('info.connection', false, true);
263
+ return;
264
+ }
265
+
266
+ const datas = json.result[0].datas;
267
+
268
+ // Update connection state
269
+ this.setState('info.connection', true, true);
270
+
271
+ // Update all states
272
+ if (datas.length > 0 && datas[0] && datas[0].value !== undefined) {
273
+ const pvPower = parseFloat(datas[0].value.toFixed(3));
274
+ this.setState('pvPower', pvPower, true);
275
+ }
276
+
277
+ if (datas.length > 1 && datas[1] && datas[1].value !== undefined) {
278
+ const genPower = parseFloat(datas[1].value.toFixed(3));
279
+ this.setState('generationPower', genPower, true);
280
+ }
281
+
282
+ if (datas.length > 2 && datas[2] && datas[2].value !== undefined) {
283
+ const soc = datas[2].value;
284
+ this.setState('soc', soc, true);
285
+ }
286
+
287
+ if (datas.length > 3 && datas[3] && datas[3].value !== undefined) {
288
+ const load = parseFloat(datas[3].value.toFixed(3));
289
+ this.setState('load', load, true);
290
+ }
291
+
292
+ if (datas.length > 4 && datas[4] && datas[4].value !== undefined) {
293
+ const gridCons = parseFloat(datas[4].value.toFixed(3));
294
+ this.setState('gridConsumption', gridCons, true);
295
+ }
296
+
297
+ if (datas.length > 5 && datas[5] && datas[5].value !== undefined) {
298
+ const feedin = parseFloat(datas[5].value.toFixed(3));
299
+ this.setState('feedinPower', feedin, true);
300
+ }
301
+
302
+ if (datas.length > 6 && datas[6] && datas[6].value !== undefined) {
303
+ const charge = parseFloat(datas[6].value.toFixed(3));
304
+ this.setState('batCharge', charge, true);
305
+ }
306
+
307
+ if (datas.length > 7 && datas[7] && datas[7].value !== undefined) {
308
+ const discharge = parseFloat(datas[7].value.toFixed(3));
309
+ this.setState('batDischarge', discharge, true);
310
+ }
311
+
312
+ this.log.debug('Data successfully updated');
313
+ } catch (parseError) {
314
+ this.log.error(
315
+ `Error processing API response: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
316
+ );
317
+ this.log.error(`Raw data: ${responseData}`);
318
+ this.setState('info.connection', false, true);
319
+ }
320
+ });
321
+
322
+ response.on('error', err => {
323
+ this.log.error(`Response error: ${err.message}`);
324
+ this.setState('info.connection', false, true);
325
+ });
326
+ });
327
+
328
+ request.on('error', err => {
329
+ this.log.error(`Request error: ${err.message}`);
330
+ this.setState('info.connection', false, true);
331
+ });
332
+
333
+ request.write(data);
334
+ request.end();
335
+ } catch (e) {
336
+ this.log.error(`Exception error: ${e instanceof Error ? e.message : String(e)}`);
337
+ if (e instanceof Error && e.stack) {
338
+ this.log.error(e.stack);
339
+ }
340
+ this.setState('info.connection', false, true);
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Is called when adapter shuts down - callback has to be called under any circumstances!
346
+ *
347
+ * @param {() => void} callback - Callback function to call when cleanup is complete
348
+ */
349
+ onUnload(callback) {
350
+ try {
351
+ // Clear interval
352
+ if (this.updateInterval) {
353
+ clearInterval(this.updateInterval);
354
+ this.updateInterval = null;
355
+ }
356
+
357
+ callback();
358
+ } catch {
359
+ callback();
360
+ }
361
+ }
362
+ }
363
+
364
+ if (require.main !== module) {
365
+ // Export the constructor in compact mode
366
+ /**
367
+ * @param {Partial<utils.AdapterOptions>} [options] - Adapter options
368
+ */
369
+ module.exports = options => new Foxesscloud(options);
370
+ } else {
371
+ // otherwise start the instance directly
372
+ new Foxesscloud();
373
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "iobroker.foxesscloud",
3
+ "version": "0.1.0",
4
+ "description": "ioBroker adapter to retrieve data from FoxESS Cloud API",
5
+ "author": {
6
+ "name": "skvarel",
7
+ "email": "skvarel@inventwo.com"
8
+ },
9
+ "homepage": "https://github.com/inventwo/ioBroker.foxesscloud",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "ioBroker",
13
+ "FoxESS",
14
+ "Enpal",
15
+ "Solar",
16
+ "PV",
17
+ "Battery",
18
+ "Energy"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/inventwo/ioBroker.foxesscloud.git"
23
+ },
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
27
+ "dependencies": {
28
+ "@iobroker/adapter-core": "^3.3.2"
29
+ },
30
+ "devDependencies": {
31
+ "@alcalzone/release-script": "^5.0.0",
32
+ "@alcalzone/release-script-plugin-iobroker": "^4.0.0",
33
+ "@alcalzone/release-script-plugin-license": "^4.0.0",
34
+ "@alcalzone/release-script-plugin-manual-review": "^4.0.0",
35
+ "@iobroker/adapter-dev": "^1.5.0",
36
+ "@iobroker/eslint-config": "^2.2.0",
37
+ "@iobroker/testing": "^5.2.2",
38
+ "dotenv": "^17.2.3",
39
+ "eslint": "^9.0.0",
40
+ "mocha": "^10.2.0",
41
+ "should": "^13.2.3"
42
+ },
43
+ "main": "main.js",
44
+ "files": [
45
+ "admin/",
46
+ "main.js",
47
+ "io-package.json",
48
+ "LICENSE"
49
+ ],
50
+ "scripts": {
51
+ "test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js}\"",
52
+ "test:package": "mocha test/package --exit",
53
+ "test:integration": "mocha test/integration --exit",
54
+ "test": "npm run test:package",
55
+ "check": "tsc --noEmit -p tsconfig.check.json",
56
+ "check:ts": "tsc --noEmit",
57
+ "lint": "eslint --ext .js,.jsx",
58
+ "translate": "translate-adapter",
59
+ "release": "release-script",
60
+ "release:patch": "release-script patch --yes",
61
+ "release:minor": "release-script minor --yes",
62
+ "release:major": "release-script major --yes"
63
+ }
64
+ }