Solar Forecast PV

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" }

rule:

// 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 = "http://xxx.xxx.x.x:xxxx/write?db=openhab_db&precision=s"
    
// 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"
    
  x++
}

//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:

2 Likes

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.

Hello,
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

:slight_smile:

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"
    when
        Time cron "0 0 23 * * ?" //whenver you want
    then 
        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 = LocalDateTime.now // 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)
        }
end

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
configuration:
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.

Readme

My Config

Hi,

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(LocalDateTime.now.plusHours(1))
        //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

:sweat_smile:

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 https://github.com/weymann/OH3-SolarForecast-Drops/blob/main/4.0/org.openhab.binding.solarforecast-4.0.0-SNAPSHOT.jar 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?

Thanks,
Paul

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 https://github.com/weymann/openhab-addons/blob/solar-forecast/bundles/org.openhab.binding.solarforecast/src/main/resources/OH-INF/addon/addon.xml 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 https://smedley.id.au/tmp/org.openhab.binding.solarforecast-4.1.0-SNAPSHOT.jar 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");

rules.JSRule({
    name: "Solcast_PV_Plane_Raw_JSON_Response",
    description: "Solcast_PV_Plane_Raw_JSON_Response",
    triggers: [
        triggers.ItemStateChangeTrigger('Solcast_PV_Plane_Raw_JSON_Response')
    ],
    execute: (event) => {
        const influxdb_uri   = "http://192.168.0.68:8086/write?db=openhab_db&precision=ms";
        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;
            do
            {
                //console.log(timestamp);
                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());

            //console.log(influx_content);
            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);
        }
        else
        {
            console.log("beginDT: " + beginDT + " endDT: " + endDT);
        }
    },
    tags: [],
    id: "Solcast_PV_Plane_Raw_JSON_Response"
});
type or paste code here