Automatic Solar battery charging timings based on forecast weather - Page 1
Forum section: electronics / general

author: IanJ
author_rank: Unknown
timestamp: September 11, 2024, 11:01:40 am
content: Hi all,I have a 4kW solar & 10kWh battery system, and an Octopus Flux tariff with my energy provider.2am to 5am the cheap rate electricity tariff kicks in which is nearly 50% off the normal rate so it makes sense to charge the 10kWh battery during that time ready for the next day.But of course, if it's going to be sunny the next day then don't bother charging the batteries overnight and let the solar panels do it instead.Some folks log-in to their battery inverter app manually after looking at tomorrows weather online and making a determination of what timings to put on the battery charging.......yeuccchhhh, I couldn't be bothered!So, I have automated this with a Windows app running on my home server.At 11:55pm the app goes off to openweathermap.org via it's API and gets the next days irridiance forecast for my lat/long and then via a ruleset works out how much to charge the batteries the coming 2am to 5am cheap rate.....if at all.The app them goes off via the Solis API and updates the charging settings in the inverter. I don't lift a finger.Today is day 1 of this now up and running. Tomorrows irridiance figure is 3699 and so my app has determined a half hour charge should be enough.I'll have to monitor it over the next few weeks under different weather conditions and adjust the ruleset accordingly, but basically my system is completely hands-off now.Here's some of the VB.NET code from my app........API keys etc removed.If anyone else can benefit from it................PS. It's a Solis battery inverter.Future works:Spare battery capacity could be exported back to the grid between 4pm and 7pm which is nearly 30% more than grid cost. Hmmmm!Code:Imports System.Security.CryptographyImports System.TextImports Newtonsoft.Json.LinqImports Newtonsoft.JsonImports System.Net.HttpImports System.GlobalizationImports System.Net.Http.HeadersImports System.Text.RegularExpressionsImports SystemImports System.ThreadingImports System.Runtime.InteropServicesPartial Class FormMainPrivate Sub ButtonChargeSet_Click(sender As Object, e As EventArgs) Handles ButtonChargeSet.ClickIf CheckBox7.Checked And CheckBox8.Checked ThenCall OpenWeatherIrridiance() ' Manually get irridiance figures and then send updated settings back to Solis Inverter (battery charge discharge timings)End IfEnd SubPrivate Sub SendToSolis()Try' Check user entered data, abort sub with pop-up if wrongDim Charge1SetOn As String = TextBoxChargeSetOn.TextDim Charge1SetOff As String = TextBoxChargeSetOff.Text' Define the regex pattern for XX:XX formatDim timePattern As String = "^\d{2}:\d{2}$"' Check if the strings match the patternIf Not Regex.IsMatch(Charge1SetOn, timePattern) OrElse Not Regex.IsMatch(Charge1SetOff, timePattern) Then' If either string does not match the pattern, exit the subMessageBox.Show("Please enter the time in the format XX:XX")Exit SubEnd IfDim key As String = "#####################" ' Private key from SolisDim keySecret As String = "############################" ' Secret key from Solis' Create a comma-separated value for cid 103 using the inputs and default valuesDim value As String = $"70,50,{Charge1SetOn},{Charge1SetOff},00:00,00:00,70,50,00:00,00:00,00:00,00:00,70,50,00:00,00:00,00:00,00:00"' Create the map for the API requestDim map As New Dictionary(Of String, Object) From {{"inverterSn", "##############"}, ' Replace with your actual inverter serial number{"cid", "103"},{"value", value} ' Set the parameters as a single comma-separated string}' Serialize the map to JSON for the API requestDim body As String = JsonConvert.SerializeObject(map)Dim ContentMd5 As String = GetDigest(body)Dim  As String = GetGMTTime()Dim path As String = "/v2/api/control"Dim param As String = "POST" & vbLf & ContentMd5 & vbLf & "application/json" & vbLf &  & vbLf & pathDim sign As String = HmacSHA1Encrypt(param, keySecret)Dim url As String = "https://www.soliscloud.com:13333" & path ' URL for the control endpointDim client As New HttpClient()Dim requestBody As HttpContent = New StringContent(body, Encoding.UTF8, "application/json")' Set Content-Type and Content-MD5requestBody.Headers.ContentType = New MediaTypeHeaderValue("application/json") With {.CharSet = "UTF-8"}requestBody.Headers.ContentMD5 = Convert.FromBase64String(ContentMd5)Dim request As New HttpRequestMessage(HttpMethod.Post, url)request.Headers.Add("Authorization", "API " & key & ":" & sign)request.Headers.Add("Date", )request.Content = requestBody' Send the requestDim response As HttpResponseMessage = client.SendAsync(request).Result ' Non-async for timing purposesDim result As String = response.Content.ReadAsStringAsync().ResultRunningSeq.Text = "Irridiance received, Battery settings updated"Catch ex As ExceptionConsole.WriteLine(ex.ToString())End TryEnd SubPrivate Sub CheckBox7_CheckedChanged(sender As Object, e As EventArgs) Handles CheckBox7.CheckedChangedIf CheckBox7.Checked = True ThenButtonChargeSet.Enabled = TrueElseButtonChargeSet.Enabled = FalseEnd IfEnd SubPrivate Sub OpenWeatherIrridiance()On Error GoTo ErrorHandler' get solar irridiance from openweathermap.org' API = ##################################' appid = API keyIf CheckBox8.Checked = True ThenDim TomorrowDate As String = DateTime.Now.AddDays(1).ToString("yyyy-MM-dd") ' tomorrow date, so run this sub before midnightDim IrridianceAPIkey As String = "################################"' openweathermap ping retryDim deviceAddress As String = "openweathermap.org"Dim maxRetries As Integer = 3Dim retryDelaySeconds As Double = 0.2' Call FunctionIf TryPingDevice(deviceAddress, maxRetries, retryDelaySeconds) Then' Ping was successful, continue with your specific actionsDim IrridianceData As String = ""IrridianceData = New System.Net.WebClient().DownloadString("https://api.openweathermap.org/energy/1.0/solar/data?lat=56.9734&lon=-2.2252&date=" & TomorrowDate & "&" & "appid=" & IrridianceAPIkey)IsOkIrridiance = True' Turn on the LEDLEDirridiance.State = OnOffLed.LedState.OnSmallYellow' Start the timer so the LED will light for 1secIndicatorTimer3.Interval = 2000 ' 1 secondIndicatorTimer3.Start()' Sample return' {"lat":56.9734,"lon":2.2252,"date":"2024-09-12","tz":"+00:00","sunrise":"2024-09-12T05:16:09","sunset":"2024-09-12T18:16:56","irradiance":{"daily":[{"clear_sky":{"ghi":4454.41,"dni":8160.83,"dhi":974.92},"cloudy_sky":{"ghi":1176.86,"dni":0.0,"dhi":1176.86}}],"hourly":[{"hour":0,"clear_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0},"cloudy_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0}},{"hour":1,"clear_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0},"cloudy_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0}},{"hour":2,"clear_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0},"cloudy_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0}},{"hour":3,"clear_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0},"cloudy_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0}},{"hour":4,"clear_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0},"cloudy_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0}},{"hour":5,"clear_sky":{"ghi":13.3,"dni":92.91,"dhi":13.27},"cloudy_sky":{"ghi":3.83,"dni":0.0,"dhi":3.83}},{"hour":6,"clear_sky":{"ghi":112.37,"dni":436.99,"dhi":48.97},"cloudy_sky":{"ghi":31.06,"dni":0.0,"dhi":31.06}},{"hour":7,"clear_sky":{"ghi":244.8,"dni":617.18,"dhi":70.05},"cloudy_sky":{"ghi":61.2,"dni":0.0,"dhi":61.2}},{"hour":8,"clear_sky":{"ghi":372.29,"dni":715.88,"dhi":83.97},"cloudy_sky":{"ghi":98.83,"dni":0.0,"dhi":98.83}},{"hour":9,"clear_sky":{"ghi":478.25,"dni":774.19,"dhi":93.27},"cloudy_sky":{"ghi":136.31,"dni":0.0,"dhi":136.31}},{"hour":10,"clear_sky":{"ghi":551.52,"dni":806.91,"dhi":98.91},"cloudy_sky":{"ghi":153.17,"dni":0.0,"dhi":153.17}},{"hour":11,"clear_sky":{"ghi":584.97,"dni":820.27,"dhi":101.33},"cloudy_sky":{"ghi":160.0,"dni":0.0,"dhi":160.0}},{"hour":12,"clear_sky":{"ghi":575.45,"dni":816.47,"dhi":100.67},"cloudy_sky":{"ghi":151.91,"dni":0.0,"dhi":151.91}},{"hour":13,"clear_sky":{"ghi":523.81,"dni":794.88,"dhi":96.9},"cloudy_sky":{"ghi":130.95,"dni":0.0,"dhi":130.95}},{"hour":14,"clear_sky":{"ghi":434.94,"dni":751.78,"dhi":89.75},"cloudy_sky":{"ghi":108.77,"dni":0.0,"dhi":108.77}},{"hour":15,"clear_sky":{"ghi":317.69,"dni":678.03,"dhi":78.58},"cloudy_sky":{"ghi":79.5,"dni":0.0,"dhi":79.5}},{"hour":16,"clear_sky":{"ghi":185.27,"dni":550.99,"dhi":61.93},"cloudy_sky":{"ghi":46.36,"dni":0.0,"dhi":46.36}},{"hour":17,"clear_sky":{"ghi":59.13,"dni":298.83,"dhi":35.37},"cloudy_sky":{"ghi":14.8,"dni":0.0,"dhi":14.8}},{"hour":18,"clear_sky":{"ghi":0.62,"dni":5.51,"dhi":1.96},"cloudy_sky":{"ghi":0.15,"dni":0.0,"dhi":0.15}},{"hour":19,"clear_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0},"cloudy_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0}},{"hour":20,"clear_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0},"cloudy_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0}},{"hour":21,"clear_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0},"cloudy_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0}},{"hour":22,"clear_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0},"cloudy_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0}},{"hour":23,"clear_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0},"cloudy_sky":{"ghi":0.0,"dni":0.0,"dhi":0.0}}]}}' Pull total irridiance value from returned stringIf (IrridianceData.Length() > 2000) Then ' usually 2885 approx.' Your JSON stringDim json As String = IrridianceData' Parse the JSON stringDim data As JObject = JObject.Parse(json)' Get the hourly irradiance arrayDim hourlyIrradiance As JArray = data("irradiance")("hourly")' Initialize variables to store the total irradiance for both clear and cloudy skiesDim totalClearSkyIrradiance As Double = 0Dim totalCloudySkyIrradiance As Double = 0' Loop through each hourly object and sum the "ghi" (global horizontal irradiance) for both clear and cloudy skiesFor Each hourData As JObject In hourlyIrradianceDim clearSkyGhi As Double = hourData("clear_sky")("ghi")Dim cloudySkyGhi As Double = hourData("cloudy_sky")("ghi")totalClearSkyIrradiance += clearSkyGhitotalCloudySkyIrradiance += cloudySkyGhiNext' Output the total summed irradiance for both clear and cloudy skiesConsole.WriteLine("Total summed irradiance for the day (clear sky): " & totalClearSkyIrradiance.ToString())Console.WriteLine("Total summed irradiance for the day (cloudy sky): " & totalCloudySkyIrradiance.ToString())' You can use either of the summed values or calculate an average for decision makingDim overallIrradiance As Double = (totalClearSkyIrradiance + totalCloudySkyIrradiance) / 2Irridiance.Text = overallIrradiance' Output the overall irradianceConsole.WriteLine("Overall summed irradiance: " & overallIrradiance.ToString())' now determine if batteries should charge. Figures below from ChatGPT' Overcast days: 200-500 W/m²' Cloudy days: 500-1000 W/m²' Cloudy/sunny days: 1000-2000 W/m²' Sunny winter days: 2000-3000 W/m²' Sunny summer days: 5000-7000 W/m²If overallIrradiance <= 500 ThenTextBoxChargeSetOn.Text = "02:00"TextBoxChargeSetOff.Text = "05:00"End IfIf overallIrradiance > 500 And overallIrradiance <= 1000 ThenTextBoxChargeSetOn.Text = "02:00"TextBoxChargeSetOff.Text = "04:30"End IfIf overallIrradiance > 1000 And overallIrradiance <= 2000 ThenTextBoxChargeSetOn.Text = "02:00"TextBoxChargeSetOff.Text = "04:00"End IfIf overallIrradiance > 2000 And overallIrradiance <= 3000 ThenTextBoxChargeSetOn.Text = "02:00"TextBoxChargeSetOff.Text = "03:30"End IfIf overallIrradiance > 3000 And overallIrradiance <= 5000 ThenTextBoxChargeSetOn.Text = "02:00"TextBoxChargeSetOff.Text = "02:30"End IfIf overallIrradiance > 5000 And overallIrradiance <= 7000 ThenTextBoxChargeSetOn.Text = "00:00"TextBoxChargeSetOff.Text = "00:00"End IfMy.Settings.data40 = TextBoxChargeSetOn.TextMy.Settings.data41 = TextBoxChargeSetOff.TextMy.Settings.Save()Call SendToSolis() ' Got the irridiance value so can now send the required settings to Solis' Turn off the LEDLEDirridiance.State = OnOffLed.LedState.OffSmallBlackEnd IfElseDim currentDateAndTime As DateTime = DateTime.NowDim formattedDateTime As String = currentDateAndTime.ToString("dd-MM-yyyy HH:mm", CultureInfo.InvariantCulture)ErrorCode.Text = formattedDateTime & " " & "openweathermap.org ping fail" 'ToErrorString(Err) ' display error statusIsOkIrridiance = FalseLEDirridiance.State = OnOffLed.LedState.OffSmall ' fail RED ledExit SubEnd IfEnd IfErrorHandler:End SubPrivate Async Sub Battery()Dim batteryPower As Double = 0If CheckBox4.Checked Then ' Solar read must have been done successfully before OB418 read can take place' Async/Await: The HttpClient operations are now asynchronous, preventing the UI thread from being blocked.TryDim key As String = "######################" ' Private key from SolisDim keySecret As String = "################################" ' Secret key from SolisDim map As New Dictionary(Of String, Object) From {{"pageNo", 1},{"pageSize", 10}}Dim body As String = JsonConvert.SerializeObject(map)Dim ContentMd5 As String = GetDigest(body)Dim  As String = GetGMTTime()Dim path As String = "/v1/api/inverterList"Dim param As String = "POST" & vbLf & ContentMd5 & vbLf & "application/json" & vbLf &  & vbLf & pathDim sign As String = HmacSHA1Encrypt(param, keySecret)Dim url As String = "https://www.soliscloud.com:13333" & path ' Url from SolisDim client As New HttpClient()Dim requestBody As HttpContent = New StringContent(body, Encoding.UTF8, "application/json")' Set Content-Type and Content-MD5 directly when creating StringContentrequestBody.Headers.ContentType = New MediaTypeHeaderValue("application/json") With {.CharSet = "UTF-8"}requestBody.Headers.ContentMD5 = Convert.FromBase64String(ContentMd5)Dim request As New HttpRequestMessage(HttpMethod.Post, url)request.Headers.Add("Authorization", "API " & key & ":" & sign)request.Headers.Add("Date", )request.Content = requestBodyDim response As HttpResponseMessage = client.SendAsync(request).Result ' non-asynchronous - Sub will wait for response, stopwatch records properlyDim result As String = response.Content.ReadAsStringAsync().Result' Now pull data from JSON stringDim jsonObject As JObject = JObject.Parse(result) ' Parse the JSON string' Access the "records" array within the "page" propertyDim recordsArray As JArray = jsonObject.SelectToken("data.page.records")' Check if the "records" array is not null and contains at least one itemIf recordsArray IsNot Nothing AndAlso recordsArray.Any() Then' Access the first item in the "records" arrayDim firstRecord As JObject = recordsArray.First' Access the "batteryCapacitySoc" property within the first recordDim batteryCapacitySoc As Double = 0If firstRecord.TryGetValue("batteryCapacitySoc", batteryCapacitySoc) ThenIsOkBattery = TrueLEDbattery.State = OnOffLed.LedState.OnSmallBatteryCapacity.Text = Math.Round(batteryCapacitySoc, 0).ToString() ' 0dpElseIsOkBattery = FalseLEDbattery.State = OnOffLed.LedState.OffSmallEnd If' Access the "batterypower" property within the first record, also charging statusIf DataGlitch = False ThenIf firstRecord.TryGetValue("batteryPower", batteryPower) ThenIsOkBattery = TrueLEDbattery.State = OnOffLed.LedState.OnSmallIf batteryPower > 0 ThenBattStatus.Text = "Charging"ElseIf batteryPower < 0 ThenBattStatus.Text = "Discharging"ElseBattStatus.Text = "Static"End IfbatteryPower *= 1000 ' kW to WBattChg.Text = Math.Round(batteryPower, 0).ToString() ' 0dpElseIsOkBattery = FalseLEDbattery.State = OnOffLed.LedState.OffSmallEnd IfEnd If' House ConsumptionIf DataGlitch = False ThenDim solarWValue As Double = Val(SolarW.Text)Dim consumptionValue As Double = solarWValue + OB418DataMeterPwr - batteryPowerConsumption.Text = FormatNumber(consumptionValue, 1).Replace(",", "")End IfDataGlitch = False ' reset glitch flagRunningSeq.Text = "Received Solis battery data"Else' Handle the case where "records" array is empty or nullIsOkBattery = FalseLEDbattery.State = OnOffLed.LedState.OffSmallEnd IfCatch ex As ExceptionConsole.WriteLine(ex.ToString())End TryEnd IfEnd SubFunction HmacSHA1Encrypt(encryptText As String, KeySecret As String) As StringDim data As Byte() = Encoding.UTF8.GetBytes(KeySecret)Dim secretKey As New HMACSHA1(data)Dim text As Byte() = Encoding.UTF8.GetBytes(encryptText)Dim result As Byte() = secretKey.ComputeHash(text)Return Convert.ToBase64String(result)End FunctionFunction GetGMTTime() As StringReturn DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'", CultureInfo.InvariantCulture)End FunctionFunction GetDigest(test As String) As StringDim result As String = ""TryUsing md5 As System.Security.Cryptography.MD5 = System.Security.Cryptography.MD5.Create()Dim data As Byte() = md5.ComputeHash(Encoding.UTF8.GetBytes(test))result = Convert.ToBase64String(data)End UsingCatch ex As ExceptionConsole.WriteLine(ex.ToString())End TryReturn resultEnd FunctionEnd Class

