iobroker.apg-info 0.1.21 → 0.1.22

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/CHANGELOG_OLD.md CHANGED
@@ -1,4 +1,7 @@
1
1
  # Older changes
2
+ ## 0.1.17 (2025-06-03)
3
+ * (HGlab01) Add retry mechanism for Entsoe
4
+
2
5
  ## 0.1.16 (2025-05-18)
3
6
  * (HGlab01) Optimize Entsoe (Swiss market) requests
4
7
  * (HGlab01) Extend timeout for Api calls to 30 seconds
package/README.md CHANGED
@@ -34,6 +34,10 @@ For more details check https://transparency.entsoe.eu/content/static_content/Sta
34
34
  Placeholder for the next version (at the beginning of the line):
35
35
  ### __WORK IN PROGRESS__
36
36
  -->
37
+ ### 0.1.22 (2025-10-21)
38
+ * (HGlab01) Implement retry mechanism for API calls
39
+ * (HGlab01) add turn on/off for peak hours and market prices
40
+
37
41
  ### 0.1.21 (2025-10-13)
38
42
  * (HGlab01) Support quater-hourly tarifs
39
43
  * (HGlab01) Bump iobroker-jsonexplorer to 0.2.1
@@ -49,9 +53,6 @@ For more details check https://transparency.entsoe.eu/content/static_content/Sta
49
53
  * (HGlab01) Log finetuning
50
54
  * (HGlab01) Bump axios to 1.10.0
51
55
 
52
- ### 0.1.17 (2025-06-03)
53
- * (HGlab01) Add retry mechanism for Entsoe
54
-
55
56
  ## License
56
57
  MIT License
57
58
 
