Skip to content
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • 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. [Skript] Wetter.com Forecast/Vorhersage

NEWS

  • Jahresrückblick 2025 – unser neuer Blogbeitrag ist online! ✨
    BluefoxB
    Bluefox
    16
    1
    922

  • Neuer Blogbeitrag: Monatsrückblick - Dezember 2025 🎄
    BluefoxB
    Bluefox
    13
    1
    688

  • Weihnachtsangebot 2025! 🎄
    BluefoxB
    Bluefox
    25
    1
    1.9k

[Skript] Wetter.com Forecast/Vorhersage

Geplant Angeheftet Gesperrt Verschoben JavaScript
5 Beiträge 4 Kommentatoren 154 Aufrufe 7 Watching
  • Ä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.
  • S Offline
    S Offline
    Schimi
    schrieb am zuletzt editiert von Schimi
    #1

    Hallo zusammen,

    da wetter.com die alten API-Versionen abschaltet, habe ich (in Zusammenarbeit mit Gemini) ein Skript für die API v4.0 (Meteonomiqs) erstellt. (und weil es keinen Adapter dafür gibt)

    Das Skript ist darauf ausgelegt, "Plug & Play" zu funktionieren, die Daten sauber in 0_userdata.0 zu strukturieren und dabei die Limits des Free-Accounts (100 Abrufe/Monat) im Blick zu behalten.

    Features
    API v4.0 Support: Nutzt die aktuellen Endpunkte von Meteonomiqs/wetter.com.

    Automatische Struktur: Legt alle benötigten Ordner und Datenpunkte unter 0_userdata.0.wetter_com selbständig an.

    Standort-Automatik: Liest Breiten- und Längengrad direkt aus den ioBroker-Systemeinstellungen.

    • Fallback/Override: Manuelle Koordinaten können im Skript hinterlegt werden (z.B. für Ferienhäuser).

    Limit-Wächter: Überwacht die Anzahl der API-Abrufe (täglich und monatlich).

    • Bei Fehler 429 (Limit erreicht) wird eine Warnung ausgegeben und der Abruf gestoppt, statt das Log vollzuschreiben.

    Server-Schutz (Random Cron): Um die API nicht zu überlasten, wenn viele User das Skript nutzen, werden die Abrufzeiten bei jedem Skriptstart zufällig innerhalb sinnvoller Zeitfenster (morgens 00:02–05:00 Uhr und nachmittags 13:02–17:00 Uhr) generiert.

    Cleanup: Wenn man die Anzahl der Vorhersage-Tage reduziert, werden überflüssige Datenpunkte automatisch gelöscht.

    Datenpunkte:

    • Datum

    • Min/Max Temperatur

    • Wetterzustand (Text & Code für Icons)

    • Regenwahrscheinlichkeit & Menge

    • Sonnenstunden

    • Wind (Geschwindigkeit & Richtung)

    Voraussetzungen
    API Key: Ihr benötigt einen kostenlosen API-Key von Meteonomiqs/wetter.com. (Free-Paket wählen).

    • https://www.meteonomiqs.com/de/wetter-api/#heading_PricePackages/

    Koordinaten: Sollten in den ioBroker Haupteinstellungen (System -> Einstellungen) hinterlegt sein.

    • (Kann im Skript, im Bereich "STANDORT KONFIGURATION", angepasst/geändert werden)

    Installation & Konfiguration

    1. Neues JS-Skript im ioBroker anlegen.

    2. Code hineinkopieren.

    3. Im Bereich --- KONFIGURATION --- euren API_KEY eintragen.

    4. Optional: FORECAST_DAYS anpassen (Standard: 7 Tage).

    info-Datenpunkt
    Unter 0_userdata.0.wetter_com.info findet ihr Datenpunkte wie requests_month oder next_schedules, die ihr in der VIS anzeigen könnt, um euren Verbrauch zu überwachen.

    /**
    * ioBroker Script: Wetter.com Forecast API v4.0
    * API: https://doc.meteonomiqs.com/doc/forecast_v4_0.html
    * * Changelog:
    * 1.4.6: Feature: Manuelle Standort-Konfiguration (Fallback & Override) hinzugefügt.
    * 1.4.5: FIX: Syntax-Fehler im Header behoben.
    * 1.4.4: Anpassung an Monatslimit (100 Requests).
    * 1.4.3: Korrektur "last_sync" Formatierung.
    * 1.4.1: Randomisierung der Abrufzeiten.
    * 1.4.0: Info-Datenpunkte hinzugefügt.
    * * free API-Key anfordern: https://www.meteonomiqs.com/de/wetter-api/#heading_PricePackages/
    */
    
    // --- KONFIGURATION ---
    const API_KEY = 'DEIN_API_KEY_HIER'; // <-- BITTE HIER DEINEN API-KEY EINTRAGEN
    const BASE_URL = 'https://forecast.meteonomiqs.com/v4_0';
    const DP_PATH = '0_userdata.0.wetter_com';
    const LANGUAGE = 'de-de';
    const FORECAST_DAYS = 7; 
    
    // --- STANDORT KONFIGURATION ---
    // Hier Koordinaten eintragen für Fallback oder spezifischen Standort (z.B. '52.520')
    const MANUAL_LATITUDE = ''; 
    const MANUAL_LONGITUDE = '';
    
    // true  = Nutze IMMER die manuellen Koordinaten (Ignoriert ioBroker-Einstellungen)
    // false = Nutze manuelle Koordinaten nur als Fallback, falls im System keine hinterlegt sind
    const FORCE_MANUAL_LOCATION = false; 
    
    // --- RANDOMISIERUNG DER ZEITEN ---
    
    /**
    * Erzeugt eine zufällige Cron-Zeit innerhalb eines Fensters
    * @param {number} startHour 
    * @param {number} endHour 
    * @param {number} minMinute (optional, z.B. 2 für 00:02)
    */
    function getRandomCron(startHour, endHour, minMinute = 0) {
       const hour = Math.floor(Math.random() * (endHour - startHour + 1)) + startHour;
       let minute;
       if (hour === startHour) {
           minute = Math.floor(Math.random() * (60 - minMinute)) + minMinute;
       } else if (hour === endHour) {
           minute = 0; 
       } else {
           minute = Math.floor(Math.random() * 60);
       }
       return `${minute} ${hour} * * *`;
    }
    
    // Zeitfenster 1: 00:02 bis 05:00
    const cron1 = getRandomCron(0, 5, 2);
    // Zeitfenster 2: 13:02 bis 17:00
    const cron2 = getRandomCron(13, 17, 2);
    
    console.log(`[Wetter.com] Schedules für heute gesetzt auf: "${cron1}" und "${cron2}"`);
    
    // --- HILFSFUNKTIONEN ---
    
    /**
    * Formatiert ein Datum manuell in deutsches Format: "DD.MM.YYYY"
    */
    function formatToGermanDate(dateStr) {
       if (!dateStr) return '';
       const date = new Date(dateStr);
       if (isNaN(date.getTime())) return dateStr;
       const day = String(date.getDate()).padStart(2, '0');
       const month = String(date.getMonth() + 1).padStart(2, '0');
       const year = date.getFullYear();
       return `${day}.${month}.${year}`;
    }
    
    /**
    * Ermittelt die Koordinaten basierend auf Konfiguration und System-Einstellungen
    */
    async function getCoordinates() {
       // 1. Override: Manuelle Koordinaten erzwingen
       if (FORCE_MANUAL_LOCATION && MANUAL_LATITUDE && MANUAL_LONGITUDE) {
            console.log(`[Wetter.com] Nutze manuell konfigurierte Koordinaten (Override).`);
            return { lat: parseFloat(MANUAL_LATITUDE).toFixed(3), lon: parseFloat(MANUAL_LONGITUDE).toFixed(3) };
       }
    
       // 2. System: Versuche ioBroker Einstellungen zu lesen
       const systemCoords = await new Promise((resolve) => {
           getObject('system.config', (err, obj) => {
               if (!err && obj && obj.common && obj.common.latitude && obj.common.longitude) {
                   resolve({
                       lat: parseFloat(obj.common.latitude).toFixed(3),
                       lon: parseFloat(obj.common.longitude).toFixed(3)
                   });
               } else {
                   resolve(null);
               }
           });
       });
    
       if (systemCoords) return systemCoords;
    
       // 3. Fallback: Nutze manuelle Koordinaten, falls System fehlschlug
       if (MANUAL_LATITUDE && MANUAL_LONGITUDE) {
            console.log('[Wetter.com] Warnung: Keine System-Koordinaten gefunden. Nutze Fallback-Koordinaten aus Skript.');
            return { lat: parseFloat(MANUAL_LATITUDE).toFixed(3), lon: parseFloat(MANUAL_LONGITUDE).toFixed(3) };
       }
    
       return null;
    }
    
    /**
    * Erstellt die Struktur inkl. Info-Datenpunkten
    */
    async function ensureStructure(path, index, isInfo = false) {
       if (isInfo) {
           await createStateAsync(`${DP_PATH}.info.last_sync`, '', false, { name: 'Letztes erfolgreiches Update', type: 'string', role: 'text' });
           await createStateAsync(`${DP_PATH}.info.requests_today`, 0, false, { name: 'Anfragen heute', type: 'number', role: 'value' });
           await createStateAsync(`${DP_PATH}.info.requests_month`, 0, false, { name: 'Anfragen aktueller Monat', type: 'number', role: 'value' });
           await createStateAsync(`${DP_PATH}.info.next_schedules`, '', false, { name: 'Geplante Abrufe', type: 'string', role: 'text' });
           return;
       }
    
       const states = {
           'date': { name: 'Datum', type: 'string', role: 'text', def: '' },
           'temp_max': { name: 'Max Temperatur', type: 'number', unit: '°C', role: 'value.temperature.max', def: 0 },
           'temp_min': { name: 'Min Temperatur', type: 'number', unit: '°C', role: 'value.temperature.min', def: 0 },
           'weather_text': { name: 'Wetterzustand', type: 'string', role: 'weather.state', def: '' },
           'weather_code': { name: 'Wetter Code', type: 'number', role: 'value', def: 0 },
           'prec_probability': { name: 'Regenrisiko', type: 'number', unit: '%', role: 'value.precipitation.probability', def: 0 },
           'prec_sum': { name: 'Regenmenge', type: 'number', unit: 'mm', role: 'value.precipitation', def: 0 },
           'sun_hours': { name: 'Sonnenstunden', type: 'number', unit: 'h', role: 'value.sunshine', def: 0 },
           'wind_speed_max': { name: 'Windböen Max', type: 'number', unit: 'km/h', role: 'value.speed.wind.gust', def: 0 },
           'wind_direction': { name: 'Windrichtung', type: 'string', role: 'weather.direction', def: '' }
       };
    
       for (const [id, config] of Object.entries(states)) {
           const fullId = `${path}.${id}`;
           await createStateAsync(fullId, config.def, false, {
               name: `Tag ${index}: ${config.name}`,
               type: config.type,
               role: config.role,
               unit: config.unit || '',
               read: true,
               write: false
           });
       }
    }
    
    async function performRequest(url, options) {
       return new Promise((resolve, reject) => {
           httpGet(url, options, (error, response) => {
               if (error) return reject(new Error(error));
               if (response.statusCode === 429) return reject(new Error('LIMIT_REACHED'));
               if (response.statusCode !== 200) return reject(new Error(`HTTP Status ${response.statusCode}`));
               resolve(response);
           });
       });
    }
    
    async function cleanupObsoleteDays() {
       const channels = $(`${DP_PATH}.day_*`);
       channels.each(function(id) {
           const parts = id.split('.');
           const lastPart = parts[parts.length - 1]; 
           const dayIndex = parseInt(lastPart.replace('day_', ''));
           if (!isNaN(dayIndex) && dayIndex >= FORECAST_DAYS) {
               deleteObject(id, true);
           }
       });
    }
    
    /**
    * Aktualisiert die Info-Datenpunkte (Zähler und Zeitstempel)
    */
    async function updateUsageInfo() {
       const now = new Date();
       
       // Manuelle Formatierung
       const day = String(now.getDate()).padStart(2, '0');
       const month = String(now.getMonth() + 1).padStart(2, '0');
       const year = now.getFullYear();
       const hours = String(now.getHours()).padStart(2, '0');
       const minutes = String(now.getMinutes()).padStart(2, '0');
       const seconds = String(now.getSeconds()).padStart(2, '0');
       
       const timestamp = `${day}.${month}.${year} ${hours}:${minutes}:${seconds}`;
       await setStateAsync(`${DP_PATH}.info.last_sync`, String(timestamp), true);
       
       // Tageszähler erhöhen
       const currentCountState = await getStateAsync(`${DP_PATH}.info.requests_today`);
       const currentCount = currentCountState ? (currentCountState.val || 0) : 0;
       await setStateAsync(`${DP_PATH}.info.requests_today`, currentCount + 1, true);
    
       // Monatszähler erhöhen
       const currentMonthState = await getStateAsync(`${DP_PATH}.info.requests_month`);
       const currentMonthCount = currentMonthState ? (currentMonthState.val || 0) : 0;
       await setStateAsync(`${DP_PATH}.info.requests_month`, currentMonthCount + 1, true);
       
       const s1 = cron1.split(' ');
       const s2 = cron2.split(' ');
       await setStateAsync(`${DP_PATH}.info.next_schedules`, `${s1[1].padStart(2,'0')}:${s1[0].padStart(2,'0')} Uhr & ${s2[1].padStart(2,'0')}:${s2[0].padStart(2,'0')} Uhr`, true);
    }
    
    // --- LOGIK ---
    
    async function fetchWeatherData() {
       try {
           const coords = await getCoordinates();
           if (!coords) {
               console.error('[Wetter.com] Fehler: Keine Koordinaten gefunden (Weder System noch Fallback)!');
               return;
           }
           
           const url = `${BASE_URL}/forecast/${coords.lat}/${coords.lon}/summary`;
           const options = { headers: { 'x-api-key': API_KEY, 'Accept-Language': LANGUAGE } };
    
           try {
               const response = await performRequest(url, options);
               const data = JSON.parse(response.data);
               if (data && data.items) {
                   await processForecastData(data.items);
                   await cleanupObsoleteDays();
                   await updateUsageInfo();
               }
           } catch (err) {
               if (err.message === 'LIMIT_REACHED') {
                   console.error('[Wetter.com] Fehler 429: Das monatliche Abruflimit (100 Anfragen) wurde erreicht.');
               } else {
                   console.error(`[Wetter.com] Fehler: ${err.message}`);
               }
           }
       } catch (e) {
           console.error(`[Wetter.com] Script-Fehler: ${e.message}`);
       }
    }
    
    async function processForecastData(items) {
       await ensureStructure('', 0, true); 
       const daysToProcess = Math.min(items.length, FORECAST_DAYS);
    
       for (let i = 0; i < daysToProcess; i++) {
           const day = items[i];
           const dayPath = `${DP_PATH}.day_${i}`;
           await ensureStructure(dayPath, i);
    
           await setStateAsync(`${dayPath}.date`, formatToGermanDate(day.date), true);
           await setStateAsync(`${dayPath}.temp_max`, day.temperature?.max ?? 0, true);
           await setStateAsync(`${dayPath}.temp_min`, day.temperature?.min ?? 0, true);
           await setStateAsync(`${dayPath}.weather_text`, day.weather?.text || '', true);
           await setStateAsync(`${dayPath}.weather_code`, day.weather?.state ?? 0, true);
           await setStateAsync(`${dayPath}.prec_probability`, day.prec?.probability ?? 0, true);
           await setStateAsync(`${dayPath}.prec_sum`, day.prec?.sum ?? 0, true);
           await setStateAsync(`${dayPath}.sun_hours`, day.sunHours ?? 0, true);
           await setStateAsync(`${dayPath}.wind_speed_max`, day.wind?.max ?? 0, true);
           await setStateAsync(`${dayPath}.wind_direction`, day.wind?.direction || '', true);
       }
       console.log(`[Wetter.com] Update von ${daysToProcess} Tagen abgeschlossen.`);
    }
    
    // Zähler jeden Tag um Mitternacht zurücksetzen
    schedule("0 0 * * *", () => {
       setState(`${DP_PATH}.info.requests_today`, 0, true);
    });
    
    // Zähler jeden Monat (am 1. um 00:00) zurücksetzen
    schedule("0 0 1 * *", () => {
       setState(`${DP_PATH}.info.requests_month`, 0, true);
    });
    
    schedule(cron1, fetchWeatherData);
    schedule(cron2, fetchWeatherData);
    
    ensureStructure('', 0, true).then(() => {
       fetchWeatherData();
    });
    

    1 Antwort Letzte Antwort
    3
    • Pedder007P Offline
      Pedder007P Offline
      Pedder007
      schrieb zuletzt editiert von
      #2

      Hi @schimi, evtl. bin ich ja gerade blind, aber wo finde ich Dein Script? :-)

      Pedder
      All @Proxmox/Bookworm auf HP Elitedesk 800 G4; Zigbee: ZigStar (LAN), ~110Devices
      Unifi, Motioneye/3Reolinks, PiHole, Bosch CS7800i via BBQKees/EMS-ESP, Fronius/BYD 11kWp via Modbus
      Under construction: Smart-WoMo auf Raspi4

      sigi234S S 2 Antworten Letzte Antwort
      0
      • Pedder007P Pedder007

        Hi @schimi, evtl. bin ich ja gerade blind, aber wo finde ich Dein Script? :-)

        sigi234S Online
        sigi234S Online
        sigi234
        Forum Testing Most Active
        schrieb zuletzt editiert von
        #3

        @Pedder007 sagte in [Skript] Wetter.com Forecast/Vorhersage:

        Hi @schimi, evtl. bin ich ja gerade blind, aber wo finde ich Dein Script? :-)

        Klick mal auf Spoiler

        Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
        Immer Daten sichern!

        1 Antwort Letzte Antwort
        0
        • Pedder007P Pedder007

          Hi @schimi, evtl. bin ich ja gerade blind, aber wo finde ich Dein Script? :-)

          S Offline
          S Offline
          Schimi
          schrieb zuletzt editiert von
          #4

          @Pedder007 wie @sigi234 schon schrieb.... :-)

          0e527a28-1eb5-456d-bcd0-897f1a089dbe-image.png

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

            @schimi
            Ich finde dein Script absolut perfekt! Vielen lieben Dank, wollte schon lange die Werte von wetter.com mal haben.
            Da mir das so gut gefällt würde ich gerne komplett umsteigen.

            Hat irgendwer eine Lösung oder vielleicht ein fertiges Widget für VIS, wo man Symbole anzeigen kann?

            Man müsste ja den "weather_text" in ein Symbol umwandlen und dann mit "String img src" oder "Image 8" Widget arbeiten, aber dazu müsste man erstmal alle Zustände von "weather_text" haben. Aber vielleicht hat sich ja einer schon die Mühe gemacht?

            1 Antwort Letzte Antwort
            0
            Antworten
            • In einem neuen Thema antworten
            Anmelden zum Antworten
            • Älteste zuerst
            • Neuste zuerst
            • Meiste Stimmen


            Support us

            ioBroker
            Community Adapters
            Donate

            966

            Online

            32.6k

            Benutzer

            82.0k

            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