author: Siwastaja
author_rank: Super Contributor
timestamp: September 11, 2024, 11:27:57 am
content: Interesting to hear!I mean, we do this as a product/service. It's a physical box which connects to the battery inverter through RS485 or modbus TCP (Solis hybrid being one of the supported models) and to the Internet to get prices/weather forecasts/configuration/user bypass controls from our cloud service. So far it hasn't taken weather forecasts into account for battery optimization, and people have had to fine-tune optimization algorithm settings to get good results and that has sucked to the point of causing some well deserved complaints.We made it the simple way by having a control for maximum SoC during night/early morning charging, instead of controlling it by the length of charge time. This way, if there is still excess solar from previous day, nothing is put into the battery, but if it is empty, a little bit charging is done to supply the morning peak use before the generation really exceeds consumption. But even then this requires manual tuning of this "charge up to SoC%" setting; in winter you want to increase it, maybe up to nearly 100%. And if you really want good results, even daily increase it for cloudy days. Sucks.As such now I'm in progress of writing a more comprehensive battery optimization algorithm and can share some ideas, like:Take consumption history into account automatically, too. I'm now doing this by tracking energy meter net sums which are negative on export, positive on import. Track hourly minimums over a few weeks of history and they represent what would be exported on a fully sunny day (well assuming there was at least one sunny day within the period). Net forecast is better than production forecast because maybe there is some load which often/always comes on at a certain time.When hourly net forecasts show export, multiple them by hourly solar level estimates, sum them up for the day and now you know more exactly how much room you have to reserve into the battery.I have this tendency that when it comes to designing an algorithm with complex interactions, at some point I just resort to some brute-forcing. So create a model (input with price, weather, energy consumption/production forecasts which are constant over iterations) which runs with not too much CPU time and loop over thousands of different combinations of controls, outputting money made for each set of parameters. Then pick the winner and possibly finetune it.

