Solis battery inverter and Octopus Agile setup

I have a Solis SOL-3.0K-RAI-48ES-5G-AC-V2 battery inverter and want to control it based on live Agile pricing based on each half hour slot, to charge on cheap rate, then reserve the charge until its well worth using.
If anyone has got this fully working I’d be interested to know how.

But for reference here is where I’m at:
First of all I bought myself a DLS-LAN stick, this turned out to be a waste of time and money as it doesn’t appear to be compatible with the above battery inverter and the TCP to RS485 modbus bridge appeared none functional.
Therefore I bought this TCP to RS485 Bridge:
Waveshare - Industrial Serial Server - RS485 to RJ45 Ethernet
UK shopping link:
https://thepihut.com/products/industrial-serial-server-rs485-to-rj45-ethernet?variant=41374103666883&currency=GBP&utm_medium=product_sync&utm_source=google&utm_content=sag_organic&utm_campaign=sag_organic&gclid=CjwKCAiAleOeBhBdEiwAfgmXf-NPLph_Ursv_8xT6P7aMt8IpRxhXuxhXDVqI14lHbs4fbq5tk_5SBoCBZYQAvD_BwE

This was straight forward to setup via its own webserver page.
In home assistant setup the Modbus connection in config.yaml as follows:

modbus:
  - name: "solis"
    type: tcp
    host: 192.168.0.200
    port: 502
    sensors:
      - name: solisinvertertemperature
        slave: 1
        address: 33093
        input_type: input
        unit_of_measurement: "°C"
        scan_interval: 60
        data_type: uint16
        count: 1
        scale: 0.1
        
      - name: solisbattarysoc
        slave: 1
        address: 33139
        input_type: input
        unit_of_measurement: "%"
        scan_interval: 60
        data_type: uint16
        count: 1

      - name: solisbatterycurrent
        slave: 1
        address: 33134
        input_type: input
        unit_of_measurement: A
        scan_interval: 60
        count: 1
        precision: 1
        data_type: uint16
        scale: 0.1

Make sure the host and port match the bridge.
Your instance of home assistant will need rebooting to to fire up the TCP modbus.
You will then be able to keep and eye on the battery inverter status.

Install the Octopus addon through HACS which grabs data via their API, then create an instance through integrations, the setup should link you to the Octopus API page to generate and enable the API to your account.

Now you should have access to all the information needed to get started with the automations.

TBC.

Feel free to add more Modbus registers. I found adding the battery direction was useful:

      - name: solisbatterydirection
        slave: 1
        address: 33135
        input_type: input
        scan_interval: 60
        count: 1
        data_type: uint16

Then create a template in your sensors section of yaml:

  - platform: template
    sensors:
      solis_battery_charging:
        value_template: >-
          {% set t = (states('sensor.solisbatterydirection') | float(1)) %}
          {% if t == 1 %}
            {{ (states('sensor.solisbatterycurrent') |  float * -1.0 ) }}
          {% else %}
            {{ (states('sensor.solisbatterycurrent') | float * 1.0 ) }}
          {% endif %}
        friendly_name: "Signed Battery Current"
        unit_of_measurement: 'Amps'

The above should give you a signed value for battery current where + is charging and - is discharging.
Now we need some automation:
I created a template to determine what mode I would like the inverter in, as below:

  - platform: template
    sensors:
      agile_battery_control:
        value_template: >-
          {% if float(states('sensor.octopus_energy_electricity_z12ab12345_1234567890123_current_rate')) >= 0.33 %}
            Discharge
          {% elif 0.165 < float(states('sensor.octopus_energy_electricity_z12ab12345_1234567890123_current_rate')) < 0.33  %}
            Reserve
          {% else %}
            Charge
          {% endif %}
        friendly_name: "Agile Battery Control"

