Solar Forecast PV

Putting each value from json into a dedicated channel will blow up the channel numbers drastically. But you can easily get these values from rules.
I assume you’re interested in hourly energy forecast?
Use Rule Action described in readme to calculate energy between 2 timestamps.
Rule example at the bottom shall give you a hint how this can be applied.

on the one hand: they are. on the other: there’s plenty of bindings working that way. Not only every single meteorogical weather-binding and then there’s some dynamic energy pricing bindings dealing also with loads of channels. I don’t see that as bad? and as Johannes pointed out, it would decrease the logic in rules by far - and thus lead to less load with users wanting to use hourly forecasted values. Instead of running rules on (the poorly designed) JSON over and over again, you can just use the items, you’ll create. For those, who don’t need hourly forecast, they could simply leave the channels unconfigured?
There’s plenty of use cases, where you could project starting things on times with expected high PV excess, having items for that would be easier than parsing the JSON multiple times.


Hallo Bernd, @weymann

thanks for your reply. Actually I am interested in the hourly power forecast, not the energy.
For my energymanger it would be good to now how much power would be (most likely) available over the day to plan the warm water heating with my heat pump (…and that guy burns 3000W, so enough sun is needed :wink: ). As I don’t have a battery i have to use the PV Power directly… Currently the logic decides based on the average Grid Power from the last 15 min and changes a traffic light model.
Problem is: once started, heat pump has to operate for while… that’s not as easy as with an electrical heater which can be just switched on and off as often as you like.
For example: if there is a cloudy forenoon, but pure sun at the afternoon I would postpone the heating and vise versa…

How ever, I try with the Rule action and feed some items for that. But still, if its not much effort (can’t judge honestly) integration as channels would help for others as well.


It works now using the rule action.
This would run once a day and update all forecast items (I created 13 of them for each forecast hour, maybe more are needed as days are getting longer…)