author: IanJ
author_rank: Unknown
timestamp: September 11, 2024, 11:37:45 am
content: I did talk to Solis back in January when I first had this idea, and they were receptive but that's as far as it went. I was actually hoping Solis would integrate this functionality into their inverters.....more sales!I already had an app running monitoring my energy usage, Solar PV output etc.......it made sense to extend the app. I know some folks are using Home Assistant for something similar.Thanks for the tips, I'm quite eager to see how things go.......I'll probably extend my app to record irridiance forecast values against battery capacity to see where my ruleset needs tweaked......summer and winter.Ian.

author: IanJ
author_rank: Unknown
timestamp: September 11, 2024, 11:59:55 am
content: Here's my app.......It doubles as an Aircon control app for my workshop.It's quite a dense form, the server display is only 1024x768 and runs on a touchscreen on my workshop wall.The app also writes a webpage containing most of the stats and graphs and pops it onto my webserver so I can access from anywhere.

author: mag_therm
author_rank: Frequent Contributor
timestamp: September 11, 2024, 01:13:07 pm
content: Ian, that looks good.I have been developing/testing a similar system since Jan 2024 on the mini solar battery system here used to power the ham radio station.With two NOS panels,in parallel, there is a nominal 3 kW.h 42 V bus and a 1 kW.h 13.8V bus each controlled by two 300 W home brew DC converters.The outer loop of the converters is controlled, along with a 14 point datalogger, by python via tty on linux.I use Pyowm API to obtain next day UV Index. Based on UV Index, a calculation of battery ullage is made at 8:00pm local.(0 UTC)Fo example, UV Index of 10 will switch off the Utility earlier in pm than a UVI of 4.https://app.box.com/s/a3pfju14qjllbnp3qdwjj258gs603atpIt basically works but there are problems to overcome. The main one is intermittent clouds that occur in this area through summer.The pyowm is inadequately documented for me, and does not seem to provide an enumeration of forecast cloud cover (as used in aviation for example)A further problem is that the batteries are floating, and can not take all the solar power available.I am thinking of adding a third "raw" bus with Li battery and a separate bidirectional controller to take the presently unusable energy.I will look at openweathermap.org when I do next round of python updates ( next winter).

