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 +21 -0
- package/README.md +106 -0
- package/admin/adapter-config.d.ts +15 -0
- package/admin/foxesscloud-logo.png +0 -0
- package/admin/foxesscloud.png +0 -0
- package/admin/i18n/de.json +9 -0
- package/admin/i18n/en.json +9 -0
- package/admin/i18n/es.json +9 -0
- package/admin/i18n/fr.json +9 -0
- package/admin/i18n/it.json +9 -0
- package/admin/i18n/nl.json +9 -0
- package/admin/i18n/pl.json +9 -0
- package/admin/i18n/pt.json +9 -0
- package/admin/i18n/ru.json +9 -0
- package/admin/i18n/uk.json +9 -0
- package/admin/i18n/zh-cn.json +9 -0
- package/admin/jsonConfig.json +44 -0
- package/io-package.json +117 -0
- package/main.js +373 -0
- package/package.json +64 -0
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
|
+

|
|
2
|
+
|
|
3
|
+
## ioBroker adapter for FoxESS Cloud
|
|
4
|
+
|
|
5
|
+
 
|
|
6
|
+
[](https://www.npmjs.com/package/iobroker.foxesscloud)
|
|
7
|
+
[](https://www.npmjs.com/package/iobroker.foxesscloud)
|
|
8
|
+
|
|
9
|
+
[](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
|
+
}
|
package/io-package.json
ADDED
|
@@ -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
|
+
}
|