@@ -1,263 +1,286 @@
1
- {
2
- "i18n": false,
3
- "type": "tabs",
4
- "tabsStyle": {
5
- "width": "calc(100% - 100px)"
6
- },
7
- "items": {
8
- "General": {
9
- "type": "panel",
10
- "label": "General",
11
- "items": {
12
- "threshold": {
13
- "type": "number",
14
- "label": {
15
- "en": "Threshold market price (€ct/kWh)",
16
- "de": "Schwellenmarktpreis (€ct/kWh)",
17
- "ru": "Порог рыночная цена (€ct/kWh)",
18
- "pt": "Preço de mercado limiar (€ct/kWh)",
19
- "nl": "Dreig de marktprijs",
20
- "fr": "Prix du marché seuil (€ct/kWh)",
21
- "it": "Prezzo di mercato (€ct/kWh)",
22
- "es": "Precio del mercado de propiedad (€ct/kWh)",
23
- "pl": "Cena rynkowa (ok",
24
- "uk": "Потрібна ціна ринку (€ct/kWh)",
25
- "zh-cn": "市场价格(ct/kWh)"
26
- },
27
- "min": 0,
28
- "step": 0.1,
29
- "newLine": true,
30
- "xs": 12,
31
- "sm": 12,
32
- "md": 2,
33
- "lg": 1,
34
- "xl": 1
35
- },
36
- "country": {
37
- "type": "select",
38
- "options": [
39
- {
40
- "label": {
41
- "en": "Swiss",
42
- "de": "Schweiz",
43
- "ru": "Swiss",
44
- "pt": "Swiss",
45
- "nl": "Swiss",
46
- "fr": "Swiss",
47
- "it": "Swiss",
48
- "es": "Swiss",
49
- "pl": "Swiss",
50
- "uk": "Swiss",
51
- "zh-cn": "Swiss"
52
- },
53
- "value": "ch"
54
- },
55
- {
56
- "label": {
57
- "en": "Austria",
58
- "de": "Österreich",
59
- "ru": "Austria",
60
- "pt": "Austria",
61
- "nl": "Austria",
62
- "fr": "Austria",
63
- "it": "Austria",
64
- "es": "Austria",
65
- "pl": "Austria",
66
- "uk": "Austria",
67
- "zh-cn": "Austria"
68
- },
69
- "value": "at"
70
- },
71
- {
72
- "label": {
73
- "en": "Germany",
74
- "de": "Deutschland",
75
- "ru": "Germany",
76
- "pt": "Germany",
77
- "nl": "Germany",
78
- "fr": "Germany",
79
- "it": "Germany",
80
- "es": "Germany",
81
- "pl": "Germany",
82
- "uk": "Germany",
83
- "zh-cn": "Germany"
84
- },
85
- "value": "de"
86
- }
87
- ],
88
- "label": {
89
- "en": "Country of the market",
90
- "de": "Land der Strombörse"
91
- },
92
- "xs": 12,
93
- "sm": 12,
94
- "md": 6,
95
- "lg": 4,
96
- "xl": 4
97
- },
98
- "forecast": {
99
- "type": "checkbox",
100
- "label": "Forecast based on 10.15 auction (works in AT and DE)",
101
- "default": false,
102
- "xs": 12,
103
- "sm": 12,
104
- "md": 6,
105
- "lg": 4,
106
- "xl": 4
107
- }
108
- }
109
- },
110
- "Calculation": {
111
- "type": "panel",
112
- "label": "Calculation",
113
- "items": {
114
- "calculate": {
115
- "newLine": true,
116
- "type": "checkbox",
117
- "label": {
118
- "en": "Activate price calculation",
119
- "de": "Preiskalkulation aktivieren"
120
- },
121
- "xs": 12,
122
- "sm": 12,
123
- "md": 6,
124
- "lg": 4,
125
- "xl": 4
126
- },
127
- "feeAbsolute": {
128
- "newLine": true,
129
- "type": "number",
130
- "label": {
131
- "en": "Provider fee absolute (€ct/kWh)",
132
- "de": "Provider Gebühr Absolut (€ct/kWh)"
133
- },
134
- "hidden": "!data.calculate",
135
- "xs": 12,
136
- "sm": 12,
137
- "md": 2,
138
- "lg": 1,
139
- "xl": 1
140
- },
141
- "feeRelative": {
142
- "type": "number",
143
- "label": {
144
- "en": "Provider fee (%)",
145
- "de": "Provider Gebühr (%)"
146
- },
147
- "hidden": "!data.calculate",
148
- "xs": 12,
149
- "sm": 12,
150
- "md": 2,
151
- "lg": 1,
152
- "xl": 1
153
- },
154
- "charges": {
155
- "type": "number",
156
- "label": {
157
- "en": "other taxes (%)",
158
- "de": "weitere Abgaben (%)"
159
- },
160
- "hidden": "!data.calculate",
161
- "xs": 12,
162
- "sm": 12,
163
- "md": 2,
164
- "lg": 1,
165
- "xl": 1
166
- },
167
- "gridCosts": {
168
- "type": "number",
169
- "label": {
170
- "en": "grid costs (€ct/kWh)",
171
- "de": "Netzentgelte (€ct/kWh)"
172
- },
173
- "hidden": "!data.calculate",
174
- "xs": 12,
175
- "sm": 12,
176
- "md": 2,
177
- "lg": 1,
178
- "xl": 1
179
- },
180
- "vat": {
181
- "type": "number",
182
- "label": {
183
- "en": "VAT (%)",
184
- "de": "USt (%)"
185
- },
186
- "hidden": "!data.calculate",
187
- "xs": 12,
188
- "sm": 12,
189
- "md": 2,
190
- "lg": 1,
191
- "xl": 1
192
- },
193
- "_text0": {
194
- "type": "staticText",
195
- "text": "",
196
- "newLine": true,
197
- "hidden": "!data.calculate",
198
- "xs": 12,
199
- "sm": 12,
200
- "md": 12,
201
- "lg": 12,
202
- "xl": 12
203
- },
204
- "_text1": {
205
- "type": "staticText",
206
- "text": {
207
- "de": "Calculation: <br> provider = tradePrice * Abs(tradePrice * providerFee) + feeAbsolute; <br> charges = (tradePrice + provider) * otherTaxes; <br> vat = (tradePrice + provider + charges + gridCosts) * vat; <br> price = tradePrice + provider + charges + gridCosts + vat;",
208
- "en": "Calculation: <br> provider = tradePrice * tradePrice * providerFee + feeAbsolute; <br> charges = (tradePrice + provider) * otherTaxes; <br> vat = (tradePrice + provider + charges + gridCosts) * vat; <br> price = tradePrice + provider + charges + gridCosts + vat;"
209
- },
210
- "newLine": true,
211
- "hidden": "!data.calculate",
212
- "xs": 12,
213
- "sm": 12,
214
- "md": 12,
215
- "lg": 12,
216
- "xl": 12
217
- }
218
- }
219
- },
220
- "EntsoeToken": {
221
- "type": "panel",
222
- "label": "Entsoe Token",
223
- "items": {
224
- "tokenEncrypted": {
225
- "newLine": true,
226
- "type": "text",
227
- "label": {
228
- "en": "Token",
229
- "de": "Token"
230
- },
231
- "xs": 12,
232
- "sm": 12,
233
- "md": 6,
234
- "lg": 4,
235
- "xl": 4
236
- },
237
- "_text0": {
238
- "type": "staticText",
239
- "text": "",
240
- "newLine": true,
241
- "xs": 12,
242
- "sm": 12,
243
- "md": 12,
244
- "lg": 12,
245
- "xl": 12
246
- },
247
- "_text1": {
248
- "type": "staticText",
249
- "text": {
250
- "de": "Token is only needed for Swiss market. How to request a token is described in the readme.",
251
- "en": "Token is only needed for Swiss market. How to request a token is described in the readme."
252
- },
253
- "newLine": true,
254
- "xs": 12,
255
- "sm": 12,
256
- "md": 12,
257
- "lg": 12,
258
- "xl": 12
259
- }
260
- }
261
- }
262
- }
263
- }
1
+ {
2
+ "i18n": false,
3
+ "type": "tabs",
4
+ "tabsStyle": {
5
+ "width": "calc(100% - 100px)"
6
+ },
7
+ "items": {
8
+ "General": {
9
+ "type": "panel",
10
+ "label": "General",
11
+ "items": {
12
+ "threshold": {
13
+ "type": "number",
14
+ "label": {
15
+ "en": "Threshold market price (€ct/kWh)",
16
+ "de": "Schwellenmarktpreis (€ct/kWh)",
17
+ "ru": "Порог рыночная цена (€ct/kWh)",
18
+ "pt": "Preço de mercado limiar (€ct/kWh)",
19
+ "nl": "Dreig de marktprijs",
20
+ "fr": "Prix du marché seuil (€ct/kWh)",
21
+ "it": "Prezzo di mercato (€ct/kWh)",
22
+ "es": "Precio del mercado de propiedad (€ct/kWh)",
23
+ "pl": "Cena rynkowa (ok",
24
+ "uk": "Потрібна ціна ринку (€ct/kWh)",
25
+ "zh-cn": "市场价格(ct/kWh)"
26
+ },
27
+ "min": 0,
28
+ "step": 0.1,
29
+ "newLine": true,
30
+ "xs": 12,
31
+ "sm": 12,
32
+ "md": 3,
33
+ "lg": 2,
34
+ "xl": 2
35
+ },
36
+ "country": {
37
+ "type": "select",
38
+ "options": [
39
+ {
40
+ "label": {
41
+ "en": "Swiss",
42
+ "de": "Schweiz",
43
+ "ru": "Swiss",
44
+ "pt": "Swiss",
45
+ "nl": "Swiss",
46
+ "fr": "Swiss",
47
+ "it": "Swiss",
48
+ "es": "Swiss",
49
+ "pl": "Swiss",
50
+ "uk": "Swiss",
51
+ "zh-cn": "Swiss"
52
+ },
53
+ "value": "ch"
54
+ },
55
+ {
56
+ "label": {
57
+ "en": "Austria",
58
+ "de": "Österreich",
59
+ "ru": "Austria",
60
+ "pt": "Austria",
61
+ "nl": "Austria",
62
+ "fr": "Austria",
63
+ "it": "Austria",
64
+ "es": "Austria",
65
+ "pl": "Austria",
66
+ "uk": "Austria",
67
+ "zh-cn": "Austria"
68
+ },
69
+ "value": "at"
70
+ },
71
+ {
72
+ "label": {
73
+ "en": "Germany",
74
+ "de": "Deutschland",
75
+ "ru": "Germany",
76
+ "pt": "Germany",
77
+ "nl": "Germany",
78
+ "fr": "Germany",
79
+ "it": "Germany",
80
+ "es": "Germany",
81
+ "pl": "Germany",
82
+ "uk": "Germany",
83
+ "zh-cn": "Germany"
84
+ },
85
+ "value": "de"
86
+ }
87
+ ],
88
+ "label": {
89
+ "en": "Country of the market",
90
+ "de": "Land der Strombörse"
91
+ },
92
+ "xs": 12,
93
+ "sm": 12,
94
+ "md": 6,
95
+ "lg": 4,
96
+ "xl": 4
97
+ },
98
+ "forecast": {
99
+ "newLine": true,
100
+ "type": "checkbox",
101
+ "label": "Forecast based on 10.15 auction (works in AT and DE)",
102
+ "default": false,
103
+ "xs": 12,
104
+ "sm": 12,
105
+ "md": 6,
106
+ "lg": 4,
107
+ "xl": 4
108
+ },
109
+ "peakHours": {
110
+ "newLine": true,
111
+ "type": "checkbox",
112
+ "label": "Check peak hours (works for Austria)",
113
+ "default": false,
114
+ "xs": 12,
115
+ "sm": 12,
116
+ "md": 6,
117
+ "lg": 4,
118
+ "xl": 4
119
+ },
120
+ "marketPrices": {
121
+ "newLine": true,
122
+ "type": "checkbox",
123
+ "label": "Check market prices",
124
+ "default": false,
125
+ "xs": 12,
126
+ "sm": 12,
127
+ "md": 6,
128
+ "lg": 4,
129
+ "xl": 4
130
+ }
131
+ }
132
+ },
133
+ "Calculation": {
134
+ "type": "panel",
135
+ "label": "Calculation",
136
+ "items": {
137
+ "calculate": {
138
+ "newLine": true,
139
+ "type": "checkbox",
140
+ "label": {
141
+ "en": "Activate price calculation",
142
+ "de": "Preiskalkulation aktivieren"
143
+ },
144
+ "xs": 12,
145
+ "sm": 12,
146
+ "md": 6,
147
+ "lg": 4,
148
+ "xl": 4
149
+ },
150
+ "feeAbsolute": {
151
+ "newLine": true,
152
+ "type": "number",
153
+ "label": {
154
+ "en": "Provider fee absolute (€ct/kWh)",
155
+ "de": "Provider Gebühr Absolut (€ct/kWh)"
156
+ },
157
+ "hidden": "!data.calculate",
158
+ "xs": 12,
159
+ "sm": 12,
160
+ "md": 3,
161
+ "lg": 2,
162
+ "xl": 2
163
+ },
164
+ "feeRelative": {
165
+ "type": "number",
166
+ "label": {
167
+ "en": "Provider fee (%)",
168
+ "de": "Provider Gebühr (%)"
169
+ },
170
+ "hidden": "!data.calculate",
171
+ "xs": 12,
172
+ "sm": 12,
173
+ "md": 3,
174
+ "lg": 2,
175
+ "xl": 2
176
+ },
177
+ "charges": {
178
+ "type": "number",
179
+ "label": {
180
+ "en": "other taxes (%)",
181
+ "de": "weitere Abgaben (%)"
182
+ },
183
+ "hidden": "!data.calculate",
184
+ "xs": 12,
185
+ "sm": 12,
186
+ "md": 3,
187
+ "lg": 2,
188
+ "xl": 2
189
+ },
190
+ "gridCosts": {
191
+ "type": "number",
192
+ "label": {
193
+ "en": "grid costs (€ct/kWh)",
194
+ "de": "Netzentgelte (€ct/kWh)"
195
+ },
196
+ "hidden": "!data.calculate",
197
+ "xs": 12,
198
+ "sm": 12,
199
+ "md": 3,
200
+ "lg": 2,
201
+ "xl": 2
202
+ },
203
+ "vat": {
204
+ "type": "number",
205
+ "label": {
206
+ "en": "VAT (%)",
207
+ "de": "USt (%)"
208
+ },
209
+ "hidden": "!data.calculate",
210
+ "xs": 12,
211
+ "sm": 12,
212
+ "md": 3,
213
+ "lg": 2,
214
+ "xl": 2
215
+ },
216
+ "_text0": {
217
+ "type": "staticText",
218
+ "text": "",
219
+ "newLine": true,
220
+ "hidden": "!data.calculate",
221
+ "xs": 12,
222
+ "sm": 12,
223
+ "md": 12,
224
+ "lg": 12,
225
+ "xl": 12
226
+ },
227
+ "_text1": {
228
+ "type": "staticText",
229
+ "text": {
230
+ "de": "Calculation: <br> provider = tradePrice * Abs(tradePrice * providerFee) + feeAbsolute; <br> charges = (tradePrice + provider) * otherTaxes; <br> vat = (tradePrice + provider + charges + gridCosts) * vat; <br> price = tradePrice + provider + charges + gridCosts + vat;",
231
+ "en": "Calculation: <br> provider = tradePrice * tradePrice * providerFee + feeAbsolute; <br> charges = (tradePrice + provider) * otherTaxes; <br> vat = (tradePrice + provider + charges + gridCosts) * vat; <br> price = tradePrice + provider + charges + gridCosts + vat;"
232
+ },
233
+ "newLine": true,
234
+ "hidden": "!data.calculate",
235
+ "xs": 12,
236
+ "sm": 12,
237
+ "md": 12,
238
+ "lg": 12,
239
+ "xl": 12
240
+ }
241
+ }
242
+ },
243
+ "EntsoeToken": {
244
+ "type": "panel",
245
+ "label": "Entsoe Token",
246
+ "items": {
247
+ "tokenEncrypted": {
248
+ "newLine": true,
249
+ "type": "text",
250
+ "label": {
251
+ "en": "Token",
252
+ "de": "Token"
253
+ },
254
+ "xs": 12,
255
+ "sm": 12,
256
+ "md": 6,
257
+ "lg": 4,
258
+ "xl": 4
259
+ },
260
+ "_text0": {
261
+ "type": "staticText",
262
+ "text": "",
263
+ "newLine": true,
264
+ "xs": 12,
265
+ "sm": 12,
266
+ "md": 12,
267
+ "lg": 12,
268
+ "xl": 12
269
+ },
270
+ "_text1": {
271
+ "type": "staticText",
272
+ "text": {
273
+ "de": "Token is only needed for Swiss market. How to request a token is described in the readme.",
274
+ "en": "Token is only needed for Swiss market. How to request a token is described in the readme."
275
+ },
276
+ "newLine": true,
277
+ "xs": 12,
278
+ "sm": 12,
279
+ "md": 12,
280
+ "lg": 12,
281
+ "xl": 12
282
+ }
283
+ }
284
+ }
285
+ }
286
+ }
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "apg-info",
4
- "version": "0.1.21",
4
+ "version": "0.1.22",
5
5
  "news": {
6
+ "0.1.22-alpha.0": {
7
+ "en": "Implement retry mechanism for API calls\nadd turn on/off for peak hours and market prices",
8
+ "de": "Implementieren Retry-Mechanismus für API-Anrufe\nan-/ausschalten für spitzenzeiten und marktpreise",
9
+ "ru": "Внедрение механизма retry для вызовов API\nдобавить включение / выключение в часы пик и рыночные цены",
10
+ "pt": "Implementar mecanismo de repetição para chamadas de API\nadicionar ligar/desligar para horas de pico e preços de mercado",
11
+ "nl": "Implementeer opnieuw proberen mechanisme voor API-oproepen\nhet toevoegen van aan/uit voor piekuren en marktprijzen",
12
+ "fr": "Mettre en place un mécanisme de ré-essai pour les appels API\najouter la mise en marche/arrêt pour les heures de pointe et les prix du marché",
13
+ "it": "Meccanismo di implementazione per le chiamate API\naggiungere turn on/off per ore di punta e prezzi di mercato",
14
+ "es": "Implementar el mecanismo de reingreso para llamadas API\nañadir encendido / apagado para horas pico y precios del mercado",
15
+ "pl": "Wdrożenie mechanizmu ponownego testowania połączeń API\ndodać włączenie / wyłączenie dla godzin szczytu i cen rynkowych",
16
+ "uk": "Впровадження механізму птиху для викликів API\nдодайте зворотній зв’язок за попередні години та ринкові ціни",
17
+ "zh-cn": "执行 API 调用重试机制\n增加高峰时数和市场价格的开启/关闭"
18
+ },
19
+ "0.1.22": {
20
+ "en": "Implement retry mechanism for API calls\nadd turn on/off for peak hours and market prices",
21
+ "de": "Implementieren Retry-Mechanismus für API-Anrufe\nan-/ausschalten für spitzenzeiten und marktpreise",
22
+ "ru": "Внедрение механизма retry для вызовов API\nдобавить включение / выключение в часы пик и рыночные цены",
23
+ "pt": "Implementar mecanismo de repetição para chamadas de API\nadicionar ligar/desligar para horas de pico e preços de mercado",
24
+ "nl": "Implementeer opnieuw proberen mechanisme voor API-oproepen\nhet toevoegen van aan/uit voor piekuren en marktprijzen",
25
+ "fr": "Mettre en place un mécanisme de ré-essai pour les appels API\najouter la mise en marche/arrêt pour les heures de pointe et les prix du marché",
26
+ "it": "Meccanismo di implementazione per le chiamate API\naggiungere turn on/off per ore di punta e prezzi di mercato",
27
+ "es": "Implementar el mecanismo de reingreso para llamadas API\nañadir encendido / apagado para horas pico y precios del mercado",
28
+ "pl": "Wdrożenie mechanizmu ponownego testowania połączeń API\ndodać włączenie / wyłączenie dla godzin szczytu i cen rynkowych",
29
+ "uk": "Впровадження механізму птиху для викликів API\nдодайте зворотній зв’язок за попередні години та ринкові ціни",
30
+ "zh-cn": "执行 API 调用重试机制\n增加高峰时数和市场价格的开启/关闭"
31
+ },
6
32
  "0.1.21": {
7
33
  "en": "Support quater-hourly tarifs\nBump iobroker-jsonexplorer to 0.2.1",
8
34
  "de": "Vier-Stunden-Preise unterstützen\nBump iobroker-jsonexplorer auf 0,2.1",
@@ -67,32 +93,6 @@
67
93
  "pl": "prekursor json- explorer v0.2 gotowość\nAksykony bumpy do 1.12.2",
68
94
  "uk": "prepeare json-explorer v0.2 готовність\nБампер аксіос до 1.12.2",
69
95
  "zh-cn": "预选json-探索器 v0.2 准备状态\n横轴改为1.12.2"
70
- },
71
- "0.1.20-alpha.0": {
72
- "en": "prepeare json-explorer v0.2 readiness\nBump axios to 1.12.2",
73
- "de": "prepeare json-explorer v0.2 bereitschaft\nBump axios zu 1.12.2",
74
- "ru": "предварительная готовность json-explorer v0.2\nАксиос накачки до 1.12.2",
75
- "pt": "prepeare json-explorer v0.2 pronto\nBump axios a 1.12.2",
76
- "nl": "prepeare json-explorer v0.2 gereedheid\nBump axios naar 1.12.2",
77
- "fr": "prepeare json-explorer v0.2 préparation\nAxios de pompe au 1.12.2",
78
- "it": "prepeare json-explorer v0.2 prontezza\nAssio di bump a 1.12.2",
79
- "es": "prepeare json-explorer v0.2 preparedness\nAxios de bomba a 1.12.2",
80
- "pl": "prekursor json- explorer v0.2 gotowość\nAksykony bumpy do 1.12.2",
81
- "uk": "prepeare json-explorer v0.2 готовність\nБампер аксіос до 1.12.2",
82
- "zh-cn": "预选json-探索器 v0.2 准备状态\n横轴改为1.12.2"
83
- },
84
- "0.1.19": {
85
- "en": "use encrypted token in config",
86
- "de": "verwendung verschlüsselter token in config",
87
- "ru": "использовать зашифрованный токен в конфигурации",
88
- "pt": "usar o token criptografado na configuração",
89
- "nl": "gebruik versleuteld token in config",
90
- "fr": "utiliser un jeton chiffré dans config",
91
- "it": "utilizzare token crittografato in configurazione",
92
- "es": "uso encriptado token en config",
93
- "pl": "użyj szyfrowanego symbolu w konfigu",
94
- "uk": "використовуйте зашифрований токен в конфігурації",
95
- "zh-cn": "在配置中使用加密的令牌"
96
96
  }
97
97
  },
