Weiter zum Inhalt
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. JavaScript
  5. [TypeSkript] Wetter.com Forecast/Vorhersage

NEWS

  • Neuer ioBroker-Blog online: Monatsrückblick März/April 2026
    BluefoxB
    Bluefox
    8
    1
    385

  • Verwendung von KI bitte immer deutlich kennzeichnen
    HomoranH
    Homoran
    10
    1
    338

  • Monatsrückblick Januar/Februar 2026 ist online!
    BluefoxB
    Bluefox
    18
    1
    949

[TypeSkript] Wetter.com Forecast/Vorhersage

Geplant Angeheftet Gesperrt Verschoben JavaScript
66 Beiträge 10 Kommentatoren 3.1k Aufrufe 16 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • nik82N nik82

    @schimi
    Hi, hätte eine kurze Frage: Ich hab mit Iobroker wegen anderem Thema jetzt viel rumprobieren müssen und hab dazu immer wieder Iobroker neustarten müssen, deswegen hab ich leider jetzt schon einen "Monatslimit erreicht".
    Gibts da eine Möglichkeit das zu unterbinden, auch wenn man mal mehr Neustarts macht? :-)
    Danke im Voraus.

    HomoranH Nicht stören
    HomoranH Nicht stören
    Homoran
    Global Moderator Administrators
    schrieb am zuletzt editiert von
    #57

    @nik82 sagte in [TypeSkript] Wetter.com Forecast/Vorhersage:

    Gibts da eine Möglichkeit das zu unterbinden, auch wenn man mal mehr Neustarts macht?

    hast du vorher die Instanz komplett deaktiviert?

    kein Support per PN! - Fragen im Forum stellen -
    Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
    Das Forum freut sich über eine Spende. Benutzt dazu den Spendenbutton oben rechts. Danke!
    der Installationsfixer: curl -fsL https://iobroker.net/fix.sh | bash -

    1 Antwort Letzte Antwort
    0
    • nik82N Offline
      nik82N Offline
      nik82
      Most Active
      schrieb am zuletzt editiert von
      #58

      Nein da hab ich nicht dran gedacht ehrlich gesagt, aber die Scripts starten ja dann wieder wenn ich die Instanz starte oder?
      Klar wenn ich mehrere Starts hintereinander mache dann macht das Sinn, aber ich habe mit meiner Testerei immer so dermaßen den iobroker zerschossen, dass ich einfach meine Proxmox VM jedes mal wieder hergestellt habe.
      Ist auch nicht so wichtig, weil da wird ja nicht so oft so krass dran rumgefummelt, aber wenns im Script nur eine Kleinigkeit ist, dann könnte man das ja eventuell einbauen :-)

      1 Antwort Letzte Antwort
      0
      • S Online
        S Online
        Schimi
        schrieb am zuletzt editiert von
        #59

        Deswegen bin ich auch immer ins Limit... aber ausser das du das Skript deaktivierst und erst nach deinen experimenten wieder aktivierst, fällt mir nichts ein....

        Falls du Skripte testest, könntest du eine zweite JavaSkript Instanz dafür nehmen, wenn diese dann neugestartet wird, bleiben die anderen Skripte unangetastet...

        ansonsten bleibt nur einen weiteren API key zu beantragen...

        1 Antwort Letzte Antwort
        1
        • nik82N Offline
          nik82N Offline
          nik82
          Most Active
          schrieb am zuletzt editiert von nik82
          #60

          Alles klar, passt, danke dir :-)
          Ich glaube ich hab mir schonmal einen zweiten Key besorgt, aber der checkt wahrscheinlich die gleiche externe IP und sperrt dann auch wieder, kann das sein?

          EDIT
          Hab jetzt grad mal mit einer anderen Emailadresse ein neuen Api Key angefordert und dann eingetragen, log sagt dann aber wieder Monatslimit erreicht.
          Hab jetzt Fritzbox (aber leider erst nach neuen Api Key Eintrag) gestartet, jetzt hab ich neue externe IP, aber immer noch Limit. Kann das sein?
          Muss ich evtl. neue externe IP haben und dann erst den neuen Api Key eintragen?

          1 Antwort Letzte Antwort
          0
          • S Online
            S Online
            Schimi
            schrieb am zuletzt editiert von
            #61

            wenn du einen eneun API key einträgst, musst du die Datenpunkte auch auf 0 setzen, er fragt die Datenpunkte ab...

            Die API haut nun (ich meine) einen 404 oder so raus und sagt nicht speziell dass, das Limit voll ist (deswegen zähle ich selber)

            1 Antwort Letzte Antwort
            1
            • nik82N Offline
              nik82N Offline
              nik82
              Most Active
              schrieb am zuletzt editiert von
              #62

              @schimi
              So sorry, da hät ich natürlich selber drauf kommen können das ich den Datenpunkt auf 0 setze.
              Jetzt gehts wieder. Danke dir. :-)

              1 Antwort Letzte Antwort
              0
              • J Offline
                J Offline
                Jogis
                schrieb am zuletzt editiert von
                #63

                hallo, ich habe mich auch für den skript entschieden, bis 29.03 hat noch alles gut funktioniert und dann die Meldung "script.js.wetter_com: [Wetter.com] Monatslimit erreicht (100/100). Abruf gestoppt." seit dem funktioniert der Abruf vom Daten nicht. Was muss ich machen, das wieder Daten aktualisiert werden? Grüße

                1 Antwort Letzte Antwort
                0
                • S Online
                  S Online
                  Schimi
                  schrieb am zuletzt editiert von Schimi
                  #64

                  @Jogis schaue mal ob du die neuste Version hast....

                  Dann wäre es vielleicht mal nicht verkehrt, das Skript zu Stoppen > den "wetter_com" Ordner und userdata komplett löschen und das Skript neu starten damit es die Datenpunkte neu anlegt.

                  Der API-Key wird nun im entsprechenden Datenpunkt gespeichert.....

                  die aktuelle Version läuft bei mir nun schon "recht lange" fehlerfrei... diesen Monat habe ich auch den iobroker nicht so oft gestartet (dadurch war das 100 Limit kein Problem).
                  Falls dir immernoch zuviele abfragen angezeigt werden... entweder bis zum 01.05 warten oder einen neuen API key beantragen...

                  b91cb225-6089-465a-85f4-7ad043fbe411-image.jpeg

                  1 Antwort Letzte Antwort
                  0
                  • J Offline
                    J Offline
                    Jogis
                    schrieb am zuletzt editiert von
                    #65

                    Ich habe den Zähler "0_userdata.0.wetter_com.info.requests_month 100" auf 0 gesetzt jetzt kommen Daten wieder, benutze skript 2.6.1, iobroker läuft schon paar Monate und wird nicht neu gestartet. Frage nur warum im Monat April der Zähler nicht auf 0 gestellt war und die Daten nicht aktualisiert wurden? API key ist der gleiche.

                    1 Antwort Letzte Antwort
                    0
                    • S Schimi

                      [TrueSkript] Wetter.com Forecast API v4.0 (Meteonomiqs)

                      Dieses Skript importiert Wetterdaten von der neuen Wetter.com API (Meteonomiqs v4.0). Es ist hochperformant, bereinigt veraltete Daten automatisch und bietet detaillierte Vorhersagen in drei Ebenen.

                      🌟 Funktionen

                      • Tages-Zusammenfassung (Summary): Max/Min Temperaturen, Regenrisiko, Windböen, Bewölkung und Luftfeuchtigkeit für bis zu 16 Tage.

                      • Tagesabschnitte (Spaces): Detaillierte Daten für Vormittag, Nachmittag, Abend und Nacht.

                      • Stündliche Vorhersage (Hourly): Präzise Stundenwerte (Temp, Windchill, Regenmenge, Windrichtung, Feuchte) für heute und morgen.

                      • Limit-Überwachung: Das Skript erkennt den HTTP-Status 429 und informiert im Log, falls das monatliche Limit (100 Calls im Free-Tier) erreicht ist.

                      • Auto-Cleanup: Beim Ändern der Vorhersage-Tage werden veraltete Datenpunkte rekursiv gelöscht.

                      🛠 Installation

                      1. Erstelle im ioBroker ein neues Skript im Ordner common.

                      2. Wähle oben rechts als Typ zwingend TypeScript (NICHT JavaScript) aus.

                      3. Kopiere den Code hinein und speichere ihn.

                      ⚙️ Konfiguration

                      Am Anfang des Skripts findest du den Konfigurationsbereich:

                      • ENABLE_HOURLY_DATA / ENABLE_SPACES_DATA: Schaltet die Detail-Ebenen ein/aus.

                      Standort: Das Skript nutzt standardmäßig die Koordinaten aus den ioBroker-Systemeinstellungen. Über FORCE_MANUAL_LOCATION können manuelle Werte gesetzt werden.

                      • Datenpunkt: 0_userdata.0.wetter_com.info.api_key: Dein persönlicher Key von Meteonomiqs.

                      • Datenpunkt: 0_userdata.0.wetter_com.info.forecast_days: Anzahl der Tage (Standard: 7, max. 16).

                      📝 Hinweis zur Technik

                      info-Datenpunkt

                      • Unter “0_userdata.0.wetter_com.info” findet ihr Datenpunkte, die ihr in der VIS anzeigen könnt, um euren Verbrauch zu überwachen.

                      • Icon-URL wird als Datenpunkt ausgegeben

                      • NEU (seit Version 1.4.10): Wochentage werden ausgegeben. Anpassung an eingestellte Sprache im ioBroker (mit fallback im Skript).

                      der "weather_text" wird auch nach Systemsprache angepasst (z.B. "Leicht bewölkt" vs. "Cloudy").

                      • free API-Key anfordern: https://www.meteonomiqs.com/de/wetter-api/#heading_PricePackages/
                        (100 API-Aufrufe pro Monat (>3 pro Tag))
                      • letztes Update: 23.04.2026 - 08:57 Uhr

                      /**
                      * [Wetter.com Forecast API v4.0 (TrueScript)]
                      * * CHANGELOG:
                      * - 2.6.2: 2026-03-29 - FEATURE: Manueller Reset & Log-Präzision.
                      * - Datenpunkt 'info.force_reset' als Button integriert, um den Monatszähler administrativ nullen zu können.
                      * - Log-Ausgabe des Hard-Stops verständlicher formuliert (weist auf Auto-Resume am Monatsanfang hin).
                      * - 2.6.1: FIX: Konfigurations-Datenpunkte beschreibbar gemacht.
                      * - 2.6.0: ULTRA-PERFORMANCE & EDGE-CASE FIXES (RAM-Cache, wcomWait entfernt).
                      * * KONTEXT:
                      * - Hardware: ioBroker Server | Schnittstellen: Meteonomiqs API v4.0 (HTTP)
                      * * BEKANNTE PROBLEME / TÜCKEN:
                      * - Strikte timeouts bei HTTP-Calls erforderlich, um die Single-Thread-Natur der JS-Engine nicht mit hängenden Sockets zu belasten.
                      * * ZIELE:
                      * - Maximale Effizienz (Zero-Churn, Zero-I/O Overhead) und 100% typsichere Ausfallsicherheit unter Budget-Einhaltung.
                      */
                      
                      // --- KONFIGURATION ---
                      const CONFIG = {
                         // SECURITY: Den API Key NIEMALS hier im Klartext speichern!
                         DP_API_KEY: '0_userdata.0.wetter_com.info.api_key', 
                         DP_FORECAST_DAYS: '0_userdata.0.wetter_com.info.forecast_days',
                         // @KI_HINWEIS: Manueller Override-Schalter hinzugefügt
                         DP_FORCE_RESET: '0_userdata.0.wetter_com.info.force_reset', 
                         
                         BASE_URL: 'https://forecast.meteonomiqs.com/v4_0',
                         ICON_BASE_URL: 'https://cs3.wettercomassets.com/wcomv5/images/icons/weather',
                         DP_PATH: '0_userdata.0.wetter_com',
                         DEFAULT_LANGUAGE: 'de',
                         ENABLE_HOURLY: true,
                         ENABLE_SPACES: true,
                         MONTHLY_LIMIT: 100,
                         LOG_LEVEL: 'info' as 'debug' | 'info' | 'warn' | 'error',
                         LOCATION: {
                             LAT: '',
                             LON: '',
                             FORCE_MANUAL: false
                         }
                      };
                      
                      // --- STATISCHE DEFINITIONEN ---
                      const STATE_DEFS: Record<string, { name: string; type: iobJS.CommonType; role: string; unit?: string; init: any }> = {
                         'date': { name: 'Datum', type: 'string', role: 'text', init: '' },
                         'day_name': { name: 'Wochentag', type: 'string', role: 'text', init: '' },
                         'temp_max': { name: 'Max Temp', type: 'number', unit: '°C', role: 'value.temperature.max', init: 0 },
                         'temp_min': { name: 'Min Temp', type: 'number', unit: '°C', role: 'value.temperature.min', init: 0 },
                         'weather_text': { name: 'Wetter', type: 'string', role: 'weather.state', init: '' },
                         'weather_icon': { name: 'Icon URL', type: 'string', role: 'weather.icon', init: '' },
                         'prec_probability': { name: 'Regenrisiko', type: 'number', unit: '%', role: 'value.precipitation.probability', init: 0 },
                         'prec_sum': { name: 'Regenmenge', type: 'number', unit: 'mm', role: 'value.precipitation', init: 0 },
                         'wind_gusts': { name: 'Windböen', type: 'number', unit: 'km/h', role: 'value.speed.wind.gust', init: 0 },
                         'wind_speed_max': { name: 'Max. Windgeschwindigkeit', type: 'number', unit: 'km/h', role: 'value.speed.wind.max', init: 0 },
                         'sun_hours': { name: 'Sonnenstunden', type: 'number', unit: 'h', role: 'value.sun', init: 0 },
                         'clouds': { name: 'Bewölkung', type: 'number', unit: '%', role: 'value', init: 0 },
                         'humidity': { name: 'Relative Feuchte', type: 'number', unit: '%', role: 'value.humidity', init: 0 }
                      };
                      
                      // --- INTERFACES ---
                      
                      type FetchSource = 'morning' | 'afternoon' | 'start' | 'key_update' | 'days_update' | 'force_reset';
                      
                      interface WetterComValue {
                         avg?: number;
                         value?: number;
                         sum?: number;
                         max?: number;
                         min?: number;
                      }
                      
                      interface WetterComWeather {
                         state: number;
                         text: string;
                         icon?: string;
                      }
                      
                      interface WetterComWind {
                         avg?: number | WetterComValue;
                         min?: number | WetterComValue;
                         max?: number | WetterComValue;
                         gusts?: number | WetterComValue | { value: number | null };
                         direction?: string;
                         unit?: string;
                      }
                      
                      interface WetterComPrec {
                         probability: number;
                         sum: number | WetterComValue;
                      }
                      
                      interface ForecastSummary {
                         date: string;
                         weather: WetterComWeather;
                         temperature: { min: number | WetterComValue; max: number | WetterComValue; avg?: number | WetterComValue };
                         wind: WetterComWind;
                         prec: WetterComPrec;
                         clouds: number | WetterComValue;
                         relativeHumidity: number | WetterComValue;
                         sunHours?: number;
                      }
                      
                      interface ForecastSpaceSegment {
                         temperature: number | WetterComValue;
                         weather: WetterComWeather;
                         prec: WetterComPrec;
                         wind: WetterComWind;
                         clouds: number | WetterComValue;
                         relativeHumidity: number | WetterComValue;
                      }
                      
                      interface ForecastSpace {
                         morning?: ForecastSpaceSegment;
                         afternoon?: ForecastSpaceSegment;
                         evening?: ForecastSpaceSegment;
                         night?: ForecastSpaceSegment;
                      }
                      
                      interface ForecastHourly {
                         from: string;
                         date: string;
                         weather: WetterComWeather;
                         temperature: number | WetterComValue;
                         windchill: number | WetterComValue;
                         wind: WetterComWind;
                         prec: WetterComPrec;
                         relativeHumidity: number | WetterComValue;
                      }
                      
                      interface WetterComResponse {
                         summary: ForecastSummary[];
                         spaces: ForecastSpace[];
                         hourly: ForecastHourly[];
                      }
                      
                      interface SystemConfig {
                         lat: string | null;
                         lon: string | null;
                         lang: string;
                      }
                      
                      // --- GLOBALE VARIABLEN ---
                      let isFetching: boolean = false;
                      const ensuredPaths = new Set<string>();
                      
                      // --- HILFSFUNKTIONEN ---
                      
                      function wcomLog(msg: string, level: 'debug' | 'info' | 'warn' | 'error' = 'info'): void {
                         const levels = { debug: 0, info: 1, warn: 2, error: 3 };
                         if (levels[level] >= levels[CONFIG.LOG_LEVEL]) {
                             log(`[Wetter.com] ${msg}`, level);
                         }
                      }
                      
                      function wcomExtractValue(val: any): number {
                         if (val === null || val === undefined) return 0;
                         if (typeof val === 'number') {
                             if (isNaN(val)) {
                                 wcomLog('API lieferte explizites NaN als number-Typ', 'debug');
                                 return 0;
                             }
                             return val;
                         }
                         if (typeof val === 'object') {
                             if (val.value !== undefined && val.value !== null) return val.value;
                             if (val.avg !== undefined && val.avg !== null) return val.avg;
                             if (val.sum !== undefined && val.sum !== null) return val.sum;
                             if (val.max !== undefined && val.max !== null) return val.max;
                             if (val.min !== undefined && val.min !== null) return val.min;
                         }
                         const parsed = parseFloat(String(val));
                         if (isNaN(parsed)) {
                             if (String(val).trim() !== '') {
                                 wcomLog(`Unerwarteter Nicht-Zahlenwert (NaN) von API empfangen: "${val}"`, 'debug');
                             }
                             return 0;
                         }
                         return parsed;
                      }
                      
                      function wcomFormatDate(dateInput: string | Date): string {
                         if (!dateInput) return '';
                         const date = typeof dateInput === 'string' ? new Date(dateInput) : dateInput;
                         if (isNaN(date.getTime())) return String(dateInput);
                         return `${String(date.getDate()).padStart(2, '0')}.${String(date.getMonth() + 1).padStart(2, '0')}.${date.getFullYear()}`;
                      }
                      
                      function wcomGetDayName(dateStr: string, locale: string): string {
                         if (!dateStr) return '';
                         const date = new Date(dateStr);
                         if (isNaN(date.getTime())) return '';
                         return date.toLocaleDateString(locale, { weekday: 'long' });
                      }
                      
                      async function wcomGetSystemSettings(): Promise<SystemConfig | null> {
                         let coords: { lat: string; lon: string } | null = null;
                         if (CONFIG.LOCATION.FORCE_MANUAL && CONFIG.LOCATION.LAT && CONFIG.LOCATION.LON) {
                             const lat = parseFloat(CONFIG.LOCATION.LAT);
                             const lon = parseFloat(CONFIG.LOCATION.LON);
                             if (!isNaN(lat) && !isNaN(lon)) {
                                 coords = { lat: lat.toFixed(3), lon: lon.toFixed(3) };
                             } else {
                                 wcomLog('Manuelle Koordinaten sind ungültig (NaN).', 'error');
                                 return null;
                             }
                         }
                      
                         const systemConf: SystemConfig = await new Promise((resolve) => {
                             getObject('system.config', (err, obj: any) => {
                                 if (!err && obj && obj.common) {
                                     const sysLat = obj.common.latitude !== undefined && obj.common.latitude !== null ? parseFloat(String(obj.common.latitude)).toFixed(3) : null;
                                     const sysLon = obj.common.longitude !== undefined && obj.common.longitude !== null ? parseFloat(String(obj.common.longitude)).toFixed(3) : null;
                                     
                                     resolve({
                                         lat: sysLat,
                                         lon: sysLon,
                                         lang: obj.common.language || CONFIG.DEFAULT_LANGUAGE
                                     });
                                 } else { 
                                     resolve({ lat: null, lon: null, lang: CONFIG.DEFAULT_LANGUAGE }); 
                                 }
                             });
                         });
                      
                         if (!coords && systemConf.lat && systemConf.lon) coords = { lat: systemConf.lat, lon: systemConf.lon };
                         return coords ? { ...coords, lang: systemConf.lang } : null;
                      }
                      
                      async function wcomEnsureSubStructure(path: string, name: string, type: 'device' | 'channel' = 'channel'): Promise<void> {
                         if (!path || ensuredPaths.has(path)) return;
                         if (!existsObject(path)) {
                             await extendObjectAsync(path, {
                                 type: type,
                                 common: { name: name },
                                 native: {}
                             });
                         }
                         ensuredPaths.add(path);
                      }
                      
                      async function wcomEnsureState(path: string, init: any, type: iobJS.CommonType, name: string, role: string, unit?: string, writeable: boolean = false): Promise<void> {
                         if (ensuredPaths.has(path)) return;
                         if (!existsObject(path)) {
                             await createStateAsync(path, init, false, { name, type, role, unit: unit || '', read: true, write: writeable } as any);
                         }
                         ensuredPaths.add(path);
                      }
                      
                      async function wcomEnsureDayStates(path: string, index: number): Promise<void> {
                         const promises = Object.entries(STATE_DEFS).map(([id, cfg]) => {
                             return wcomEnsureState(`${path}.${id}`, cfg.init, cfg.type, `Tag ${index}: ${cfg.name}`, cfg.role, cfg.unit);
                         });
                         
                         await Promise.all(promises);
                      }
                      
                      async function wcomHttpGetAsync(url: string, options: any): Promise<any> {
                         let timeoutId: NodeJS.Timeout;
                         
                         const fetchPromise = new Promise((resolve, reject) => {
                             httpGet(url, options, (err, response) => {
                                 if (err) reject(err);
                                 else resolve(response);
                             });
                         });
                      
                         const timeoutPromise = new Promise((_, reject) => {
                             timeoutId = setTimeout(() => reject(new Error('HTTP Timeout nach 10 Sekunden')), 10000);
                         });
                      
                         try {
                             return await Promise.race([fetchPromise, timeoutPromise]);
                         } finally {
                             if (timeoutId!) clearTimeout(timeoutId);
                         }
                      }
                      
                      // --- LOGIK ---
                      
                      async function wcomCheckBudget(source: FetchSource): Promise<boolean> {
                         const requestState = await getStateAsync(`${CONFIG.DP_PATH}.info.requests_month`);
                         const currentUsage = requestState && requestState.val !== null ? Number(requestState.val) : 0;
                         
                         if (currentUsage >= CONFIG.MONTHLY_LIMIT) {
                             // @KI_HINWEIS: Log-Message präzisiert, um Klarheit über den Auto-Resume am 1. zu schaffen.
                             wcomLog(`Monatslimit erreicht (${currentUsage}/${CONFIG.MONTHLY_LIMIT}). Skript pausiert automatisch bis zum 01. des Folgemonats.`, 'warn');
                             return false;
                         }
                      
                         if (source === 'start') {
                             const todayState = await getStateAsync(`${CONFIG.DP_PATH}.info.requests_today`);
                             if (todayState && todayState.val !== null && Number(todayState.val) > 0) {
                                 wcomLog(`Skript-Neustart erkannt. Abruf übersprungen, da heute bereits Daten geladen wurden.`, 'debug');
                                 return false;
                             }
                         }
                      
                         const now = new Date();
                         const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
                         const daysLeft = daysInMonth - now.getDate(); 
                      
                         if (source === 'afternoon' || (source === 'start' && now.getHours() >= 12)) {
                             const callsNeededFor2xDaily = (daysLeft * 2) + 1; 
                             if (currentUsage + callsNeededFor2xDaily > CONFIG.MONTHLY_LIMIT) {
                                 wcomLog(`SPARMODUS AKTIV: Nachmittags-Abruf übersprungen (Budget-Schutz). Verbrauch: ${currentUsage}`, 'warn');
                                 return false;
                             }
                         }
                      
                         if (source === 'morning' || (source === 'start' && now.getHours() < 12)) {
                             const callsNeededFor1xDaily = daysLeft + 1;
                             if (currentUsage + callsNeededFor1xDaily > CONFIG.MONTHLY_LIMIT) {
                                 if (now.getDate() % 2 !== 0) {
                                     wcomLog(`NOTLAUF AKTIV: Morgen-Abruf übersprungen (Budget extrem niedrig). Verbrauch: ${currentUsage}`, 'warn');
                                     return false;
                                 }
                             }
                         }
                      
                         return true;
                      }
                      
                      async function wcomCheckDailyReset(): Promise<void> {
                         const nowStr = wcomFormatDate(new Date()); 
                         const lastSyncState = await getStateAsync(`${CONFIG.DP_PATH}.info.last_sync`);
                         
                         if (lastSyncState && typeof lastSyncState.val === 'string') {
                             const lastSyncDate = lastSyncState.val.split(' ')[0]; 
                             if (lastSyncDate && lastSyncDate !== nowStr) {
                                 wcomLog('Tageswechsel erkannt. Setze requests_today auf 0.', 'debug');
                                 await setStateAsync(`${CONFIG.DP_PATH}.info.requests_today`, 0, true);
                             }
                         }
                      }
                      
                      async function wcomFetchWeatherData(source: FetchSource = 'start'): Promise<void> {
                         if (isFetching) {
                             wcomLog('Abruf läuft bereits (Lock aktiv). Abbruch.', 'debug');
                             return;
                         }
                         isFetching = true;
                      
                         try {
                             await wcomEnsureSubStructure(CONFIG.DP_PATH, 'Wetter.com Forecast', 'device');
                             await wcomEnsureSubStructure(`${CONFIG.DP_PATH}.info`, 'Informationen');
                             await wcomEnsureState(`${CONFIG.DP_PATH}.info.requests_month`, 0, 'number', 'Anfragen Monat', 'value');
                             await wcomEnsureState(`${CONFIG.DP_PATH}.info.requests_today`, 0, 'number', 'Anfragen heute', 'value');
                             
                             await wcomEnsureState(CONFIG.DP_API_KEY, '', 'string', 'Wetter.com API Key', 'text', '', true);
                             await wcomEnsureState(CONFIG.DP_FORECAST_DAYS, 7, 'number', 'Vorhersage Tage', 'value', '', true);
                             // @KI_HINWEIS: Initialisierung des Reset-Buttons
                             await wcomEnsureState(CONFIG.DP_FORCE_RESET, false, 'boolean', 'Manueller Zähler-Reset', 'button', '', true);
                      
                             if (source === 'start') {
                                 await extendObjectAsync(CONFIG.DP_API_KEY, { common: { write: true } });
                                 await extendObjectAsync(CONFIG.DP_FORECAST_DAYS, { common: { write: true } });
                             }
                      
                             const apiKeyObj = await getStateAsync(CONFIG.DP_API_KEY);
                             const apiKeyValue = apiKeyObj ? String(apiKeyObj.val).trim() : '';
                      
                             if (!apiKeyValue || apiKeyValue.length < 10) {
                                 wcomLog(`Bitte gültigen API-Key im beschreibbaren Datenpunkt '${CONFIG.DP_API_KEY}' eintragen!`, 'error');
                                 return;
                             }
                      
                             const daysObj = await getStateAsync(CONFIG.DP_FORECAST_DAYS);
                             let forecastDays = daysObj && daysObj.val !== null ? Number(daysObj.val) : 7;
                             forecastDays = Math.max(1, Math.min(forecastDays, 16));
                      
                             await wcomCheckDailyReset();
                      
                             const allowFetch = await wcomCheckBudget(source);
                             if (!allowFetch) return;
                      
                             const settings = await wcomGetSystemSettings();
                             if (!settings) return;
                             
                             wcomLog(`Abruf gestartet für Lat: ${settings.lat}, Lon: ${settings.lon} (Trigger: ${source}, Tage: ${forecastDays})`, 'info');
                      
                             const url: string = `${CONFIG.BASE_URL}/forecast/${settings.lat}/${settings.lon}`;
                             const options = { headers: { 'x-api-key': apiKeyValue, 'Accept-Language': settings.lang } };
                      
                             const response = await wcomHttpGetAsync(url, options);
                      
                             if (response && response.statusCode === 429) {
                                 wcomLog('Das Limit von 100 API-Calls im Monat ist ausgeschöpft (HTTP 429).', 'error');
                                 await setStateAsync(`${CONFIG.DP_PATH}.info.requests_month`, CONFIG.MONTHLY_LIMIT, true);
                                 return;
                             }
                      
                             if (response && response.statusCode !== 200) {
                                 wcomLog(`API-Fehler: HTTP ${response.statusCode}`, 'error');
                                 return;
                             }
                      
                             let data: WetterComResponse;
                             try {
                                 data = JSON.parse(response.data);
                             } catch (e) { 
                                 wcomLog('Konnte API-Antwort nicht parsen.', 'error');
                                 return; 
                             }
                      
                             if (data && data.summary) {
                                 await wcomProcessForecastData(data, settings.lang, forecastDays);
                                 await wcomCleanupObsoleteDays(forecastDays);
                                 await wcomUpdateUsageInfo();
                             }
                      
                         } catch (e: any) { 
                             wcomLog(`Script-Fehler: ${e.message}`, 'error'); 
                         } finally {
                             isFetching = false;
                         }
                      }
                      
                      async function wcomProcessForecastData(data: WetterComResponse, lang: string, forecastDays: number): Promise<void> {
                         await wcomEnsureSubStructure(CONFIG.DP_PATH, 'Wetter.com Forecast', 'device');
                         await wcomEnsureState(`${CONFIG.DP_PATH}.info.last_sync`, '', 'string', 'Letztes Update', 'text');
                      
                         const maxDays: number = Math.min((data.summary ?? []).length, forecastDays);
                         let totalWrites = 0;
                      
                         for (let i = 0; i < maxDays; i++) {
                             const dayWriteBuffer: Promise<any>[] = [];
                             const day: ForecastSummary = data.summary[i];
                             const dayPath: string = `${CONFIG.DP_PATH}.day_${i}`;
                             
                             await wcomEnsureSubStructure(dayPath, `Tag ${i}`);
                             await wcomEnsureDayStates(dayPath, i);
                      
                             const iconName = `d_${day.weather?.state ?? 0}.svg`;
                      
                             dayWriteBuffer.push(
                                 setStateChangedAsync(`${dayPath}.date`, String(wcomFormatDate(day.date)), true),
                                 setStateChangedAsync(`${dayPath}.day_name`, String(wcomGetDayName(day.date, lang)), true),
                                 setStateChangedAsync(`${dayPath}.temp_max`, wcomExtractValue(day.temperature?.max), true),
                                 setStateChangedAsync(`${dayPath}.temp_min`, wcomExtractValue(day.temperature?.min), true),
                                 setStateChangedAsync(`${dayPath}.weather_text`, String(day.weather?.text || ''), true),
                                 setStateChangedAsync(`${dayPath}.weather_icon`, `${CONFIG.ICON_BASE_URL}/${iconName}`, true),
                                 setStateChangedAsync(`${dayPath}.prec_probability`, wcomExtractValue(day.prec?.probability), true),
                                 setStateChangedAsync(`${dayPath}.prec_sum`, wcomExtractValue(day.prec?.sum), true),
                                 setStateChangedAsync(`${dayPath}.wind_gusts`, wcomExtractValue(day.wind?.gusts), true),
                                 setStateChangedAsync(`${dayPath}.wind_speed_max`, wcomExtractValue(day.wind?.max ?? day.wind?.avg), true),
                                 setStateChangedAsync(`${dayPath}.sun_hours`, wcomExtractValue(day.sunHours), true),
                                 setStateChangedAsync(`${dayPath}.clouds`, wcomExtractValue(day.clouds), true),
                                 setStateChangedAsync(`${dayPath}.humidity`, wcomExtractValue(day.relativeHumidity), true)
                             );
                      
                             if (CONFIG.ENABLE_SPACES && data.spaces && data.spaces[i]) {
                                 const spacesPath: string = `${dayPath}.spaces`;
                                 await wcomEnsureSubStructure(spacesPath, 'Tagesabschnitte');
                                 const segments: (keyof ForecastSpace)[] = ['morning', 'afternoon', 'evening', 'night'];
                                 
                                 for (const seg of segments) {
                                     const sData = data.spaces[i][seg];
                                     if (!sData) continue;
                                     const sPath: string = `${spacesPath}.${seg}`;
                                     await wcomEnsureSubStructure(sPath, seg);
                                     
                                     await Promise.all([
                                         wcomEnsureState(`${sPath}.temp`, 0, 'number', 'Temperatur', 'value.temperature', '°C'),
                                         wcomEnsureState(`${sPath}.text`, '', 'string', 'Wetter', 'weather.state'),
                                         wcomEnsureState(`${sPath}.prec_prob`, 0, 'number', 'Regenrisiko', 'value.precipitation.probability', '%'),
                                         wcomEnsureState(`${sPath}.prec_sum`, 0, 'number', 'Regenmenge', 'value.precipitation', 'mm'),
                                         wcomEnsureState(`${sPath}.wind_speed`, 0, 'number', 'Windgeschwindigkeit', 'value.speed.wind', 'km/h'),
                                         wcomEnsureState(`${sPath}.wind_gusts`, 0, 'number', 'Windböen', 'value.speed.wind.gust', 'km/h'),
                                         wcomEnsureState(`${sPath}.clouds`, 0, 'number', 'Bewölkung', 'value', '%'),
                                         wcomEnsureState(`${sPath}.humidity`, 0, 'number', 'Relative Feuchte', 'value.humidity', '%')
                                     ]);
                      
                                     dayWriteBuffer.push(
                                         setStateChangedAsync(`${sPath}.temp`, wcomExtractValue(sData.temperature), true),
                                         setStateChangedAsync(`${sPath}.text`, String(sData.weather?.text || ''), true),
                                         setStateChangedAsync(`${sPath}.prec_prob`, wcomExtractValue(sData.prec?.probability), true),
                                         setStateChangedAsync(`${sPath}.prec_sum`, wcomExtractValue(sData.prec?.sum), true),
                                         setStateChangedAsync(`${sPath}.wind_speed`, wcomExtractValue(sData.wind?.avg), true),
                                         setStateChangedAsync(`${sPath}.wind_gusts`, wcomExtractValue(sData.wind?.gusts), true),
                                         setStateChangedAsync(`${sPath}.clouds`, wcomExtractValue(sData.clouds), true),
                                         setStateChangedAsync(`${sPath}.humidity`, wcomExtractValue(sData.relativeHumidity), true)
                                     );
                                 }
                             }
                      
                             if (CONFIG.ENABLE_HOURLY && i <= 1 && data.hourly) {
                                 const dateIso: string = day.date.split('T')[0];
                                 const hourlyPath: string = `${dayPath}.hourly`;
                      
                                 await wcomEnsureSubStructure(hourlyPath, 'Stündlich');
                                 const dayHours = (data.hourly ?? []).filter((h: ForecastHourly) => (h.from || h.date).startsWith(dateIso));
                                 
                                 for (const h of dayHours) {
                                     const hourDate: Date = new Date(h.from || h.date);
                                     const hourNum: number = hourDate.getHours();
                                     const hourLabel: string = String(hourNum).padStart(2, '0');
                                     const hPath: string = `${hourlyPath}.${hourLabel}`;
                                     
                                     await wcomEnsureSubStructure(hPath, `${hourLabel}:00 Uhr`);
                                     
                                     const hourIcon = (hourNum >= 18 || hourNum < 6) ? `n_${h.weather?.state ?? 0}.svg` : `d_${h.weather?.state ?? 0}.svg`;
                      
                                     await Promise.all([
                                         wcomEnsureState(`${hPath}.time`, '', 'string', 'Uhrzeit', 'text'),
                                         wcomEnsureState(`${hPath}.temp`, 0, 'number', 'Temperatur', 'value.temperature', '°C'),
                                         wcomEnsureState(`${hPath}.windchill`, 0, 'number', 'Gefühlt', 'value.temperature', '°C'),
                                         wcomEnsureState(`${hPath}.weather_text`, '', 'string', 'Wetter', 'weather.state'),
                                         wcomEnsureState(`${hPath}.weather_icon`, '', 'string', 'Wetter Icon', 'weather.icon'),
                                         wcomEnsureState(`${hPath}.prec_prob`, 0, 'number', 'Regenwahrscheinlichkeit', 'value.precipitation.probability', '%'),
                                         wcomEnsureState(`${hPath}.prec_sum`, 0, 'number', 'Regenmenge', 'value.precipitation', 'mm'),
                                         wcomEnsureState(`${hPath}.wind_speed`, 0, 'number', 'Windgeschwindigkeit', 'value.speed.wind', 'km/h'),
                                         wcomEnsureState(`${hPath}.wind_dir`, '', 'string', 'Windrichtung', 'weather.direction'),
                                         wcomEnsureState(`${hPath}.wind_gusts`, 0, 'number', 'Windböen', 'value.speed.wind.gust', 'km/h'),
                                         wcomEnsureState(`${hPath}.humidity`, 0, 'number', 'Relative Feuchte', 'value.humidity', '%')
                                     ]);
                      
                                     dayWriteBuffer.push(
                                         setStateChangedAsync(`${hPath}.time`, `${hourLabel}:00`, true),
                                         setStateChangedAsync(`${hPath}.temp`, wcomExtractValue(h.temperature), true),
                                         setStateChangedAsync(`${hPath}.windchill`, wcomExtractValue(h.windchill), true),
                                         setStateChangedAsync(`${hPath}.weather_text`, String(h.weather?.text || ''), true),
                                         setStateChangedAsync(`${hPath}.weather_icon`, `${CONFIG.ICON_BASE_URL}/${hourIcon}`, true),
                                         setStateChangedAsync(`${hPath}.prec_prob`, wcomExtractValue(h.prec?.probability), true),
                                         setStateChangedAsync(`${hPath}.prec_sum`, wcomExtractValue(h.prec?.sum), true),
                                         setStateChangedAsync(`${hPath}.wind_speed`, wcomExtractValue(h.wind?.avg), true),
                                         setStateChangedAsync(`${hPath}.wind_dir`, String(h.wind?.direction || ''), true),
                                         setStateChangedAsync(`${hPath}.wind_gusts`, wcomExtractValue(h.wind?.gusts), true),
                                         setStateChangedAsync(`${hPath}.humidity`, wcomExtractValue(h.relativeHumidity), true)
                                     );
                                 }
                             }
                             
                             totalWrites += dayWriteBuffer.length;
                             await Promise.all(dayWriteBuffer);
                         }
                         wcomLog(`Update von ${maxDays} Tagen abgeschlossen (${totalWrites} Werte prozessiert).`, 'info');
                      }
                      
                      async function wcomCleanupObsoleteDays(forecastDays: number): Promise<void> {
                         for (let i = forecastDays; i <= 25; i++) {
                             const path: string = `${CONFIG.DP_PATH}.day_${i}`;
                             if (existsObject(path)) {
                                 await deleteObjectAsync(path, true);
                             }
                         }
                      }
                      
                      async function wcomUpdateUsageInfo(): Promise<void> {
                         const now: Date = new Date();
                         const timestamp: string = `${String(now.getDate()).padStart(2,'0')}.${String(now.getMonth()+1).padStart(2,'0')}.${now.getFullYear()} ${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}`;
                         
                         await setStateAsync(`${CONFIG.DP_PATH}.info.last_sync`, String(timestamp), true);
                         
                         const countToday = await getStateAsync(`${CONFIG.DP_PATH}.info.requests_today`);
                         await setStateAsync(`${CONFIG.DP_PATH}.info.requests_today`, (countToday && countToday.val !== null ? Number(countToday.val) : 0) + 1, true);
                      
                         const countMonth = await getStateAsync(`${CONFIG.DP_PATH}.info.requests_month`);
                         await setStateAsync(`${CONFIG.DP_PATH}.info.requests_month`, (countMonth && countMonth.val !== null ? Number(countMonth.val) : 0) + 1, true);
                      }
                      
                      // --- ZEITSTEUERUNG & TRIGGER ---
                      
                      function wcomGetRandomCron(startHour: number, endHour: number, minMinute: number = 0): string {
                         const hour: number = Math.floor(Math.random() * (endHour - startHour + 1)) + startHour;
                         let minute: number = (hour === startHour) ? Math.floor(Math.random() * (60 - minMinute)) + minMinute : (hour === endHour ? 0 : Math.floor(Math.random() * 60));
                         return `${minute} ${hour} * * *`;
                      }
                      
                      schedule("0 0 1 * *", async () => {
                         const path = `${CONFIG.DP_PATH}.info.requests_month`;
                         if (existsState(path)) {
                             await setStateAsync(path, 0, true);
                             wcomLog('Monatszähler zurückgesetzt.', 'info');
                         }
                      });
                      
                      schedule(wcomGetRandomCron(0, 5, 2), () => wcomFetchWeatherData('morning'));
                      schedule(wcomGetRandomCron(13, 17, 2), () => wcomFetchWeatherData('afternoon'));
                      
                      on({ id: CONFIG.DP_API_KEY, change: 'ne' }, (obj) => {
                         if (obj.state && typeof obj.state.val === 'string' && obj.state.val.trim().length >= 10) {
                             wcomLog('Änderung des API-Keys erkannt. Starte sofortigen Test-Abruf...', 'info');
                             wcomFetchWeatherData('key_update');
                         }
                      });
                      
                      on({ id: CONFIG.DP_FORECAST_DAYS, change: 'ne' }, (obj) => {
                         if (obj.state && obj.state.val !== null) {
                             wcomLog(`Änderung der Vorhersage-Tage auf ${obj.state.val} erkannt. Starte Aktualisierung & Bereinigung...`, 'info');
                             wcomFetchWeatherData('days_update');
                         }
                      });
                      
                      // @KI_HINWEIS: Trigger für manuellen Budget-Reset
                      on({ id: CONFIG.DP_FORCE_RESET, change: 'any', val: true }, async () => {
                         wcomLog('Manueller Reset ausgelöst. Setze Monatszähler auf 0.', 'warn');
                         await setStateAsync(`${CONFIG.DP_PATH}.info.requests_month`, 0, true);
                         await setStateAsync(CONFIG.DP_FORCE_RESET, false, true); // Button zurücksetzen
                         wcomFetchWeatherData('force_reset');
                      });
                      
                      // Initialer Aufruf beim Skriptstart
                      wcomFetchWeatherData('start');
                      

                      P Offline
                      P Offline
                      pk68
                      schrieb zuletzt editiert von
                      #66

                      Hi @Schimi , danke für das Skript.

                      Ich glaube, es gibt einen kleinen Bug bei den stündlichen Daten. Die Werte für die ersten beiden Stunden des Tages werden falsch zugeordnet. Zum Debuggen habe ich das Skript angepasst und den Datenpunkt "from" mitschreiben lassen. Da ergibt sich folgendes Bild:

                      0_userdata.0.wetter_com.day_0.hourly.23.from = 2026-04-25T21:00:00Z
                      0_userdata.0.wetter_com.day_1.hourly.00.from = 2026-04-26T22:00:00Z
                      0_userdata.0.wetter_com.day_1.hourly.01.from = 2026-04-26T23:00:00Z
                      0_userdata.0.wetter_com.day_1.hourly.02.from = 2026-04-26T00:00:00Z
                      

                      Die TImestamps sind ja UTC, also 2h Versatz zu Deutschland. Die Uhrzeiten sind alle ok, aber bei 00:00 und 01:00 Uhr stimmt das Datum nicht. Da müsste als Tag der 25. drin stehen.

                      1 Antwort Letzte Antwort
                      0

                      Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

                      Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

                      Mit deinem Input könnte dieser Beitrag noch besser werden 💗

                      Registrieren Anmelden
                      Antworten
                      • In einem neuen Thema antworten
                      Anmelden zum Antworten
                      • Älteste zuerst
                      • Neuste zuerst
                      • Meiste Stimmen


                      Support us

                      ioBroker
                      Community Adapters
                      Donate

                      489

                      Online

                      32.8k

                      Benutzer

                      82.8k

                      Themen

                      1.3m

                      Beiträge
                      Community
                      Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                      ioBroker Community 2014-2025
                      logo
                      • Anmelden

                      • Du hast noch kein Konto? Registrieren

                      • Anmelden oder registrieren, um zu suchen
                      • Erster Beitrag
                        Letzter Beitrag
                      0
                      • Home
                      • Aktuell
                      • Tags
                      • Ungelesen 0
                      • Kategorien
                      • Unreplied
                      • Beliebt
                      • GitHub
                      • Docu
                      • Hilfe