Meteoserver sun radiation

Use this forum to discuss possible implementation of a new feature before opening a ticket.
A developer shall edit the topic title with "[xxx]" where xxx is the id of the accompanying tracker id.
Duplicate posts about the same id. +1 posts are not allowed.

Moderators: leecollings, remb0

Post Reply
HvdW
Posts: 612
Joined: Sunday 01 November 2015 22:45
Target OS: Raspberry Pi / ODroid
Domoticz version: 2023.2
Location: Twente
Contact:

Meteoserver sun radiation

Post by HvdW »

It is a challenge to optimize the percentage direct use from the PV panels.
One way is to know when the sun shines and how much the global radiation intensity is in Watt per m2
At Meteoserver the data can be obtained like

Code: Select all

https://data.meteoserver.nl/api/solar.php?locatie=Utrecht&key=abcd1234
using your own (free) key.
The API allows 500 free calls per month, like one call per 2 hours.

My suggestion
Can you build a Meteoserver sun radiation hardware addon like the Enever addon

PS
I can build something python or something dzVents but I don't know how and cannot find how I can create a sensor like the Enever one with bar charts.
barchart.png
barchart.png (40.86 KiB) Viewed 221 times
PPS
Here is a dzVents script that fills a text Sensor
You can edit the name of the textSensor, the API key and the local threshold = 400 -- Minimal 400 W/m² to be "Sunny" gelden

Code: Select all

return {
    on = {
        timer = {'every 2 hours'},
        httpResponses = {'meteoserver_api_call'}
    },
    data = {
        lastUpdate = { initial = '' }
    },
    execute = function(domoticz, item)
        local api_url = "https://data.meteoserver.nl/api/solar.php?locatie=Utrecht&key=abcd1234"

        -- Functie om tijdvakken met hoge straling te vinden
        local function findSunnyPeriods(forecastData)
            local threshold = 400 -- Minimaal 400 W/m² om als "zonnig" te gelden
            local sunnyHours = {}
            
            -- Identificeer alle uren boven de drempelwaarde
            for _, hour in ipairs(forecastData) do
                if tonumber(hour.gr_w) >= threshold then
                    table.insert(sunnyHours, {
                        time = hour.time,
                        cet = hour.cet,
                        gr_w = hour.gr_w
                    })
                end
            end
            
            -- Groepeer aaneengesloten uren
            local periods = {}
            if #sunnyHours > 0 then
                local startTime = sunnyHours[1].time
                local endTime = startTime
                local currentGr = sunnyHours[1].gr_w
                
                for i = 2, #sunnyHours do
                    if sunnyHours[i].time - sunnyHours[i-1].time <= 3600 then -- Max 1 uur verschil
                        endTime = sunnyHours[i].time
                        currentGr = currentGr + sunnyHours[i].gr_w
                    else
                        table.insert(periods, {
                            start = os.date("%H:%M", startTime),
                            eind = os.date("%H:%M", endTime),
                            straling = math.floor(currentGr / ((endTime - startTime)/3600 + 1)) -- Gemiddelde W/m²
                        })
                        startTime = sunnyHours[i].time
                        endTime = startTime
                        currentGr = sunnyHours[i].gr_w
                    end
                end
                
                -- Voeg laatste periode toe
                table.insert(periods, {
                    start = os.date("%H:%M", startTime),
                    eind = os.date("%H:%M", endTime),
                    straling = math.floor(currentGr / ((endTime - startTime)/3600 + 1))
                })
            end
            
            -- Sorteer periodes op stralingsintensiteit (hoog naar laag)
            table.sort(periods, function(a, b) return a.straling > b.straling end)
            return periods
        end

        -- API-aanvraag bij timer-event
        if (item.isTimer) then
            domoticz.openURL({
                url = api_url,
                method = 'GET',
                callback = 'meteoserver_api_call'
            })
        
        -- Verwerk API-respons
        elseif (item.isHTTPResponse and item.ok) then
            local json = item.json
            if (json and json.current and json.current[1]) then
                local current = json.current[1]
                local now = os.date("%H:%M")
                
                -- Bouw basisbericht
                local sensor_text = "🌡️ Nu: " .. current.temp .. "°C | "
                sensor_text = sensor_text .. "☁️ " .. current.tc .. "% | "
                sensor_text = sensor_text .. "☀️ " .. current.gr_w .. " W/m²\n\n"
                
                -- Voeg komende 3 uur toe (vandaag)
                sensor_text = sensor_text .. "⏳ Komende uren:\n"
                local hours_added = 0
                for _, hour in ipairs(json.forecast) do
                    if os.date("%Y-%m-%d", hour.time) == os.date("%Y-%m-%d") then
                        sensor_text = sensor_text .. os.date("%H:%M", hour.time) .. ": " .. hour.gr_w .. " W/m²\n"
                        hours_added = hours_added + 1
                        if hours_added >= 3 then break end
                    end
                end
                
                -- Analyseer morgen
                local tomorrow_forecast = {}
                for _, hour in ipairs(json.forecast) do
                    if os.date("%Y-%m-%d", hour.time) == os.date("%Y-%m-%d", os.time() + 86400) then
                        table.insert(tomorrow_forecast, hour)
                    end
                end
                
                local sunny_periods = findSunnyPeriods(tomorrow_forecast)
                
                -- Voeg beste zonperiodes toe
                sensor_text = sensor_text .. "\n🔆 Morgen optimaal:\n"
                if #sunny_periods > 0 then
                    for i, period in ipairs(sunny_periods) do
                        if i <= 3 then -- Toon max 3 beste periodes
                            sensor_text = sensor_text .. period.start .. "-" .. period.eind .. ": " .. period.straling .. " W/m² gem.\n"
                        end
                    end
                else
                    sensor_text = sensor_text .. "Geen sterke zonneschijn verwacht\n"
                end
                
                -- Update sensor
                domoticz.devices("Zonnestraling").updateText(sensor_text)
                domoticz.log("Zonnestraling bijgewerkt om " .. now, domoticz.LOG_INFO)
            end
        end
    end
}
Last edited by HvdW on Sunday 08 June 2025 14:02, edited 1 time in total.
Bugs bug me.
User avatar
waltervl
Posts: 5844
Joined: Monday 28 January 2019 18:48
Target OS: Linux
Domoticz version: 2024.7
Location: NL
Contact:

Re: Meteoserver sun radiation

Post by waltervl »

If you are in the Netherlands you can use the Buienradar integration. https://wiki.domoticz.com/Buienradar
It has a sun power sensor.
Domoticz running on Udoo X86 (on Ubuntu)
Devices/plugins: ZigbeeforDomoticz (with Xiaomi, Ikea, Tuya devices), Nefit Easy, Midea Airco, Omnik Solar, Goodwe Solar
HvdW
Posts: 612
Joined: Sunday 01 November 2015 22:45
Target OS: Raspberry Pi / ODroid
Domoticz version: 2023.2
Location: Twente
Contact:

Re: Meteoserver sun radiation

Post by HvdW »

waltervl wrote: Sunday 08 June 2025 13:07 If you are in the Netherlands you can use the Buienradar integration. https://wiki.domoticz.com/Buienradar
It has a sun power sensor.
Thanks Walter, didn't know that.
However it doesn't include a sunpower prediction.
The prediction is what I am looking for to be able to switch on washing machine, dishwasher and other devices at the optimal hours.
The EV is already automated to charge whenever there is a surplus of energy. (except when full or not connected)
Bugs bug me.
HvdW
Posts: 612
Joined: Sunday 01 November 2015 22:45
Target OS: Raspberry Pi / ODroid
Domoticz version: 2023.2
Location: Twente
Contact:

Re: Meteoserver sun radiation

Post by HvdW »

I updated the Sunpower script.

Code: Select all

