Skip to content

Commit b8a6c7e

Browse files
committed
fixed problems with api
1 parent 53ee0a1 commit b8a6c7e

File tree

6 files changed

+461
-264
lines changed

6 files changed

+461
-264
lines changed

drivers/cz-spot-prices/api.js

Lines changed: 236 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,26 @@
33
const Homey = require('homey');
44
const axios = require('axios');
55
const PriceCalculator = require('../../helpers/PriceCalculator');
6+
const Logger = require('../../helpers/Logger');
67

78
class SpotPriceAPI {
8-
constructor(homey) {
9-
this.homey = homey;
10-
this.baseUrl = 'https://spotovaelektrina.cz/api/v1/price';
11-
this.backupUrl = 'https://www.ote-cr.cz/cs/kratkodobe-trhy/elektrina/denni-trh/@@chart-data';
12-
this.exchangeRateUrl = 'https://data.kurzy.cz/json/meny/b[6].json';
13-
this.exchangeRate = 25.25;
14-
this.homeyTimezone = this.homey.clock.getTimezone();
15-
this.priceCalculator = new PriceCalculator(this.homey);
16-
this.logger = null;
17-
}
9+
constructor(homey, deviceContext = 'SpotPriceAPI') { // zde byl problém
10+
this.homey = homey;
11+
// Vytvoříme vlastní instanci loggeru pro API
12+
this.logger = new Logger(this.homey, deviceContext); // používáme deviceContext, ne context
13+
// Defaultně zapneme logging pro API
14+
this.logger.setEnabled(true);
15+
16+
this.baseUrl = 'https://spotovaelektrina.cz/api/v1/price';
17+
const today = new Date().toISOString().slice(0, 10); // získá datum ve formátu RRRR-MM-DD
18+
this.backupUrl = `https://www.ote-cr.cz/cs/kratkodobe-trhy/elektrina/denni-trh/@@chart-data?date=${today}`;
19+
this.exchangeRateUrl = 'https://data.kurzy.cz/json/meny/b[6].json';
20+
this.exchangeRate = 25.25;
21+
this.homeyTimezone = this.homey.clock.getTimezone();
22+
this.priceCalculator = new PriceCalculator(this.homey, 'PriceCalculator');
23+
24+
this.logger.debug('SpotPriceAPI inicializován');
25+
}
1826

1927
setLogger(logger) {
2028
this.logger = logger;
@@ -133,8 +141,24 @@ class SpotPriceAPI {
133141
// Pokus o získání dat z primárního API
134142
const data = await this._fetchFromPrimaryAPI(timeoutMs);
135143

144+
// Logování vrácených dat z `_fetchFromPrimaryAPI`
145+
if (this.logger) {
146+
this.logger.debug('Výsledná data vrácená z _fetchFromPrimaryAPI', { data });
147+
}
148+
149+
// Ověříme, že `data` je správně definována, a logujeme
150+
if (!data) {
151+
this.logger.error('Chyba: data jsou undefined po volání _fetchFromPrimaryAPI');
152+
throw new Error('Data z primárního API jsou undefined');
153+
}
154+
155+
// Logování dat před validací
156+
if (this.logger) {
157+
this.logger.debug('Data předaná do validatePriceData', { hoursToday: data });
158+
}
159+
136160
// Validace dat z primárního API
137-
if (!this.priceCalculator.validatePriceData(data.hoursToday)) {
161+
if (!this.priceCalculator.validatePriceData(data)) {
138162
throw new Error('Neplatný formát dat z primárního API');
139163
}
140164

@@ -145,7 +169,7 @@ class SpotPriceAPI {
145169
this.logger.log('Data úspěšně získána z primárního API', { source: 'Primary API' });
146170
}
147171

148-
return data.hoursToday;
172+
return data;
149173

150174
} catch (error) {
151175
spotElektrinaError = error;
@@ -212,119 +236,214 @@ class SpotPriceAPI {
212236

213237
// Pomocná metoda pro volání primárního API
214238
async _fetchFromPrimaryAPI(timeoutMs) {
215-
const url = `${this.baseUrl}/get-prices-json`;
216-
const controller = new AbortController();
217-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
239+
const url = `${this.baseUrl}/get-prices-json`;
240+
let timeout;
218241

219-
if (this.logger) {
220-
this.logger.debug('Volání primárního API pro získání cen', { url, timeoutMs });
221-
}
242+
if (this.logger) {
243+
this.logger.debug('Volání primárního API pro získání cen', { url, timeoutMs });
244+
}
222245

223-
try {
224-
const response = await fetch(url, { signal: controller.signal });
225-
clearTimeout(timeout);
226-
227-
if (!response.ok) {
228-
const errorMessage = `HTTP error! status: ${response.status}`;
229-
if (this.logger) {
230-
this.logger.error('Chyba při volání primárního API', new Error(errorMessage), { url, status: response.status });
231-
}
232-
throw new Error(errorMessage);
233-
}
234-
235-
const data = await response.json();
236-
237-
if (!Array.isArray(data.hoursToday) || data.hoursToday.length !== 24) {
238-
const invalidDataError = new Error('Neplatná struktura dat z API');
239-
if (this.logger) {
240-
this.logger.error('Neplatná struktura dat z primárního API', invalidDataError, { url });
241-
}
242-
throw invalidDataError;
243-
}
244-
245-
if (this.logger) {
246-
this.logger.debug('Data úspěšně načtena z primárního API', { url, dataLength: data.hoursToday.length });
247-
}
248-
249-
return data;
250-
251-
} catch (error) {
252-
if (error.name === 'AbortError') {
253-
const timeoutError = new Error('Timeout při volání API');
254-
if (this.logger) {
255-
this.logger.error('Timeout při volání primárního API', timeoutError, { url, timeoutMs });
256-
}
257-
throw timeoutError;
258-
}
259-
260-
if (this.logger) {
261-
this.logger.error('Neočekávaná chyba při volání primárního API', error, { url });
262-
}
263-
throw error;
264-
265-
} finally {
266-
clearTimeout(timeout);
267-
}
268-
}
246+
try {
247+
const source = axios.CancelToken.source();
248+
timeout = setTimeout(() => {
249+
source.cancel(`Timeout při volání primárního API po ${timeoutMs}ms`);
250+
}, timeoutMs);
251+
252+
const response = await axios.get(url, { cancelToken: source.token });
253+
clearTimeout(timeout);
269254

255+
if (response.status !== 200) {
256+
const errorMessage = `HTTP error! status: ${response.status}`;
257+
if (this.logger) {
258+
this.logger.error('Chyba při volání primárního API', new Error(errorMessage), { url, status: response.status });
259+
}
260+
throw new Error(errorMessage);
261+
}
262+
263+
const data = response.data;
264+
265+
// Logování celé vstupní hodnoty z API bez ořezání
266+
if (this.logger) {
267+
this.logger.debug('Vstupní hodnota z primárního API', {
268+
url,
269+
receivedData: data // Log celého objektu bez ořezání
270+
});
271+
}
272+
273+
// Ověření struktury dat
274+
if (!data.hoursToday || !Array.isArray(data.hoursToday) || data.hoursToday.length !== 24) {
275+
const invalidDataError = new Error('Neplatná struktura dat z API');
276+
if (this.logger) {
277+
this.logger.error('Neplatná struktura dat z primárního API', invalidDataError, {
278+
url,
279+
receivedData: data // Log celého objektu bez ořezání při chybě
280+
});
281+
}
282+
throw invalidDataError;
283+
}
284+
285+
// Logování celé výstupní hodnoty z funkce bez ořezání
286+
if (this.logger) {
287+
this.logger.debug('Výstupní hodnota z _fetchFromPrimaryAPI', {
288+
hoursToday: data.hoursToday // Log celého pole `hoursToday` bez ořezání
289+
});
290+
}
291+
292+
// Vrátíme pouze pole `hoursToday`
293+
return data.hoursToday;
294+
295+
} catch (error) {
296+
if (axios.isCancel(error)) {
297+
const timeoutError = new Error('Timeout při volání API');
298+
if (this.logger) {
299+
this.logger.error('Timeout při volání primárního API', timeoutError, { url, timeoutMs });
300+
}
301+
throw timeoutError;
302+
}
303+
304+
if (this.logger) {
305+
this.logger.error('Neočekávaná chyba při volání primárního API', error, { url });
306+
}
307+
throw error;
308+
309+
} finally {
310+
if (timeout) clearTimeout(timeout);
311+
}
312+
}
270313

271314
async getBackupDailyPrices(device) {
272-
try {
273-
if (this.logger) {
274-
this.logger.debug('Začátek získávání cen ze záložního API', { url: this.backupUrl });
275-
}
276-
277-
await this.updateExchangeRate();
278-
const timeInfo = this.getCurrentTimeInfo();
279-
280-
if (this.logger) {
281-
this.logger.debug('Načítání dat z backup API s parametry', { date: timeInfo.date });
282-
}
283-
284-
const response = await axios.get(this.backupUrl, {
285-
params: { report_date: timeInfo.date }
286-
});
287-
288-
const data = response.data;
289-
const dataLine = data?.data?.dataLine.find(line => line.title === "Cena (EUR/MWh)");
290-
291-
if (!dataLine || !Array.isArray(dataLine.point)) {
292-
const errorMessage = 'Invalid data structure from backup API';
293-
if (this.logger) {
294-
this.logger.error(errorMessage, new Error(errorMessage), { url: this.backupUrl });
295-
}
296-
throw new Error(errorMessage);
297-
}
298-
299-
const hoursToday = dataLine.point.slice(0, 24).map((point, index) => {
300-
let priceCZK = point.y * this.exchangeRate;
301-
return { hour: index, priceCZK: parseFloat(priceCZK.toFixed(2)) };
302-
});
303-
304-
// Validace dat pomocí PriceCalculatoru
305-
if (!this.priceCalculator.validatePriceData(hoursToday)) {
306-
const validationError = new Error('Invalid backup price data format');
307-
if (this.logger) {
308-
this.logger.error('Chyba validace dat záložního API', validationError, { url: this.backupUrl });
309-
}
310-
throw validationError;
311-
}
312-
313-
if (this.logger) {
314-
this.logger.debug('Úspěšné načtení cen ze záložního API', { url: this.backupUrl, dataLength: hoursToday.length });
315-
}
316-
317-
return hoursToday;
315+
try {
316+
if (this.logger) {
317+
this.logger.debug('Začátek získávání cen ze záložního API', { url: this.backupUrl });
318+
}
319+
320+
await this.updateExchangeRate();
321+
const timeInfo = this.getCurrentTimeInfo();
322+
323+
if (this.logger) {
324+
this.logger.debug('Načítání dat z backup API s parametry', { date: timeInfo.date });
325+
}
326+
327+
const response = await axios.get(this.backupUrl, {
328+
params: { report_date: timeInfo.date }
329+
});
330+
331+
const data = response.data;
332+
333+
// Najdeme data pro ceny v EUR/MWh
334+
const dataLine = data?.data?.dataLine.find(line => line.title === "Cena (EUR/MWh)");
335+
336+
if (!dataLine || !Array.isArray(dataLine.point)) {
337+
const errorMessage = 'Invalid data structure from backup API';
338+
if (this.logger) {
339+
this.logger.error(errorMessage, new Error(errorMessage), { url: this.backupUrl });
340+
}
341+
throw new Error(errorMessage);
342+
}
343+
344+
// Kontrola vstupních dat
345+
if (dataLine.point.length < 24) {
346+
const error = new Error(`Nedostatečný počet hodin ve vstupních datech: ${dataLine.point.length}`);
347+
if (this.logger) {
348+
this.logger.error('Chyba vstupních dat', error);
349+
}
350+
throw error;
351+
}
352+
353+
// Map pro konverzi hodin 1-24 na 0-23
354+
const hourMap = new Map([...Array(24)].map((_, i) => [i + 1, i === 24 ? 0 : i]));
355+
356+
// Převod hodin a výpočet ceny v CZK
357+
const hoursToday = dataLine.point.slice(0, 24).map(point => {
358+
try {
359+
// Převod x (1-24) na hour (0-23)
360+
const inputHour = parseInt(point.x, 10);
361+
if (!hourMap.has(inputHour)) {
362+
throw new Error(`Neplatná vstupní hodina: ${inputHour}`);
363+
}
364+
const hour = hourMap.get(inputHour);
365+
366+
// Zpracování ceny
367+
const priceEUR = parseFloat(point.y);
368+
if (isNaN(priceEUR)) {
369+
throw new Error(`Neplatná cena pro hodinu ${inputHour}: ${point.y}`);
370+
}
371+
372+
const priceCZK = priceEUR * this.exchangeRate;
373+
374+
if (this.logger) {
375+
this.logger.debug('Mapování hodiny', {
376+
vstupníHodina: inputHour,
377+
výstupníHodina: hour,
378+
vstupníCenaEUR: priceEUR,
379+
výstupníCenaCZK: priceCZK
380+
});
381+
}
382+
383+
return {
384+
hour,
385+
priceCZK: parseFloat(priceCZK.toFixed(2)),
386+
priceEur: priceEUR // pro debugging
387+
};
388+
} catch (error) {
389+
if (this.logger) {
390+
this.logger.error('Chyba při zpracování hodinových dat', error, {
391+
point,
392+
exchangeRate: this.exchangeRate
393+
});
394+
}
395+
throw error;
396+
}
397+
});
398+
399+
// Seřazení podle hodin (0-23)
400+
hoursToday.sort((a, b) => a.hour - b.hour);
318401

402+
// Kontrola výstupních dat
403+
const hoursCheck = new Set(hoursToday.map(h => h.hour));
404+
if (hoursCheck.size !== 24 || ![...hoursCheck].every(h => h >= 0 && h <= 23)) {
405+
const error = new Error('Neplatná transformace hodin');
406+
if (this.logger) {
407+
this.logger.error('Chyba výstupních dat', error, {
408+
uniqueHours: [...hoursCheck].sort((a, b) => a - b)
409+
});
410+
}
411+
throw error;
412+
}
413+
414+
// Validace dat pomocí PriceCalculatoru
415+
if (!this.priceCalculator.validatePriceData(hoursToday)) {
416+
const validationError = new Error('Invalid backup price data format');
417+
if (this.logger) {
418+
this.logger.error('Chyba validace dat záložního API', validationError, {
419+
url: this.backupUrl,
420+
sampleData: hoursToday[0]
421+
});
422+
}
423+
throw validationError;
424+
}
425+
426+
if (this.logger) {
427+
this.logger.debug('Úspěšné načtení cen ze záložního API', {
428+
url: this.backupUrl,
429+
dataLength: hoursToday.length,
430+
firstHour: hoursToday[0],
431+
lastHour: hoursToday[23]
432+
});
433+
}
434+
435+
return hoursToday;
436+
319437
} catch (error) {
320-
if (this.logger) {
321-
this.logger.error('Chyba při získávání cen ze záložního API', error, { url: this.backupUrl });
322-
} else {
323-
this.homey.error('Error fetching backup daily prices:', error);
324-
}
325-
throw error;
438+
if (this.logger) {
439+
this.logger.error('Chyba při získávání cen ze záložního API', error, {
440+
url: this.backupUrl,
441+
exchangeRate: this.exchangeRate
442+
});
443+
}
444+
throw error;
326445
}
327-
}
446+
}
328447

329448
async updateCurrentValues(device) {
330449
try {

0 commit comments

Comments
 (0)