Hoymiles MS-A2 Battery and MQTT

Hello all, I want to share what I learned when I set up my the Hoymiles MS-A2 with the MQTT capabilities.

You need to upgrade the MS-A2 to FW V01.05.xx (release 06.06.2025) or later. App needs to be V2.1 or later.

Enable MQTT: App/Settings/MQTT Service/Enable → your settings
Video (german) explains it in detail.

The Auto discovery only provides two values, as the others do not contain unique IDs. Add this to your configuration.yaml to have proper entities. Change the “XXX” to the number of your MS-A2:

mqtt:
  sensor:
  - name: "State of Charge"
    unique_id: hoymiles_msa2_soc
    state_topic: "homeassistant/sensor/MSA-2XXXXXXXXXXX/quick/state"
    value_template: "{{ value_json.soc | float(1) }}"
    unit_of_measurement: "%"
    device_class: battery
    state_class: measurement
    device:
      name: "MSA-2XXXXXXXXXXX"
      identifiers: "2XXXXXXXXXXX"
      manufacturer: "Hoymiles"
      model: "MS-A2"

  - name: "Batterie Temperatur"
    unique_id: hoymiles_msa2_batterie_temperatur
    state_topic: "homeassistant/sensor/MSA-2XXXXXXXXXXX/device/state"
    value_template: "{{ value_json.bat_temp }}"
    unit_of_measurement: "°C"
    device_class: temperature
    state_class: measurement
    device:
      name: "MSA-2XXXXXXXXXXX"
      identifiers: "2XXXXXXXXXXX"
      manufacturer: "Hoymiles"
      model: "MS-A2"

  - name: "Charge Today"
    unique_id: hoymiles_msa2_charge_today
    state_topic: "homeassistant/sensor/MSA-2XXXXXXXXXXX/system/state"
    value_template: "{{ value_json.chg_e }}"
    unit_of_measurement: "Wh"
    device_class: energy
    state_class: total_increasing
    device:
      name: "MSA-2XXXXXXXXXXX"
      identifiers: "2XXXXXXXXXXX"
      manufacturer: "Hoymiles"
      model: "MS-A2"

  - name: "Discharge Today"
    unique_id: hoymiles_msa2_discharge_today
    state_topic: "homeassistant/sensor/MSA-2XXXXXXXXXXX/system/state"
    value_template: "{{ value_json.dchg_e }}"
    unit_of_measurement: "Wh"
    device_class: energy
    state_class: total_increasing
    device:
      name: "MSA-2XXXXXXXXXXX"
      identifiers: "2XXXXXXXXXXX"
      manufacturer: "Hoymiles"
      model: "MS-A2"

  - name: "Power from(+)/to(-) Battery"
    unique_id: hoymiles_msa2_power_to_from_battery
    state_topic: "homeassistant/sensor/MSA-2XXXXXXXXXXX/quick/state"
    value_template: "{{ value_json.bat_p }}"
    unit_of_measurement: "W"
    device_class: power
    state_class: measurement
    device:
      name: "MSA-2XXXXXXXXXXX"
      identifiers: "2XXXXXXXXXXX"
      manufacturer: "Hoymiles"
      model: "MS-A2"

  - name: "Battery State"
    unique_id: hoymiles_msa2_state
    state_topic: "homeassistant/sensor/MSA-2XXXXXXXXXXX/quick/state"
    value_template: "{{ value_json.bat_sts }}"
    device_class: enum
    options:
      - discharge
      - charge
      - standby
    device:
      name: "MSA-2XXXXXXXXXXX"
      identifiers: "2XXXXXXXXXXX"
      manufacturer: "Hoymiles"
      model: "MS-A2"

If you want to use MQTT to control the battery, change
homeassistant/select/MSA-XXXXXXXXXX/ems_mode/command from general to mqtt_ctrl and update homeassistant/number/MSA-XXXXXXXXXX/power_ctrl/set values every 59 seconds.

Update interval: minimum 0.2 seconds, no longer than 1 minute. It will automatically switch back to “inherent logic” after 1 minute. And within the minute, the value must be changed. It is sufficient to change the decimal place, e.g., 100 —> 100.1.

E.g. to prevent the Battery from discharging at night change the value from 0.0 to 0.1 every 55 seconds.

If you want to use the inherent logic change homeassistant/select/MSA-XXXXXXXXXX/ems_mode/command from mqtt_ctrl to general.

Documentation posted here, direct link. At least the numbering and the update intervals are not correct, may be updated in the future.

**note:**  
`supported_topics` : indicates the topics that the device supports publishing and subscribing.  
`quick_state` : Topics published by the device for quick updates of the device itself and system status, see Section 10.  
`device_state` : A topic published by the device for timed updates of the device's own state, see Section 11.  
`system_state` : The topic published by the device for the scheduled update of the system state, see Section 12.  
`switch_ctrl` : The topic that the device responds to for device on/off control, see Section 7.  Not supported for now
`ems_mode` : The topic that the device responds to for setting the EMS mode, see Section 8.  
`power_ctrl` : The topic that the device responds to for controlling power, see Section 9.  

11. Device real-time Data (Device Release)
*The device is released at intervals of 1 second, including the device and system.*


Second-level data (Device publishing)
*Devices are published at intervals of 5 minutes.*

**note:**  
`type` : equipment port type (`grid_on` : and so, `grid_off` : so, `inv` : inverter)  
`v` : Device port voltage (minimum unit: 0.1V)  
`i` : Device port current (minimum unit: 0.01A)  
`f` : Device port frequency (minimum unit: 0.01Hz)  
`p` : Active power of the device port (minimum unit: 0.1W)  
`q` : Device port reactive power (minimum unit: 0.1VAR)  
`ein` : Device port input power of the day (minimum unit: 1Wh)  
`eout` : Device port output power of the day (minimum unit: 1Wh)  
`etin` : Historical cumulative input power of the device port (minimum unit: 1Wh)  
`etout` : Historical cumulative output power of device ports (minimum unit: 1Wh)  
`bat_sts` : Device battery status (`standby` : on standby, `charge` : charging, `discharge` : discharging, `lock` : locked)  
`bat_v` : Device battery voltage (minimum unit: 0.01V)  
`bat_i` : Device battery current (minimum unit: 0.01A)  
`bat_p` : Device battery power (minimum unit: 0.1W)  
`bat_temp` : Device battery temperature (minimum unit: 0.1 ° C)  
`soc` : Remaining battery power of the device (minimum unit: 0.01%)  
`rssi` : Device signal value (minimum unit: 1dBm)  

System real-time data (Device release)
*Devices are released at intervals of 5 minutes. This topic is only available for host and standalone devices.*

**note:**  
`pv_p` : System photovoltaic power (minimum unit: 0.1W)  
`plug_p` : System socket power (minimum unit: 0.1W)  
`bat_p` : System battery power (minimum unit: 0.1W)  
`grid_p` : System grid power (minimum unit: 0.1W)  
`load_p` : System load power (minimum unit: 0.1W)  
`sp_p` : System smart socket power (minimum unit: 0.1W)  
`soc` : System battery power (minimum unit: 0.01%)  
`pv_e` : Photovoltaic power generation of the system on the day (minimum unit: 1Wh)  
`dchg_e` : Battery side discharge capacity of the system for the day (minimum unit: 1Wh)  
`chg_e` : System charge on the battery side for the day (minimum unit: 1Wh)  
`plug_out_e` : System output of grid-connected sockets on the day (minimum unit: 1Wh)  
`plug_in_e` : System grid-connected socket input power of the day (minimum unit: 1Wh)  
`ems_mode` : The current EMS mode of the system (`general`: power controlled by the device itself,`mqtt_ctrl`: Power controlled by mqtt commands) 

EMS Mode configuration (Device Release)
**note:**  
`options` : System EMS mode (` general` : default, when enabled, the device manages energy through inherent logic, `mqtt_ctrl` : When enabled, the device responds to mqtt instructions to control power, which can be managed through the `homeassistant/number/<dev_id>/power_ctrl/set` topic) 

Power Control (Device Subscription)
**note:**  
See `min` and `max` in the `homeassistant/number/<dev_id>/power_ctrl/config` theme. (-1000 and 2000)

4 Likes

First automation I did was to delay discharging at night to reach the lowest shortly SoC point shortly before charging again.

Pretty static in the moment, needs improvements, but a good example to show how the MQTT interface works.

Further Ideas:
Prevent discharge under 100W, as the efficiency is really low in that range.

alias: MSA discharge stop 60 %
description: Stop discharge at 60% and start discharging again 3 hours before sunrise and more than 4kWh production predicted
triggers:
  - type: battery_level
    device_id: f9a40a47a1dc82d0c957be7d16b22039 #Battery
    entity_id: 21838c99796f4deef16f70125d25ba35 #SoC
    domain: sensor
    trigger: device
    below: 60
conditions: []
actions:
  - device_id: f9a40a47a1dc82d0c957be7d16b22039 #Battery
    domain: select
    entity_id: ee9494d8ad59ea190d8753bc5f021c86 #..._mqtt_select
    type: select_option
    option: mqtt_ctrl
  - repeat:
      while:
        - condition: template
          value_template: |
            {% set sunrise = state_attr('sun.sun', 'next_rising') %} 
            {% if (as_timestamp(sunrise) - as_timestamp(now())) > (3 * 60 * 60) 
               and 
               states('sensor.energy_production_today') | float > 4 %} 
              true
            {% else %}
              false
            {% endif %}
      sequence:
        - device_id: f9a40a47a1dc82d0c957be7d16b22039 #Battery
          domain: number
          entity_id: 92433e10be5767d336de1e2060cebdb6 #Power control set
          type: set_value
          value: 0
        - delay: "00:00:55" #change value every minute
        - device_id: f9a40a47a1dc82d0c957be7d16b22039 #Battery
          domain: number
          entity_id: 92433e10be5767d336de1e2060cebdb6 #Power control set
          type: set_value
          value: 0.1
        - delay: "00:00:55" #change value every minute
mode: single

Worked quite well, battery was discharged to 11% and then the solar energy production was high enough to charge the battery again. The green rectangle highlights the time the automation is active:

2 Likes

If you have two or more MS-A2 in a master/slave config you can address the system as well as the individual state:

…
state_topic: "homeassistant/sensor/MSA-2xxNUMMER_MASTER/quick/state"
value_template: "{{ value_json.sys_soc }}"
…
state_topic: "homeassistant/sensor/MSA-2xxNUMMER_MASTER/quick/state"
value_template: "{{ value_json.soc }}"

state_topic: "homeassistant/sensor/MSA-2xxNUMMER_SLAVE/quick/state"
value_template: "{{ value_json.soc }}"
1 Like

thank you @skipper22hassio - I think about an automation that not use the standard way between MS-A2 and Shelly 3em, so HA could control. You could ignore a specific Phase, for example.

Exactly. I’m also trying to automate one phase, but something’s not working.

1 Like

Same in my case - my automation is jumping to much. Maybe someone has a good idea