return {
    on = {
        timer = {
            'every 3 hours'
        },
        httpResponses = {
            'solar_data'
        }
    },
    execute = function(domoticz, trigger)
        -- Configuratie
        local powerThreshold = 50  -- Drempelwaarde voor weergave (W/m²)
        local daysToShow = 3      -- Aantal dagen om weer te geven (inclusief vandaag)
        local maxDays = 6         -- Maximum aantal dagen dat kan worden weergegeven
        
        -- Beperk het aantal dagen tot het maximum
        daysToShow = math.min(daysToShow, maxDays)
        
        -- Helper functie voor spaties
        local function spaces(count)
            return ("&nbsp;"):rep(count)
        end
        
        -- Bereken gecorrigeerde zoninstraling
        local function calculateAdjustedIrradiance(hourData)
            local gr_w = tonumber(hourData.gr_w) or 0
            local lc = tonumber(hourData.lc) or 0
            local mc = tonumber(hourData.mc) or 0
            local hc = tonumber(hourData.hc) or 0
            
            local lowCloudFactor = 0.85
            local midCloudFactor = 0.60
            local highCloudFactor = 0.30
            
            local cloudAttenuation = (lc*lowCloudFactor + mc*midCloudFactor + hc*highCloudFactor) / 100
            cloudAttenuation = math.min(cloudAttenuation, 0.9)
            
            return math.floor(gr_w * (1 - cloudAttenuation) + 0.5)
        end

        local function generateBar(value)
            local adjustedValue = value or 0
            local symbol = adjustedValue >= 100 and "■" or "|"
            local coloredSymbol = "<span style='color:#FFA500'>"..symbol.."</span>"
    
            -- Bepaal aantal symbolen (1 voor 50-99, meer voor hogere waarden)
            local count = adjustedValue >= 100 and math.floor(adjustedValue/100) or 1
    
            -- Alleen tonen als boven drempelwaarde (50)
            return adjustedValue >= 50 and string.rep(coloredSymbol, count) or ""
        end

        -- Hoofdlogica
        if trigger.isTimer then
            domoticz.openURL({
                url = 'https://data.meteoserver.nl/api/solar.php?locatie=Utrech&key=abcd1234',
                method = 'GET',
                callback = 'solar_data'
            })
        elseif trigger.isHTTPResponse then
            local data = trigger.json
            if not data then return end
            
            -- Bepaal datums die we willen tonen (van vandaag tot vandaag + daysToShow-1)
            local datesToDisplay = {}
            for i = 0, daysToShow-1 do
                table.insert(datesToDisplay, os.date('%d-%m-%Y', os.time() + 86400 * i))
            end
            
            local outputLines = {}
            local totalDataLineCount = 0  -- Totaal teller voor dataregels
            
            for dayIndex, currentDate in ipairs(datesToDisplay) do
                -- Verzamel alle data voor deze datum
                local dateData = {}
                for _, hour in ipairs(data.forecast) do
                    if string.sub(hour.cet, 1, 10) == currentDate then
                        table.insert(dateData, hour)
                    end
                end
                
                -- Voeg datumheader toe (alleen inspringen vanaf 2e dag)
                local dateHeader = "Verwachte zoninstraling ("..currentDate.."):"
                if dayIndex > 1 then dateHeader = spaces(15)..dateHeader end
                table.insert(outputLines, dateHeader)
                
                -- Voeg kolomheaders toe (zelfde inspringing als datumheader)
                local columnHeader = "Uur   Origineel    Gecorrigeerd"
                if dayIndex > 1 then columnHeader = spaces(15)..columnHeader end
                table.insert(outputLines, columnHeader)
                
                -- Voeg scheidingslijn toe (zelfde inspringing)
                local separator = "-----------------------------"
                if dayIndex > 1 then separator = spaces(15)..separator end
                table.insert(outputLines, separator)
                
                local dateDataLineCount = 0  -- Teller voor dataregels per datum
                
                for _, hour in ipairs(dateData) do
                    local time = string.sub(hour.cet, 12, 16)
                    local original = tonumber(hour.gr_w) or 0
                    local adjusted = calculateAdjustedIrradiance(hour)
                    
                    if adjusted >= powerThreshold then
                        dateDataLineCount = dateDataLineCount + 1
                        totalDataLineCount = totalDataLineCount + 1
                        
                        local lineContent = string.format(
                            "%s %03d W/m²   %03d W/m² %s",
                            time,
                            original,
                            adjusted,
                            generateBar(adjusted)
                        )

                        -- Alle regels inspringen vanaf 2e dag
                        if dayIndex > 1 then lineContent = spaces(15)..lineContent end
                        
                        -- Voor de eerste dag: eerste 2 dataregels zonder extra inspringing, daarna met
                        if dayIndex == 1 and dateDataLineCount > 2 then
                            lineContent = spaces(15)..lineContent
                        end
                        
                        table.insert(outputLines, lineContent)
                    end
                end
                
                -- Voeg melding toe als geen data beschikbaar is voor deze datum
                if dateDataLineCount == 0 then
                    local noDataMsg = "Geen significante zoninstraling verwacht"
                    if dayIndex > 1 then noDataMsg = spaces(15)..noDataMsg end
                    table.insert(outputLines, noDataMsg)
                end
                
                -- Voeg lege regel toe tussen datums, behalve na laatste datum
                if currentDate ~= datesToDisplay[#datesToDisplay] then
                    table.insert(outputLines, "")
                end
            end
            
            domoticz.devices('Zonnestraling').updateText(table.concat(outputLines, "\n"))
        end
    end
}
It gives nice information for your local sun radiation expectations.
Sun radiation corrected for your local cloud coverage, high, medium and low clouds.
SunPower.jpg
SunPower.jpg (64.79 KiB) Viewed 124 times
Bugs bug me.
User avatar
RonkA
Posts: 115
Joined: Tuesday 14 June 2022 12:57
Target OS: NAS (Synology & others)
Domoticz version: 2025.1
Location: Harlingen
Contact:

Re: Meteoserver sun radiation

Post by RonkA »

I like what i see from the fore last script, i am curious how correct the prediction will be, but it looks a lot easier than the daily lookup of the sun graph from Weeronline..
zongrafiek.PNG
zongrafiek.PNG (44.8 KiB) Viewed 109 times

The thing i don't like from the 'mainstream' weather-models is the fact that the nearest weather-station is 28 kilometer away from my home resulting in incorrect values and predictions..
SolarEdge ModbusTCP - Kaku - Synology NAS - Watermeter - ESPEasy - DS18b20
Work in progress = Life in general..
HvdW
Posts: 612
Joined: Sunday 01 November 2015 22:45
Target OS: Raspberry Pi / ODroid
Domoticz version: 2023.2
Location: Twente
Contact:

Re: Meteoserver sun radiation

Post by HvdW »

RonkA wrote: Monday 09 June 2025 21:01 The thing i don't like from the 'mainstream' weather-models is the fact that the nearest weather-station is 28 kilometer away from my home resulting in incorrect values and predictions..
In my case it's 15 kilometers.
I adapted the script to catch the real values in the log.

Code: Select all

            if debugMode then
            local debugLines = {
                "DEBUG VERGELIJKING (225Wp panelen, 15 jaar oud, 95% rendement):",
                "Tijd   Origineel  Geschat  Gecorrigeerd  Geschat  Werkelijk"
            }
    
                for _, hour in ipairs(data.forecast) do
                    if string.sub(hour.cet, 1, 10) == os.date('%d-%m-%Y') then
                        local original = tonumber(hour.gr_w) or 0
                        local adjusted = calculateAdjustedIrradiance(hour)
                
                        if adjusted >= 50 or original >= 50 then
                            table.insert(debugLines, string.format(
                                "%s  %3dW/m² %5dW  %3dW/m² %5dW  %s",
                                string.sub(hour.cet, 12, 16),
                                original, schatVermogen(original),
                                adjusted, schatVermogen(adjusted),
                                "--"  -- Hier later je werkelijke meting invullen
                            ))
                        end
                    end
                end
                domoticz.log(table.concat(debugLines, "\n"), domoticz.LOG_INFO)
            end
Because of the distance and the one hour prediction I'll have to wait for weather circumstance where there is a stable clear sky or stable cloud coverage during several hours and check where the log output can help me understand.

EDIT
Adapted the script again.
The script runs every 2 hours at daytime.
It compares sun radiation and cloud coverage at hour 0, hour 1 and hour2.
If these are equal we have stable circumstances (even at 15 km distance from the nearest weather station)
It collects WhToday from the inverter at hour 0 and hour 2.
These data (if equal) are stored in a file sun_radiation_impact.csv
The header of the file (the collected data) is this
Timestamp;StartUur;EindUur;Origineel (W/m²);Gecorrigeerd (W/m²);Bewolking (%);WhVerschil

In a few months time the data can be analyzed.
Bugs bug me.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest