Solaredge Modbus Configuration for Single Inverter and Battery

Sorry I meant how did you fix the Tesla power flow card to stop it doing whacky things for when you charge/discharge the battery to/from the grid. Mine appears to be doing the same.
It returns with incorrect figures when that happens, I think the problem has been around for a while…

I broke it down to first principles with basic flow expressions before I amended any of the code. For example, what should logically happen when the battery is charging from the grid versus when it’s charging from solar? Then I looked at the history graphs of what was happening with the flows I had and what the battery output, house consumption, imported power etc were doing and how I could use them to get an accurate flow. Then it was a matter of test and adjust, test and adjust multiple times using the template editor. It was during that process that I realised the biggest problem with doing it that way was that if you’re using live data for testing, that data might not be correct. So I would get everything working based on that “incorrect” live data, then wonder why it wouldn’t work when I plugged it into the live code. So I figured out how to generate test variables that mimicked what the data should be to ensure the code would work under all conditions in the live code.

The template sensors I now have are 99.9% accurate for all conditions. I’m not often awake when the battery is grid charging but I took this screenshot a few nights ago when I was also charging the EV:

And this one during a recent forced discharge of the battery:

All the details are in my GitHub repo, which is linked further up this thread.

1 Like

Thanks a lot for all of that work!
I have currently no meter on my solaredge, but I have a 1Pmeter with esphome (nodemcu) on the grid itself. So combining, I can have all the info…
BUT, I have an issue… due to the fact that even by setting the same interval to collect data, well it’s not in sync.
So sometimes I receive the Return to the grid before or after the solar panel power for example → result I can have negative house consumption peaks… or not accurate deductions.

So my question is the following, can we find a way to force to update the values perfectly in sync?

I think if I put the interval to 1 sec for esphome and the solaredge… well. I will not see the problem. But is it not very performance consuming?

I know it’s not specific to the Modbus, but can help for people with the config like me.

thanks in advance for the help!

It’s not possible to get perfect sync on updates across different components.

Even at 1 second you’re actually saying “start another polling cycle 1 second after the current cycle finishes” which will be 1 + X seconds that it takes to run which can vary depending on how busy the system is and the response time of the inverter itself, so even at 1 second there will be drift which will still show up in your graphs.

Indeed… and will at the end misalign the real consumptions! Coz I have peaks positive when solar Energie rises faster than the export… and peaks negative when solar Energie drops before the export… To decrease that difference, maybe taking average between 2 values everytime!?.. but even, will not be aligned…
Hum…
I could get rid of the drops, by sending the previous value when the export is bigger that the solar produce.
But the other one rising that just follow after… complicate to level it.

Thanks for the great work.
However, I am stuck. When I parse the Dashboard config, I get this error:

No idea how to approch this or what might be missing. All lovelace cards from post #1 and all steps from the repo were followed step by step…

Feel free to raise a PR against my repo i’ll happily take your fixes

Yes and No…

Yes i have forecast.solar. But current diff between offpeak/peak for me is 1p… so i just charge overnight regardless… I am adding an ‘away’ automation to let the battery drop to 50% and only charge to there then refill from solar but again more of a gamble than anything…

@ryanm101 I wasn’t allowing it long enough to switch over. @WillCodeForCats answer above showed the right function to use so once I did this, and waited a short while the system did as it should.

Great work all, thank you.

Hey guys, I picked up this section of errors in my HA logs last night and wonder if anyone can help me decipher it:

2023-07-23 23:38:57.422 ERROR (MainThread) [homeassistant.helpers.event] Error while dispatching event for sensor.solaredge_i1_dc_power to <Job track state_changed event {'sensor.solaredge_i1_ac_power', 'sensor.solaredge_i2_dc_power', 'sensor.solar_inverter_efficiency', 'sensor.solaredge_i2_ac_power', 'sensor.solaredge_i1_dc_power'} HassJobType.Callback <bound method TrackTemplateResultInfo._refresh of <TrackTemplateResultInfo {Template<template=({{ states('sensor.solaredge_i1_dc_power') | is_number and states('sensor.solaredge_i1_ac_power') | is_number }}) renders=6372>: <RenderInfo Template<template=({{ states('sensor.solaredge_i1_dc_power') | is_number and states('sensor.solaredge_i1_ac_power') | is_number }}) renders=6372> all_states=False all_states_lifecycle=False domains=frozenset() domains_lifecycle=frozenset() entities=frozenset({'sensor.solaredge_i1_ac_power', 'sensor.solaredge_i1_dc_power'}) rate_limit=None has_time=False exception=None is_static=False>, Template<template=({% set i1_dc_power = states('sensor.solaredge_i1_dc_power') | float(0) %} {% set i2_dc_power = states('sensor.solaredge_i2_dc_power') | float(0) %} {% set i1_ac_power = states('sensor.solaredge_i1_ac_power') | float(0) %} {% set i2_ac_power = states('sensor.solaredge_i2_ac_power') | float(0) %} {% set inverter_efficiency = states('sensor.solar_inverter_efficiency') %}
{% if (is_state('sensor.solar_inverter_efficiency', 'unknown')) %}
  1
{% elif ((i1_dc_power + i2_dc_power) <= 100 or (i1_ac_power + i2_ac_power)  <= 100) %}
  {{ inverter_efficiency }}
{% else %}
  {{ (i1_ac_power + i2_ac_power ) / (i1_dc_power + i2_dc_power) }}
{% endif %}) renders=12846>: <RenderInfo Template<template=({% set i1_dc_power = states('sensor.solaredge_i1_dc_power') | float(0) %} {% set i2_dc_power = states('sensor.solaredge_i2_dc_power') | float(0) %} {% set i1_ac_power = states('sensor.solaredge_i1_ac_power') | float(0) %} {% set i2_ac_power = states('sensor.solaredge_i2_ac_power') | float(0) %} {% set inverter_efficiency = states('sensor.solar_inverter_efficiency') %}
{% if (is_state('sensor.solar_inverter_efficiency', 'unknown')) %}
  1
{% elif ((i1_dc_power + i2_dc_power) <= 100 or (i1_ac_power + i2_ac_power)  <= 100) %}
  {{ inverter_efficiency }}
{% else %}
  {{ (i1_ac_power + i2_ac_power ) / (i1_dc_power + i2_dc_power) }}
{% endif %}) renders=12846> all_states=False all_states_lifecycle=False domains=frozenset() domains_lifecycle=frozenset() entities=frozenset({'sensor.solaredge_i1_ac_power', 'sensor.solaredge_i2_dc_power', 'sensor.solar_inverter_efficiency', 'sensor.solaredge_i2_ac_power', 'sensor.solaredge_i1_dc_power'}) rate_limit=None has_time=False exception=None is_static=False>, Template<template=(mdi:percent-outline) renders=6424>: <RenderInfo Template<template=(mdi:percent-outline) renders=6424> all_states=False all_states_lifecycle=False domains=frozenset() domains_lifecycle=frozenset() entities=frozenset() rate_limit=None has_time=False exception=None is_static=True>}>>>
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/sensor/__init__.py", line 592, in state
    numerical_value = float(value)  # type:ignore[arg-type]
                      ^^^^^^^^^^^^