val solarforecastActions = getActions("solarforecast","solarforecast:fs-plane:MyPlane")
val hourPowerState1 = solarforecastActions.getPower(

this gives my the forecast for the power in 9 hours.

Could you tell me which argument I need to use to get the forecast for a specific hour of the day?
for example hour 7 of today (or hour 7 of tomorrow).
then I could periodically update the items but always having the correct hour.

Agree! E.g. openweathermap is providing a f**kload of channels. I think 1000+. Don’t misunderstand: No blaming!
Question yourself:
Instead of connecting items to specific points in the future wouldn’t it be nice to simply query e.g. the temperature for any Date & Time?

I assume you didn’t have a look into the mentioned readme, right?
There’s no json parsing at all. It’s a one liner to get Power or Energy for any point of time and for any desired duration.

// replace YOUR_ID with solar forecast bridge uid
val solarforecastActions = getActions("solarforecast","solarforecast:fs-site:YOUR_ID") 
// yadda yadda yadda - your code
val hourPlusOnePowerState = solarforecastActions.getPower(

I’ve some douvts, please prove.
@Johannes_K wants to start his heat pump as early as possible in the best possible time window. Heat pump will run 136 min (or whatever duration you want), with 3kw means 6,8kw/h .

rule "Solar Forecast find Window"
        Time cron "0 0 * * * ?" // whatever e.g. forecast changed
        // setup
        val solarforecastActions = getActions("solarforecast","solarforecast:sc-site:231aeb3f85")
        // use Astro binding to get sunset time very easy
        val sunset = (LokaleSonnendaten_Set_Start.state as DateTimeType).getZonedDateTime().toLocalDateTime()
        var startTime =
        // Parameters - put in what you want
        val duration = 136 // runtime heatpump in minutes
        val kwhNeeded = 6.8 // needed for heat pump
        val granularity = 5 // 5 min time steps
        // return values - best time to pick with lowest possible delta!
        var bestPickTime = startTime 
        var delta = Double.MAX_VALUE
        // now calculate
        while(startTime.isBefore(sunset) && delta > 0.0) {
                 val energy = solarforecastActions.getEnergy(startTime,startTime.plusMinutes(duration))
                 val actualDelta = kwhNeeded - (energy as Number).doubleValue
                 if(actualDelta < delta ) {
                      bestPickTime = startTime
                      delta = actualDelta
                 startTime = startTime.plusMinutes(granularity)
        logInfo("SF Tests","Best Pick: " +bestPickTime+ " with Delta "+delta)

Now let’s see your math with hourly items …


oh. I did read the docs, but this slipped somehow (I shouldn’t always read so fast…).
A binding offering Actions for calculation is a very elegant solution! I guess, that’s really something to consider also for other bindings dealing with loads of identical data - like dynamic pricing like aWATTar, Tibber, …

Just a thought:

        while(startTime.isBefore(sunset) && delta > 0.0) {
                 val energy = solarforecastActions.getEnergy(startTime,startTime.plusMinutes(duration))
                 val actualDelta = kwhNeeded - (energy as Number).doubleValue
                 if(actualDelta < delta ) {
                      bestPickTime = startTime
                      delta = actualDelta
                 startTime = startTime.plusMinutes(granularity)

I see this like in many use cases: finding the best hours for Tibber/aWATTar/ENTSO-e/Energi/…
Presently the aWATTar binding offers this as seperate Bestprice Things, others would like to follow suit or streamline the effort as discussed here:

Do you see a chance to reuse similar requirements (“Find best time for XX amount of energy and YY amount of duration within ZZ interval”) over multiple bindings?

1 Like

Thanks for this binding @weymann .

Are there any version constraints regarding the OHC version for running this binding? E.g. OH 4.0.0.M1 has been released last night and it seem not to work with that milestone.

It might make sense to add a version range to the title of this topic. Thanks.

Both the Java upgrade and the Karaf upgrade break compatibility between addons build for 3.4.x and 4.0.0, so no wonder that this doesn‘t work.

Compile with the following maven options and it should fix that as long as you didnt add anything specific to Java 17 or the newer karaf…

-Dohc.version=3.2.0 -Dmaven.compiler.source=11 -Dkaraf.version=4.3.7

I changed the title of this thread to “Solar Forecast PV [3.3.0;4.0.0)”. Hope it helps.

Thanks for providing this cool binding to optimize energy consumption at home.

I installed the binding using without any API key … unfortunately, all linked items remain NULL and do not get an update. Do you have any hints?.

I am using OH 3.2. and installed the binding via webfrontend.



I 'm using OH3.4, all linked items shows “NULL”.

Any ideas?


Just upgraded to OH3.4.3 and now all Solar Forcast Items show NULL value.
(I’m using one “fs-site” and one “sc-site” thing)

The binding worked well until the upgrade.

The log shows these messages regarding Solar Forecast:

Did the OH server simply “fire” to many requests to the Solcast API?
The API manual reads:
“429 Too Many Requests The request exceeds the available rate limit at your current subscription level.”

Are there any ideas as to the cause of the issue or what I could do to fix this issue?

Thanks and kind regards,

1 Like

Hi Ward,

Do you have by chance the same code ready for the Solcast forecast? Really great to have the forecast visualized in Grafana in order to be able to see how it fits over the day to the upcoming real values.

On a quick check, Solcast provides much more realistic values for my case compared to Forecast Solar. Not sure if I have setup something incorrectly by Forecast Solar, but I couldn´t find a failure yet.

I also switched to Solcast last week, because the values from Forecast Solar were not correct anymore.
but I didn’t changed my script to add these values in influxdb. Maybe I’ll check in the next days. Keep you posted.

I had the same problem. The solution was to change the refresh interval to 120 in the solcast bridge of solar forecast pv.

Done :slight_smile:

I first created 3 items in OH, so I cna also use the data in the OH charts

Number        solcast_pv_estimate                  "Forecast pv_estimate"           (solcast_home) ["Point"]
Number        solcast_pv_estimate10                "Forecast pv_estimate10"         (solcast_home) ["Point"]
Number        solcast_pv_estimate90                "Forecast pv_estimate90"         (solcast_home) ["Point"]

For the DSL rule, I assume you also have the Channel configured which contains the RAW data. the rule is triggered everytime the item changes

String        solcast_home_raw_json_response       "Raw JSON Response"              (solcast_home) ["Point"]  { channel="solarforecast:sc-plane:solcast_home:solcast_PV:raw" }


// import solcast_home_raw_json_response
val forecastJSON = solcast_home_raw_json_response.state.toString
//logInfo("Forecast.Solcast", forecastJSON)

// influx uri
val String influxdb_uri = ""
// json parse
var String forecasts_entries = transform("JSONPATH", "$..forecasts", forecastJSON)
forecasts_entries = forecasts_entries.replace("[","").replace("]","").replace("{","").replace("}","NEXT")
var Integer count = forecasts_entries.split("NEXT,").length()
//logInfo("Forecast.Solcast", "-" + forecasts_entries.toString + "-")
//logInfo("Forecast.Solcast", "number of entries: " + count.toString)

var Integer x = 0
var influx_content = ""
while (x < count ) {
  //logInfo("Forecast.Solcast", x.toString)
  var String forecasts = forecasts_entries.split("NEXT,").get(x)
  forecasts = "{" + forecasts.replace("NEXT","") + "}"
  //logInfo("Forecast.Solcast", forecasts.toString)
  val period_end = transform("JSONPATH", "$.period_end", forecasts)
  val pv_estimate = transform("JSONPATH", "$.pv_estimate", forecasts)
  val pv_estimate10 = transform("JSONPATH", "$.pv_estimate10", forecasts)
  val pv_estimate90 = transform("JSONPATH", "$.pv_estimate90", forecasts)
  //val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
  val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.'0000000Z'") //2023-05-12T12:00:00.0000000Z
  var period_end_formatted = LocalDateTime.parse(period_end, formatter).atZone(ZoneId.systemDefault())
  var int timezoneAdd = 0
  if (period_end_formatted.toString.contains("+01:00")) {
    timezoneAdd = 1
  else if (period_end_formatted.toString.contains("+02:00")) {
    timezoneAdd = 2
  period_end_formatted = period_end_formatted.plusHours(timezoneAdd) // add hour for timezone if not correct
  //logInfo("Forecast.Solcast", period_end_formatted.toString + ": " + pv_estimate)
  val period_end_formatted_seconds = period_end_formatted.toEpochSecond  
  influx_content = influx_content + "solcast_pv_estimate value=" + pv_estimate + " " + period_end_formatted_seconds.toString +"\n"
  influx_content = influx_content + "solcast_pv_estimate10 value=" + pv_estimate10 + " " + period_end_formatted_seconds.toString +"\n"
  influx_content = influx_content + "solcast_pv_estimate90 value=" + pv_estimate90 + " " + period_end_formatted_seconds.toString +"\n"

//logInfo("Forecast.Solcast", influx_content)
// send to influx
influx_content = influx_content.substring(0, influx_content.length()-2)
var responds = sendHttpPostRequest(influxdb_uri, "--data-binary", influx_content, 3000)
logInfo("Forecast.Solcast", "Influx responds {}", responds)

In the rule I play a bit with the timezone, because it’s not correct for me (although my local settings are correct in OH)

Hi @wars,

Very great. Thank you very much. I will give it a try.

By the way, in the meanwhile I found an alternative solution for getting the data into Grafana which might be interesting for you as well. They can be read into Grafana via the JSON API. The disadvantage is, that there the data are not stored permanently. The advantage on the other side is, that it shows always the latest data in the forecast.

To set it up, you need to install the JSON API as data source in Grafana. As URL you set the link to your RAW-Forecast item of the openHAB REST API. You can easily find it via the API Explorer under the development tools in openHAB. It looks like the following example: “https://XXX.XXX.XXX.XXX:XXXX/rest/items/PV_Vorhersage_Solcast_Sued_RAWJSON/state”
Then you activate Basic Authentification and apply your openHAB login credentials for authentification.

Within Grafana, the data can be used as follows:


Very nice! Will give it a try too :slight_smile:

I must say that I use the echarts of OH more than Grafana nowdays, because it’s better integrated and lots of possibilities. Although creating graphs with Grafana is still much easier.

here is a link to a story on how I abused Solar Forecast PV to reduce the running hours of my ground source heat pump: Control a water heater and ground source heat pump based on cheap hours of spot priced electricity - #245 by timo12357


Dear JSON Parsers @wars @Intenos

really nice way to visualize the forecast for the next days - I like it!

Just note there are Thing Actions in place to make it more comfortable. Raw json channels are available for each plane but for site there’s no json.

I personally have two solar planes on my site so this comes quite handy. Below rule shows in 5 minute steps the combined power of 2 planes in 5 minute steps (or whatever you want) till the end of forecast.

rule "AAA Solcast Power Forecast"
        Time cron "0 0 23 * * ?" //whenver you want
        val solarforecastActions = getActions("solarforecast","solarforecast:sc-site:YOUR_ID")
        val endDT = solarforecastActions.getForecastEnd // get latest forecast date/time
        logInfo("SF Tests","End: "+endDT)
        val granularityInMinutes = 5 //define your granularity 
        var observedDateTime = // variable to track observe date/time
        while(observedDateTime.isBefore(endDT)) { // ensure you're not querying data beyond latest forecast date/time 
             val powerAvg = solarforecastActions.getPower(observedDateTime)
             val powerOptimistic = solarforecastActions.getPower(observedDateTime,"optimistic")
             val powerPessimistic = solarforecastActions.getPower(observedDateTime,"pessimistic")
             logInfo("SF Tests","Power => Avg: "+powerAvg+" Optimistic: "+powerOptimistic+" Pessimistic: "+powerPessimistic)
             observedDateTime = observedDateTime.plusMinutes(granularityInMinutes)