Change the meter number to your own!
This gives me 3 modes:
1)“Charge” when the price drops below 16.5p
2)“Reserve” the battery charge while price is between 16.5p and below 33p
3)“Discharge” when the price is above 33p
Change the prices to suit your own application.

Now I added some automation’s based on the above 3 modes of operation I would like, here is what I have BUT IT DOES NOT WORK AS ID REALLY LIKE, I’m hoping someone has achieved what I’d like and tell me how they have done it, hence this post!

- alias: Agile Charge
  trigger:
  - platform: state
    entity_id: sensor.agile_battery_control
    from:
      - "Reserve"
      - "Discharge"
    to: "Charge"
  action:
    - service: modbus.write_register
      data:
        hub: solis
        unit: 1
        address: 43143
        value:
          - 0
          - 0
          - 23
          - 59
          - 0
          - 0
          - 0
          - 0
    - delay: 
        seconds: 2
    - service: modbus.write_register
      data:
        hub: solis
        unit: 1
        address: 43141
        value: 190
    - delay: 
        seconds: 2
    - service: modbus.write_register
      data:
        hub: solis
        unit: 1
        address: 43110
        value: 3

- alias: Agile Reserve
  trigger:
  - platform: state
    entity_id: sensor.agile_battery_control
    from:
      - "Charge"
      - "Discharge"
    to: "Reserve"
  action:
    - service: modbus.write_register
      data:
        hub: solis
        unit: 1
        address: 43143
        value:
          - 0
          - 0
          - 0
          - 0
          - 0
          - 0
          - 23
          - 59
    - delay: 
        seconds: 2
    - service: modbus.write_register
      data:
        hub: solis
        unit: 1
        address: 43142
        value: 0
    - delay: 
        seconds: 2
    - service: modbus.write_register
      data:
        hub: solis
        unit: 1
        address: 43110
        value: 3

- alias: Agile Discharge
  trigger:
  - platform: state
    entity_id: sensor.agile_battery_control
    from:
      - "Reserve"
      - "Charge"
    to: "Discharge"
  action:
    - service: modbus.write_register
      data:
        hub: solis
        unit: 1
        address: 43143
        value:
          - 0
          - 0
          - 0
          - 0
          - 0
          - 0
          - 0
          - 0
    - delay: 
        seconds: 2
    - service: modbus.write_register
      data:
        hub: solis
        unit: 1
        address: 43142
        value: 20
    - delay: 
        seconds: 2
    - service: modbus.write_register
      data:
        hub: solis
        unit: 1
        address: 43110
        value: 1

To set in charge mode my intention was to use timed mode to force a charge, I.e. set in timed charge mode all day.
To set in Reserve mode my intention was to use timed mode to force a discharge but at 0A, I.e. set in timed charge mode all day.
And in discharge mode simply revert back to normal “self use” mode.

I have had several problems with this though, through earlier testing I found the inverter only seems to react to events made in plenty of advance.
Over the last few nights we’ve had some cheap slots and the automation has charged and reserved as required. BUT I have 2 rather large issues, in the way I have setup reserve mode, the battery does not charge from excess solar and on the first daily occurrence of discharge I.e. when I set the inverter back to “self use” mode it will not respond to any further requests to run in timed use mode.

All Modbus registers are based on this document:
Battery Inverter Registers

So I’m currently looking for suggestions?

1 Like

I’ve got a little bit further with this, it seems toggling the timed charge/discharge flag does cause issues. If I leave it set and simply set all the times to zero, self use mode can be achieved and then I can revert back to timed charging.:grin: All I need to do now is find a way to achieve some kind of battery reserve mode, I tried changing the Max discharge limit to 0 or a very low number but it seems the BMS value over rides it.:disappointed:

I use the Backup Mode. Works very well, particularly for Grid Demand Management periods.

Set inverter Over Discharge SOC to, say, 20%
Backup SOC to, say, 60%.

Backup mode - ON. Battery maintained at 60% leaving room for excess solar.
Backup mode -OFF. Battery has available charge to support the load.

Just toggle the Backup Mode bit in the energy-storage control register.

Thanks Geoff
I probably could get that control bit to achieve what I want, however I’m not sure its present on my inverter (not on the document I’ve been using anyway) All I have is these 4 bits:

Blockquote
BIT00 Spontaneous mode switch (see
user manual)
0—Off 1—On
BIT01 Optimized revenue mode
switch (see user manual, timed
charge/discharge)
0—Off 1—On
BIT02 Energy storage off-grid mode
switch (see user manual)
0—Off 1—On
BIT03 Battery wake-up switch (1 -
wake-up enable 0 - wake-up is
not enabled, see user manual)
0—Off 1—On

I don’t think any of them resemble Backup Mode. Unless you’ve got a more up to date document???

I am fortunate to have the hybrid model with the UI setting screen. My Energy Storage settings includes a backup mode. Setting each mode to on using the UI, one at a time, enabled me to work out which bit was which. The backup SOC setting was more challenging, but setting it to something unusual like 73% and block scanning all registers narrowed down a few candidates, and changing it on the UI confirmed the specific register involved.

I am aware that my model, being newer, has a number of departures from the register map of older models, so it is always worth checking everything very carefully. If your model does not have a backup facility, then it is unlikely that this mode is available in your firmware.

Hi, this looks amazing but a little over my head. I’m wondering if there’s an easier / software only method to do this now that the Solis app allows remote control? I’ve been using this to manually update my charge settings each day but would love to automate it based on the agile numbers.

Hi, did you managed to get this working? I have just set up my Solis inverter with Modbus and im scratching my head on the best way to achieve the 3 ‘states’ you have listed.

I Don’t believe Solis have got Agile integrated into their app yet so I’m afraid your stuck with HA automations.

Yes and No! I got some automation’s setup which worked fairly reliably but the Inverter did not respond on the odd occasion. I’ve got a Solis ticket open to try and resolve this and to be fair they have been helpful (seem we’ve found the fault, just need a solution). Once I have something reliable I’ll post how it was achieved.

Great, thanks!
What is the unreliable solution you are using, is it the one listed above?

Hi Chris
Yes it is. Although for reserve mode I now use charge timings with a very low current (reg:43141) as it seemed the most reliable.
I did add some automations to exit reserve mode on excess solar too.
After many months of trying I’ve had to keep intervening by toggling timed charge mode on the inverter itself. I found just changing times (reg:43143 - 43150) often meant long reaction delays from the inverter, up to several hours so not ideal for 30min slots. And just changing the timed charge flag (reg:43110, bit 1) works instantaneously (good for 30 min slots) but when it fails needs an inverter interaction.

Quick update:
For the UI side I added a value to set the agile rate at which to charge:

input_number:
  agile_cheap_rate:
    name: Agile Charge Price
    initial: 0.15
    min: -0.30
    max: 0.60
    step: 0.001
    mode: box

A template to automatically wouk out the discharge rate based on inverter efficiency and battery round trip.

  - platform: template
    sensors:
      battery_discharge_rate:
        value_template: >-
          {{ (states('input_number.agile_cheap_rate') |  float * 1.19 ) }}
        friendly_name: "Battery Discharge Rate"
        unit_of_measurement: '£/kWh'

And of course use these numbers in the control mode template:

  - platform: template
    sensors:
      agile_battery_control:
        value_template: >-
          {% if float(states('sensor.octopus_energy_electricity_z12qu12345_123456789_current_rate')) >= float(states('sensor.battery_discharge_rate')) %}
            Discharge
          {% elif float(states('input_number.agile_cheap_rate')) < float(states('sensor.octopus_energy_electricity_z12qu12345_123456789_current_rate')) < float(states('sensor.battery_discharge_rate'))  %}
            Reserve
          {% else %}
            Charge
          {% endif %}
        friendly_name: "Agile Battery Control"