ValueError: could not convert string to float: 'unavailable'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/event.py", line 274, in _async_dispatch_entity_id_event
    hass.async_run_hass_job(job, event)
  File "/usr/src/homeassistant/homeassistant/core.py", line 619, in async_run_hass_job
    hassjob.target(*args)
  File "/usr/src/homeassistant/homeassistant/helpers/event.py", line 1156, in _refresh
    self.hass.async_run_hass_job(self._job, event, updates)
  File "/usr/src/homeassistant/homeassistant/core.py", line 619, in async_run_hass_job
    hassjob.target(*args)
  File "/usr/src/homeassistant/homeassistant/helpers/template_entity.py", line 362, in _handle_results
    self.async_write_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 742, in async_write_ha_state
    self._async_write_ha_state()
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 810, in _async_write_ha_state
    state = self._stringify_state(available)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 748, in _stringify_state
    if (state := self.state) is None:
                 ^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/components/sensor/__init__.py", line 594, in state
    raise ValueError(
ValueError: Sensor sensor.solar_inverter_efficiency has device class 'None', state class 'None' unit '%' and suggested precision 'None' thus indicating it has a numeric value; however, it has the non-numeric value: 'unavailable' (<class 'str'>)

I’m happy to work my way through it eventually. Just wondering if anyone having a quick glance through can spot anything obvious, thanks.

Hi All,

Sorry if I’m late to the party on this one, or missing something glaringly obvious - but I had my SolarEdge battery installed on Friday and I’ve just managed to get access to the modbus, so I can see the charging modes.

I’m trying to rig up a NodeRed flow, that will charge the battery on demand if tomorrows electric rate is higher than todays - as I know in advance what my variable rates are going to be.

I’ve got access to the drop down showing me the charging modes - but struggling to see which one would actually start the charge? I’ve had a look down this thread, but couldn’t spot anything.

Screenshot 2023-07-24 144622

Thanks
Leacho

1 Like

Looking through @ryanm101 's repo, it appears to accomplish most battery control tasks (though not discharging to grid?) by setting the storage_default_mode, rather than the storage control (command?) mode. As I understand it, you need to set the storage control mode to ‘Remote’, and then select a default mode.

See: https://www.photovoltaikforum.com/core/attachment/88445-power-control-open-protocol-for-solaredge-inverters-pdf/ (specifically Appendix B).

The Storage Control Mode should be set to Remote Control and the Storage Command Mode should be set to Charge from Solar Power and Grid. Just watch the value of the Storage Command Timeout. It defaults to 3600s or 1 hour, so whatever you set it to, the Storage Command Mode will revert to whatever the Storage Default Mode is after 1 hour, unless you first change the Storage Command Timeout to a higher value before changing the Storage Command Mode.

I manage to discharge our battery to the grid no problem. Its not this repo that gives you control, its the HACS integration by @WillCodeForCats that does that by opening up access to your inverter’s Storage Control settings via modbus into HA. This repo provides data for tracking, using and displaying data in dashboards and automations etc. To discharge your battery to the grid, all you need do is set the Storage Command Mode to Discharge to Maximize Export.

Also, technically, the Storage Default Mode should be left at whatever mode you most often use, which for me is MSC. I then use the Storage Command Timeout to set a time period for whatever Storage Command Mode I change to. That way I don’t have to mess about setting timers to change it back after however long. Once the Storage Command Timeout runs down, it automatically reverts back to the Storage Default Mode.

Absolutely spot on - Thanks @P6Dave - just what I needed!!

GitHub - ryanm101/hasolarcfg: Solar Package for Home assistant using Solaredge Modbus multi integration has the automation you’re looking for.

Interestingly - I’ve noticed tonight that the timeout had no effect - as a test I set the battery to charge from grid and after 2 hours it’s still going - not sure if that is a solaredge thing or something I’m doing - but thought I would mention it :slight_smile:

The first time I tried using the timeout I couldn’t get it to work either. I got quite frustrated and gave up. Then, quite by chance, I was discharging the battery to the grid and changed the start time for the discharge to create a longer period of discharge (I use input numbers for those sort of settings as it makes changing them much easier from the UI). Anyway, it stopped discharging early and the only reason I could figure out was that I’d left the timeout at the shorter period and it had run down. Now that I’ve figured it out, it works every time.

Firstly, make sure you have everything set correctly before you start.

So check that you have:

Storage Control Mode to Remote Control
Storage Default Mode to Maximise Self Consumption
Storage Command Mode to Maximize Self Consumption

Then check the value of the Storage Command Timeout, it should be 3600 as this is the default value and no matter what you set it to (max being 86400s or 24 hours), after 24 hours it will reset to 3600. I try to ensure it is there the majority of the time.

Just bear in mind that if you change either the Storage Default Mode or the Storage Command Mode before changing the Storage Command Timeout the inverter will lock that setting in for whatever value the Storage Command Timeout was set to before you changed them. This is really important and is probably what confuses most people trying to use it. Changing a setting again while the Timeout is already counting down makes your inverter do weird things at weird times. Like changing a setting when you didn’t expect it to, or not changing a setting when you expected it to, such as in your case. So it’s worthwhile checking the value of the Timeout before starting and if it’s not 3600 then it must still be counting down from whatever setting it was previously at. In that case, check it every hour until it is 3600 then start again.

Once everything is set as above, this is my script for charging the battery from the grid:

This is the yaml:

alias: Grid Charge Battery
sequence:
  - service: number.set_value
    data:
      value: >-
        {{ states('input_number.storage_command_timeout_for_discharge_to_grid')
        | float(0) }}
    target:
      entity_id: number.solaredge_i1_storage_command_timeout
    alias: Set Storage Command Timeout to Input Helper value
  - device_id: 952193e87f675ba7f7e32eda5500c572
    domain: select
    entity_id: select.solaredge_i1_storage_command_mode
    type: select_option
    option: Charge from Solar Power and Grid
    alias: Set Storage Command Mode to Charge from Solar Power and Grid
  - delay:
      hours: 0
      minutes: 0
      seconds: 10
      milliseconds: 0
  - service: number.set_value
    data:
      value: >-
        {{ states('input_number.storage_command_timeout_default_period') |
        float(0) }}
    target:
      entity_id: number.solaredge_i1_storage_command_timeout
    alias: Set Storage Command Timeout to Default via helper
mode: single
icon: mdi:battery-charging

For my use case, the input_number.storage_command_timeout_for_discharge_to_grid helper is set to 10800 or 3 hours, which is the length of my Peak Export and Cheap Import periods so I use the same helper when charging and discharging.

The input_number.storage_command_timeout_default_period helper is always set to 3600
This script is called from within my main grid charge automation, but should work as a standalone you can call simply with a time based trigger.

Have another go and see how you get on.

I should probably write a community guide on this. It’s all documented in the Solaredge Technical Note - Power Control Protocol for Solaredge Inverters, which is somewhere online, but I can’t seem to find it just now. SE are very good at hiding important documents that they don’t want us as customers to see. I have a pdf of it here if you want to see it.

1 Like

@P6Dave this is an excellent writeup - thank you so much for that information - interesting re: the Timeout - I’ll make sure that it’s set either way.

I use Node Red for my automations, so I’ll make a flow that follows your logic and then I’ll upload it here for others to use.

Thanks again
Leacho

1 Like

I have an annoying bug with two sensors. I have a fresh install of HA, so no leftovers from any old installations.

sensor.solar_imported_power_daily and
sensor.solar_imported_power_weekly

keep resetting to a unitless sensor. No problem with monthly and yearly sensors, though.
I can go to the states menu in developer options and set “unit_of_measurement” to kWh manually, that works, but after a few minutes it will go back to unitless.

I have also edited core.restore_state in .storage and set “native_unit_of_measurement” to kWh (it said “null” before), but no matter, this will also reset to null.

The source sensor (sensor.solar_imported_power_kwh) is correctly measuring in kWh, also.
Any idea WHY this happened in first instance and, more importand, how to permanently fix this?

@P6Dave please can you share also the yaml of your beautiful dashboard? if i understand correctly you also have history in the dashboard right?

Thanks!