98
98
  "titleLang": {
@@ -184,6 +184,8 @@
184
184
  "threshold": 10,
185
185
  "country": "at",
186
186
  "forecast": false,
187
+ "peakHours": true,
188
+ "marketPrices": true,
187
189
  "calculate": false,
188
190
  "feeAbsolute": 1.49,
189
191
  "feeRelative": 0,
package/main.js CHANGED
@@ -10,9 +10,10 @@ const { version } = require('./package.json');
10
10
 
11
11
  // Constants
12
12
  const MAX_DELAY = 25000; //25000
13
+ const API_TIMEOUT = 20000; //20000
13
14
 
14
15
  // @ts-expect-error axios.create is ok
15
- const axiosInstance = axios.create({ timeout: 30000 }); //30000
16
+ const axiosInstance = axios.create({ timeout: API_TIMEOUT });
16
17
 
17
18
  class ApgInfo extends utils.Adapter {
18
19
  /**
@@ -62,6 +63,8 @@ class ApgInfo extends utils.Adapter {
62
63
  }
63
64
 
64
65
  this.calculate = this.config.calculate ?? false;
66
+ this.peakHours = this.config.peakHours ?? false;
67
+ this.marketPrices = this.config.marketPrices ?? false;
65
68
 
66
69
  if (this.calculate == true) {
67
70
  this.feeAbsolute = this.config.feeAbsolute ?? 0;
@@ -114,8 +117,8 @@ class ApgInfo extends utils.Adapter {
114
117
  this.log.info(`Delay execution by ${callApiDelay}ms to better spread API calls`);
115
118
  await jsonExplorer.sleep(callApiDelay);
116
119
  await jsonExplorer.setLastStartTime();
117
- const resultPeakHours = await this.executeRequestPeakHours();
118
- const resultMarketPrice = await this.executeMarketPrice(country, forecast);
120
+
121
+ const [resultPeakHours, resultMarketPrice] = await Promise.all([this.executeRequestPeakHours(), this.executeMarketPrice(country, forecast)]);
119
122
 
120
123
  if (resultPeakHours == 'error' || resultMarketPrice == 'error') {
121
124
  this.terminate ? this.terminate(utils.EXIT_CODES.UNCAUGHT_EXCEPTION) : process.exit(0);
@@ -139,22 +142,47 @@ class ApgInfo extends utils.Adapter {
139
142
  }
140
143
  }
141
144
 
142
- /*
143
145
  /**
144
- * Is called if a subscribed state changes
145
- * @param {string} id
146
- * @param {ioBroker.State | null | undefined} state
146
+ * Makes an API call with retry logic.
147
+ *
148
+ * @param {string} uri The URI to call.
149
+ * @param {string} methodName The name of the calling method for logging.
150
+ * @param {(response: any) => any} processResponse A function to process the successful response.
151
+ * @returns {Promise<any>} The processed response or null on final server error.
147
152
  */
148
- /*
149
- onStateChange(id, state) {
150
- if (state) {
151
- // The state was changed
152
- this.log.debug(`state ${id} changed: ${state.val} (ack = ${state.ack})`);
153
- } else {
154
- // The state was deleted
155
- this.log.debug(`state ${id} deleted`);
153
+ async _apiCallWithRetry(uri, methodName, processResponse) {
154
+ let attempts = 0;
155
+ const maxAttempts = 3;
156
+ let delay = 10 * 1000; // 10 seconds
157
+
158
+ while (attempts < maxAttempts) {
159
+ try {
160
+ const response = await axiosInstance.get(uri);
161
+ if (response?.data == null) {
162
+ throw new Error(`Respone empty for URL ${uri} with status code ${response.status}`);
163
+ }
164
+ this.log.debug(`Response in ${methodName}(): [${response.status}] ${JSON.stringify(response.data)}`);
165
+ console.log(`Response in ${methodName}(): [${response.status}] ${JSON.stringify(response.data)}`);
166
+ return processResponse(response);
167
+ } catch (error) {
168
+ attempts++;
169
+ if (attempts >= maxAttempts) {
170
+ // @ts-expect-error response may exist
171
+ const errorMessage = error.response?.data ? `with response ${JSON.stringify(error.response.data)}` : '';
172
+ this.log.error(`Error in ${methodName}() attempt ${attempts}/${maxAttempts}: ${error} ${errorMessage}`);
173
+ console.error(`Error in ${methodName}() attempt ${attempts}/${maxAttempts}: ${error} ${errorMessage}`);
174
+ // @ts-expect-error response may exist
175
+ if (error.response?.status >= 500) {
176
+ return null; // On final attempt for server errors, resolve with null
177
+ }
178
+ throw error; // Otherwise rethrow
179
+ }
180
+ this.log.info(`Retrying in ${delay / 1000}s for ${methodName}...`);
181
+ await jsonExplorer.sleep(delay);
182
+ delay *= 2; // Exponential backoff (10s, 20s)
183
+ }
156
184
  }
157
- }*/
185
+ }
158
186
 
159
187
  /**
160
188
  * Retrieves peak hours from REST-API
@@ -163,33 +191,7 @@ class ApgInfo extends utils.Adapter {
163
191
  let uri = `https://awareness.cloud.apg.at/api/v1/PeakHourStatus`;
164
192
  this.log.debug(`API-Call ${uri}`);
165
193
  console.log(`API-Call ${uri}`);
166
- return new Promise((resolve, reject) => {
167
- axiosInstance
168
- .get(uri)
169
- .then(response => {
170
- if (!response || !response.data) {
171
- throw new Error(`getDataPeakHours(): Respone empty for URL ${uri} with status code ${response.status}`);
172
- } else {
173
- this.log.debug(`Response in getDataPeakHours(): [${response.status}] ${JSON.stringify(response.data)}`);
174
- console.log(`Response in getDataPeakHours(): [${response.status}] ${JSON.stringify(response.data)}`);
175
- resolve(response?.data ?? null);
176
- }
177
- })
178
- .catch(error => {
179
- if (error?.response?.data) {
180
- console.error(`Error in getDataPeakHours(): ${error} with response ${JSON.stringify(error.response.data)}`);
181
- this.log.error(`Error to get peak hours ${error} with response ${JSON.stringify(error.response.data)}`);
182
- } else {
183
- console.error(`Error in getDataPeakHours(): ${error}`);
184
- this.log.error(`Error to get peak hours ${error}`);
185
- }
186
- if (error?.response?.status >= 500) {
187
- resolve(null);
188
- } else {
189
- reject(error);
190
- }
191
- });
192
- });
194
+ return this._apiCallWithRetry(uri, 'getDataPeakHours', response => response?.data ?? null);
193
195
  }
194
196
 
195
197
  /**
@@ -198,7 +200,7 @@ class ApgInfo extends utils.Adapter {
198
200
  * @param {boolean} tomorrow true means it is the next day, false means today
199
201
  * @param {string} country country of the market
200
202
  */
201
- async getDataDayAheadExaa(tomorrow, country) {
203
+ async getDataExaa(tomorrow, country) {
202
204
  let day = cleanDate(new Date());
203
205
  if (tomorrow) {
204
206
  day = addDays(day, 1);
@@ -209,33 +211,7 @@ class ApgInfo extends utils.Adapter {
209
211
  this.log.debug(`API-Call ${uri}`);
210
212
  console.log(`API-Call ${uri}`);
211
213
 
212
- return new Promise((resolve, reject) => {
213
- axiosInstance
214
- .get(uri)
215
- .then(response => {
216
- if (!response || !response.data) {
217
- throw new Error(`getDataDayAheadExaa(): Respone empty for URL ${uri} with status code ${response.status}`);
218
- } else {
219
- this.log.debug(`Response in getDataDayAheadExaa(): [${response.status}] ${JSON.stringify(response.data)}`);
220
- console.log(`Response in getDataDayAheadExaa(): [${response.status}] ${JSON.stringify(response.data)}`);
221
- resolve(response?.data?.data ?? null);
222
- }
223
- })
224
- .catch(error => {
225
- if (error?.response?.data) {
226
- console.error(`Error in getDataDayAheadExaa(): ${error} with response ${JSON.stringify(error.response.data)}`);
227
- this.log.error(`Error to get market price (Exaa) ${error} with response ${JSON.stringify(error.response.data)}`);
228
- } else {
229
- console.error(`Error in getDataDayAheadExaa(): ${error}`);
230
- this.log.error(`Error to get market price (Exaa) ${error}`);
231
- }
232
- if (error?.response?.status >= 500) {
233
- resolve(null);
234
- } else {
235
- reject(error);
236
- }
237
- });
238
- });
214
+ return this._apiCallWithRetry(uri, 'getDataExaa', response => response?.data?.data ?? null);
239
215
  }
240
216
 
241
217
  /**
@@ -243,7 +219,7 @@ class ApgInfo extends utils.Adapter {
243
219
  *
244
220
  * @param {string} country country of the market
245
221
  */
246
- async getDataDayAheadExaa1015(country) {
222
+ async getDataExaa1015(country) {
247
223
  country = country.toUpperCase();
248
224
  const day = addDays(cleanDate(new Date()), 1);
249
225
 
@@ -252,36 +228,11 @@ class ApgInfo extends utils.Adapter {
252
228
  this.log.debug(`API-Call ${uri}`);
253
229
  console.log(`API-Call ${uri}`);
254
230
 
255
- return new Promise((resolve, reject) => {
256
- axiosInstance
257
- .get(uri)
258
- .then(response => {
259
- if (!response || !response.data) {
260
- throw new Error(`getDataDayAheadExaa1015(): Respone empty for URL ${uri} with status code ${response.status}`);
261
- } else {
262
- this.log.debug(`Response in getDataDayAheadExaa1015(): [${response.status}] ${JSON.stringify(response.data)}`);
263
- console.log(`Response in getDataDayAheadExaa1015(): [${response.status}] ${JSON.stringify(response.data)}`);
264
- if (country == 'AT') {
265
- resolve(response?.data?.AT?.price ?? null);
266
- } else {
267
- resolve(response?.data?.DE?.price ?? null);
268
- }
269
- }
270
- })
271
- .catch(error => {
272
- if (error?.response?.data) {
273
- console.error(`Error in getDataDayAheadExaa1015(): ${error} with response ${JSON.stringify(error.response.data)}`);
274
- this.log.error(`Error to get market price (Exaa1015) ${error} with response ${JSON.stringify(error.response.data)}`);
275
- } else {
276
- console.error(`Error in getDataDayAheadExaa1015(): ${error}`);
277
- this.log.error(`Error to get market price (Exaa1015) ${error}`);
278
- }
279
- if (error?.response?.status >= 500) {
280
- resolve(null);
281
- } else {
282
- reject(error);
283
- }
284
- });
231
+ return this._apiCallWithRetry(uri, 'getDataExaa1015', response => {
232
+ if (country === 'AT') {
233
+ return response?.data?.AT?.price ?? null;
234
+ }
235
+ return response?.data?.DE?.price ?? null;
285
236
  });
286
237
  }
287
238
 
@@ -291,7 +242,7 @@ class ApgInfo extends utils.Adapter {
291
242
  * @param {boolean} tomorrow true means it is the next day, false means today
292
243
  * @param {string} country country of the market
293
244
  */
294
- async getDataDayAheadAwattar(tomorrow, country) {
245
+ async getDataAwattar(tomorrow, country) {
295
246
  const day0 = cleanDate(new Date());
296
247
  let start = 0;
297
248
  let end = 0;
@@ -313,33 +264,7 @@ class ApgInfo extends utils.Adapter {
313
264
  }
314
265
  this.log.debug(`API-Call ${uri}`);
315
266
  console.log(`API-Call ${uri}`);
316
- return new Promise((resolve, reject) => {
317
- axiosInstance
318
- .get(uri)
319
- .then(response => {
320
- if (!response || !response.data) {
321
- throw new Error(`getDataDayAheadAwattar(): Respone empty for URL ${uri} with status code ${response.status}`);
322
- } else {
323
- this.log.debug(`Response in getDataDayAheadAwattar(): [${response.status}] ${JSON.stringify(response.data)}`);
324
- console.log(`Response in getDataDayAheadAwattar(): [${response.status}] ${JSON.stringify(response.data)}`);
325
- resolve(response.data);
326
- }
327
- })
328
- .catch(error => {
329
- if (error?.response?.data) {
330
- console.error(`Error in getDataDayAheadAwattar(): ${error} with response ${JSON.stringify(error.response.data)}`);
331
- this.log.error(`Error to get market price (Awattar) ${error} with response ${JSON.stringify(error.response.data)}`);
332
- } else {
333
- console.error(`Error in getDataDayAheadAwattar(): ${error}`);
334
- this.log.error(`Error to get market price (Awattar) ${error}`);
335
- }
336
- if (error?.response?.status >= 500) {
337
- resolve(null);
338
- } else {
339
- reject(error);
340
- }
341
- });
342
- });
267
+ return this._apiCallWithRetry(uri, 'getDataAwattar', response => response.data);
343
268
  }
344
269
 
345
270
  /**
@@ -348,7 +273,7 @@ class ApgInfo extends utils.Adapter {
348
273
  * @param {boolean} tomorrow means it is the next day, false means today
349
274
  * @param {string} country country of the market
350
275
  */
351
- async getDataDayAheadEntsoe(tomorrow, country) {
276
+ async getDataEntsoe(tomorrow, country) {
352
277
  const url = 'https://web-api.tp.entsoe.eu/api?documentType=A44';
353
278
  const securityToken = this.token;
354
279
 
@@ -381,29 +306,9 @@ class ApgInfo extends utils.Adapter {
381
306
  this.log.debug(`API-Call ${uri}`);
382
307
  console.log(`API-Call ${uri}`);
383
308
 
384
- return new Promise((resolve, reject) => {
385
- axiosInstance
386
- .get(uri)
387
- .then(response => {
388
- if (!response || !response.data) {
389
- throw new Error(`getDataDayAheadEntsoe(): Respone empty for URL ${uri} with status code ${response.status}`);
390
- } else {
391
- this.log.debug(`Response in getDataDayAheadEntsoe(): [${response.status}] ${JSON.stringify(response.data)}`);
392
- console.log(`Response in getDataDayAheadEntsoe(): [${response.status}] ${JSON.stringify(response.data)}`);
393
- let result = xml2js(response.data);
394
- resolve(result?.Publication_MarketDocument ?? null);
395
- }
396
- })
397
- .catch(error => {
398
- if (error?.response?.data) {
399
- console.error(`Error in getDataDayAheadEntsoe(): ${error} with response ${JSON.stringify(error.response.data)}`);
400
- this.log.warn(`Error to get market price (Entsoe) ${error} with response ${JSON.stringify(error.response.data)}`);
401
- } else {
402
- console.error(`Error in getDataDayAheadEntsoe(): ${error}`);
403
- this.log.warn(`Error to get market price (Entsoe) ${error}`);
404
- }
405
- reject(error);
406
- });
309
+ return this._apiCallWithRetry(uri, 'getDataEntsoe', response => {
310
+ const result = response?.data == null ? null : xml2js(response.data);
311
+ return result?.Publication_MarketDocument ?? null;
407
312
  });
408
313
  }
409
314
 
@@ -414,6 +319,13 @@ class ApgInfo extends utils.Adapter {
414
319
  * @param {boolean} forecast also checks 10.15 auction for next day
415
320
  */
416
321
  async executeMarketPrice(country, forecast) {
322
+ if (this.marketPrices == false) {
323
+ const statesToDelete = await this.getStatesAsync(`marketprice*`);
324
+ for (const idS in statesToDelete) {
325
+ await this.delObjectAsync(idS);
326
+ }
327
+ return null;
328
+ }
417
329
  this.log.debug('Execute market price retrieval');
418
330
  let source1 = null;
419
331
  const configTraversJsonFalse = { replaceName: true, replaceID: true, level: 3, validateAttribute: false };
@@ -510,9 +422,6 @@ class ApgInfo extends utils.Adapter {
510
422
  jDay1BelowThresholdq.numberOfSlots = days1Belowq;
511
423
  jDay1AboveThresholdq.numberOfSlots = days1Aboveq;
512
424
 
513
- await this.createCharts(arrAll0, arrAll1, source1, false);
514
- await this.createCharts(arrAll0q, arrAll1q, null, true);
515
-
516
425
  await jsonExplorer.traverseJson(jDay0, 'marketprice.today', configTraversJsonFalse);
517
426
  await jsonExplorer.traverseJson(jDay0BelowThreshold, 'marketprice.belowThreshold.today', configTraversJsonFalse);
518
427
  await jsonExplorer.traverseJson(jDay0AboveThreshold, 'marketprice.aboveThreshold.today', configTraversJsonFalse);
@@ -527,6 +436,12 @@ class ApgInfo extends utils.Adapter {
527
436
  await jsonExplorer.traverseJson(jDay1BelowThresholdq, 'marketprice_quarter_hourly.belowThreshold.tomorrow', configTraversJsonFalse);
528
437
  await jsonExplorer.traverseJson(jDay1AboveThresholdq, 'marketprice_quarter_hourly.aboveThreshold.tomorrow', configTraversJsonFalse);
529
438
 
439
+ //copy objets to use this for charts later
440
+ const arrAll0Copy = structuredClone(arrAll0);
441
+ const arrAll1Copy = structuredClone(arrAll1);
442
+ const arrAll0qCopy = structuredClone(arrAll0q);
443
+ const arrAll1qCopy = structuredClone(arrAll1q);
444
+
530
445
  //now it is time to sort by prcie
531
446
  arrBelow0.sort(compareSecondColumn);
532
447
  arrBelow1.sort(compareSecondColumn);
@@ -679,6 +594,9 @@ class ApgInfo extends utils.Adapter {
679
594
  await jsonExplorer.stateSetCreate('marketprice_quarter_hourly.today.average', 'average', price0Avgq, false);
680
595
  await jsonExplorer.stateSetCreate('marketprice_quarter_hourly.tomorrow.average', 'average', price1Avgq, false);
681
596
 
597
+ await this.createCharts(arrAll0Copy, arrAll1Copy, source1, false);
598
+ await this.createCharts(arrAll0qCopy, arrAll1qCopy, null, true);
599
+
682
600
  await jsonExplorer.checkExpire('marketprice.*');
683
601
  await jsonExplorer.checkExpire('marketprice_quarter_hourly.*');
684
602
  await jsonExplorer.deleteObjectsWithNull('marketprice.*Threshold.*');
@@ -758,14 +676,13 @@ class ApgInfo extends utils.Adapter {
758
676
  async _getAndProcessMarketData(country, forecast) {
759
677
  let prices0Awattar, prices1Awattar, prices0Exaa, prices1Exaa, prices1Exaa1015;
760
678
 
761
- const eXaaToday = await this.getDataDayAheadExaa(false, country);
762
- const eXaaTomorrow = await this.getDataDayAheadExaa(true, country);
679
+ const [eXaaToday, eXaaTomorrow] = await Promise.all([this.getDataExaa(false, country), this.getDataExaa(true, country)]);
763
680
 
764
- //check for provider for tody
681
+ //check for provider for today
765
682
  prices0Exaa = eXaaToday?.h ?? null;
766
683
  if (prices0Exaa == null) {
767
684
  this.log.info(`No market data from Exaa for today, let's try Awattar`);
768
- prices0Awattar = await this.getDataDayAheadAwattar(false, country);
685
+ prices0Awattar = await this.getDataAwattar(false, country);
769
686
  if (prices0Awattar?.data?.[0]) {
770
687
  this.log.info('Todays market data from Awattar available');
771
688
  this.log.debug(`Todays market data result from Awattar is: ${JSON.stringify(prices0Awattar)}`);
@@ -780,14 +697,14 @@ class ApgInfo extends utils.Adapter {
780
697
  prices1Exaa = eXaaTomorrow?.h ?? null;
781
698
  if (prices1Exaa == null) {
782
699
  this.log.info(`No market data from Exaa for tomorrow, let's try Awattar`);
783
- prices1Awattar = await this.getDataDayAheadAwattar(true, country);
700
+ prices1Awattar = await this.getDataAwattar(true, country);
784
701
  if (prices1Awattar?.data?.[0]) {
785
702
  this.log.info('Tomorrows market data from Awattar available');
786
703
  this.log.debug(`Tomorrow market data result from Awattar is: ${JSON.stringify(prices1Awattar)}`);
787
704
  } else {
788
705
  if (forecast) {
789
706
  this.log.info('No market data from Awattar for tomorrow , last chance Exaa 10.15 auction!');
790
- const eXaa1015 = await this.getDataDayAheadExaa1015(country);
707
+ const eXaa1015 = await this.getDataExaa1015(country);
791
708
  prices1Exaa1015 = eXaa1015;
792
709
  if (prices1Exaa1015) {
793
710
  this.log.info('Market data from Exaa 10.15 auction available');
@@ -851,19 +768,13 @@ class ApgInfo extends utils.Adapter {
851
768
  let prices0Entsoe, prices1Entsoe;
852
769
 
853
770
  try {
854
- [prices0Entsoe, prices1Entsoe] = await Promise.all([
855
- this.getDataDayAheadEntsoe(false, country),
856
- this.getDataDayAheadEntsoe(true, country),
857
- ]);
771
+ [prices0Entsoe, prices1Entsoe] = await Promise.all([this.getDataEntsoe(false, country), this.getDataEntsoe(true, country)]);
858
772
  } catch (error) {
859
773
  if (String(error).includes('read ECONNRESET') || String(error).includes('timeout') || String(error).includes('socket hang up')) {
860
774
  this.log.info(`Entsoe request failed. Let's wait 3 minutes and try again...`);
861
775
  await jsonExplorer.sleep(3 * 60 * 1000);
862
776
  this.log.info(`OK! Let's try again now!`);
863
- [prices0Entsoe, prices1Entsoe] = await Promise.all([
864
- this.getDataDayAheadEntsoe(false, country),
865
- this.getDataDayAheadEntsoe(true, country),
866
- ]);
777
+ [prices0Entsoe, prices1Entsoe] = await Promise.all([this.getDataEntsoe(false, country), this.getDataEntsoe(true, country)]);
867
778
  } else {
868
779
  throw error;
869
780
  }
@@ -1008,6 +919,13 @@ class ApgInfo extends utils.Adapter {
1008
919
  * Handles json-object and creates states for peak hours
1009
920
  */
1010
921
  async executeRequestPeakHours() {
922
+ if (this.peakHours == false) {
923
+ const statesToDelete = await this.getStatesAsync(`peakTime.*`);
924
+ for (const idS in statesToDelete) {
925
+ await this.delObjectAsync(idS);
926
+ }
927
+ return null;
928
+ }
1011
929
  try {
1012
930
  let result = await this.getDataPeakHours();
1013
931
  this.log.debug(`Peak hour result is: ${JSON.stringify(result)}`);
@@ -1098,7 +1016,7 @@ class ApgInfo extends utils.Adapter {
1098
1016
  }
1099
1017
  }*/
1100
1018
  } catch (error) {
1101
- let eMsg = `Error in ExecuteRequestPeakHours(): ${error})`;
1019
+ let eMsg = `Error in ExecuteRequestPeakHours(): ${error}`;
1102
1020
  this.log.error(eMsg);
1103
1021
  console.error(eMsg);
1104
1022
  this.sendSentry(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.apg-info",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "author": {
5
5
  "name": "HGlab01",
6
6
  "email": "myiobrokeradapters@gmail.com"
@@ -50,7 +50,7 @@
50
50
  "@iobroker/eslint-config": "^2.2.0",
51
51
  "@iobroker/testing": "^5.1.1",
52
52
  "@types/gulp": "^4.0.17",
53
- "@types/node": "^24.7.2",
53
+ "@types/node": "^24.8.1",
54
54
  "@types/proxyquire": "^1.3.31",
55
55
  "proxyquire": "^2.1.3"
56
56
  },