incub77 / solis2mqtt

Modbus RTU / RS485 to MQTT bridge for Solis Mini solar inverter.
GNU General Public License v3.0
53 stars 12 forks source link

Writing Registry Values #7

Open NickSutton opened 1 year ago

NickSutton commented 1 year ago

Hi,

So I've got this pretty much reading most things I need but was wondering what modifications would be needed to write to the inverter? Ie Forced charge / discharge periods etc.

I've been following this HomeAssistant thread but not sure how this could work using this integration. I know no Python either...

Thanks for any assistance

Edit: Still trying to get this off the ground. Can anyone explain the purpose of this code, taken from solis2mqtt.py

def on_mqtt_message(self, client, userdata, msg):
        for el in self.register_cfg:
            if el['name'] == msg.topic.split('/')[-2]:
                register_cfg = el['modbus']
                break

        str_value = msg.payload.decode('utf-8')
        if 'number_of_decimals' in register_cfg and register_cfg['number_of_decimals'] > 0:
            value = float(str_value)
        else:
            value = int(str_value)
        with self.inverter_lock:
            try:
                self.inverter.write_register(register_cfg['register'],
                                             value,
                                             register_cfg['number_of_decimals'],
                                             register_cfg['write_function_code'],
                                             register_cfg['signed'])
            except (minimalmodbus.NoResponseError, minimalmodbus.InvalidResponseError):
                if not self.inverter_offline:
                    logging.exception(f"Error while writing message to inverter. Topic: '{msg.topic}, "
                                      f"Value: '{str_value}', Register: '{register_cfg['register']}'.")
NickSutton commented 1 year ago

I was able to make some progress with this, and it's actually fairly straight forward as the developer has already included the code to write a change to the inverter on receipt of a MQTT message.

So, for anyone else wanting to achieve the same these are the steps I put in place:

Subscribing to MQTT Topics Your remote Pi Zero W, or whatever you have attached to the inverter is busy sending information from the solis to Home Assistant via MQTT. Presently it is not listening for replies from HA. To do this we need to get the Pi Zero W (or whatever) to subscribe to a 'topic' so it can listen out for those communications.

This piece of code, already included in solis2mqtt.py does just that:

    def subscribe(self):
        for entry in self.register_cfg:
            if 'write_function_code' in entry['modbus']:
                if not self.mqtt.on_message:
                    self.mqtt.on_message = self.on_mqtt_message
                logging.info("Subscribing to: "+self.cfg['inverter']['name'] + "/" + entry['name'] + "/set")
                self.mqtt.persistent_subscribe(self.cfg['inverter']['name'] + "/" + entry['name'] + "/set")

If we break this down we can see that the code is checking each of the registry blocks (in solis_modbus.yaml) for a write_function_code entry. If such an entry exists then MQTT will subscribe to a topic named as:

"+self.cfg['inverter']['name'] + "/" + entry['name'] + "/set"

The above naming draws from the config.yaml to get the inverter name, typically solis2mqtt followed by the name of the register block that contains the write_function_code entry. So could be something like:

solis2mqtt/write_storage_control/set

Now, in solis_modbus.yaml add a block in the following format:

- name: write_storage_control
  active: true
  modbus:
    register: 43110
    number_of_decimals: 0
    write_function_code: 6
    input_type: holding
    signed: false

It would be handy if we could build a list of the writable register codes here, 43110 is used to change the storage control value (typically 33/35)

Restart the process on the remote Pi Zero W with sudo systemctl restart solis2mqtt to take in to account the new register block.

You should see in the log that the topic (in this case) solis2mqtt/write_storage_control/set has been subscribed to, which means the Pi Zero W is now listening.

From HA you can use dev tools > services to issue a MQTT communication in a designated topic:

Screenshot 2022-11-28 at 12 42 31

The above should set your storage control value to 35 (if it is 33 currently etc).

To change different settings on the inverter just add new register blocks (with the correct register value!!) then issue new values to that MQTT topic.

Disclaimer: I don't know what I'm talking about most of the time and figured all this out in the middle of the night but it seems to work, as does my inverter. That said, if you mess this up you might well brick your inverter and need a replacement etc. All of the above at your own risk, I accept no responsibility for anything, at all, ever.

NickSutton commented 1 year ago

43110 - Storage control, usually 33 / 35 43000 - Inverter date/time: Year 43001 - Inverter date/time: Month 43002 - Inverter date/time: Day 43003 - Inverter date/time: Hour 43004 - Inverter date/time: Minute

43141 is the fixed period charge current in deci-Amps (ie 500 -> 50A) for dynamically changing the charge current limit

43141 - Time Charge Current 43142 - Time Discharge Current 43143 - Time Charge Start Hour 43144 - Time Charge Start Minute 43145 - Time Charge End Hour 43146 - Time Charge End Minute

This link is very useful: https://www.boards.ie/discussion/2058224611/solar-pv-monitoring-automation-thread/p25?fbclid=IwAR2J6yGwUrC6bol7VZ3YeoKDHZnY2H0ybJRIUNlsszz5l51GBee8dowKDng

fboundy commented 1 year ago

43110 - is defined bit-wise as follows:

    Bit 0 (1)  - Spontaneous Mode      input_boolean.solis_storage_mode_spontaneous
    Bit 1 (2)  - Timed Mode            input_boolean.solis_storage_mode_timed
    Bit 2 (4)  - Off-Grid Mode         input_boolean.solis_storage_mode_off_grid
    Bit 3 (8)  - Battery Wake-Up Mode  input_boolean.solis_storage_mode_wake_up
    Bit 4 (16) - Backup Mode           input_boolean.solis_storage_mode_backup
    Bit 5 (32) - Grid Charge Mode      input_boolean.solis_storage_mode_grid_charge

So for timed charging you need both Bit 5 (32) and Bit 1 (2). Bit 0 seems to tell it to spontaneously follow the mode outside of timed periods (ie Self-Use). Hence 35 gives you timed charging (Bit 1 set) and 33 gives you normal Self-Use but with grid charging enabled.

NickSutton commented 1 year ago

Registers needed in solis2mqtt_modbus.yaml to write a charging window and current limit.

(This means the solis will charge for the full duration of the charging window, at the lowest possible current which should be kinder to batteries)

# Writable registry entries
# Battery charge Limit
- name: write_battery_charge_limit
  active: true
  modbus:
    register: 43141
    number_of_decimals: 0
    write_function_code: 6
    input_type: holding
    signed: false

# Write charging window
- name: write_charging_window_start_hours
  active: true
  modbus:
    register: 43143
    number_of_decimals: 0
    write_function_code: 6
    input_type: holding
    signed: false

- name: write_charging_window_start_mins
  active: true
  modbus:
    register: 43144
    number_of_decimals: 0
    write_function_code: 6
    input_type: holding
    signed: false

- name: write_charging_window_close_hours
  active: true
  modbus:
    register: 43145
    number_of_decimals: 0
    write_function_code: 6
    input_type: holding
    signed: false

- name: write_charging_window_close_mins
  active: true
  modbus:
    register: 43146
    number_of_decimals: 0
    write_function_code: 6
    input_type: holding
    signed: false
NickSutton commented 1 year ago

HA Script to write the charging window to the solis.

You will need to create the input_datetime helpers to set the start and end times

Settings > Devices & Services > Helpers > Create Helper > Date and/or Time > Time only

Then create the script below by pasting: Settings > Automations & Scenes > Scripts > Add New (View in YAML)

alias: Write Solis Charging Window
sequence:
  - service: mqtt.publish
    data:
      topic: solis2mqtt/write_charging_window_start_hours/set
      payload_template: "{{(states('input_datetime.charging_start_time')).split(':')[0] | int}}"
  - service: mqtt.publish
    data:
      topic: solis2mqtt/write_charging_window_start_mins/set
      payload_template: "{{(states('input_datetime.charging_start_time')).split(':')[1] | int}}"
  - service: mqtt.publish
    data:
      topic: solis2mqtt/write_charging_window_close_hours/set
      payload_template: "{{(states('input_datetime.charging_end_time')).split(':')[0] | int}}"
  - service: mqtt.publish
    data:
      topic: solis2mqtt/write_charging_window_close_mins/set
      payload_template: "{{(states('input_datetime.charging_end_time')).split(':')[1] | int}}"
mode: single
icon: mdi:clock-digital

Run that then check the charging window on the solis. Disclaimer from above post applies.

NickSutton commented 1 year ago

To set the charging window we need some new entities and a new helper to set the charge target

Settings > Devices & Services > Helpers > Create Helper > Number

Also make sure you are pulling the battery voltage from the solis:

- name: battery_voltage
  description: Battery Voltage
  unit: V
  active: true
  modbus:
    register: 33133
    read_type: register
    number_of_decimals: 1
    function_code: 4
    signed: false
  homeassistant:
    device: sensor
    state_class: measurement
    device_class: voltage

In config.yaml create the following entities:

template:
  - sensor:
    ####### SOLIS DYNAMIC CHARGING
    - name: 'Charging Time Remaining'
      icon: 'mdi:timelapse'
      state: >
            {% if today_at(states('input_datetime.charging_end_time')) < now() -%}
                {{(((today_at(states('input_datetime.charging_end_time')) -
                  today_at(states('input_datetime.charging_start_time'))))
                  .total_seconds() / 36) | int / 100 }}
            {%- else -%}
                {{ max((((today_at(states('input_datetime.charging_end_time')) - 
                    max(now(), today_at(states('input_datetime.charging_start_time'))))
                    .total_seconds() / 36) | int) / 100) }}
            {%- endif %}

    - name: 'Target Charge Limit'
      icon: 'mdi:sine-wave'
      unit_of_measurement: 'A'
      state: >
            {{ ((
                (states('input_number.charge_target')|int / 100) - 
                (states('sensor.battery_soc')|int / 100)
               ) * 
              10000 / 
              states('sensor.battery_voltage')|int /
              states('sensor.charging_time_remaining')|int
              ) | round(1)
            }}

Then create the following script

alias: Set Solis Charging Current
sequence:
  - service: mqtt.publish
    data:
      topic: solis2mqtt/write_battery_charge_limit/set
      payload_template: "{{ states('sensor.target_charge_limit')|int * 10 }}"
mode: single
icon: mdi:sine-wave
NickSutton commented 1 year ago

Lastly, here is the automation I've used to call the two scripts every 20 mins within the charging window.

alias: Solis Overnight Inverter Charge Rate
description: >-
  Set Solis Inverter overnight charge rate so that it lasts the duration of the
  off peak period
trigger:
  - platform: time_pattern
    minutes: "0"
  - platform: time_pattern
    minutes: "20"
  - platform: time_pattern
    minutes: "40"
  - platform: time
    at: input_datetime.charging_start_time
condition:
  - condition: time
    after: input_datetime.charging_start_time
    before: input_datetime.charging_end_time
action:
  - service: script.set_solis_charging_current
    data: {}
  - service: script.write_solis_charging_window
    data: {}
mode: single

Huge thanks to @fboundy, without whom I'd have never got started with this