Condensing boiler VICTRIX SUPERIOR 35 PLUS, esphome, modbus (RS485)

Hi to all,

I have just put the boiler VICTRIX SUPERIOR 35 PLUS control into operation using the MODBUS interface (they call it BMS), so I want to share with the community my findings and working solution.

I chose esphome installed on ESP32-WROOM-32 with UART to RS 485 converter.

The converter is connected to the terminals D+, D- and GND(24)

My YAML configuration is as follows (names of the sensors are in the Czech language, but I tried to add a comment explaining what it is)

substitutions:
  settings_skipped_updates: "30"
  bus_name: "bms"

esphome:
  name: kotel
  friendly_name: kotel

esp32:
  board: esp32dev
  framework:
    type: arduino

logger:
  baud_rate: 0
  level: ERROR

time:
  - platform: sntp
    id: sntp_time
    timezone: Europe/Prague

interval:
  - interval: 20sec
    then:
      - if:
          condition:
            switch.is_on: do_heater
          then:
            - number.set: 
                id: boiler_control_heat_num
                value: 0x0055
     
api:
  encryption:
    key: redacted
  reboot_timeout: 1h

ota:
  - platform: esphome
    password: redacted

web_server:
  port: 80

wifi:
  ssid: redacted
  password: redacted
  domain: redacted

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: redacted
    password: redacted

captive_portal:
    
uart:
  id: mod_bus
  tx_pin: GPIO16
  rx_pin: GPIO17
  baud_rate: 19200 # default is 9600
  stop_bits: 1
  parity: EVEN
  #debug:
  #  sequence:
  #    - lambda: UARTDebug::log_binary(direction, bytes, ':');

# https://www.vipsgas.cz/User_Files/f/images/stories/soubory/navody/immergas/victrix-superior-2021-specifikace-protokol-modbus-web.pdf
modbus:
  id: modbus1
  #send_wait_time: 500ms
  uart_id: mod_bus

modbus_controller:
  - id: bms
    ## the Modbus device addr
    address: 1 #0x1
    update_interval: 10s
    command_throttle: 100ms
    modbus_id: modbus1
    setup_priority: -10    
    on_offline:
      then:
        - logger.log: "Boiler goes offline!"
    on_online:
      then:
        - logger.log: "Boiler back online!"        


sensor:  
  - platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
    name: "WiFi Signal dB"
    id: wifi_signal_db
    update_interval: 60s
    entity_category: "diagnostic"
    
  - platform: copy # Reports the WiFi signal strength in %
    source_id: wifi_signal_db
    name: "WiFi Signal Percent"
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "Signal %"
    entity_category: "diagnostic"
    
  - platform: internal_temperature
    name: "Internal Temperature"
    update_interval: 300s
    entity_category: "diagnostic"

  - platform: homeassistant
    id: hass_obyvak_temp
    internal: True
    entity_id: sensor.obyvak_ws_temperature

  - platform: homeassistant
    id: hass_obyvak_hum
    internal: True
    entity_id: sensor.obyvak_ws_humidity
     
  - platform: modbus_controller
    modbus_controller_id: $bus_name
    name: "Boiler 256 RAW bit"  
    id: boiler_control_raw
    register_type: holding
    address: 256
    
  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    # Outlet temperature from the primary heat exchanger with a resolution of 0.1 °C
    name: "Teplota výstupu"
    id: boiler_output_temp
    address: 0x300
    register_type: read
    value_type: S_WORD
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    device_class: temperature
    state_class: measurement
    filters:
      - multiply:  0.1
      
  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Return temperature with a resolution of 0.1 °C
    name: "Teplota zpátečky"  
    address: 0x301
    register_type: read
    value_type: S_WORD
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    device_class: temperature
    state_class: measurement
    filters:
      - multiply:  0.1      
      
  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    id: tuv_modbus_temp
    #DHW temperature with a resolution of 0.1 °C
    name: "Teplota TUV"  
    address: 0x302
    register_type: read
    value_type: S_WORD
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    device_class: temperature
    state_class: measurement
    filters:
      - multiply:  0.1

  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Flue gas temperature in °C
    name: "Teplota spalin"  
    address: 0x303
    register_type: read
    value_type: S_WORD
    unit_of_measurement: "°C"
    accuracy_decimals: 0
    device_class: temperature
    state_class: measurement
    filters:
      - multiply:  1

