Solar Forecast PV

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)

I don’t know if this has been mentioned here, but for ForecastSolar you only allow full values, no floating values. Since I only have a small 0,8 kWp solar module i checked their api. It is actually possible to use float values. I just changed the value from 1 to 0.8 in the Openhab code section and it still works.

UID: solarforecast:fs-plane:…
label: ForecastSolar PV Plane
thingTypeUID: solarforecast:fs-plane
dampAM: 0.25
azimuth: -30
declination: 38
dampPM: 0.25
kwp: 0.8
refreshInterval: 30
bridgeUID: solarforecast:fs-site:…

Don’t know where this information is coming from. Double checked in readme and code - both floating values. I’ve 2 planes, both with floating values working since the beginning.


My Config


I am running the binding on the forecastsolar API however the horizon setting doesnt seem to work for me. My graph forecast doesnt change regardless of what I configure. I had a quick look at the code. It seems to me like the horizon query param is concatenated with a ‘?’. However the damping factor has already been set this way. As far as i know, the second and any subsequent query param should be concatenated with a ‘&’.
Or am I missing something here?

Btw thanks for the great work.

Best regards Felix

True - fixed it.

1 Like

I just noticed the following error in my log file:

Script execution of rule with UID 'solcast-1' failed: 'getPower' is not a member of 'org.openhab.core.thing.binding.ThingActions'; line 35, column 37, length 61 in solcast

