KEBA Wallbox integration via ModBus - also for Master-Slave setups

Just in case it helps someone else:
I have tried other pre-cooked KEBA Wallbox integrations but have failed in a setup with multiple wallboxes.
It turned out that using the ModBus integration was actually rather easy and works like a charm for me.

It consists of 3 elements:

  1. The actual Modus Configuration to read the sensors
  2. Some template sensors to translate sensor reading e.g. into readable text
  3. For write-only-registers in the wallboxes you can simply use the build in modbus service calls in your automations.

For reference please see my configuration. You have to copy the sections for each of your wallboxes and just put the IP address in and change name and sensor names accordingly. I show below just one station (I have 2).
I am still waiting for feedback from KEBA on how load balancing works if you write new values into the max charging current registers, but assume for now that those will be just a reduction of the max charging current for an individual callbox and load balancing works within the max current as set via the dip switches in the hardware of the master.

  1. Modbus section
    Change the callbox name and sensor names as you like them
- name: "WallboxLinks"
  close_comm_on_error: true
  delay: 5
  timeout: 5
  type: tcp
  host: xxx.xxx.xxx.xxx # your callbox IP address goes here
  port: 502


  sensors:
    # Lesbare Sensoren
    - name: WBLinks_charging_state
      address: 1000
      scan_interval: 30
      data_type: uint32
    - name: WBLinks_cable_state
      address: 1004
      scan_interval: 30
      data_type: uint32
    - name: WBLinks_error_code # 0=kein Error
      address: 1006
      scan_interval: 600
      data_type: uint32
    - name: WBLinks_charging_current_P1
      address: 1008
      scan_interval: 60
      data_type: uint32
      scale: 0.001
      precision: 1
      unit_of_measurement: "A"
      device_class: current
    - name: WBLinks_charging_current_P2
      address: 1010
      scan_interval: 60
      data_type: uint32
      scale: 0.001
      precision: 1
      unit_of_measurement: "A"
      device_class: current
    - name: WBLinks_charging_current_P3
      address: 1012
      scan_interval: 60
      data_type: uint32
      scale: 0.001
      precision: 1
      unit_of_measurement: "A"
      device_class: current
    - name: WBLinks_serial_number
      address: 1014
      scan_interval: 86400
      data_type: uint32
    - name: WBLinks_product_features
      address: 1016
      scan_interval: 86400
      data_type: uint32
    - name: WBLinks_firmware_version
      address: 1018
      scan_interval: 86400
      data_type: uint32
    - name: WBLinks_active_power
      address: 1020
      scan_interval: 60
      data_type: uint32
      scale: 0.001
      precision: 1
      unit_of_measurement: "W"
      device_class: power
    - name: WBLinks_total_energy
      address: 1036
      scan_interval: 600 # every 10 min
      data_type: uint32
      scale: 0.0001
      unit_of_measurement: "kWh"
      precision: 2
      state_class: total_increasing
      device_class: energy
    - name: WBLinks_voltage_P1
      address: 1040
      scan_interval: 60
      data_type: uint32
      unit_of_measurement: "V"
      device_class: voltage
    - name: WBLinks_voltage_P2
      address: 1042
      scan_interval: 60
      data_type: uint32
      unit_of_measurement: "V"
      device_class: voltage
    - name: WBLinks_voltage_P3
      address: 1044
      scan_interval: 60
      data_type: uint32
      unit_of_measurement: "V"
      device_class: voltage
    - name: WBLinks_power_factor
      address: 1046
      scan_interval: 60
      data_type: uint32
      scale: 0.1
      unit_of_measurement: "%"
    - name: WBLinks_max_charging_current
      address: 1100
      scan_interval: 60
      data_type: uint32
      scale: 0.001
      precision: 1
      unit_of_measurement: "A"
      device_class: current
    - name: WBLinks_max_supported_charging_current
      address: 1110
      scan_interval: 86400           #*
      data_type: uint32
      scale: 0.001
      unit_of_measurement: "A"
      precision: 1
      device_class: current
    - name: WBLinks_session_energy
      address: 1502
      scan_interval: 600           #*
      data_type: uint32
      scale: 0.0001
      precision: 2
      unit_of_measurement: "kWh"
      device_class: energy
  1. Template sensors
    Not very efficient code but it does the job
# ****************************************************
# Wallboxen: Transform some sensors into text
# ****************************************************

- sensor:
    - name: WBLinks_charging_state_text
      state: >
        {% if states('sensor.WBLinks_charging_state')|int == 0 %}
          starting
        {% else %}
        {% if states('sensor.WBLinks_charging_state')|int == 1 %}
          not ready
        {% else %}
        {% if states('sensor.WBLinks_charging_state')|int == 2 %}
          ready
        {% else %}
        {% if states('sensor.WBLinks_charging_state')|int == 3 %}
          charging
        {% else %}
        {% if states('sensor.WBLinks_charging_state')|int == 4 %}
          error
        {% else %}
        {% if states('sensor.WBLinks_charging_state')|int == 5 %}
          interrupted
        {% endif %}
        {% endif %}
        {% endif %}
        {% endif %}
        {% endif %}
        {% endif %}

- sensor:
    - name: WBLinks_cable_state_text
      state: >
        {% if states('sensor.wblinks_cable_state')|int == 7 %}
          cable_ready
        {% else %}
          cable_not_ready
        {% endif %}
  1. syntax for writing registers for max charging current, enabling/disabling a station, setting a session target energy for the next loading session.
    You have to just take the parts without the # and use it in automations. In my case I have created a helper for the max charging current which then gets written to the wallbox.
    # WriteOnly Daten
    # in Automationen wie folgt verwenden:
    #
    # **** maximalen Ladestrom auf den Wert setzen, der in "input_number.wallboxlinks_target_current" steht
    # service: modbus.write_register
    # data:
    #  address: 5004
    #  hub: WallboxLinks
    #  value: "{{states('input_number.wallboxlinks_target_current')}}". # or put a fixed value here
    #
    # **** Session Energie setzen
    # service: modbus.write_register
    # data:
    #  address: 5010
    #  hub: WallboxLinks
    #  value: # put value for session energy here
    #
    # **** Ladestation sperren oder einschalten (enable/disable)
    # service: modbus.write_register
    # data:
    #  address: 5014
    #  hub: WallboxLinks
    #  value: "0"      # einschalten/enable mit "1"

Enjoy!

Update on Load Balancing when using multiple wallboxes/ charging stations (after information from KEBA). The behavior is as expected so this is only for confirmation:

The max current set via the dip switches in the master station hardware provides the framework in which all the other wallboxes act. So, if you reduce the max current possible for a given station via the ModBus commands you can set it only to a current below the max current set by the hardware dip switches. That reduced current is then only valid for the particular station that you set it for. if you set it at the master station by ModBus command it is only valid for that master and not for the slaves attached to it in a charging network. Load balancing will then continue to work between those stations and shares the max current as set via the hardware dip switches.

So you just add this to the configuration.yaml ?

Did you try the official HA integration for this, too? Keba Charging Station - Home Assistant

Correct, Just add to your configuration, respect. the individual yams files if you have broken up your config into several files.
The „official“ integration only supports a single wallbox. If you only have one you will be just fine,

Ok, I’ve got it working, I had to make some adjustments:

The IP may look strange. That is, because I’ve routed/NATed it via a second PC for testing.
The Charging Network is isolated from internet and I’m accessing the P30x Hotspot to read via modbus.
To make this work I’ve added these rules to the intermediate PC:
(enp57s0f1 is the wifi interface name)

https://serverfault.com/a/1017016

# for Modbus
iptables -A PREROUTING -t nat -p tcp -i enp57s0f1 --dport 502 -j DNAT --to-destination 192.168.2.1:502
iptables -A POSTROUTING -t nat -p tcp -d 192.168.2.1 --dport 502 -j MASQUERADE
iptables -A FORWARD -p tcp -d 192.168.2.1 --dport 502 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

# to access the webGui
iptables -A PREROUTING -t nat -p tcp -i enp57s0f1 --dport 80 -j DNAT --to-destination 192.168.2.1:80
iptables -A POSTROUTING -t nat -p tcp -d 192.168.2.1 --dport 80 -j MASQUERADE
iptables -A FORWARD -p tcp -d 192.168.2.1 --dport 80 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

This is not a permanent solution as the x30 seems to shut down the Hotspot after some hours and
I found no way to make it come back again without hitting the breaker to make it reboot (FW 1.5.1).
Also, I also can’t access any c-boxes as they are on the charging LAN network (192.168.42.x)

This is my home assistant config code (reduced example):

modbus:
    - name: "p30x"
      close_comm_on_error: true
      delay: 5
      timeout: 5
      type: tcp
      host: 192.168.0.131  # your callbox IP address goes here
      port: 502

      sensors:
        - name: p30x_charging_state
          address: 1000
          scan_interval: 30
          data_type: uint32
        - name: p30x_cable_state
          address: 1004
          scan_interval: 30
          data_type: uint32
        - name: p30x_error_code # 0=kein Error
          address: 1006
          scan_interval: 600
          data_type: uint32
        - name: p30x_active_power
          address: 1020
          scan_interval: 60
          data_type: uint32
          scale: 0.001
          precision: 1
          unit_of_measurement: "W"
          device_class: power
        - name: p30x_total_energy
          address: 1036
          scan_interval: 600 # every 10 min
          data_type: uint32
          scale: 0.0001
          unit_of_measurement: "kWh"
          precision: 2
          state_class: total_increasing
          device_class: energy
        - name: p30x_session_energy
          address: 1502
          scan_interval: 600           #*
          data_type: uint32
          scale: 0.0001
          precision: 2
          unit_of_measurement: "kWh"
          device_class: energy
  
  # ****************************************************
  # Wallboxen: Transform some sensors into text
  # ****************************************************
  
  sensor wallboxen:
    - platform: template
      sensors:
        p30x_cable_state_text:
          friendly_name: WB Kabel
          unique_id: p30x_cable_state_text
          value_template: >
            {% if states('sensor.p30x_cable_state')|int == 7 %}
              cable_ready
            {% else %}
              cable_not_ready
            {% endif %}

I have a only 1 Keba P20
Normal integretion in configuration.yaml and ik works.
But i have so little data and not all entitys contain any data …
Anybody has this working properly in 2024?
image