# Outdoor temperature with a resolution of 0.1 °C only with connected outdoor temperature probe
#  - platform: modbus_controller                       #
#    modbus_controller_id: $bus_nme
#    name: "Venkovni teplota"  
#    address: 0x304
#    #register_type: holding
#    register_type: read
#    value_type: S_WORD
#    unit_of_measurement: "°C"
#    accuracy_decimals: 1
#    device_class: temperature
#    state_class: measurement
#    filters:
#      - multiply:  0.1
#
  - platform: modbus_controller                      
    modbus_controller_id: $bus_name
    #Modulation level with 0.1% resolution
    name: "Úroveň modulace"  
    address: 0x307
    #register_type: holding
    register_type: read
    value_type: U_WORD
    unit_of_measurement: "%"
    accuracy_decimals: 1
    device_class: power_factor
    state_class: measurement
    filters:
      - lambda: |-
          if (x == 32768) return 0;
          else return x * 0.1;

  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Calculated heating output temperature with a resolution of 0.1 °C
    name: "Vypočtená výstupní teplota"  
    address: 0x308
    register_type: read
    value_type: U_WORD
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    device_class: temperature
    state_class: measurement
    filters:
     - multiply:  0.1

  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Heating output temperature with a resolution of 0.1 °C only with a connected HVDT temperature probe
    name: "Teplota za HVDT"  
    address: 0x309
    #register_type: holding
    register_type: read
    value_type: U_WORD
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    device_class: temperature
    state_class: measurement
    filters:
      - lambda: |-
          if (x == 32768) return 0;
          else return x * 0.1;    

  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Measured DHW inlet temperature with a resolution of 0.1 °C only for boilers equipped with a DHW inlet temperature probe
    name: "Vstupní teplota TUV"  
    address: 0x30A
    #register_type: holding
    register_type: read
    value_type: U_WORD
    unit_of_measurement: "°C"
    accuracy_decimals: 2
    device_class: temperature
    state_class: measurement
    filters:
      - lambda: |-
          if (x == 32768) return 0;
          else return x;   

  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #DHW flow in l/min - only boilers equipped with DHW flow measurement
    name: "Průtok TUV"  
    address: 0x30B
    register_type: read
    value_type: U_WORD
    unit_of_measurement: "L/min"
    accuracy_decimals: 0
    device_class: volume_flow_rate
    state_class: measurement
    filters:
      - lambda: |-
          if (x == 32768) return 0;
          else return x;   

  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Pump flow rate in l/min
    name: "Průtok čerpadlem"  
    address: 0x30C
    register_type: read
    value_type: U_WORD
    unit_of_measurement: "L/min"
    accuracy_decimals: 0
    device_class: volume_flow_rate
    state_class: measurement
    filters:
      - lambda: |-
          if (x == 32768) return 0;
          else return x;   

  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Fan speed in rpm
    name: "Rychlost ventilátoru"  
    address: 0x30D
    register_type: read
    value_type: U_WORD
    unit_of_measurement: "Hz"
    accuracy_decimals: 2
    device_class: frequency
    state_class: measurement
    filters:
      - multiply:  1

  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Burner operating hours (High word)
    name: "Provozní hodiny hořáku (High word)"  
    id: burner_op_hours_HIGH
    address: 0x349
    internal: True
    register_type: read
    value_type: U_WORD
    accuracy_decimals: 0
    
  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Burner operating hours (Low word)
    name: "Provozní hodiny hořáku (Low word)"  
    id: burner_op_hours_LOW
    address: 0x34A
    internal: True
    register_type: read
    value_type: U_WORD
    accuracy_decimals: 0

  - platform: template
    #Total burner operating hours = 65535 x High word + Low word
    name: "Provozní hodiny hořáku"
    update_interval: 60s
    lambda: |-
      return id(burner_op_hours_HIGH).state * 65535 + id(burner_op_hours_LOW).state;
    state_class: TOTAL_INCREASING
    device_class: duration
    unit_of_measurement: "h"

  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Maximum heating temperature limit with a resolution of 0.1 °C
    name: "Maximální teplota vytápění"  
    address: 0x400
    register_type: read
    value_type: S_WORD
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    device_class: temperature
    state_class: measurement
    filters:
      - multiply:  0.1   

  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Minimum heating temperature limit with a resolution of 0.1 °C.
    name: "Minimalni teplota vytápění"  
    address: 0x401
    register_type: read
    value_type: S_WORD
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    device_class: temperature
    state_class: measurement
    filters:
      - multiply:  0.1

  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Maximum DHW temperature limit with a resolution of 0.1 °C.
    name: "Maximální teplota TUV"  
    address: 0x402
    register_type: read
    value_type: S_WORD
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    device_class: temperature
    state_class: measurement
    filters:
      - multiply:  0.1

  - platform: modbus_controller                       
    modbus_controller_id: $bus_name
    #Minimum DHW temperature limit with a resolution of 0.1 °C.
    name: "Minimalni teplota TUV"  
    address: 0x403
    register_type: read
    value_type: S_WORD
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    device_class: temperature
    state_class: measurement
    filters:
      - multiply:  0.1

text_sensor:
  - platform: modbus_controller
    modbus_controller_id: $bus_name
    id: boiler_error
    icon: mdi:car-brake-low-pressure
    name: "Error code"
    address: 0x1
    raw_encode: HEXBYTES
    bitmask: 0
    register_type: read
    lambda: |-
      uint16_t state_val = modbus_controller::word_from_hex_str(x, 0);
      return x;      

select:
  - platform: modbus_controller
    use_write_multiple: true
    modbus_controller_id: $bus_name
    #Boiler control
    name: "Ovládání kotle"
    id: boiler_control_option
    address: 256
    entity_category: config
    value_type: U_WORD

    optionsmap:
      #Reserved
      "Rezervováno": 0  # BIT 0 ~ 0
      #Enable DHW heating
      "Povolit ohřev TUV": 2 # BIT 1 ~ DEC=2, HEX=2
      #Enable heating and hot water heating
      "Povolit vytápění a ohřev TUV": 4 # BIT 2 ~ DEC=4, HEX=4
      #Deactivation of the BOOST function
      "Deaktivace funkce BOOST": 512 # BIT 9 ~ DEC=512, HEX=200
    lambda: |-
      if ((x & 0x206) == 0)
        return  std::string("Rezervováno");
      if ((x & 0x206) == 2)
        return  std::string("Povolit ohřev TUV");
      if ((x & 0x206) == 4)
        return  std::string("Povolit vytápění a ohřev TUV");
      if ((x & 0x206) == 512)
        return  std::string("Deaktivace funkce BOOST");
      return {};
    write_lambda: |-
      uint16_t unmodified =  id(boiler_control_raw).state;
      uint16_t modified = ((unmodified & ~0x206) | value);
      return modified;


number:
  - platform: modbus_controller                     
    modbus_controller_id: $bus_name                 
    id: boiler_heating_temperature
    use_write_multiple: false
    #Setting the heating temperature with a resolution of 0.1 °C
    #Example: enter 732 to set the output temperature to 73.2 °C.
    name: "Nastavení vytápění"
    address: 514
    max_value: 85
    min_value: 20
    value_type: U_WORD
    multiply: 10
    unit_of_measurement: "°C"
    device_class: temperature

  - platform: modbus_controller                     
    modbus_controller_id: $bus_name                 
    id: boiler_water_storage_temperature
    register_type: holding
    use_write_multiple: false
    #Setting the DHW temperature with a resolution of 0.1 °C
    #Example: write 455 to set the DHW temperature to 45.5 °C.
    name: "Nastaveni TUV"
    address: 515
    max_value: 60
    min_value: 10
    value_type: U_WORD
    multiply: 10
    unit_of_measurement: "°C"
    device_class: temperature

  - platform: modbus_controller
    modbus_controller_id: $bus_name
    name: "Boiler 512 (heat) RAW"
    internal: True
    id: boiler_control_heat_num
    register_type: holding
    address: 512
    max_value: 85
    min_value: 0
    step: 85

switch:
  - platform: template
    id: do_heater
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF
    #Enable heating
    name: "Povolit topeni"
    lambda: |-
      return id(do_heater).state;

climate:
  - platform: thermostat
    id: boiler_heat_thermostat
    on_boot_restore_from: memory
    #Heating thermostat
    name: "Termostat topení"
    icon: mdi:heat-pump

    visual:
      min_temperature: 16
      max_temperature: 25
      temperature_step: 0.1

    # Sensors
    sensor: hass_obyvak_temp
    humidity_sensor: hass_obyvak_hum

    # Presets
    default_preset: startup
    preset:
      - name: startup
        default_target_temperature_low: 19.0
        mode: "OFF"

      - name: comfort
        default_target_temperature_low: 21.0
        mode: heat

      - name: home
        default_target_temperature_low: 20.0
        mode: heat

      - name: sleep
        default_target_temperature_low: 18.4
        mode: heat

      - name: away
        default_target_temperature_low: 16.0
        mode: heat
    preset_change:
      - logger.log: Preset has been changed!
    
    min_heating_off_time: 60s
    min_heating_run_time: 60s
    min_idle_time: 60s
    startup_delay: true
    set_point_minimum_differential: 1
    heat_overrun: 0.5
    heat_deadband: 0.2

    heat_action:
      - switch.turn_on: do_heater
    idle_action:
      - switch.turn_off: do_heater
            
  • Setting boiler operating mode is solved using SELECT (id: boiler_control_option)
  • Setting DHW temperature is only possible in “Enable DHW heating” mode
  • Setting heating temperature is only possible in “Enable heating and hot water heating” mode
  • For heating, it is necessary to write the value 85(dec) / 0x0055 hex to the HOLDING register at address 512 on a regular basis (every 20 seconds)
interval:
  - interval: 20sec
    then:
      - if:
          condition:
            switch.is_on: do_heater
          then:
            - number.set: 
                id: boiler_control_heat_num
                value: 0x0055

Maybe it will be useful to someone

Thank’s for sharing.

That sounds like very stupid from them, BMS is commonly known as battery management system.