The rule:

        val solarforecastActions = getActions("solarforecast","solarforecast:sc-site:homeSite")

        // get power forecast in 1 hour
        var hourPlusOnePowerState = solarforecastActions.getPower(
        //logInfo("SF Tests","Hour+1 power state: "+ hourPlusOnePowerState.toString)
        if (hourPlusOnePowerState instanceof Number) { SolcastHourPlusOnePowerState.sendCommand(hourPlusOnePowerState as Number) }

This used to work in the past. Only thing I could think of that has changed recently, is that my OH was updated from 3.4.2 to 4.0.0M1, even though I had it on hold in apt. I downgraded to 3.4.4. When I look at my Grafana charts, the last update was June 5th, around 7am. And that’s also the time I had the unwanted upgrade. I cleaned my cache a few times but that didn’t help.

Any idea what could cause this?

Try cleaning the cache. But i’s apparently an issue with your upgrade procedure and not related to the solar forecast binding so you shouldn’t be asking in this thread.

1 Like


Meanwhile I tried to remove thje binding, clean the cache again, installed the latest version of the binding. Still the same error.

I will look somewhere else on this forum for some help. Thanks.

I can’t seem to find this in the Marketplace any more, possibly as I’m now running 4.0

I found a jar at but after copying it into the addones folder, I get an error:

org.osgi.framework.BundleException: Could not resolve module: org.openhab.binding.solarforecast [261]
  Unresolved requirement: Import-Package: org.json; version="[20180813.0.0,20180814.0.0)"

Any hints on how to resolve?


Looks like update json version · weymann/openhab-addons@9154824 · GitHub is the fix…

I built the jar with this fix, and it works, but you can’t add a new thing using the gui as there is a reference to mercedesme in rather than solarforecast

I have the same thing in a brand new fresh install of 4.0.1. I don’t see the binding in the Community Marketplace.

For now, you can drop a copy of into /usr/share/openhab/addons (assuming you’re running openhab on a pi or similar)

1 Like

In the depths of Github you can find the JAR here and that works in den /addons folder

Inspired by the DSL rules from above, I implemented the following rule in OH4 javascript:

const { UNDEF } = require("@runtime");

    name: "Solcast_PV_Plane_Raw_JSON_Response",
    description: "Solcast_PV_Plane_Raw_JSON_Response",
    triggers: [
    execute: (event) => {
        const influxdb_uri   = "";
        const influx_header  = new Map();
            influx_header.set("Authorization", "Token fsBS4pQ...wvQpg==");
            influx_header.set("Accept", "application/json");
        const solarforecastAction = actions.Things.getActions('solarforecast', 'solarforecast:sc-site:3b3f4a0b98');
        const now = new Date();

        let beginDT = solarforecastAction.getForecastBegin();
        let endDT   = solarforecastAction.getForecastEnd();
        if ( (beginDT != time.LocalDateTime.MIN) && (endDT != time.LocalDateTime.MIN) ) // getForecastEnd returns MIN instead of MAX - wrong in docu
            beginDT = new Date(beginDT);    // to jsDate
            endDT   = new Date(endDT);      // to jsDate
            //console.log("beginDT: " + beginDT + " endDT: " + endDT);

            let influx_content = "";
            let timestamp = beginDT;
                const epoch = timestamp.getTime();

                const powerState = solarforecastAction.getPower(timestamp);
                if ( powerState != UNDEF )
                    influx_content = influx_content + "Solcast_PV_Plane_Power_Forecast value=" + powerState * 1000.0 + " " + epoch + "\n"; // from kW to W - value is returned as Number and not Quantity - wrong in docu
                let powerStateOpt;
                let powerStatePes;
                if ( timestamp.getTime() >= now.getTime() )
                    powerStateOpt = solarforecastAction.getPower(timestamp, "optimistic");
                    if ( powerStateOpt != UNDEF )
                        influx_content = influx_content + "Solcast_PV_Plane_Power_ForecastOpt value=" + powerStateOpt * 1000.0 + " " + epoch + "\n";
                    powerStatePes = solarforecastAction.getPower(timestamp, "pessimistic");
                    if ( powerStatePes != UNDEF )
                        influx_content = influx_content + "Solcast_PV_Plane_Power_ForecastPes value=" + powerStatePes * 1000.0 + " " + epoch + "\n";
                //console.log("powerState: " + powerState + " powerStateOpt: " + powerStateOpt + " powerStatePes: " + powerStatePes);
                timestamp.setMinutes(timestamp.getMinutes() + 30);
            } while (timestamp.getTime() <= endDT.getTime());

            const respone = actions.HTTP.sendHttpPostRequest(influxdb_uri, "text/plain; charset=utf-8", influx_content, influx_header, 10000);
            console.log("Solcast_PV_Plane_Raw_JSON_Response: '" + respone + "', beginDT: " + beginDT + ", endDT: " + endDT + ", timestamp: " + timestamp);
            console.log("beginDT: " + beginDT + " endDT: " + endDT);
    tags: [],
    id: "Solcast_PV_Plane_Raw_JSON_Response"
type or paste code here


I’m plotting the current ForecastSolarSite_ActualPowerForecast item while overlaying it with my actual data from the solar power plant. There is an offset of 1h, the forecast runs 1h early. Is there any way to correct that in the binding or setting? Maybe a DST offset issue?!?


Are you sure it’s really a shift?

I’m doing something simlar and in principle Forecast.Solar is a bit optimistic on “clear days” with bright sunshine. But it just looks as shifted - blue line forecast, green line real energy production

Please double check if raw channel from plane thing JSON contains correct timestamps. Matches for my location Germany with sunrise around 6:56 and sundawn at 19:48. But too optimistic values.

Hi Bernd,

thanks, I checked the Plane Raw_JSON_Response and it looks good. Timestamps and sun rise/set times are correct. I guess you are interpolating the current values from the current time period?

2023-09-14 19:09:52.655 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'ForecastSolarPVPlane1_ActualPowerForecast' changed from 0.316 kW to 0.255 kW

relates to:

"2023-09-14 19:00:00":300,"2023-09-14 19:23:16":0

Hmm, seems like the data comes in that way. The funny thing is when I post-process the timestamps of the predicted data by +1h, it matches perfectly :exploding_head:

Any chance to get a (hidden) option for adding a custom offset?


Correct. In order not to provide these big hourly steps in the graph

What does this mean?
You already showed this in the logs. Imagine there’s an offset +1h the forecast binding is delivering still values one hour after sundawn! This would open more questions than problems solved.

Write a simple 2 liner rule to check behavior:
Define an item

Number:Power            PowerOffsetItem

and a mini rule

rule "Forecast Offset"
        Time cron "0 0/1 * * * ?"
        val solarforecastActions = getActions("solarforecast","solarforecast:fs-site:0d595e3485")

Voila - your offset is implemented :slight_smile: