solar power prediction
Moderators: leecollings, remb0
-
JanJaap
- Posts: 229
- Joined: Thursday 12 October 2017 20:46
- Target OS: Raspberry Pi / ODroid
- Domoticz version: Dev
- Location: the Netherlands
- Contact:
solar power prediction
Hey,
Has anyone already built some integration to fetch predicted solar power? Would be interested in light of controlling home batteries. First step would be to compare prediction vs actuals..... Solarpanel.forecast for example seems like a potential target to pull form.
Has anyone already built some integration to fetch predicted solar power? Would be interested in light of controlling home batteries. First step would be to compare prediction vs actuals..... Solarpanel.forecast for example seems like a potential target to pull form.
RPi 3, Domoticz dev version, Aeon ZWave stick (with a whole bunch of slaves), Zigbee using Zigbee2MQTT, Nest thermo, P1 smart meter on RPi Zero
- psubiaco
- Posts: 233
- Joined: Monday 20 August 2018 9:38
- Target OS: Raspberry Pi / ODroid
- Domoticz version: Beta
- Location: Italy
- Contact:
Re: solar power prediction
I don't remeber where I found this dzVents script, but it works. Try it!
Code: Select all
return {
on = {
timer = {
'at *:50'
},
httpResponses = {
'solarforecastEAST' -- must match with the callback passed to the openURL command
}
},
logging = {
level = domoticz.LOG_INFO,
marker = 'get solar forecast',
},
execute = function(domoticz, item)
local idxSolarForecastCounter=2481 -- device with 24h forecast: type 'Managed counter', Energy generated
local idxSolarForecast=2477 -- device holding the forecast for the next hour: type 'Custom sensor', axis: 'Wh'
if (item.isTimer) then
print("EAST")
domoticz.openURL({
url = 'https://api.forecast.solar/estimate/watthours/period/45.8812/12.1833/15/-90/2.7',
method = 'GET',
callback = 'solarforecastEAST', -- see httpResponses above.
})
end
if (item.isHTTPResponse) then
if (item.ok) then
--domoticz.log('item.data ' .. item.data .. '***************************', domoticz.LOG_INFO)
if (item.isJSON) then
domoticz.utils.dumpTable(item)
local messagetype=item.json.message["type"]
domoticz.log("message type" .. messagetype, domoticz.LOG_INFO)
if messagetype=="success" then
local currentHR=os.date("%Y-%m-%d %H:00:00")
local oneHRahead=os.date("%Y-%m-%d %H:00:00",os.time()+1*60*60)
local twoHRahead=os.date("%Y-%m-%d %H:00:00",os.time()+2*60*60)
local forecastCurrentHR=tonumber(item.json.result[currentHR])
if forecastCurrentHR==nil then
forecastCurrentHR=0
end
local forecastOneHR=tonumber(item.json.result[oneHRahead])
if forecastOneHR==nil then
forecastOneHR=0
end
local forecastTwoHR=tonumber(item.json.result[twoHRahead])
if forecastTwoHR==nil then
forecastTwoHR=0
end
domoticz.log("solar forecast for next three hours :" .. forecastCurrentHR .. "+" .. forecastOneHR .. " + " .. forecastTwoHR .. " WattHR", domoticz.LOG_INFO)
domoticz.devices(idxSolarForecast).updateCustomSensor(forecastOneHR)
local updateHour=0
for id = 1, 24 do
if id<10 then
updateHour="0"..tostring(id)
else
updateHour=tostring(id)
end
domoticz.devices(idxSolarForecastCounter).updateHistory(os.date("%Y-%m-%d ")..updateHour..":00:00","0;0")
domoticz.devices(idxSolarForecastCounter).updateHistory(os.date("%Y-%m-%d ",os.time()+24*60*60)..updateHour..":00:00","0;0")
end
local response=item.json.result
for datehour,value in pairs(response) do
domoticz.log("solar forecast date "..domoticz.utils.stringSplit(datehour)[1].." hour "..domoticz.utils.stringSplit(domoticz.utils.stringSplit(datehour)[2],":")[1].." value "..value,domoticz.LOG_INFO)
local previousHour=domoticz.utils.stringSplit(domoticz.utils.stringSplit(datehour)[2],":")[1]-1
if previousHour<10 then
previousHour="0"..tostring(previousHour)
else
previousHour=tostring(previousHour)
end
domoticz.log("previousHour "..previousHour)
if value>0 then
sensorDateHour=domoticz.utils.stringSplit(datehour)[1].." "..domoticz.utils.stringSplit(domoticz.utils.stringSplit(datehour)[2],":")[1]..":00:00"
sValueStr="0;"..value
domoticz.log("sensorDateHour "..sensorDateHour.." sValueStr "..sValueStr,domoticz.LOG_INFO)
domoticz.devices(idxSolarForecastCounter).updateHistory(sensorDateHour,sValueStr)
if sensorDateHour==currentHR then
domoticz.devices(idxSolarForecastCounter).updateCounter(value)
end
end
end
else
domoticz.log("no successfull message", domoticz.LOG_INFO)
end
else
domoticz.log('is not json', domoticz.LOG_INFO)
end
else
domoticz.log('There was a problem handling the request', domoticz.LOG_INFO)
domoticz.log(item, domoticz.LOG_INFO)
end
end
end
}
Paolo
--
I use DomBus modules to charge EV car, get a full alarm system, control heat pump, fire alarm detection, lights and much more. Video
Facebook page - Youtube channel
--
I use DomBus modules to charge EV car, get a full alarm system, control heat pump, fire alarm detection, lights and much more. Video
Facebook page - Youtube channel
-
JanJaap
- Posts: 229
- Joined: Thursday 12 October 2017 20:46
- Target OS: Raspberry Pi / ODroid
- Domoticz version: Dev
- Location: the Netherlands
- Contact:
Re: solar power prediction
Tnx! Looks simple enough, might convert it to plugin.
RPi 3, Domoticz dev version, Aeon ZWave stick (with a whole bunch of slaves), Zigbee using Zigbee2MQTT, Nest thermo, P1 smart meter on RPi Zero
- psubiaco
- Posts: 233
- Joined: Monday 20 August 2018 9:38
- Target OS: Raspberry Pi / ODroid
- Domoticz version: Beta
- Location: Italy
- Contact:
Re: solar power prediction
Wow... keep us informed if you'll write a plugin doing that! Thanks a lotJanJaap wrote: Saturday 01 February 2025 20:02 Tnx! Looks simple enough, might convert it to plugin.
Paolo
--
I use DomBus modules to charge EV car, get a full alarm system, control heat pump, fire alarm detection, lights and much more. Video
Facebook page - Youtube channel
--
I use DomBus modules to charge EV car, get a full alarm system, control heat pump, fire alarm detection, lights and much more. Video
Facebook page - Youtube channel
-
willemd
- Posts: 735
- Joined: Saturday 21 September 2019 17:55
- Target OS: Raspberry Pi / ODroid
- Domoticz version: 2024.1
- Location: The Netherlands
- Contact:
Re: solar power prediction
here is my dzVents script to load solar forecast for my location (I replaced my location with XXXX an YYYY) and my panel specs (angles and capacity)
Please check the API specification for the other parameters in the call.
Please check the API specification for the other parameters in the call.
Code: Select all
return {
on = {
timer = {
'every hour'
},
httpResponses = {
'solarforecast' -- must match with the callback passed to the openURL command
}
},
logging = {
level = domoticz.LOG_INFO,
marker = 'get solar forecast',
},
execute = function(domoticz, item)
local idxSolarForecast=106 -- device holding the forecast for the next hour
local idxSolarForecastCounter=186
if (item.isTimer) then
domoticz.openURL({
url = 'https://api.forecast.solar/estimate/watthours/period/XXXX/YYYY/55/58/2.75',
method = 'GET',
callback = 'solarforecast', -- see httpResponses above.
})
end
if (item.isHTTPResponse) then
if (item.ok) then
--domoticz.log('item.data ' .. item.data .. '***************************', domoticz.LOG_INFO)
if (item.isJSON) then
domoticz.utils.dumpTable(item)
local messagetype=item.json.message["type"]
domoticz.log("message type" .. messagetype, domoticz.LOG_INFO)
if messagetype=="success" then
local currentHR=os.date("%Y-%m-%d %H:00:00")
local oneHRahead=os.date("%Y-%m-%d %H:00:00",os.time()+1*60*60)
local twoHRahead=os.date("%Y-%m-%d %H:00:00",os.time()+2*60*60)
local forecastCurrentHR=tonumber(item.json.result[currentHR])
if forecastCurrentHR==nil then
forecastCurrentHR=0
end
local forecastOneHR=tonumber(item.json.result[oneHRahead])
if forecastOneHR==nil then
forecastOneHR=0
end
local forecastTwoHR=tonumber(item.json.result[twoHRahead])
if forecastTwoHR==nil then
forecastTwoHR=0
end
domoticz.log("solar forecast for next three hours :" .. forecastCurrentHR .. "+" .. forecastOneHR .. " + " .. forecastTwoHR .. " WattHR", domoticz.LOG_INFO)
domoticz.devices(idxSolarForecast).updateCustomSensor(forecastOneHR)
-- set previous forecast to zero on managed counter
local updateHour=0
for id = 1, 24 do
if id<10 then
updateHour="0"..tostring(id)
else
updateHour=tostring(id)
end
domoticz.devices(idxSolarForecastCounter).updateHistory(os.date("%Y-%m-%d ")..updateHour..":00:00","0;0")
domoticz.devices(idxSolarForecastCounter).updateHistory(os.date("%Y-%m-%d ",os.time()+24*60*60)..updateHour..":00:00","0;0")
end
-- load new forecast onto managed counter
local response=item.json.result
for datehour,value in pairs(response) do
domoticz.log("solar forecast date "..domoticz.utils.stringSplit(datehour)[1].." hour "..domoticz.utils.stringSplit(domoticz.utils.stringSplit(datehour)[2],":")[1].." value "..value,domoticz.LOG_INFO)
local previousHour=domoticz.utils.stringSplit(domoticz.utils.stringSplit(datehour)[2],":")[1]-1
if previousHour<10 then
previousHour="0"..tostring(previousHour)
else
previousHour=tostring(previousHour)
end
domoticz.log("previousHour "..previousHour)
if value>0 then
sensorDateHour=domoticz.utils.stringSplit(datehour)[1].." "..domoticz.utils.stringSplit(domoticz.utils.stringSplit(datehour)[2],":")[1]..":00:00"
sValueStr="0;"..value
domoticz.log("sensorDateHour "..sensorDateHour.." sValueStr "..sValueStr,domoticz.LOG_INFO)
domoticz.devices(idxSolarForecastCounter).updateHistory(sensorDateHour,sValueStr)
if sensorDateHour==currentHR then
domoticz.devices(idxSolarForecastCounter).updateCounter(value)
end
end
end
else
domoticz.log("no successfull message", domoticz.LOG_INFO)
end
else
domoticz.log('is not json', domoticz.LOG_INFO)
end
else
domoticz.log('There was a problem handling the request', domoticz.LOG_INFO)
domoticz.log(item, domoticz.LOG_INFO)
end
end
end
}
-
JanJaap
- Posts: 229
- Joined: Thursday 12 October 2017 20:46
- Target OS: Raspberry Pi / ODroid
- Domoticz version: Dev
- Location: the Netherlands
- Contact:
Re: solar power prediction
ok I can fetch data, so far so good. As I never use the dzEvents I'm a bit puzzled to how the sensor update works. Is the 24 hr forecast counter updated per hour? So only one hour form now? Or is it always giving 24 hrs ahead but all hours being updated as time passes?
See first part: JanJaapKo/SolarForecast
See first part: JanJaapKo/SolarForecast
RPi 3, Domoticz dev version, Aeon ZWave stick (with a whole bunch of slaves), Zigbee using Zigbee2MQTT, Nest thermo, P1 smart meter on RPi Zero
-
erwindob
- Posts: 5
- Joined: Saturday 20 January 2024 12:03
- Target OS: Raspberry Pi / ODroid
- Domoticz version:
- Contact:
Re: solar power prediction
@JanJaap
Looks promising,
I see the values in my log, but not yet in the devices. Keeping an eye on updates.
Looks promising,
I see the values in my log, but not yet in the devices. Keeping an eye on updates.
-
Toulon7559
- Posts: 859
- Joined: Sunday 23 February 2014 17:56
- Target OS: Raspberry Pi / ODroid
- Domoticz version: <2025
- Location: Hengelo(Ov)/NL
- Contact:
Re: solar power prediction
Try this weblink as introduction & explanation for the scripts earlier in this thread.
Set1 = RPI-Zero+RFXCom433+S0PCM+Shield for BMP180/DS18B20/RS485+DDS238-1ZNs
Set2 = RPI-3A++RFLinkGTW+ESP8266s+PWS_WS7000
Common = KAKUs+3*PVLogger+PWS_TFA_Nexus
plus series of 'satellites' for dedicated interfacing, monitoring & control.
Set2 = RPI-3A++RFLinkGTW+ESP8266s+PWS_WS7000
Common = KAKUs+3*PVLogger+PWS_TFA_Nexus
plus series of 'satellites' for dedicated interfacing, monitoring & control.
-
eatme1972
- Posts: 1
- Joined: Friday 30 January 2026 16:52
- Target OS: Raspberry Pi / ODroid
- Domoticz version:
- Contact:
Re: solar power prediction
I installed the Solar Forecast plugin from Jan-Jaap Kostelijk in my Domoticz, but I can not fill in my location anywhere. The counter also stays on 0 KWH.
What am i missing?
What am i missing?
-
JanJaap
- Posts: 229
- Joined: Thursday 12 October 2017 20:46
- Target OS: Raspberry Pi / ODroid
- Domoticz version: Dev
- Location: the Netherlands
- Contact:
Re: solar power prediction
The location is taken from your Domoticz settings. Then fill out all mandatory settings in the plugin hardware page. And then I think you need to wait for the evening as i remember only polling then to avoid rate limits.
If it then still doesnt do anything i need screenshot of HW config and debug logging.
Oh and the values only appear in the logging of the device, as there is never an actual value.
If it then still doesnt do anything i need screenshot of HW config and debug logging.
Oh and the values only appear in the logging of the device, as there is never an actual value.
RPi 3, Domoticz dev version, Aeon ZWave stick (with a whole bunch of slaves), Zigbee using Zigbee2MQTT, Nest thermo, P1 smart meter on RPi Zero
-
HvdW
- Posts: 663
- Joined: Sunday 01 November 2015 22:45
- Target OS: Raspberry Pi / ODroid
- Domoticz version: 2023.2
- Location: Twente
- Contact:
Re: solar power prediction
Here is another Solar Power forecast which takes the cloud overcast into account.
Just creatae a TextSensor named Zonnestraling.
The local function calculateAdjustedIrradiance(hourData) calculates the impact of low clouds (lc), medium clouds (mc) and high clouds (hc)
Visit https://data.meteoserver.nl/ to obtain a key.
Code: Select all
return {
on = {
timer = {
'every 2 hours at daytime' -- Metingen alleen overdag
},
httpResponses = {
'solar_data'
}
},
data = {
measurementCycle = { initial = {
startHour = nil,
startWh = nil,
measurements = {}
}}
},
execute = function(domoticz, trigger)
-- ##### CONFIGURATIE #####
local powerThreshold = 50 -- Minimale weergave drempel (W/m²)
local dataLogging = true -- CSV logging aan/uit
local logFilePath = "/home/pi/csvData/radiation_impact.csv"
local daysToShow = 2 -- Number of days to display (including today)
-- ##### HELPER FUNCTIES #####
local function spaces(count)
return (" "):rep(count)
end
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
if adjustedValue < 50 then return "" end
local symbol = adjustedValue >= 100 and "■" or "|"
local count = adjustedValue >= 100 and math.floor(adjustedValue/100) or 1
return string.rep("<span style='color:#FFA500'>"..symbol.."</span>", count)
end
local function appendToCSV(timestamp, data)
local file = io.open(logFilePath, "a")
if file then
file:write(timestamp .. ";" .. table.concat(data, ";") .. "\n")
file:close()
else
domoticz.log("Kan logbestand niet openen: " .. logFilePath, domoticz.LOG_ERROR)
end
end
-- ##### HOOFDLOGICA #####
if trigger.isTimer then
-- Haal zoninstralingsdata op
domoticz.openURL({
url = 'https://data.meteoserver.nl/api/solar.php?locatie=YourHomeTown&key=abcdef123456',
method = 'GET',
callback = 'solar_data'
})
elseif trigger.isHTTPResponse then
local data = trigger.json
if not data then return end
local now = os.date("*t")
local currentHour = now.hour
local solarDevice = domoticz.devices('Zonnepanelen')
local whToday = solarDevice.WhToday
-- Verwerk huidige meting
local currentInstraling = 0
local currentAdjusted = 0
local currentClouds = 0
for _, hour in ipairs(data.forecast) do
if string.sub(hour.cet, 12, 13) == string.format("%02d", currentHour) then
currentInstraling = tonumber(hour.gr_w) or 0
currentAdjusted = calculateAdjustedIrradiance(hour)
currentClouds = tonumber(hour.lc) + tonumber(hour.mc) + tonumber(hour.hc)
break
end
end
-- ##### DATA LOGGING #####
if dataLogging then
-- Initieer CSV met header indien niet bestaat
if not io.open(logFilePath) then
appendToCSV("Timestamp", {
"StartUur", "EindUur",
"Instraling_Origineel", "Instraling_Gecorrigeerd",
"CloudCover", "WhToday_Verschil"
})
end
-- Controleer of we een nieuwe cyclus starten (elke even uur)
if currentHour % 2 == 0 then
-- Nieuwe cyclus begint
domoticz.data.measurementCycle = {
startHour = currentHour,
startWh = whToday,
measurements = {
{
hour = currentHour,
instraling = currentInstraling,
adjusted = currentAdjusted,
clouds = currentClouds
}
}
}
else
-- Voeg meting toe aan bestaande cyclus
table.insert(domoticz.data.measurementCycle.measurements, {
hour = currentHour,
instraling = currentInstraling,
adjusted = currentAdjusted,
clouds = currentClouds
})
-- Als we 3 metingen hebben (uur 0, 1 en 2 van de cyclus)
if #domoticz.data.measurementCycle.measurements == 3 then
local m = domoticz.data.measurementCycle.measurements
-- Controleer of alle metingen gelijk zijn
local allEqual = true
for i = 2, #m do
if m[i].adjusted ~= m[1].adjusted or m[i].clouds ~= m[1].clouds then
allEqual = false
break
end
end
-- Log alleen als alle metingen gelijk zijn
if allEqual then
local whDiff = whToday - domoticz.data.measurementCycle.startWh
appendToCSV(os.date("%Y-%m-%d %H:%M"), {
domoticz.data.measurementCycle.startHour,
currentHour,
m[1].instraling,
m[1].adjusted,
m[1].clouds,
whDiff
})
end
end
end
end
-- ##### VISUELE WEERGAVE #####
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
for dayIndex, currentDate in ipairs(datesToDisplay) do
-- Voeg datumheader toe
local dateHeader = "Verwachte zoninstraling ("..currentDate.."):"
if dayIndex > 1 then dateHeader = spaces(15)..dateHeader end
table.insert(outputLines, dateHeader)
-- Voeg kolomheaders toe
local columnHeader = "Uur Origineel Gecorrigeerd"
if dayIndex > 1 then columnHeader = spaces(15)..columnHeader end
table.insert(outputLines, columnHeader)
-- Voeg scheidingslijn toe
local separator = "-----------------------------"
if dayIndex > 1 then separator = spaces(15)..separator end
table.insert(outputLines, separator)
local dateDataLineCount = 0
for _, hour in ipairs(data.forecast) do
if string.sub(hour.cet, 1, 10) == currentDate then
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)
)
-- Inspringing voor dagen na de eerste
if dayIndex > 1 then lineContent = spaces(15)..lineContent end
-- Speciale inspringing voor eerste dag
if dayIndex == 1 and dateDataLineCount > 2 then
lineContent = spaces(15)..lineContent
end
table.insert(outputLines, lineContent)
end
end
end
-- Geen data melding
if dateDataLineCount == 0 then
local noDataMsg = "Geen significante zoninstraling verwacht"
if dayIndex > 1 then noDataMsg = spaces(15)..noDataMsg end
table.insert(outputLines, noDataMsg)
end
-- Lege regel tussen datums
if currentDate ~= datesToDisplay[#datesToDisplay] then
table.insert(outputLines, "")
end
end
-- Update device
domoticz.devices('Zonnestraling').updateText(table.concat(outputLines, "\n"))
end
end
}The local function calculateAdjustedIrradiance(hourData) calculates the impact of low clouds (lc), medium clouds (mc) and high clouds (hc)
Visit https://data.meteoserver.nl/ to obtain a key.
Bugs bug me.
-
JanJaap
- Posts: 229
- Joined: Thursday 12 October 2017 20:46
- Target OS: Raspberry Pi / ODroid
- Domoticz version: Dev
- Location: the Netherlands
- Contact:
Re: solar power prediction
Hey, how reliable are these forecasts? As the ones from Solar Forecast tend to be off by more than 40% regularly....
RPi 3, Domoticz dev version, Aeon ZWave stick (with a whole bunch of slaves), Zigbee using Zigbee2MQTT, Nest thermo, P1 smart meter on RPi Zero
-
HvdW
- Posts: 663
- Joined: Sunday 01 November 2015 22:45
- Target OS: Raspberry Pi / ODroid
- Domoticz version: 2023.2
- Location: Twente
- Contact:
Re: solar power prediction
I have tried to rely on it and have come to the conclusion that it is not reliable. (a bit yes)JanJaap wrote: Saturday 31 January 2026 9:47 Hey, how reliable are these forecasts? As the ones from Solar Forecast tend to be off by more than 40% regularly....
It has to do with planning EV charging.
My conclusion: better do this manually.
The other side of the medal:
It's nice to develop something and I have nice displays now.
I made things like predicting the weather visually to be able to check what day is best for a bicycle tour, tomorrow or the day after tomorrow.
Working on airplanes flying over the house using python and dzVents instead of just dzVents.
You know, it's just a hobby keeping me off of the street.
Bugs bug me.
-
eatme1972
- Posts: 1
- Joined: Friday 30 January 2026 16:52
- Target OS: Raspberry Pi / ODroid
- Domoticz version:
- Contact:
Re: solar power prediction
Thanks JanJaap,JanJaap wrote: Friday 30 January 2026 17:19 The location is taken from your Domoticz settings. Then fill out all mandatory settings in the plugin hardware page. And then I think you need to wait for the evening as i remember only polling then to avoid rate limits.
If it then still doesnt do anything i need screenshot of HW config and debug logging.
Oh and the values only appear in the logging of the device, as there is never an actual value.
It makes sense to use Domoticz location, I didn't think of that.
I still see "0.00 KwH" in my dashboard. When I open it, I see values.
My goal is to switch on a boiler for x hrs when the solar prediction > 15 KwH.
I have solar panels on east and west, so I make 2 solar forecast hardware/devices and add them up.
Can I do this with the plugin?
Last edited by eatme1972 on Saturday 31 January 2026 13:52, edited 1 time in total.
-
JanJaap
- Posts: 229
- Joined: Thursday 12 October 2017 20:46
- Target OS: Raspberry Pi / ODroid
- Domoticz version: Dev
- Location: the Netherlands
- Contact:
Re: solar power prediction
Yeah I want to use it for home battery charging strategy vs solar production. For EV charging im simply using the "slimmer laden" app that just picks the cheapest time.HvdW wrote: Saturday 31 January 2026 11:14
I have tried to rely on it and have come to the conclusion that it is not reliable. (a bit yes)
It has to do with planning EV charging.
My conclusion: better do this manually.
The other side of the medal:
It's nice to develop something and I have nice displays now.
Definition og a hobby: maximum effort, minimum result
RPi 3, Domoticz dev version, Aeon ZWave stick (with a whole bunch of slaves), Zigbee using Zigbee2MQTT, Nest thermo, P1 smart meter on RPi Zero
-
eatme1972
- Posts: 1
- Joined: Friday 30 January 2026 16:52
- Target OS: Raspberry Pi / ODroid
- Domoticz version:
- Contact:
Re: solar power prediction
Update :
Using the dzvents script from psubiaco and claude.ai, I created a script that adds up the 24-hour yield for today and tomorrow from my east and west panels and displays this.
Now to see over the coming year if it's reasonably accurate
Using the dzvents script from psubiaco and claude.ai, I created a script that adds up the 24-hour yield for today and tomorrow from my east and west panels and displays this.
Now to see over the coming year if it's reasonably accurate
Code: Select all
return {
on = {
timer = {
'at *:50'
},
httpResponses = {
'solarforecast_oost',
'solarforecast_west'
}
},
logging = {
level = domoticz.LOG_INFO,
marker = 'get solar forecast',
},
data = {
forecastOostToday = { initial = 0 },
forecastWestToday = { initial = 0 },
forecastOostTomorrow = { initial = 0 },
forecastWestTomorrow = { initial = 0 },
oostReceived = { initial = false },
westReceived = { initial = false }
},
execute = function(domoticz, item)
local idxSolarForecastToday = 552 -- device voor VANDAAG totaal: type 'Custom sensor', axis: 'Wh'
local idxSolarForecastTomorrow = 553 -- device voor MORGEN totaal: type 'Custom sensor', axis: 'Wh'
if (item.isTimer) then
print("Ophalen voorspelling OOST en WEST voor vandaag en morgen")
-- Reset alle waarden
domoticz.data.forecastOostToday = 0
domoticz.data.forecastWestToday = 0
domoticz.data.forecastOostTomorrow = 0
domoticz.data.forecastWestTomorrow = 0
domoticz.data.oostReceived = false
domoticz.data.westReceived = false
-- Call 1: Oost (3 kWp, -90 graden)
domoticz.openURL({
url = 'https://api.forecast.solar/estimate/watthours/period/50.0000000/4.0000000/45/-90/3',
method = 'GET',
callback = 'solarforecast_oost',
})
-- Call 2: West (1.4 kWp, 90 graden)
domoticz.openURL({
url = 'https://api.forecast.solar/estimate/watthours/period/50.0000000/4.0000000/45/90/1.4',
method = 'GET',
callback = 'solarforecast_west',
})
end
if (item.isHTTPResponse) then
if (item.ok) then
if (item.isJSON) then
local messagetype = item.json.message["type"]
if messagetype == "success" then
local totalToday = 0
local totalTomorrow = 0
local today = os.date("%Y-%m-%d")
local tomorrow = os.date("%Y-%m-%d", os.time() + (24 * 60 * 60))
-- Tel alle waarden op per dag
for datehour, value in pairs(item.json.result) do
local forecastDate = string.sub(datehour, 1, 10)
if forecastDate == today then
totalToday = totalToday + tonumber(value)
elseif forecastDate == tomorrow then
totalTomorrow = totalTomorrow + tonumber(value)
end
end
-- Sla het resultaat op afhankelijk van welke callback
if item.callback == 'solarforecast_oost' then
domoticz.data.forecastOostToday = totalToday
domoticz.data.forecastOostTomorrow = totalTomorrow
domoticz.data.oostReceived = true
domoticz.log("OOST - Vandaag: " .. totalToday .. " Wh, Morgen: " .. totalTomorrow .. " Wh", domoticz.LOG_INFO)
elseif item.callback == 'solarforecast_west' then
domoticz.data.forecastWestToday = totalToday
domoticz.data.forecastWestTomorrow = totalTomorrow
domoticz.data.westReceived = true
domoticz.log("WEST - Vandaag: " .. totalToday .. " Wh, Morgen: " .. totalTomorrow .. " Wh", domoticz.LOG_INFO)
end
-- Als beide calls binnen zijn, update de devices met de totalen
if domoticz.data.oostReceived and domoticz.data.westReceived then
local grandTotalToday = domoticz.data.forecastOostToday + domoticz.data.forecastWestToday
local grandTotalTomorrow = domoticz.data.forecastOostTomorrow + domoticz.data.forecastWestTomorrow
domoticz.log("TOTAAL VANDAAG: " .. grandTotalToday .. " Wh (" .. domoticz.data.forecastOostToday .. " + " .. domoticz.data.forecastWestToday .. ")", domoticz.LOG_INFO)
domoticz.log("TOTAAL MORGEN: " .. grandTotalTomorrow .. " Wh (" .. domoticz.data.forecastOostTomorrow .. " + " .. domoticz.data.forecastWestTomorrow .. ")", domoticz.LOG_INFO)
domoticz.devices(idxSolarForecastToday).updateCustomSensor(grandTotalToday)
domoticz.devices(idxSolarForecastTomorrow).updateCustomSensor(grandTotalTomorrow)
end
else
domoticz.log("Geen succesvolle respons ontvangen", domoticz.LOG_INFO)
end
else
domoticz.log('Respons is geen JSON', domoticz.LOG_INFO)
end
else
domoticz.log('Probleem bij verwerken van het verzoek', domoticz.LOG_INFO)
domoticz.log(item, domoticz.LOG_INFO)
end
end
end
}
Last edited by eatme1972 on Saturday 31 January 2026 17:53, edited 2 times in total.
-
Artenverho
- Posts: 8
- Joined: Friday 26 December 2025 16:02
- Target OS: Raspberry Pi / ODroid
- Domoticz version:
- Contact:
Re: solar power prediction
I am using NED.nl as data source (free). It also provides prediction for solar production in (provinces of) the Netherlands. I extrapolate the capacity used value (%) for each hour to my own setup and apply correction for angle and orientation. It results are reasonably accurate, at least as accurate as their own web tool they provide as well: https://ned.nl/index.php/nl/zonne-energievoorspeller (which is not accessible through the API).
I have been running it for a month and it is pretty good aside from the occasion misfire when it comes predicting effects of sudden weather changes and it cannot predict the impact of snow on the panels at all
.
dzvents script below. be sure to make the necessary sensors, user variables and counters
EDIT: forgot to add that the counter for the daily forecast should have a divider of 10000 in order to get kwh..
I have been running it for a month and it is pretty good aside from the occasion misfire when it comes predicting effects of sudden weather changes and it cannot predict the impact of snow on the panels at all
dzvents script below. be sure to make the necessary sensors, user variables and counters
EDIT: forgot to add that the counter for the daily forecast should have a divider of 10000 in order to get kwh..
Code: Select all
--Dzvents script to fetch solar production predictions from NED.nl and adjusts/corrects for PV angle and orientation. Made by Artneverho.
return {
active = true,
logging = {
level = domoticz.LOG_INFO,
marker = 'NED-hourly-solar'
},
-- Persistent forecast storage
data = {
solar = { initial = {} },
solarAll = { initial = {} } -- store all hours for user variable
},
on = {
timer = { 'every hour' },
httpResponses = { 'NEDHourlySolar' }
},
execute = function(domoticz, item)
local idxNEDsolarforecast = 528 --provide IDX for counter that shows the forecast (updates hourly)
local idxNEDtoken = 29 --IDX for uservariable that contain NED token.
local idxSolarYesterdayForecast = 529 --idx for custom sensor that shows the final prediction for yesterday (at 23h), in total kWh
local idxSolarTomorrowForecast = 530 --idx for custom sensor that shows prediction for tomorrow at 13h in total kwh
local nameUserVariable = 'NEDsolarForecast' --name of uservariable that stors the kwh data in json.
local token = domoticz.variables(idxNEDtoken).value
local baseURL = "https://api.ned.nl/v1/utilizations?"
local pvPeakKW = 4.5 -- set peak power output of solar PV array
local panelTilt = 27 -- angle of panels
local pvAzimuth = 245 -- orientation of the panels.
local tomorrow = os.time() + 48 * 3600
local today = os.time()
local from = os.date("%Y-%m-%d", today)
local to = os.date("%Y-%m-%d", tomorrow)
local currentHour = tonumber(os.date("%H"))
-- ---- REQUEST ----
if item.isTimer then
domoticz.data.solar = {}
domoticz.data.solarAll = {} -- clear previous export
--NOTE: adjust the region to your own location by setting "point=0" to any of the values below:
-- 0 Nederland
-- 1 Groningen
-- 2 Friesland
-- 3 Drenthe
-- 4 Overijssel
-- 5 Flevoland
-- 6 Gelderland
-- 7 Utrecht
-- 8 Noord-Holland
-- 9 Zuid-Holland
-- 10 Zeeland
-- 11 Noord-Brabant
-- 12 Limburg
local url =
baseURL ..
"point=0" ..
"&type=2" ..
"&granularity=5" ..
"&granularitytimezone=1" ..
"&classification=1" ..
"&activity=1" ..
"&validfrom%5Bafter%5D=" .. from ..
"&validfrom%5Bstrictly_before%5D=" .. to
domoticz.openURL({
url = url,
method = 'GET',
callback = 'NEDHourlySolar',
headers = {
['X-AUTH-TOKEN'] = token,
['accept'] = 'application/xml'
}
})
end
-- ---- RESPONSE ----
if item.isHTTPResponse then
if not item.ok or not item.isXML then
domoticz.log('NED request failed', domoticz.LOG_ERROR)
return
end
domoticz.devices(idxNEDsolarforecast).updateCounter(0)
local totalToday = 0
local totalTomorrow = 0
local daycounter = 0
for _, entry in ipairs(item.xml.response.item or {}) do
local ts = entry.validfrom
local hour = tonumber(string.sub(ts, 12, 13))
local perc = tonumber(entry.percentage)
if hour then
-- ---- SOLAR POSITION / CORRECTIONS ----
local numOfDay = os.date('%j')
local angularSpeed = 360/365.25
local declination = math.deg(math.asin(0.3978 * math.sin(math.rad(angularSpeed) *(numOfDay - (81 - 2 * math.sin((math.rad(angularSpeed) * (numOfDay - 2))))))))
local latitude = domoticz.settings.location.latitude
local longitude = domoticz.settings.location.longitude
local function rad(deg) return deg * math.pi / 180 end
local function deg(rad) return rad * 180 / math.pi end
local timeUTC = hour
local solarHour = timeUTC + (4 * longitude / 60)
local hourlyAngle = 15 * (12 - solarHour)
local sunAltitude = math.deg(math.asin(math.sin(math.rad(latitude))* math.sin(math.rad(declination)) + math.cos(math.rad(latitude)) * math.cos(math.rad(declination)) * math.cos(math.rad(hourlyAngle))))
local sunAzimuth = math.acos((math.sin(math.rad(declination)) - math.sin(math.rad(latitude)) * math.sin(math.rad(sunAltitude))) / (math.cos(math.rad(latitude)) * math.cos(math.rad(sunAltitude) ))) * 180 / math.pi
local sinAzimuth = (math.cos(math.rad(declination)) * math.sin(math.rad(hourlyAngle))) / math.cos(math.rad(sunAltitude))
if(sinAzimuth<0) then sunAzimuth=360-sunAzimuth end
local sunAlt = sunAltitude
local sunAz = sunAzimuth
local diffuseMin = 0.12
local lowSunAltCutoff = 3
local correction = 0
if perc > 0 and sunAlt > -2 then
local azDiff = sunAz - pvAzimuth
if azDiff > 180 then azDiff = azDiff - 360 end
if azDiff < -180 then azDiff = azDiff + 360 end
local directFactor
if azDiff >= -55 then
directFactor = 1.0
elseif azDiff >= -100 then
directFactor = math.max(0.15, (azDiff + 100) / 45)
else
directFactor = 0.1
end
local altFactor = (sunAlt <= -2) and 0 or (sunAlt < 5 and 0.15 + 0.85*(sunAlt+2)/7 or 1)
directFactor = directFactor * altFactor
local diffuseFactor = 0.18 * altFactor
correction = math.min(1, directFactor + diffuseFactor)
end
-- ---- FINAL ENERGY ----
local kWh = pvPeakKW * perc * correction * 10000
-- --- STORE FOR PLOTTING / HISTORY (existing logic) ---
domoticz.data.solar[hour] = kWh
local function nedToDomoticzTime(ts)
local y,m,d,H,M,S = ts:match("(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)")
local t = os.time({year=y, month=m, day=d, hour=H, min=M, sec=S, isdst=nil}) + 3600
return os.date("%Y-%m-%d %H:%M:%S", t)
end
local dt = nedToDomoticzTime(ts)
domoticz.devices(idxNEDsolarforecast).updateHistory(dt, "0;" .. kWh)
-- --- NEW: store for user variable export
table.insert(domoticz.data.solarAll, { ts = ts, kWh = kWh })
-- --- DAILY TOTALS (existing logic) ---
if daycounter <= 24 then
totalToday = totalToday + kWh
else
totalTomorrow = totalTomorrow + kWh
end
daycounter = daycounter + 1
-- Debug log
domoticz.log(string.format("Hour %d: perc=%.4f sunAz=%.1f, sunAlt=%.1f, correction=%.3f kWh=%.3f", hour, perc, sunAz, sunAlt, correction, kWh), domoticz.LOG_INFO)
end
end
-- ---- EXPORT TO USERVARIABLE ----
local out = {}
for _, e in ipairs(domoticz.data.solarAll) do
table.insert(out, { ts = e.ts, kWh = tonumber(string.format('%.3f', e.kWh)) })
end
table.sort(out, function(a,b) return a.ts < b.ts end)
domoticz.variables(nameUserVariable).set(domoticz.utils.toJSON(out))
domoticz.log('Stored NEDsolarForecast ('..#out..' hourly entries, including tomorrow if available)', domoticz.LOG_INFO)
-- ---- DAILY CUSTOM SENSOR UPDATES (existing logic) ----
if currentHour == 23 then
local totalKWh = totalToday / 10000
domoticz.devices(idxNEDsolarforecast).updateHistory(from, '0;' .. totalToday)
domoticz.devices(idxSolarYesterdayForecast).updateCustomSensor(domoticz.utils.round(totalKWh,2))
else
domoticz.devices(idxSolarYesterdayForecast).updateCustomSensor(domoticz.devices(idxSolarYesterdayForecast).rawData[1])
end
if currentHour == 13 then
local totalKWhTomorrow = totalTomorrow / 10000
domoticz.devices(idxSolarTomorrowForecast).updateCustomSensor(domoticz.utils.round(totalKWhTomorrow,2))
else
domoticz.devices(idxSolarTomorrowForecast).updateCustomSensor(domoticz.devices(idxSolarTomorrowForecast).rawData[1])
end
domoticz.devices(idxNEDsolarforecast).updateCounter(domoticz.utils.round(totalToday,2))
domoticz.log("Total predicted solar production today: "..string.format("%.3f kWh", totalToday), domoticz.LOG_INFO)
end
end
}
-
JanJaap
- Posts: 229
- Joined: Thursday 12 October 2017 20:46
- Target OS: Raspberry Pi / ODroid
- Domoticz version: Dev
- Location: the Netherlands
- Contact:
Re: solar power prediction
Seeing you already fixed that. No you can't, the plugin you need to set up twice and then use some other script to add them.eatme1972 wrote: Saturday 31 January 2026 13:04
I have solar panels on east and west, so I make 2 solar forecast hardware/devices and add them up.
Can I do this with the plugin?
RPi 3, Domoticz dev version, Aeon ZWave stick (with a whole bunch of slaves), Zigbee using Zigbee2MQTT, Nest thermo, P1 smart meter on RPi Zero
-
JanJaap
- Posts: 229
- Joined: Thursday 12 October 2017 20:46
- Target OS: Raspberry Pi / ODroid
- Domoticz version: Dev
- Location: the Netherlands
- Contact:
Re: solar power prediction
Any insights on the accuracy of this API? Ive compared the results of last months of my plugin and saw some massive outliers where predictions were a factor 5 or more too high. And then read your comment about the snowArtenverho wrote: Saturday 31 January 2026 20:51 I am using NED.nl as data source (free). It also provides prediction for solar production in (provinces of) the Netherlands. I extrapolate the capacity used value (%) for each hour to my own setup and apply correction for angle and orientation. It results are reasonably accurate, at least as accurate as their own web tool they provide as well: https://ned.nl/index.php/nl/zonne-energievoorspeller (which is not accessible through the API).
I have been running it for a month and it is pretty good aside from the occasion misfire when it comes predicting effects of sudden weather changes and it cannot predict the impact of snow on the panels at all.
dzvents script below. be sure to make the necessary sensors, user variables and counters
EDIT: forgot to add that the counter for the daily forecast should have a divider of 10000 in order to get kwh..
I plan to put this into a plugin as well (will fire up copolit tonight) as this is usually a bit more user friendly way to fetch the data (although many users combine the data collection with the actual business logic, I like to separate those).
RPi 3, Domoticz dev version, Aeon ZWave stick (with a whole bunch of slaves), Zigbee using Zigbee2MQTT, Nest thermo, P1 smart meter on RPi Zero
-
Artenverho
- Posts: 8
- Joined: Friday 26 December 2025 16:02
- Target OS: Raspberry Pi / ODroid
- Domoticz version:
- Contact:
Re: solar power prediction
It seems the most accurate on the day itself rather predicting tomorrow. But this is probably just the Dutch in general I guess. on a sunny day like today my solar system generated 8.7kwh, the script I made predicted 8.5kwh this morning. Yesterday it predicted 6.8kwh for today.. so yeah it is not perfect but for my purpose in scheduling my home battery it works (see my other post: viewtopic.php?t=44342). The API allows quite some refreshing so I am updating the predictions every hour.JanJaap wrote: Saturday 07 February 2026 10:49 Any insights on the accuracy of this API? Ive compared the results of last months of my plugin and saw some massive outliers where predictions were a factor 5 or more too high. And then read your comment about the snow. For the normal days I see accurcy vary around 30% in both directions.
FYI, you can also read out wind production predictions from the same API. I use this to relax the economics of my home battery scheduling every time the wind energy production goes above 90% in The Netherlands. The aim here is to increase my renewable energy usage. script is very similar to the solar one:
Code: Select all
--Dzvents script to fetch wind production predictions from NED.nl and adjusts/corrects for PV angle and orientation. Made by Artneverho.
return {
on = {
timer = { 'every hour' },
httpResponses = { 'NEDHourlyWind' }
},
logging = {
level = domoticz.LOG_INFO,
marker = 'NED-hourly-wind'
},
data = {
windAll = { initial = {} } -- NEW: store all hours for user variable export
},
execute = function(domoticz, item)
local idxWindForecast = 525 -- Managed counter (%)
local idxNEDtoken = 29 --IDX for uservariable that contain NED token.
local nameUserVariable = 'NEDwindForecast' --name of uservariable that stors the kwh data in json.
local token = domoticz.variables(idxNEDtoken).value
local baseURL = "https://api.ned.nl/v1/utilizations?"
local now = os.time()
local from = os.date("%Y-%m-%d", now)
local to = os.date("%Y-%m-%d", now + 48 * 3600)
local function nedLocalDateHour(ts)
local y,m,d,H,M,S = ts:match("(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)")
local t = os.time({
year = tonumber(y),
month = tonumber(m),
day = tonumber(d),
hour = tonumber(H),
min = tonumber(M),
sec = tonumber(S),
isdst = nil
}) + 3600
return os.date("%Y-%m-%d", t), tonumber(os.date("%H", t))
end
-- ---- REQUEST ----
if item.isTimer then
domoticz.data.windAll = {} -- clear previous export
local url =
baseURL ..
"point=0" ..
"&type=1" ..
"&granularity=5" ..
"&granularitytimezone=1" ..
"&classification=1" ..
"&activity=1" ..
"&validfrom%5Bafter%5D=" .. from ..
"&validfrom%5Bstrictly_before%5D=" .. to
domoticz.openURL({
url = url,
method = 'GET',
callback = 'NEDHourlyWind',
headers = {
['X-AUTH-TOKEN'] = token,
['accept'] = 'application/xml'
}
})
end
local todayDate = os.date("%Y-%m-%d")
local currentHour = tonumber(os.date("%H"))
-- ---- RESPONSE ----
if item.isHTTPResponse then
if not item.ok or not item.isXML then
domoticz.log('NED wind request failed', domoticz.LOG_ERROR)
return
end
-- reset visible counter
domoticz.devices(idxWindForecast).updateCounter(0)
for _, entry in ipairs(item.xml.response.item or {}) do
local ts = entry.validfrom
local perc = tonumber(entry.percentage)
if ts and perc then
-- --- Store for user variable export
table.insert(domoticz.data.windAll, { ts = ts, perc = perc })
-- Domoticz-safe timestamp
local function nedToDomoticzTime(ts)
local y,m,d,H,M,S = ts:match("(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)")
local t = os.time({
year=y, month=m, day=d,
hour=H, min=M, sec=S,
isdst=nil
}) + 3600
return os.date("%Y-%m-%d %H:%M:%S", t)
end
local dt = nedToDomoticzTime(ts)
local date, hour = nedLocalDateHour(ts)
-- Existing device history update
local value = perc * 10000
domoticz.devices(idxWindForecast).updateHistory(dt, "0;" .. value)
if date == todayDate and hour == currentHour then
domoticz.devices(idxWindForecast).update(0, string.format("%.1f", value))
end
domoticz.log(string.format("Hour %s wind perc=%.2f%%", dt:sub(12,13), perc * 100), domoticz.LOG_INFO)
end
end
-- ---- EXPORT TO USERVARIABLE ----
local out = {}
for _, e in ipairs(domoticz.data.windAll) do
table.insert(out, { ts = e.ts, perc = tonumber(string.format('%.4f', e.perc)) })
end
table.sort(out, function(a,b) return a.ts < b.ts end)
domoticz.variables(nameUserVariable).set(domoticz.utils.toJSON(out))
domoticz.log('Stored NEDwindForecast ('..#out..' hourly entries, including tomorrow if available)', domoticz.LOG_INFO)
end
end
}
Who is online
Users browsing this forum: No registered users and 1 guest