UNPKG

iobroker.daswetter

Version:
818 lines 65.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /* eslint-disable prefer-template */ const axios_1 = __importDefault(require("axios")); const base_1 = __importDefault(require("./base")); const translation_1 = require("./translation"); class Meteored extends base_1.default { api_key = ""; postcode = ""; city = ""; bundesland = ""; location_hash = ""; location_description = ""; location_country = ""; language = ""; dateFormat = ""; parseTimeout = 10; useDailyForecast = true; useHourlyForecast = true; expires_hour = 0; expires_day = 0; symbols = []; days_forecast = []; hours_forecast = []; IconSet = 0; IconType = 1; PNGSize = 2; CustomPath = ""; WindIconSet = 0; WindIconType = 1; WindPNGSize = 2; WindCustomPath = ""; MoonIconSet = 0; MoonIconType = 1; MoonPNGSize = 2; MoonCustomPath = ""; CopyCurrentHour = false; url = ""; constructor(adapter, id, config) { super(adapter, id, config.name); this.api_key = typeof config.API_key === "string" ? config.API_key : ""; this.postcode = typeof config.postcode === "string" ? config.postcode : ""; this.city = typeof config.city === "string" ? config.city : ""; this.bundesland = typeof config.bundesland === "string" ? config.bundesland : ""; this.language = typeof config.language === "string" ? config.language : "DE"; this.dateFormat = typeof config.dateFormat === "string" ? config.dateFormat : "YYMMDD"; this.parseTimeout = typeof config.parseTimeout === "number" ? config.parseTimeout : 10; this.useDailyForecast = typeof config.useDailyForecast === "boolean" ? config.useDailyForecast : true; this.useHourlyForecast = typeof config.useHourlyForecast === "boolean" ? config.useHourlyForecast : true; this.IconSet = config.IconSet; this.IconType = config.IconType; this.PNGSize = config.PNGSize; this.CustomPath = config.CustomPath; this.WindIconSet = config.WindIconSet; this.WindIconType = config.WindIconType; this.WindPNGSize = config.WindPNGSize; this.WindCustomPath = config.WindCustomPath; this.MoonIconSet = config.MoonIconSet; this.MoonIconType = config.MoonIconType; this.MoonPNGSize = config.MoonPNGSize; this.MoonCustomPath = config.MoonCustomPath; this.CopyCurrentHour = config.CopyCurrentHour; } async Start() { await this.CreateObjects(); await this.GetLocation(); await this.GetSymbols(); } CheckError(status) { if (!status || typeof status !== "number") { this.logError(" no response or invalid status"); return true; } let ret = true; switch (status) { case 200: ret = false; break; case 400: this.logError(" 400 Bad Request"); break; case 401: this.logError(" 401 Unauthorized"); break; case 404: this.logError(" 404 Not Found"); break; case 429: this.logError(" 429 Too Many Requests - rate limited"); break; case 500: this.logError(" 500 Internal Server Error"); break; default: this.logError(" unexpected HTTP status " + status); break; } return ret; } async GetLocation() { this.logDebug("GetLocation called"); await this.GetLocationPostcode(); if (this.location_hash === undefined || this.location_hash == "") { await this.GetLocationFreetext(); } } async GetLocationPostcode() { this.logDebug("GetLocationPostcode called"); if (this.api_key === undefined || this.api_key == "") { this.logError("no api key available, please check settings"); return; } // Prüfen auf unsichtbare oder ungültige Zeichen const invalidChars = this.api_key.split('').filter(c => { const code = c.charCodeAt(0); // erlaubte ASCII-Zeichen 32-126 (sichtbare Zeichen) plus Tab (9) return !(code >= 32 && code <= 126) && code !== 9; }); if (invalidChars.length > 0) { this.logError(`API-Key enthält ${invalidChars.length} ungültige Zeichen: [${invalidChars.map(c => '\\u' + c.charCodeAt(0).toString(16)).join(', ')}]`); return; } else { this.logDebug("API-Key ist sauber ✅"); } if (this.postcode === undefined || this.postcode == "") { this.logInfo("Postcode not set, skipping GetLocationPostcode"); return; } const url = "https://api.meteored.com/api/location/v1/search/postalcode/" + this.postcode; const headers = { accept: "application/json", "x-api-key": this.api_key }; try { let resp; try { resp = await axios_1.default.get(url, { headers: headers, timeout: this.parseTimeout * 1000 }); } catch (err) { if (axios_1.default.isAxiosError(err)) { this.logError("axios error in GetLocationPostcode: message=" + err.message + ", code=" + (err.code || "") + ", status=" + (err.response?.status || "no-response") + ", data=" + (err.response ? JSON.stringify(err.response.data) : "undefined")); } else { this.logError("exception in GetLocationPostcode (non-axios): " + err); } return; } if (this.CheckError(resp.status)) { return; } try { // Logge die rohe Antwortdaten zur weiteren Verarbeitung/Debug this.logDebug("Meteored GetLocationPostcode response: " + JSON.stringify(resp.data)); // Sicher extrahieren: resp.data.data.locations const locations = resp && resp.data && resp.data.data && Array.isArray(resp.data.data.locations) ? resp.data.data.locations : []; if (locations.length === 0) { this.logInfo("Meteored GetLocationPostcode: no locations in response"); } else { this.logInfo("Meteored GetLocationPostcode: found " + locations.length + " locations:"); locations.forEach((loc) => { const name = loc && loc.name ? String(loc.name) : ""; const desc = loc && loc.description ? String(loc.description) : ""; const country = loc && loc.country_name ? String(loc.country_name) : ""; this.logInfo(" Name: " + name + ", Description: " + desc + ", Country: " + country); }); const cityNormalized = (this.city || "").toString().trim().toLowerCase(); const bundeslandNormalized = (this.bundesland || "").toString().trim().toLowerCase(); const match = locations.find((loc) => { const name = loc && loc.name ? String(loc.name).trim().toLowerCase() : ""; const description = loc && loc.description ? String(loc.description).trim().toLowerCase() : ""; const nameMatches = name === cityNormalized; const descriptionMatches = bundeslandNormalized === "" || description === bundeslandNormalized; return nameMatches && descriptionMatches; }); if (match) { this.location_hash = match.hash ? String(match.hash) : ""; this.location_description = match.description ? String(match.description) : ""; this.location_country = match.country_name ? String(match.country_name) : ""; this.logInfo("Meteored GetLocationPostcode: matched city \"" + this.city + "\" => hash=" + this.location_hash + ", description=" + this.location_description + ", country=" + this.location_country); } else { this.logError("Meteored GetLocationPostcode: no matching location for city \"" + this.city + "\"" + (bundeslandNormalized ? " and bundesland \"" + this.bundesland + "\"" : "")); this.logInfo("found the following locations:"); // Für jede gefundene Location eine separate Zeile loggen (Name, description, country) locations.forEach((loc) => { const name = loc && loc.name ? String(loc.name) : ""; const desc = loc && loc.description ? String(loc.description) : ""; const country = loc && loc.country_name ? String(loc.country_name) : ""; this.logInfo("Name: " + name + ", description: " + desc + ", country: " + country); }); } } await this.SetData_Location(); } catch (e) { this.logError("exception in GetLocationPostcode data parse " + e); } } catch (e) { this.logError("exception in GetLocationPostcode " + e); } } async GetLocationFreetext() { this.logDebug("GetLocationFreetext called"); if (this.api_key === undefined || this.api_key == "") { this.logError("no api key available, please check settings"); return; } const url = "https://api.meteored.com/api/location/v1/search/txt/" + this.city; const headers = { accept: "application/json", "x-api-key": this.api_key }; try { let resp; try { resp = await axios_1.default.get(url, { headers: headers, timeout: this.parseTimeout * 1000 }); } catch (err) { if (axios_1.default.isAxiosError(err)) { this.logError("axios error in GetLocationFreetext: message=" + err.message + ", code=" + (err.code || "") + ", status=" + (err.response?.status || "no-response") + ", data=" + (err.response ? JSON.stringify(err.response.data) : "undefined")); } else { this.logError("exception in GetLocationFreetext (non-axios): " + err); } return; } if (this.CheckError(resp.status)) { return; } try { // Logge die rohe Antwortdaten zur weiteren Verarbeitung/Debug this.logDebug("Meteored GetLocationFreetext response: " + JSON.stringify(resp.data)); // Sicher extrahieren: resp.data.data.locations const locations = resp && resp.data && resp.data.data && Array.isArray(resp.data.data.locations) ? resp.data.data.locations : []; if (locations.length === 0) { this.logError("Meteored GetLocationFreetext: no locations in response"); } else { this.logInfo("Meteored GetLocationFreetext: found " + locations.length + " locations:"); locations.forEach((loc) => { const name = loc && loc.name ? String(loc.name) : ""; const desc = loc && loc.description ? String(loc.description) : ""; const country = loc && loc.country_name ? String(loc.country_name) : ""; this.logInfo(" Name: " + name + ", Description: " + desc + ", Country: " + country); }); const cityNormalized = (this.city || "").toString().trim().toLowerCase(); const bundeslandNormalized = (this.bundesland || "").toString().trim().toLowerCase(); const match = locations.find((loc) => { const name = loc && loc.name ? String(loc.name).trim().toLowerCase() : ""; const description = loc && loc.description ? String(loc.description).trim().toLowerCase() : ""; const nameMatches = name === cityNormalized; const descriptionMatches = bundeslandNormalized === "" || description === bundeslandNormalized; return nameMatches && descriptionMatches; }); if (match) { this.location_hash = match.hash ? String(match.hash) : ""; this.location_description = match.description ? String(match.description) : ""; this.location_country = match.country_name ? String(match.country_name) : ""; this.logInfo("Meteored GetLocationFreetext: matched city \"" + this.city + "\" => hash=" + this.location_hash + ", description=" + this.location_description + ", country=" + this.location_country); } else { this.logError("Meteored GetLocationFreetext: no matching location for city \"" + this.city + "\"" + (bundeslandNormalized ? " and bundesland \"" + this.bundesland + "\"" : "")); this.logInfo("found the following locations:"); // Für jede gefundene Location eine separate Zeile loggen (Name, description, country) locations.forEach((loc) => { const name = loc && loc.name ? String(loc.name) : ""; const desc = loc && loc.description ? String(loc.description) : ""; const country = loc && loc.country_name ? String(loc.country_name) : ""; this.logInfo("Name: " + name + ", description: " + desc + ", country: " + country); }); } } await this.SetData_Location(); } catch (e) { this.logError("exception in GetLocationFreetext data parse " + e); } } catch (e) { this.logError("exception in GetLocationFreetext " + e); } } async GetForecastDaily() { if (this.useDailyForecast) { this.logDebug("GetForecastDaily called"); const now = Date.now(); if (this.expires_day > now) { this.logDebug("GetForecastDaily: cached data still valid until " + new Date(this.expires_day).toISOString() + ", skipping fetch"); return; } if (this.location_hash === undefined || this.location_hash == "") { this.logError("no location hash available, please check postcode and city settings"); return; } if (this.api_key === undefined || this.api_key == "") { this.logError("no api key available, please check settings"); return; } const url = "https://api.meteored.com/api/forecast/v1/daily/" + this.location_hash; const headers = { accept: "application/json", "x-api-key": this.api_key }; try { let resp; try { resp = await axios_1.default.get(url, { headers: headers, timeout: this.parseTimeout * 1000 }); } catch (err) { if (axios_1.default.isAxiosError(err)) { this.logError("axios error in GetForecastDaily: message=" + err.message + ", code=" + (err.code || "") + ", status=" + (err.response?.status || "no-response") + ", data=" + (err.response ? JSON.stringify(err.response.data) : "undefined")); } else { this.logError("exception in GetForecastDaily (non-axios): " + err); } return; } if (this.CheckError(resp.status)) { return; } try { // Logge die rohe Antwortdaten zur weitere Verarbeitung/Debug this.logDebug("Meteored GetForecastDaily response: " + JSON.stringify(resp.data)); this.expires_day = resp && resp.data && resp.data.expiracion ? Number(resp.data.expiracion) : 0; // Sicher extrahieren der URL aus resp.data.data.url und kopiere nach this.url const extractedUrl = resp && resp.data && resp.data.data && resp.data.data.url ? String(resp.data.data.url) : ""; if (extractedUrl) { this.url = extractedUrl; this.logDebug("Meteored GetForecastDaily: url=" + this.url); } // Sicher extrahieren: resp.data.data.days const rawDays = resp && resp.data && resp.data.data && Array.isArray(resp.data.data.days) ? resp.data.data.days : []; if (rawDays.length === 0) { this.logError("Meteored GetForecastDaily: no days array in response, setting empty days_forecast"); this.days_forecast = []; } else { // Mappe rohe Objekte auf das definierte day_data-Interface mit sicheren Typen/Defaults try { const mapped = rawDays.map((d) => { return { start: typeof d.start === "number" ? d.start : Number(d.start) || 0, symbol: typeof d.symbol === "number" ? d.symbol : Number(d.symbol) || 0, temperature_min: typeof d.temperature_min === "number" ? d.temperature_min : Number(d.temperature_min) || 0, temperature_max: typeof d.temperature_max === "number" ? d.temperature_max : Number(d.temperature_max) || 0, wind_speed: typeof d.wind_speed === "number" ? d.wind_speed : Number(d.wind_speed) || 0, wind_gust: typeof d.wind_gust === "number" ? d.wind_gust : Number(d.wind_gust) || 0, wind_direction: d.wind_direction ? String(d.wind_direction) : "", rain: typeof d.rain === "number" ? d.rain : Number(d.rain) || 0, rain_probability: typeof d.rain_probability === "number" ? d.rain_probability : Number(d.rain_probability) || 0, humidity: typeof d.humidity === "number" ? d.humidity : Number(d.humidity) || 0, pressure: typeof d.pressure === "number" ? d.pressure : Number(d.pressure) || 0, snowline: typeof d.snowline === "number" ? d.snowline : Number(d.snowline) || 0, uv_index_max: typeof d.uv_index_max === "number" ? d.uv_index_max : Number(d.uv_index_max) || 0, sun_in: typeof d.sun_in === "number" ? d.sun_in : Number(d.sun_in) || 0, sun_mid: typeof d.sun_mid === "number" ? d.sun_mid : Number(d.sun_mid) || 0, sun_out: typeof d.sun_out === "number" ? d.sun_out : Number(d.sun_out) || 0, moon_in: typeof d.moon_in === "number" ? d.moon_in : Number(d.moon_in) || 0, moon_out: typeof d.moon_out === "number" ? d.moon_out : Number(d.moon_out) || 0, moon_symbol: typeof d.moon_symbol === "number" ? d.moon_symbol : Number(d.moon_symbol) || 0, moon_illumination: typeof d.moon_illumination === "number" ? d.moon_illumination : Number(d.moon_illumination) || 0 }; }); this.days_forecast = mapped; this.logDebug("Meteored GetForecastDaily: parsed days_forecast count=" + this.days_forecast.length); } catch (e) { this.logError("Meteored GetForecastDaily: error mapping days array: " + e); this.days_forecast = []; } await this.SetData_ForecastDaily(); } } catch (e) { // Falls JSON.stringify fehlschlägt, logge eine kurze Meldung this.logError("exception in GetForecastDaily data parse " + e); } } catch (e) { this.logError("exception in GetForecastDaily " + e); } } else { this.logDebug("GetForecastDaily called, but skipped"); } } async GetForecastHourly() { if (this.useHourlyForecast) { this.logDebug("GetForecastHourly called"); const now = Date.now(); if (this.expires_hour > now) { this.logDebug("GetForecastHourly: cached data still valid until " + new Date(this.expires_day).toISOString() + ", skipping fetch"); return; } if (this.location_hash === undefined || this.location_hash == "") { this.logError("no location hash available, please check postcode and city settings"); return; } if (this.api_key === undefined || this.api_key == "") { this.logError("no api key available, please check settings"); return; } const url = "https://api.meteored.com/api/forecast/v1/hourly/" + this.location_hash; const headers = { accept: "application/json", "x-api-key": this.api_key }; try { let resp; try { resp = await axios_1.default.get(url, { headers: headers, timeout: this.parseTimeout * 1000 }); } catch (err) { if (axios_1.default.isAxiosError(err)) { this.logError("axios error in GetForecastHourly: message=" + err.message + ", code=" + (err.code || "") + ", status=" + (err.response?.status || "no-response") + ", data=" + (err.response ? JSON.stringify(err.response.data) : "undefined")); } else { this.logError("exception in GetForecastHourly (non-axios): " + err); } return; } if (this.CheckError(resp.status)) { return; } try { // Logge die rohe Antwortdaten zur weiteren Verarbeitung/Debug this.logDebug("Meteored GetForecastHourly response: " + JSON.stringify(resp.data)); this.expires_hour = resp && resp.data && resp.data.expiracion ? Number(resp.data.expiracion) : 0; // Sicher extrahieren: resp.data.data.hours const rawHours = resp && resp.data && resp.data.data && Array.isArray(resp.data.data.hours) ? resp.data.data.hours : []; if (rawHours.length === 0) { this.logError("Meteored GetForecastHourly: no hours array in response, setting empty hours_forecast"); this.hours_forecast = []; } else { try { const mapped = rawHours.map((h) => { return { end: typeof h.end === "number" ? h.end : Number(h.end) || 0, symbol: typeof h.symbol === "number" ? h.symbol : Number(h.symbol) || 0, night: typeof h.night === "boolean" ? h.night : (h.night === "true" || h.night === true) || false, temperature: typeof h.temperature === "number" ? h.temperature : Number(h.temperature) || 0, temperature_feels_like: typeof h.temperature_feels_like === "number" ? h.temperature_feels_like : Number(h.temperature_feels_like) || 0, wind_speed: typeof h.wind_speed === "number" ? h.wind_speed : Number(h.wind_speed) || 0, wind_gust: typeof h.wind_gust === "number" ? h.wind_gust : Number(h.wind_gust) || 0, wind_direction: h.wind_direction ? String(h.wind_direction) : "", rain: typeof h.rain === "number" ? h.rain : Number(h.rain) || 0, rain_probability: typeof h.rain_probability === "number" ? h.rain_probability : Number(h.rain_probability) || 0, humidity: typeof h.humidity === "number" ? h.humidity : Number(h.humidity) || 0, pressure: typeof h.pressure === "number" ? h.pressure : Number(h.pressure) || 0, snowline: typeof h.snowline === "number" ? h.snowline : Number(h.snowline) || 0, uv_index_max: typeof h.uv_index_max === "number" ? h.uv_index_max : Number(h.uv_index_max) || 0, clouds: typeof h.clouds === "number" ? h.clouds : Number(h.clouds) || 0 }; }); this.hours_forecast = mapped; this.logDebug("Meteored GetForecastHourly: parsed hours_forecast count=" + this.hours_forecast.length); } catch (e) { this.logError("Meteored GetForecastHourly: error mapping hours array: " + e); this.hours_forecast = []; } } await this.SetData_ForecastHourly(); } catch (e) { // Falls JSON.stringify fehlschlägt, logge eine kurze Meldung this.logError("exception in GetForecastHourly data parse " + e); } } catch (e) { this.logError("exception in GetForecastHourly " + e); } } else { this.logDebug("GetForecastHourly called, but skipped"); } } async GetSymbols() { this.logDebug("GetSymbols called"); if (this.api_key === undefined || this.api_key == "") { this.logError("no api key available, please check settings"); return; } const url = "https://api.meteored.com/api/doc/v1/forecast/symbol"; const headers = { accept: "application/json", "x-api-key": this.api_key }; try { let resp; try { resp = await axios_1.default.get(url, { headers: headers, timeout: this.parseTimeout * 1000 }); } catch (err) { if (axios_1.default.isAxiosError(err)) { this.logError("axios error in GetSymbols: message=" + err.message + ", code=" + (err.code || "") + ", status=" + (err.response?.status || "no-response") + ", data=" + (err.response ? JSON.stringify(err.response.data) : "undefined")); } else { this.logError("exception in GetSymbols (non-axios): " + err); } return; } if (this.CheckError(resp.status)) { return; } try { // Logge die rohe Antwortdaten zur weiteren Verarbeitung/Debug this.logDebug("Meteored GetSymbols response: " + JSON.stringify(resp.data)); // Sicher extrahieren: resp.data.data.symbols const rawSymbols = resp && resp.data && resp.data.data && Array.isArray(resp.data.data.symbols) ? resp.data.data.symbols : []; if (rawSymbols.length === 0) { this.logError("Meteored GetSymbols: no symbols array in response, setting empty symbols"); this.symbols = []; } else { try { const mapped = rawSymbols.map((s) => { const id = (s && (typeof s.id === "number" ? s.id : Number(s.id))) || 0; const dayObj = s && s.day ? s.day : null; const dayShort = dayObj && dayObj.short ? String(dayObj.short) : ""; const dayLong = dayObj && dayObj.long ? String(dayObj.long) : ""; return { id: id, day: { short: dayShort, long: dayLong } }; }); this.symbols = mapped; this.logDebug("Meteored GetSymbols: parsed symbols count=" + this.symbols.length); } catch (e) { this.logError("Meteored GetSymbols: error mapping symbols array: " + e); this.symbols = []; } } } catch (e) { // Falls JSON.stringify fehlschlägt, logge eine kurze Meldung this.logError("exception in GetSymbols data parse " + e); } } catch (e) { this.logError("exception in GetSymbols " + e); } } async CalculateData() { try { // Berechne Sonnenscheindauer für heute und schreibe den Wert in den entsprechenden Datenpunkt. const sunDuration = this.CalculateSunshineDuration(); const key = "location_" + this.id + ".ForecastDaily.Day_1.sunshineduration"; await this.adapter.setState(key, sunDuration, true); } catch (e) { this.logError("CalculateData error: " + e); } //todo //Wind-symbol aus Richtung und Stärke berechnen } CalculateSunshineDuration() { try { if (!Array.isArray(this.days_forecast) || this.days_forecast.length === 0) { this.logDebug("CalculateSunshineDuration: no days_forecast available"); return 0; } const today = this.days_forecast[0]; if (!today) { this.logDebug("CalculateSunshineDuration: today forecast missing"); return 0; } let sunIn = typeof today.sun_in === "number" ? today.sun_in : Number(today.sun_in) || 0; let sunOut = typeof today.sun_out === "number" ? today.sun_out : Number(today.sun_out) || 0; if (!sunIn || !sunOut || sunOut <= sunIn) { this.logDebug("CalculateSunshineDuration: invalid sun_in/sun_out values: sun_in=" + sunIn + " sun_out=" + sunOut); return 0; } // Normalisiere: falls Werte in Sekunden vorliegen, in ms umwandeln if (sunIn > 0 && sunIn < 10000000000) { sunIn = sunIn * 1000; } if (sunOut > 0 && sunOut < 10000000000) { sunOut = sunOut * 1000; } if (!Array.isArray(this.hours_forecast) || this.hours_forecast.length === 0) { this.logDebug("CalculateSunshineDuration: no hours_forecast available"); return 0; } let totalHours = 0; for (const h of this.hours_forecast) { if (!h || (h.end === undefined || h.end === null)) { continue; } let hourEnd = typeof h.end === "number" ? h.end : Number(h.end) || 0; if (hourEnd === 0) { continue; } // normalize end to ms if necessary if (hourEnd > 0 && hourEnd < 10000000000) { hourEnd = hourEnd * 1000; } const hourStart = hourEnd - 3600000; // eine Stunde vorher // Berechne Überlappung mit Tageslichtintervall const overlapStart = Math.max(hourStart, sunIn); const overlapEnd = Math.min(hourEnd, sunOut); const overlapMs = Math.max(0, overlapEnd - overlapStart); if (overlapMs <= 0) { continue; } const overlapHours = overlapMs / 3600000; // Wolkenanteil in Prozent (0..100) const clouds = (h.clouds === undefined || h.clouds === null) ? 0 : (typeof h.clouds === "number" ? h.clouds : Number(h.clouds) || 0); const cloudFactor = 1 - Math.max(0, Math.min(100, clouds)) / 100; const effectiveSunHours = overlapHours * cloudFactor; totalHours += effectiveSunHours; } // Runde auf 2 Dezimalstellen const rounded = Math.round(totalHours * 100) / 100; this.logDebug("CalculateSunshineDuration: computed sunshineduration=" + rounded + " hours"); return rounded; } catch (e) { this.logError("CalculateSunshineDuration error: " + e); return 0; } } async CreateObjects() { let key = "location_" + this.id; await this.CreateDatapoint(key, "channel", "", "", "", false, false, "location"); await this.CreateDatapoint(key + ".Location", "state", "location", "string", "", true, false, "Location name"); await this.CreateDatapoint(key + ".URL", "state", "weather.chart.url.forecast", "string", "", true, false, "Location default site URL"); if (this.useDailyForecast) { key = "location_" + this.id + ".ForecastDaily"; await this.CreateDatapoint(key, "channel", "", "", "", false, false, "ForecastDaily"); for (let d = 1; d < 6; d++) { key = "location_" + this.id + ".ForecastDaily.Day_" + d; await this.CreateDatapoint(key, "channel", "", "", "", false, false, "ForecastDaily Day_" + d); await this.CreateDatapoint(key + ".date_full", "state", "date", "string", "", true, false, "full date of forecast period (ISO string)"); await this.CreateDatapoint(key + ".date", "state", "value", "string", "", true, false, "date of forecast period (simple string)"); await this.CreateDatapoint(key + ".NameOfDay", "state", "dayofweek", "string", "", true, false, "weekday of date"); if (d == 1) { //only for today await this.CreateDatapoint(key + ".sunshineduration", "state", "value", "number", "hours", true, false, "sunshine duration of the day, based on daylight and clouds"); } else { if (await this.adapter.objectExists(key + ".sunshineduration")) { this.logWarn("unused DP " + key + ".sunshineduration deleted"); await this.adapter.delObjectAsync(key + ".sunshineduration"); } } //date based time values for further calculation await this.CreateDatapoint(key + ".start", "state", "date", "number", "", true, false, "start of forecast period [UNIX timestamp]"); await this.CreateDatapoint(key + ".symbol", "state", "value", "number", "", true, false, "Identifier for weather symbol"); await this.CreateDatapoint(key + ".symbol_URL", "state", "value", "string", "", true, false, "URL to weather symbol"); await this.CreateDatapoint(key + ".symbol_description", "state", "value", "string", "", true, false, "symbol long description"); await this.CreateDatapoint(key + ".Temperature_Min", "state", "value.temperature.min.forecast.0", "number", "°C", true, false, "Minimum temperature"); await this.CreateDatapoint(key + ".Temperature_Max", "state", "value.temperature.max.forecast.0", "number", "°C", true, false, "Maximum temperature"); await this.CreateDatapoint(key + ".Wind_Speed", "state", "value.speed.wind.forecast.0", "number", "km/h", true, false, "Wind speed"); await this.CreateDatapoint(key + ".Wind_Speed_Beauforts", "state", "value.speed.wind.forecast.0", "number", "", true, false, "Wind speed acc. Beauforts scale"); await this.CreateDatapoint(key + ".Wind_Gust", "state", "value.speed.wind.gust", "number", "km/h", true, false, "Wind gust"); await this.CreateDatapoint(key + ".Wind_Direction", "state", "weather.direction.wind.forecast.0", "string", "", true, false, "Wind direction"); await this.CreateDatapoint(key + ".Wind_symbol_URL", "state", "state", "string", "", true, false, "URL to wind symbol"); await this.CreateDatapoint(key + ".Rain", "state", "value", "number", "mm", true, false, "Accumulated rain"); await this.CreateDatapoint(key + ".Rain_Probability", "state", "value.precipitation.chance", "number", "%", true, false, "Rain probability for accumulated rain"); await this.CreateDatapoint(key + ".Humidity", "state", "value.humidity", "number", "%", true, false, "Humidity"); await this.CreateDatapoint(key + ".Pressure", "state", "value", "number", "hPa", true, false, "Pressure expressed in Millibars / hPa"); await this.CreateDatapoint(key + ".Snowline", "state", "value", "number", "m", true, false, "Snowline cote expressed in meters"); await this.CreateDatapoint(key + ".UV_index_max", "state", "value", "number", "", true, false, "Maximum UV index for day"); //string based time values for direct display await this.CreateDatapoint(key + ".Sun_in", "state", "value", "string", "", true, false, "sunrise time [string]"); await this.CreateDatapoint(key + ".Sun_mid", "state", "value", "string", "", true, false, "sun noon time [string]"); await this.CreateDatapoint(key + ".Sun_out", "state", "value", "string", "", true, false, "sunset time [string]"); await this.CreateDatapoint(key + ".Moon_in", "state", "value", "string", "", true, false, "moonrise time [string]"); await this.CreateDatapoint(key + ".Moon_out", "state", "value", "string", "", true, false, "moonset time [string]"); //date based time values for further calculation await this.CreateDatapoint(key + ".Sun_in_full", "state", "date", "number", "", true, false, "sunrise time [Unix timestamp]"); await this.CreateDatapoint(key + ".Sun_mid_full", "state", "date", "number", "", true, false, "sun noon time [Unix timestamp]"); await this.CreateDatapoint(key + ".Sun_out_full", "state", "date", "number", "", true, false, "sunset time [Unix timestamp]"); await this.CreateDatapoint(key + ".Moon_in_full", "state", "date", "number", "", true, false, "moonrise time [Unix timestamp]"); await this.CreateDatapoint(key + ".Moon_out_full", "state", "date", "number", "", true, false, "moonset time [Unix timestamp]"); await this.CreateDatapoint(key + ".Moon_symbol", "state", "value", "number", "", true, false, "Identifier for moon symbol"); await this.CreateDatapoint(key + ".Moon_symbol_URL", "state", "value", "string", "", true, false, "URL to moon symbol"); await this.CreateDatapoint(key + ".Moon_illumination", "state", "value", "number", "%", true, false, "Percentage of illuminated moon"); } } if (this.useHourlyForecast) { key = "location_" + this.id + ".ForecastHourly"; await this.CreateDatapoint(key, "channel", "", "", "", false, false, "ForecastHourly"); await this.CreateDatapoint(key + ".date_full", "state", "date", "string", "", true, false, "full date of forecast periods [ISO string]"); await this.CreateDatapoint(key + ".date", "state", "string", "string", "", true, false, "date of forecast periods [simple string]"); for (let h = 1; h < 25; h++) { key = "location_" + this.id + ".ForecastHourly.Hour_" + h; await this.CreateDatapoint(key, "channel", "", "", "", false, false, "ForecastDaily Hour_" + h); await this.CreateObjectsHourly(key); } if (this.CopyCurrentHour) { key = "location_" + this.id + ".ForecastHourly.Current"; await this.CreateDatapoint(key, "channel", "", "", "", false, false, "ForecastDaily Current Hour"); await this.CreateObjectsHourly(key); } } } async CreateObjectsHourly(key) { //daswetter.0.location_2.ForecastHourly.Hour_1.end -> 31.12.2025, 01:00:00 await this.CreateDatapoint(key + ".end", "state", "date", "number", "", true, false, "end of forecast period [Unix timestamp]"); //daswetter.0.location_2.ForecastHourly.Hour_1.time -> 01:00:00 await this.CreateDatapoint(key + ".time", "state", "value", "string", "", true, false, "end of forecast period [time string only}"); await this.CreateDatapoint(key + ".symbol", "state", "value", "number", "", true, false, "Identifier for weather symbol"); await this.CreateDatapoint(key + ".symbol_URL", "state", "value", "string", "", true, false, "weather symbol long description"); await this.CreateDatapoint(key + ".symbol_description", "state", "value", "string", "", true, false, "URL to weather symbol"); await this.CreateDatapoint(key + ".night", "state", "value", "boolean", "", true, false, "Flag that indicates if the hour is at night"); await this.CreateDatapoint(key + ".temperature", "state", "value.temperature.max.forecast.0", "number", "°C", true, false, "Temperature value"); await this.CreateDatapoint(key + ".temperature_feels_like", "state", "value.temperature.feelslike", "number", "°C", true, false, "Temperature feels like value"); await this.CreateDatapoint(key + ".wind_speed", "state", "value.speed.wind.forecast.0", "number", "km/h", true, false, "Wind speed"); await this.CreateDatapoint(key + ".wind_speed_Beauforts", "state", "value.speed.wind.forecast.0", "number", "", true, false, "Wind speed acc Beauforts scale"); await this.CreateDatapoint(key + ".wind_gust", "state", "value.speed.wind.gust", "number", "km/h", true, false, "Wind gust"); await this.CreateDatapoint(key + ".wind_direction", "state", "weather.direction.wind.forecast.0", "string", "", true, false, "Wind direction"); await this.CreateDatapoint(key + ".Wind_symbol_URL", "state", "state", "string", "", true, false, "URL to wind symbol"); await this.CreateDatapoint(key + ".rain", "state", "value", "number", "mm", true, false, "Accumulated rain"); await this.CreateDatapoint(key + ".rain_probability", "state", "value", "number", "%", true, false, "Rain probability for accumulated rain"); await this.CreateDatapoint(key + ".humidity", "state", "value.humidity", "number", "%", true, false, "Humidity"); await this.CreateDatapoint(key + ".pressure", "state", "value", "number", "hPa", true, false, "Pressure expressed in Millibars / hPa"); await this.CreateDatapoint(key + ".snowline", "state", "value", "number", "m", true, false, "Snowline cote expressed in meters"); await this.CreateDatapoint(key + ".uv_index_max", "state", "value", "number", "", true, false, "Maximum UV index for day"); await this.CreateDatapoint(key + ".clouds", "state", "value.clouds", "number", "%", true, false, "Percentage of clouds"); } async SetData_Location() { const key = "location_" + this.id; await this.adapter.setState(key + ".Location", this.city, true); } async SetData_ForecastDaily() { let key = "location_" + this.id; await this.adapter.setState(key + ".URL", this.url, true); for (let d = 1; d < 6; d++) { key = "location_" + this.id + ".ForecastDaily.Day_" + d; const day = this.days_forecast[d - 1]; const timeval = day && day.start ? day.start : 0; const startParts = timeval ? this.FormatTimestampToLocal(timeval) : { formattedTimeval: "", formattedTimevalDate: "", formattedTimevalWeekday: "", isoString: "" }; await this.adapter.setState(key + ".start", timeval, true); await this.adapter.setState(key + ".date_full", startParts.isoString, true); await this.adapter.setState(key + ".date", startParts.formattedTimevalDate, true); await this.adapter.setState(key + ".NameOfDay", startParts.formattedTimevalWeekday, true); await this.adapter.setState(key + ".symbol", day ? day.symbol : 0, true); await this.adapter.setState(key + ".symbol_URL", this.getIconUrl(day ? day.symbol : 0), true); await this.adapter.setState(key + ".symbol_description", this.getSymbolLongDescription(day ? day.symbol : 0, false), true); await this.adapter.setState(key + ".Temperature_Min", day ? day.temperature_min : 0, true); await this.adapter.setState(key + ".Temperature_Max", day ? day.temperature_max : 0, true); await this.adapter.setState(key + ".Wind_Speed", day ? day.wind_speed : 0, true); await this.adapter.setState(key + ".Wind_Speed_Beauforts", this.getWindBeaufort(day ? day.wind_speed : 0), true); await this.adapter.setState(key + ".Wind_Gust", day ? day.wind_gust : 0, true); await this.adapter.setState(key + ".Wind_Direction", day ? day.wind_direction : "", true); await this.adapter.setState(key + ".Wind_symbol_URL", this.getWindIconUrl(day ? day.wind_speed : 0, day ? day.wind_direction : ""), true); await this.adapter.setState(key + ".Rain", day ? day.rain : 0, true); await this.adapter.setState(key + ".Rain_Probability", day ? day.rain_probability : 0, true); await this.adapter.setState(key + ".Humidity", day ? day.humidity : 0, true); await this.adapter.setState(key + ".Pressure", day ? day.pressure : 0, true); await this.adapter.setState(key + ".Snowline", day ? day.snowline : 0, true); await this.adapter.setState(key + ".UV_index_max", day ? day.uv_index_max : 0, true); // Sun in const sunInRaw = day && day.sun_in ? day.sun_in : 0; const sunInParts = sunInRaw ? this.FormatTimestampToLocal(sunInRaw) : { formattedTimeval: "", formattedTimevalDate: "", formattedTimevalWeekday: "", formattedTimevalTime: "", isoString: "" }; await this.adapter.setState(key + ".Sun_in", sunInParts.formattedTimevalTime, true); await this.adapter.setState(key + ".Sun_in_full", sunInRaw, true); // Sun mid const sunMidRaw = day && day.sun_mid ? day.sun_mid : 0; const sunMidParts = sunMidRaw ? this.FormatTimestampToLocal(sunMidRaw) : { formattedTimeval: "", formattedTimevalDate: "", formattedTimevalWeekday: "", formattedTimevalTime: "", isoString: "" }; await this.adapter.setState(key + ".Sun_mid", sunMidParts.formattedTimevalTime, true); await this.adapter.setState(key + ".Sun_mid_full", sunMidRaw, true); // Sun out const sunOutRaw = day && day.sun_out ? day.sun_out : 0; const sunOutParts = sunOutRaw ? this.FormatTimestampToLocal(sunOutRaw) : { formattedTimeval: "", formattedTimevalDate: "", formattedTimevalWeekday: "", formattedTimevalTime: "", isoString: "" }; await this.adapter.setState(key + ".Sun_out", sunOutParts.formattedTimevalTime, true); await this.adapter.setState(key + ".Sun_out_full", sunOutRaw, true); // Moon in const moonInRaw = day && day.moon_in ? day.moon_in : 0; const moonInParts = moonInRaw ? this.FormatTimestampToLocal(moonInRaw) : { formattedTimeval: "", formattedTimevalDa