author: IanJ
author_rank: Unknown
timestamp: September 12, 2024, 10:17:25 am
content: Quote from: mag_therm on September 11, 2024, 01:13:07 pmNice data on your chart.I'm not sure exactly how irridiance figures operate through winter, summer, cloud cover etc......so I will be monitoring closely.You have to pay for irridiance data through openweathermap.org but it's cheap........it's 0.1p (pence) per API call.......and I need it once per day. The nice thing is you can specify latitude/longitude values for the data and so specify your exact location for the best data.I get the Outside Temp. via them also, but it's free.Ian.

author: IanJ
author_rank: Unknown
timestamp: September 12, 2024, 10:33:20 am
content: Ok, so I decided enough was enough and my app was too conjested......so I have expanded it full screen (1024x768) on the server monitor, bigger buttons, bigger/coloured text, bigger graphs. Nearly done.That's the first overnight functionality with irridiance data and updating the Solis inverter battery charging times accordingly at just before midnight and it worked just fine.One thing I might work on is having a table where I can edit the irridiance to charging ruleset without recompiling/restarting the app.Irridiance Ruleset can now by dynamically changed/applied within the app, and the number of rows is not limited:

author: Siwastaja
author_rank: Super Contributor
timestamp: September 12, 2024, 11:14:51 am
content: I personally like those kind of technical, full-of-info dashboards. Attached is an example what our thing looks like. When I suggest adding more bells and whistles to the UI, I get backlash from the others. As you can see from my attachment (it's from today), price variation can sometimes be quite large and a battery owner is able to make some money by selling into grid at correct time. And even if not selling to grid, when daily consumption exceeds that of battery capacity and production, just timing the self-use during expensive rates is valuable.

author: IanJ
author_rank: Unknown
timestamp: September 12, 2024, 11:28:18 am
content: Quote from: Siwastaja on September 12, 2024, 11:14:51 amThanks for sharing the screenshots.......it's giving me some ideas.Yup, my app is ONLY for me, not another soul on the planet will use it hence mine is very busy......and I like to just walk past the server screen and see everything/anything without having to touch the mouse/touchscreen.Ian.

author: IanJ
author_rank: Unknown
timestamp: September 12, 2024, 12:28:54 pm
content: Modified section of code on evaluating the Irridiance data forecast from OpenWeatherMap.org in order to make a better determination of what the actual figure might look like for the day.This is necessary because the data provides two sets of numbers for clear and cloudy skies......but there is data there to make a weighted value overall to speculatively decide on how long to charge the batteries for.Ian.Code:' Produces an irradiance figure that factors in whether it is cloudy or clear, we can enhance it by determining the likely irradiance' based on the ratio between cloudyand clear sky conditions, making a decision based on the comparison of irradiance values.' Cloudy vs.Clear Hour Count:' We count the number of hours where cloudy irradiance Is significantly lower than clear sky irradiance (less than 50% of clear sky irradiance).' Weighted Irradiance:' Based on the count of cloudy And clear hours, we use the total cloudy Or clear sky irradiance To calculate the most likely overall irradiance. If more hours are cloudy, the cloudy irradiance Is used; otherwise, clear sky irradiance Is used.' Purpose:' This code now takes into account both clear And cloudy conditions by weighting the irradiance based on the number of cloudy versus clear hours. It will output the irradiance figure you can use to make a decision about solar energy potential for the day.' A generally perfect sunny day max figure would be 7600.' From the data sample above it would be 5449.' Your JSON stringDim json As String = IrridianceData' Parse the JSON stringDim data As JObject = JObject.Parse(json)' Get the hourly irradiance arrayDim hourlyIrradiance As JArray = data("irradiance")("hourly")' Initialize variables to store the total irradiance for both clear and cloudy skiesDim totalClearSkyIrradiance As Double = 0Dim totalCloudySkyIrradiance As Double = 0Dim cloudyHours As Integer = 0Dim clearHours As Integer = 0' Loop through each hourly object and sum the "ghi" (global horizontal irradiance) for both clear and cloudy skiesFor Each hourData As JObject In hourlyIrradianceDim clearSkyGhi As Double = hourData("clear_sky")("ghi")Dim cloudySkyGhi As Double = hourData("cloudy_sky")("ghi")totalClearSkyIrradiance += clearSkyGhitotalCloudySkyIrradiance += cloudySkyGhi' Count hours where the cloudy irradiance is significantly lower than clear sky irradianceIf cloudySkyGhi < clearSkyGhi * 0.5 ThencloudyHours += 1ElseclearHours += 1End IfNext' Determine the weighted irradiance based on the number of cloudy and clear hoursDim weightedIrradiance As DoubleIf cloudyHours > clearHours Then' More cloudy hours, use cloudy sky irradianceweightedIrradiance = totalCloudySkyIrradianceElse' More clear hours or balanced, use clear sky irradianceweightedIrradiance = totalClearSkyIrradianceEnd If' Output the total summed irradiance for both clear and cloudy skiesConsole.WriteLine("Total summed irradiance for the day (clear sky): " & totalClearSkyIrradiance.ToString())Console.WriteLine("Total summed irradiance for the day (cloudy sky): " & totalCloudySkyIrradiance.ToString())Console.WriteLine("Cloudy hours: " & cloudyHours.ToString())Console.WriteLine("Clear hours: " & clearHours.ToString())' Output the weighted irradianceConsole.WriteLine("Weighted irradiance for decision making: " & weightedIrradiance.ToString())Irridiance.Text = CInt(weightedIrradiance).ToString() ' Update UI element with the final value

author: mag_therm
author_rank: Frequent Contributor
timestamp: September 12, 2024, 01:18:13 pm
content: Hi Ian,you have given some useful information, thanks. I presently only have have a free key to Pyowm which uses OpenWeatherMap.org.That might explain why the data I can access is so limited.In case you don't already know :https://openweathermap.org/api/solar-panels-and-energy-predictionI will subscribe and try using that API on the next python update.The data logger here polls and updates panel controller every 15 seconds (related to digital ham radio) using linux "watch" .Logger writes to an SQL. which is saved and restarted every 24 h.My plan is to write a better predictor for 2025, then use polynominal regression on the accumulated SQL data to relate actual daily kW.h to the inputparameters eg forecast irradiance ,cloud, season.I have no UI yet, I just update the .py on the run and use "DB Browser for SQLite" as display.

author: IanJ
author_rank: Unknown
timestamp: September 12, 2024, 01:27:32 pm
content: Quote from: mag_therm on September 12, 2024, 01:18:13 pmNo, I never knew about this.....thanks.EDIT: £250 per location per month for that one.....:-(I have emailed them to ask if there is another plan I could use given I need 1 API call per day only.Ian.

author: mag_therm
author_rank: Frequent Contributor
timestamp: September 12, 2024, 01:57:11 pm
content: Oh...,That API is probably aimed at the grid level battery operators.This must be a hot topic, how to utilize the battery capacity without leaving them empty at the wrong time.

author: tszaboo
author_rank: Super Contributor
timestamp: September 12, 2024, 03:12:32 pm
content: Instead of ChatGPT values, you could start by inserting your system into this:https://globalsolaratlas.info/detail?c=51.896671,13.658811,6&s=51.996927,4.934442&m=site&pv=small,135,45,4This is an example, you have to enter your own place, azimuth and elevation.I would take the DNI values, and somehow multiply it with the weather prediction values.I'm also using openweathermap for a personal project. Because there was library for their API. But I'm trying to switch to a more open/non-profit service in the future.

author: Siwastaja
author_rank: Super Contributor
timestamp: September 12, 2024, 03:16:59 pm
content: Indeed, what the actual fuck:Code:' Figures below from ChatGPT' Overcast days: 200-500 W/m²' Cloudy days: 500-1000 W/m²' Cloudy/sunny days: 1000-2000 W/m²' Sunny winter days: 2000-3000 W/m²' Sunny summer days: 5000-7000 W/m²Use simple Google search instead of ChatGPT:"typical solar irradiance power""Average annual solar radiation arriving at the top of the Earth's atmosphere is roughly 1361 W/m2. The Sun's rays are attenuated as they pass through the atmosphere, leaving maximum normal surface irradiance at approximately 1000 W/m2 at sea level on a clear day."This 1000W/m² is very well known number. It is interesting how ChatGPT really makes up stuff which is easily googleable and almost never wrong anywhere and thus one wouldexcepthigh likelihood of ChatGPT getting it right, but it doesn't work that way so simply: never use these AI services for anything.

author: IanJ
author_rank: Unknown
timestamp: September 12, 2024, 04:39:19 pm
content: Quote from: mag_therm on September 12, 2024, 01:57:11 pmI got an email back from Openweathermap.org and they said that's the only pricing around the solar-panels-and-energy-prediction API.....sadly.Ian.

author: IanJ
author_rank: Unknown
timestamp: September 12, 2024, 04:43:12 pm
content: Quote from: Siwastaja on September 12, 2024, 03:16:59 pm

author: IanJ
author_rank: Unknown
timestamp: September 13, 2024, 12:28:09 pm
content: Hi all,The first full run based on the irridiance value my software had calculated decided batteries didn't need charged. It was marginal based on my rules mind you between that and 30 minutes only.Anyways, no overnight charging done and in the morning at 7am I had 20% left.....and not long before the sun started to come up again.Note: At 12% the batteries stop outputting.So far so good.....but it will be great to see some much lower irridiance values and test the charging it calculates. Tomorrow it's meant to be a cloudier day.My energy usage is very uniform, except may Mon/Tues where it is less.Ian.

author: mag_therm
author_rank: Frequent Contributor
timestamp: September 13, 2024, 01:20:07 pm
content: Good, If you can get a succession of days with variable sun it will allow adjustment your algorithm if necessary.Yesterday here was full sun all day which is unusual in Great Lakes region.Here is the SQL Data of Watt.h over 24 h. Control is presently by UV Index as a proxy for irradiance.https://app.box.com/s/zycwg8pg9dwzd8i2e749rmyc12j16gny

author: Electrodynamic
author_rank: Regular Contributor
timestamp: September 14, 2024, 05:13:44 am
content: IanJI like your app which is heavy on data. My setup is going in a slightly different direction with esp8622/esp32 mesh wifi, Pi4, Node-Red and Grafana.I see on your app your using cooler, fan, heater offsets. My home automation setup using central AC, whole house fan, condensing furnace is similar with a few tweaks you might find useful.Every day we have two widows of opportunity in the morning and evening to short cut the cooling cycles. So if a hot day is predicted my 400w whole house fan kicks in at 6am blowing 16C outside air inside to pre cool the house to around 18C. This is enough cooling to delay my 2.5 kW AC unit from coming on by about 3 to 4 hours. Then in the evening when the outside air temp starts dropping and matches the inside air temp the AC turns off around 2 to 3 hours early and the whole house fan kicks in again. The fan blows cooler outside air through the house and into the attic space reducing radiant heat buildup. So by predicting the load I reduced the AC run time by around 6 hours which could amount to 12 or more kWh's.The smart grid seems to be moving in a similar direction and we don't need more generation if we can predict and control what we already have in a more intelligent way.I think your ahead of the curve with your 4kW solar & 10kWh battery system. Intelligent automation, prediction and off peak storage could be magnitudes cheaper than generation in the right context. Even more so if the utilities and governments start playing games like the solar energy crisis in Spain.

author: IanJ
author_rank: Unknown
timestamp: September 14, 2024, 10:10:28 am
content: Quote from: Electrodynamic on September 14, 2024, 05:13:44 amLooks like you are well under control with your system!........and giving me more ideas!Whole house fans, house aircons are not a thing here, it's generally too cold. However, I need an aircon in my small workshop to keep the temps under control given the server and test gear located in there......and I need to control temps to within 0.5degC if I am calibrating voltage sources. The reason I control the aircon via my app is down to the Daikin aircon having a 4degC hysteresis using it's own control mechanism! I was just lucky to find it could be controlled/overridden via http after I fitted a wifi module to it.I used to run an esp8266/Oled display for grabbing the SolarPV and Utility meter power stuff, but changed to an app running on my server PC a couple years ago.My server isn't the fastest thing on the planet so am always aware that I don't push my app or I/O too hard.......but despite 4 virtual serial ports (3 via Modbus), 2 local http connections, 3 external web http connections, 1 local html webpage generator (so I can monitor from anywhere) and the GUI stuff...............it's holding its own!Ian.

author: EEVblog
author_rank: Unknown
timestamp: September 14, 2024, 10:34:46 am
content: I've always thought this sort of automation would be cool, but I suspect it's too much trouble, at least for my solution.e.g. If the battery if full and the sun is still out max then I'd love to be able to automatically switch the pool heater on max and a couple of aircons to energy sink the house instead of feeding back inot the grid.Even though we have just got out of winter, I already have more than enough solar than I know what to do with.

author: IanJ
author_rank: Unknown
timestamp: September 14, 2024, 10:38:22 am
content: Next set of mods to make next to my app.......hopefully!1.If battery is depleted enough it won't cover 4pm-7pm then boost charge it in advance (3pm-4pm maybe) from the grid. This is because during those hours the electricity cost from my provider goes up significantly, so it'll pay to boost charge in advance.......DONE.2. Work out if there is any surplus battery between 4pm-7pm and high irridiance the next day because I get more than I pay for electricity (2am-5am) if I export it back to the grid during 4pm-7pm.Ian.

author: IanJ
author_rank: Unknown
timestamp: September 14, 2024, 10:39:46 am
content: Quote from: EEVblog on September 14, 2024, 10:34:46 amYes, it is a LOT of work!.....I only implemented it because I really enjoy doing it. A true glutten for punishment!Ian.

author: Siwastaja
author_rank: Super Contributor
timestamp: September 14, 2024, 11:07:25 am
content: Quote from: EEVblog on September 14, 2024, 10:34:46 amThat one is quite easy. You just need a bidirectional metering (e.g. a MODBUS meter as used by the OP) and then accumulate some counter and once it exceeds some level of export, turn relays on, and turn them back off when the accumulator is getting close to zero again.If netting period of the contract is known, then zero the accumulator for each period. If netting period is unknown or very short or there is no net metering at all, then it's a good idea to choose loads such that they are not excessively large compared to PV production so that you don't pay for import accidentally.

author: IanJ
author_rank: Unknown
timestamp: September 15, 2024, 11:44:59 am
content: GUI mods including:Giving the Charts a 12hr/24hr selection option.Now displaying todays/tomorrows Irridiance.Battery Boost added with setpoint.If battery is depleted enough it won't cover 4pm-7pm then boost charge it in advance (3pm-4pm) from the grid. This is because during those hours the electricity cost from my provider goes up significantly, so it'll pay to boost charge in advance.I thinks that enough mods for a while, time to just let it run 24/7 and monitor the results.Ian.

author: EEVblog
author_rank: Unknown
timestamp: September 16, 2024, 02:25:25 am
content: Quote from: IanJ on September 14, 2024, 10:39:46 amYeah, as much as I like this stuff, I don't really want to be constantly dicking around with it.So at the moment I just have a permanent timer that turns the pool heater on at 10am and off at 4pm regardless of the weather or battery condition. Works fine almost all the time.Aircon control would mean installing IR transmitters in every room, that's ugly, so once again, better off with just timers which I might look into to use some extra energy. At the moment we are exporting heaps of energy, and the battery isn't even close to 20% cutoff wince we upgraded it to 25kWh

author: IanJ
author_rank: Unknown
timestamp: September 19, 2024, 03:24:37 pm
content: Ok, I couldn't resist some more mods.I added a small log display to see the past 6 events. Scrolls upwards.Also added an X-axis scale to fit the types of graph charts I have created, i.e. the data scrolls in from the right and the charts are 12 or 24hr long depending on the checkbox above.I had to code the scale completely independent of the built in chart X-axis scaling because it doesn't support what I wanted. MS Chart kinda sucks!The times on the scale will scroll to the left with the data above it. As you can see I've been running it since just after 2pm.Still testing the Irridiance/Solis Inverter controlled battery charging time config each day.....but so far so good.Ian.

author: IanJ
author_rank: Unknown
timestamp: September 21, 2024, 09:48:59 am
content: Wee update,Irridiance on it's own looks like it is not enough from my testing over several different days, due to there are two irridiance values received from OpenWeatherMap.org, one for cloud based and another for clear based which has been difficult to pull into a useable figure that works for all types of weather.So, I have changed to a hybrid calculation based on irridiance and cloud cover.I'm pulling hourly irradiance and cloud cover data from the Meteosource API, adjusting the data pulled based on monthly sunrise/sunset as it varies through the year thus only considering the relevant hours for solar energy potential.Then, the hourly values are are summed to create totals, then hybrid calculating a figure from both Irr & cloud which I can then use for the daily forecast SolarPV.We'll see how this goes.......but it takes time to experience different weather days!PS. I did look for another API that gives actual SolarPV predictions, but all the ones I found are very expensive. If you know of one let me know.New sub in question for those interested.Ian.Code:Private Sub MeteosourceIrradiance()' Meteosource API configurationDim MeteosourceAPIKey As String = "###################################"Dim latitude As String = "########"Dim longitude As String = "########"Dim Timezone As String = "###############"Dim apiUrl As String = $"https://www.meteosource.com/api/v1/flexi/point?lat={latitude}&lon={longitude}&sections=hourly&timezone={Timezone}&language=en&units=auto&key={MeteosourceAPIKey}"' Ping retry settingsDim deviceAddress As String = "www.meteosource.com"Dim maxRetries As Integer = 3Dim retryDelaySeconds As Double = 0.2' Check if the checkbox is checkedIf CheckBox8.Checked = True Then' Call Function to check connectivityIf TryPingDevice(deviceAddress, maxRetries, retryDelaySeconds) Then' Ping was successfulTryDim jsonResponse As String = New WebClient().DownloadString(apiUrl)' Parse the JSON responseDim data As JObject = JObject.Parse(jsonResponse)Dim hourlyData As JArray = data("hourly")("data")' Initialize total variables for today's and tomorrow's irradiance and cloud coverDim totalTodayIrradiance As Double = 0Dim totalTodayCloudCover As Double = 0Dim todayHours As Integer = 0Dim totalTomorrowIrradiance As Double = 0Dim totalTomorrowCloudCover As Double = 0Dim tomorrowHours As Integer = 0' Define today and tomorrow's dateDim today As DateTime = DateTime.Now.DateDim tomorrow As DateTime = today.AddDays(1)' Define start and end hour variablesDim startHour As IntegerDim endHour As Integer' Get the current monthDim currentMonth As Integer = DateTime.Now.Month' Adjust based on the month (12 CASE statements for sunrise/sunset adjustment)Select Case currentMonthCase 1 ' JanuarystartHour = 10endHour = 15Case 2 ' FebruarystartHour = 9endHour = 16Case 3 ' MarchstartHour = 8endHour = 17Case 4 ' AprilstartHour = 7endHour = 19Case 5 ' MaystartHour = 6endHour = 20Case 6 ' JunestartHour = 6endHour = 21Case 7 ' JulystartHour = 6endHour = 21Case 8 ' AuguststartHour = 6endHour = 20Case 9 ' SeptemberstartHour = 7endHour = 18Case 10 ' OctoberstartHour = 8endHour = 17Case 11 ' NovemberstartHour = 9endHour = 15Case 12 ' DecemberstartHour = 10endHour = 15End Select' Clear the ListBox before adding new dataListBoxHourlyData.Items.Clear()ListBoxHourlyData.Items.Add("Forecast = " & DateTime.Now.AddDays(1).ToString("dd-MM-yyyy") & ":" & vbCrLf) ' Add the forecast date header' Loop through the hourly data and process each entryFor Each hourData As JObject In hourlyData' Get the date string from the "date" field and split by spaceDim hourDateRaw As String = CStr(hourData("date"))Dim dateAndTime As String() = hourDateRaw.Split(" "c) ' Split by space' Get the date and time separatelyDim datePart As String = dateAndTime(0) ' Left part (date)Dim timePart As String = dateAndTime(1) ' Right part (time)' Reformat the date partDim dateComponents As String() = datePart.Split("/"c) ' Split by "/"datePart = $"{dateComponents(2)}-{dateComponents(1)}-{dateComponents(0)}"' Create the full date and time stringDim fullDateTime As String = datePart & "T" & timePart' Convert fullDateTime to DateTimeDim hourDate As DateTimeTry' Use DateTime.ParseExact to specify the correct format of the fullDateTimehourDate = DateTime.ParseExact(fullDateTime, "yyyy-dd-MMTHH:mm:ss", System.Globalization.CultureInfo.InvariantCulture)' Check if the hour is within the valid daytime range' Both the Irradiance and the Cloud readings per hour are totalled to present a summed value each for the day.' The hours to consider are dependant on the sunrise to sunset times.Dim hour As Integer = hourDate.HourIf hour >= startHour And hour <= endHour Then' Check if the hour belongs to today or tomorrowIf hourDate.Date = tomorrow ThentotalTomorrowIrradiance += CDbl(hourData("irradiance")) ' sum the irradiancetotalTomorrowCloudCover += CDbl(hourData("cloud_cover")("total")) ' sum the cloud covertomorrowHours += 1' In the 2nd listbox display the hourly Irradiance and Cloud Cover valuesDim timeFormatted As String = $"{hourDate:HH:mm}" ' Format timeDim irradianceFormatted As String = $"{CDbl(hourData("irradiance"))} W/m²".PadRight(15) ' Ensure consistent widthDim cloudCoverFormatted As String = $"{CDbl(hourData("cloud_cover")("total"))} %".PadRight(10) ' Ensure consistent widthDim outputString As String = timeFormatted & vbTab & irradianceFormatted & vbTab & cloudCoverFormattedListBoxHourlyData.Items.Add(outputString) ' Add the formatted string to the ListBoxEnd IfEnd IfCatch ex As FormatException' If date parsing fails, print a debug message'Debug.Print($"Failed to parse date: {fullDateTime} - {ex.Message}")End TryNext' Save contents of ListBoxHourlyData' Create a StringCollection to hold the ListBox itemsDim itemsToSave As New Specialized.StringCollection()' Add each item in the ListBox to the StringCollectionFor Each item As String In ListBoxHourlyData.ItemsitemsToSave.Add(item)Next' Save the StringCollection to My.SettingsMy.Settings.ListBoxItems = itemsToSaveMy.Settings.Save()' Calculate averages for tomorrow - not usedDim avgTomorrowIrradiance As Double = If(tomorrowHours > 0, totalTomorrowIrradiance / tomorrowHours, 0)Dim avgTomorrowCloudCover As Double = If(tomorrowHours > 0, totalTomorrowCloudCover / tomorrowHours, 0)' Hybrid calculation for tomorrow's irradiance/cloud value' The hybrid Irr/Cloud value is the total Irradiance value modified my the total cloud cover value.' No clouds all day then the total Irradiance value all all but unchanged.' Full clouding all day then the value is brought way, way down.' The final value goes off to be compared through the ruleset in the Solis dub.HybridTomorrowIrradianceCloud = totalTomorrowIrradiance * (1 - (totalTomorrowCloudCover / ((endHour - startHour + 1) * 100))) ' Value that will be used to calculate on/off charging times for Solis Sub' Log the forecast data to a text fileDim logData As String = $"{DateTime.Now.AddDays(1):dd-MM-yyyy HH:mm}" & vbCrLf &$" Forecasted Total Irradiance: {totalTomorrowIrradiance} W/m²" & vbCrLf &$" Forecasted Total Cloud Cover: {totalTomorrowCloudCover} %" & vbCrLf &$" Hybrid Irr/Cloud Value: {Math.Floor(HybridTomorrowIrradianceCloud).ToString()}" & vbCrLf &$" Current Battery Level: {BatteryCapacity.Text} %" & vbCrLf &"------------------------------------------------------------" & vbCrLfDim logFileName As String = $"ForecastLog.txt"Dim logFilePath As String = System.IO.Path.Combine(Application.StartupPath, logFileName) ' Change to your desired log directory' Append log data to the fileSystem.IO.File.AppendAllText(logFilePath, logData)' Update GUIHybridIrrCloud.Text = CInt(HybridTomorrowIrradianceCloud).ToString()IrridianceTomorrow.Text = CInt(totalTomorrowIrradiance).ToString() ' Update UI element with the final valueCloudTomorrow.Text = CInt(totalTomorrowCloudCover).ToString() ' Update UI element with the final valueIrradianceCloudForecastDate.Text = DateTime.Now.AddDays(1).ToString("dd-MM-yyyy")' Hybrid value for GUI'HybridIrrCloud.Text = CInt(HybridTomorrowIrradianceCloud).ToString() ' Update UI element with the final valueMy.Settings.data40 = HybridIrrCloud.TextMy.Settings.data41 = IrridianceTomorrow.TextMy.Settings.data43 = CloudTomorrow.TextMy.Settings.data44 = DateTime.Now.AddDays(1).ToString("dd-MM-yyyy")My.Settings.Save()Catch ex As Exception' Handle any exceptions during data retrievalDim currentDateAndTime As DateTime = DateTime.NowDim formattedDateTime As String = currentDateAndTime.ToString("dd-MM-yyyy HH:mm", CultureInfo.InvariantCulture)AddToLogBox(formattedDateTime & " " & "Error fetching data from Meteosource API: " & ex.Message, Color.Red)IsOkIrridiance = FalseLEDirridiance.State = OnOffLed.LedState.OffSmall ' fail RED LEDExit SubEnd TryElse' Ping failed, handle the errorDim currentDateAndTime As DateTime = DateTime.NowDim formattedDateTime As String = currentDateAndTime.ToString("dd-MM-yyyy HH:mm", CultureInfo.InvariantCulture)AddToLogBox(formattedDateTime & " " & "Error: Unable to reach Meteosource API.", Color.Red)IsOkIrridiance = FalseLEDirridiance.State = OnOffLed.LedState.OffSmall ' fail RED LEDExit SubEnd IfEnd IfEnd Sub

author: IanJ
author_rank: Unknown
timestamp: September 27, 2024, 04:42:31 pm
content: With the updated code in my previous post.....here's a couple of screenshots below.So at just before midnight yesterday the code calculated a hybrid Irradiance/Cloud value of 1503 and thus per the ruleset it required 1hr of charge between 2am and 3am.The battery charge for that 1hr brought it up to just under 80%.3am onwards the battery discharged into the house until about 9am when the sun started to appear.On and off the battery charged & discharged with the SolarPV (cloudy day) and reached a peak of 100% just and no more as you can see on the graph before the SolarPV dropped low.The fact it reached a peak of 100% just and no more is the win for me.So far so good!Ian.

author: Siwastaja
author_rank: Super Contributor
timestamp: September 27, 2024, 05:48:17 pm
content: Do you have some kind of hourly spot pricing? If not and cost of import was the same for those night hours then surely one more optimization would be to avoid cycling the battery and just buy energy from grid, both for better battery lifetime and because there are some losses (maybe 4-5% loss back-and-forth assuming 98% efficiency one way). I mean like in the attachment; when price stays constant over some fixed period, PV production is zero during that period and desired start and end SoC are the same, then just turn off the battery to zero power setting instead of discharge-charge-discharge.But this is of course more complication to the algorithm. Interesting stuff, thanks for sharing.

author: IanJ
author_rank: Unknown
timestamp: September 27, 2024, 07:35:00 pm
content: Quote from: Siwastaja on September 27, 2024, 05:48:17 pmThe battery voltage wouldn't stay horizontal on the graph like you marked up. It would continue downwards if I never charged it.The idea is to try an build up charge to last the next day. Forecasting helps, but you still don't know for sure, and worst case scenario the battery runs out of charge before or during the 4pm to 7pm timeslot where the costs to import is almost double cost on the Octopus tariff.With a 10kWh battery it's not a small capacity battery, but it's also not big.......so managing it is quite a lot of things to balance.Ian.

author: Siwastaja
author_rank: Super Contributor
timestamp: September 28, 2024, 06:13:43 am
content: Quote from: IanJ on September 27, 2024, 07:35:00 pmI know, surprisingly large quiescent currents on these inverters (and maybe even batteries with their BMS and contactors) is a real issue with these batteries, it's a significant consumption averaging possibly tens of W and needs to be supplied from somewhere. You need some maintenance charging every few days.But in this case as I understood it your battery was feeding your house loads, then charged for an hour at relatively high power, then kept feeding on house loads. This definitely does not make any sense unless there is price difference between those hours. Yes, you can't avoid supplying the quiescent current of inverter+battery so you need to charge a little, but now you areadditionallyhaving efficiency losses at around 4-5% plus extra wear from unnecessary cycling.And I'm well aware these battery systems do such insane cycling even on their own (just yesterday evidenced a Growatt hybrid doing that, cycling around 10% SoC charging from grid and discharging to loads).

author: IanJ
author_rank: Unknown
timestamp: September 28, 2024, 11:37:27 am
content: Yes there is a difference between the hours......thats what quite a bit of this is all about.The benefit of charging the battery between cheap rate (half-price kWh) 2am - 5am in order to try and avoid losing all battery capacity the next day, even if that was at 10pm far outweigh taking a chance and not charging and ending up importing from the grid at the standard rate.If I knew what the house load would be in advance then yes, more calcs could be done to see if the current capacity of the battery at 23:50 would be enough to last the next day could be done without charging at 2am - 5am.In addition, I have to make sure I avoid at all costs importing from the grid between 4pm and 7pm where the rate is double.Battery maintenance, I'll let the Solis inverter take care of that and at the moment I do not have an eye on trying to maintain battery longevity at all.......at least not till I have proved my model works, and then I can do some more work on the code.Quite specific to my needs, and certainly not a 'fits all' method.Thanks for the hints, tips and dialogue, it has certainly helped me understand possible issues with my own model and has added things to think about for the future.Ian.

author: max_torque
author_rank: Super Contributor
timestamp: September 30, 2024, 06:23:19 pm
content: Interesting thread! I've been doing "automatic" solar forecasting in a basic way for about 6 years now with my home(brew) battery (ex-nissan leaf battery ). The method i use is really simple, i just let my control app crawl a number of free weather sites, parse in all the text off those sites and i simply count the number of times the folllowing words pop up "sun", "sunny", "cloud" "cloudy" "rain" "rainy" etc, and then that uses some basic weighting to form an integer scrore for tomorrow and the week ahead! Works suprisingly well, although with around 25 kWh of battery i can let my SoC varry a fair bit an still have space for solar charging and not worry about having to pull peak rate 'lecy off the grid !For example, you can pull the following summary from the BBC Weather page:"Latest forecast for UKLast updated 17 minutes agoTonightTonight, central and eastern areas of England and Wales will see cloud and heavy rain linger for a time. By dawn, the rain will be confined to East Anglia. Drier with clear spells elsewhere. Windy.TuesdayEastern England will see cloud and patchy rain linger for much of the day tomorrow. Strong winds at times, mainly in coasts. Further west and north, overall drier and sunnier, especially later on.Outlook for Wednesday to FridayOn Wednesday, far south-eastern England will see a few showers linger through the day, these persistent in the coasts where it will remain windy. Elsewhere, a mix of cloud and sunny spells, these widespread in the afternoon. Dry and settled for all on Thursday. On Friday, Scotland will be cloudy with rain in the north-west. Dry and sunnier elsewhere."The mentions of "cloud" and "rain" are clearly dominant, typical UK really

author: IanJ
author_rank: Unknown
timestamp: September 30, 2024, 09:00:15 pm
content: That’s how I thought I might have to do it till I realized hourly irradiance and cloud cover data could be pulled in.Still tweaking the gain on my formula but so far it’s been surprising how accurate it’s been.Ian

author: nali
author_rank: Frequent Contributor
timestamp: June 06, 2025, 04:36:41 pm
content: Quote from: IanJ on September 30, 2024, 09:00:15 pmApologies for the necropost... I'm considering doing something for managing power usage having just had a PV/battery system installed and waiting for my export account to be set up.I found this onehttps://docs.solcast.com.au/#155071c9-3457-47ea-a689-88fa894b0f51who have a free hobbyist / home user account which lets you set up 2 PV arrays. No affiliation, just thought it looks pretty much what I want and may be useful to others.