New Shelly TRV BLU integration

Nicely done! Thank you.
I’ve implemented the changes in my setup last evening and here is some feedback:
I have 3 Gateways:

  • first one has 5 TRVs and 2 H&T sensors
  • second one has 3 TRVs and 3 H&T sensors
  • third one has one TRV

The automation that asks for the statuses, cycles through all of the devices and sends the request for each device. I can share the automation configuration, for who wants to use it.
I’ve noticed that only one TRV per gateway had the new topic with the response.
After a bit of empiric testing, I’ve figured out, that it might had to do with sending too many requests at once to one Blu GW. So I modified the automation to include some delays between the requests.

On a separate note, yesterday evening I managed to make on Gw to crash compleetely, by just enabling the debugging over MQTT. It crashed completely, the web server that renders the local page was not starting anymore and the BLU Gw was rebooting every minute or so. I ended up doing a factory reset, which even that was no easy task, as it was crashing.

My point with this, it might be that the BLU Gw are not super-reliable, to stress them.

I’m curious what is your experience so far with this new MQTT configuration and the automation, in regards to the BLU Gw.

alias: Shelly BLU TRV get status
description: Get Shelly BLU TRV status every 5 minutes
triggers:
  - trigger: time_pattern
    minutes: /5
conditions: []
actions:
  - variables:
      devices:
        - shellyblugwg3-aabbccddeeff/status/blutrv:200
        - shellyblugwg3-ttuuvvxxyyzz/status/blutrv:200
        - shellyblugwg3-aabbccddeeff/status/blutrv:201
        - shellyblugwg3-ttuuvvxxyyzz/status/blutrv:201
  - repeat:
      for_each: "{{ devices }}"
      sequence:
        - action: mqtt.publish
          data:
            topic: "{{ repeat.item.split('/')[0] ~ '/rpc' }}"
            payload: |-
              {
                  "id": 0,
                  "src": "{{ repeat.item }}",
                  "method": "BluTRV.Call",
                  "params": {
                      "id": {{ repeat.item.split(':')[1] }}
                      "method": "TRV.GetStatus",
                      "params": {
                          "id": 0
                      }
                  }
              } | tojson
        - delay: "00:00:10"
mode: single

I only have one BLU TRV so I don’t have this problem.

I hope that polling TRV.GetStatus is just a temporary workaround. I have informed the Shelly team several times that additional data sent by the device as NotifyStatus is needed. And I mean not only the valve position but also the enable value which informs whether automatic temperature control is enable and information whether boost mode is active. Not to mention current and target temperature. Only with this data it will be possible to implement device support in Shelly integration.

Yes, that would be the right way to do it. I hope they’ll provide it with next updates.

I made a variation of the automation and of the mqtt configuration. My variant is using the ‘BluTrv.GetRemoteStatus’ API. And for whatever reason this seems to work better in my setup. I was able to bring down the automation to run every minute and use only 1 sec delay between consecutive MQTT RPC calls.

alias: Shelly BLU TRV get status v2
description: Get Shelly BLU TRV status every 1 minute
triggers:
  - trigger: time_pattern
    minutes: /1
conditions: []
actions:
  - variables:
      devices:
        - shellyblugwg3-aabbccddeeff/status/blutrv:200
        - shellyblugwg3-ttuuvvxxyyzz/status/blutrv:200
        - shellyblugwg3-aabbccddeeff/status/blutrv:201
        - shellyblugwg3-ttuuvvxxyyzz/status/blutrv:201
  - repeat:
      for_each: "{{ devices }}"
      sequence:
        - action: mqtt.publish
          data:
            topic: "{{ repeat.item.split('/')[0] ~ '/rpc' }}"
            payload: |-
              {
                  "id": {{ repeat.item.split(':')[1] }},
                  "src": "{{ repeat.item.split('/')[0] ~ '/remotestatus/' ~ repeat.item.split('/')[2] }}"
                  "method": "BluTrv.GetRemoteStatus",
                  "params": {
                      "id": {{ repeat.item.split(':')[1] }}
                  }
              } | tojson
        - delay: "00:00:1"
mode: single

and I made this modification to the MQTT climate configuration

      action_topic: "shellyblugwg3-aabbccddeeff/remotestatus/blutrv:200/rpc"
      action_template: >
        {% if value_json.result.status["trv:0"].pos | int > 0 %}
          heating
        {% else %}
          idle
        {% endif %}
1 Like

Great finding :+1:

Hi there,

I want to share my config for those with more than one TRV per Gateway.

mqtt:
  - climate:
      name: "NameOfFirstTRV"
      unique_id: "NameOfFirstTRV"
      current_temperature_topic: "NameOfGateway/status/bthomesensor:203"
      current_temperature_template: "{{ value_json.value }}"
      max_temp: 30
      min_temp: 4
      temp_step: 0.1
      temperature_state_topic: "NameOfGateway/status/bthomesensor:202"
      temperature_state_template: "{{ value_json.value }}"
      temperature_command_template: "{{ {'id': 0, 'src': 'homeassistant', 'method': 'BluTRV.Call', 'params': {'id': 200, 'method': 'TRV.SetTarget', 'params': {'id': 0, 'target_C': value | round(1)}}} | to_json }}"
      temperature_command_topic: "NameOfGateway/rpc"
      action_topic: "NameOfGateway/status/blutrv:200/rpc"
      action_template: "{% if value_json.result.pos | int > 0 %}heating{% else %}idle{% endif %}"
      mode_state_topic: "NameOfGateway/status/bthomesensor:202"
      mode_state_template: "{{ 'off' if value_json.value == 4 else 'heat' }}"
      modes: ["heat", "off"]
      mode_command_topic: "NameOfGateway/rpc"
      mode_command_template: "{% set target = 4 if value == 'off' else 21 %}{{ {'id': 0, 'src': 'homeassistant', 'method': 'BluTRV.Call', 'params': {'id': 200, 'method': 'TRV.SetTarget', 'params': {'id': 0, 'target_C': target}}} | to_json }}"
      availability:
        - topic: "NameOfGateway/status/blutrv:200"
          value_template: "{%if value_json.rpc%}online{%else%}offline{%endif%}"
      device:
        connections:
          - - bluetooth
            - aa:bb:cc:11:22:33
        name: NameOfGateway
        model: Shelly BLU TRV
        model_id: SBTR-EU867E
        manufacturer: Allterco Robotics

  - climate:
      name: "NameOfSecondTRV"
      unique_id: "NameOfSecondTRV"
      current_temperature_topic: "NameOfGateway/status/bthomesensor:207"
      current_temperature_template: "{{ value_json.value }}"
      max_temp: 30
      min_temp: 4
      temp_step: 0.1
      temperature_state_topic: "NameOfGateway/status/bthomesensor:206"
      temperature_state_template: "{{ value_json.value }}"
      temperature_command_template: "{{ {'id': 0, 'src': 'homeassistant', 'method': 'BluTRV.Call', 'params': {'id': 201, 'method': 'TRV.SetTarget', 'params': {'id': 0, 'target_C': value | round(1)}}} | to_json }}"
      temperature_command_topic: "NameOfGateway/rpc"
      action_topic: "NameOfGateway/status/blutrv:201/rpc"
      action_template: "{% if value_json.result.pos | int > 0 %}heating{% else %}idle{% endif %}"
      mode_state_topic: "NameOfGateway/status/bthomesensor:206"
      mode_state_template: "{{ 'off' if value_json.value == 4 else 'heat' }}"
      modes: ["heat", "off"]
      mode_command_topic: "NameOfGateway/rpc"
      mode_command_template: "{% set target = 4 if value == 'off' else 21 %}{{ {'id': 0, 'src': 'homeassistant', 'method': 'BluTRV.Call', 'params': {'id': 201, 'method': 'TRV.SetTarget', 'params': {'id': 0, 'target_C': target}}} | to_json }}"
      availability:
        - topic: "NameOfGateway/status/blutrv:201"
          value_template: "{%if value_json.rpc%}online{%else%}offline{%endif%}"
      device:
        connections:
          - - bluetooth
            - aa:bb:cc:11:22:33
        name: NameOfGateway
        model: Shelly BLU TRV
        model_id: SBTR-EU867E
        manufacturer: Allterco Robotics

  - climate:
      name: "NameOfThirdTRV"
      unique_id: "NameOfThirdTRV"
      current_temperature_topic: "NameOfGateway/status/bthomesensor:211"
      current_temperature_template: "{{ value_json.value }}"
      max_temp: 30
      min_temp: 4
      temp_step: 0.1
      temperature_state_topic: "NameOfGateway/status/bthomesensor:210"
      temperature_state_template: "{{ value_json.value }}"
      temperature_command_template: "{{ {'id': 0, 'src': 'homeassistant', 'method': 'BluTRV.Call', 'params': {'id': 202, 'method': 'TRV.SetTarget', 'params': {'id': 0, 'target_C': value | round(1)}}} | to_json }}"
      temperature_command_topic: "NameOfGateway/rpc"
      action_topic: "NameOfGateway/status/blutrv:202/rpc"
      action_template: "{% if value_json.result.pos | int > 0 %}heating{% else %}idle{% endif %}"
      mode_state_topic: "NameOfGateway/status/bthomesensor:210"
      mode_state_template: "{{ 'off' if value_json.value == 4 else 'heat' }}"
      modes: ["heat", "off"]
      mode_command_topic: "NameOfGateway/rpc"
      mode_command_template: "{% set target = 4 if value == 'off' else 21 %}{{ {'id': 0, 'src': 'homeassistant', 'method': 'BluTRV.Call', 'params': {'id': 202, 'method': 'TRV.SetTarget', 'params': {'id': 0, 'target_C': target}}} | to_json }}"
      availability:
        - topic: "NameOfGateway/status/blutrv:202"
          value_template: "{%if value_json.rpc%}online{%else%}offline{%endif%}"
      device:
        connections:
          - - bluetooth
            - aa:bb:cc:11:22:33
        name: NameOfGateway
        model: Shelly BLU TRV
        model_id: SBTR-EU867E
        manufacturer: Allterco Robotics

MQTT config of the gateway:

Some notes:

  • You should change the names of the Gateways and TRV’s in the web based config AND in the app, because not all entries are synced…
  • You don’t need the Bluetooth addresses of the TRV’s in the MQTT configuration. You can just put the name of the TRV in the field “unique_id”
  • The order of the TRV IDs (200, 201, 202, …) corresponds to the order in which they are displayed in the app / how they were connected to the gateway. If you copy my config, leave all the IDs as they are and only change the name of the gateway, the name of the respective TRV, and the Bluetooth ID of the connection (Gateway MAC) - this ID (MAC) remains the same if you want to add multiple TRV’s that are connected to the same gateway!
  • You can connect at least 3 TRV’s per Gateway. I guess the Gateway can handle more. The bthome device address offset / gateway / device is 4, because every TRV has 4 (gw) bthome addresses. Here is a screenshot of a Gateway with three TRV’s connected:

And this is the result in home assistant:

I currently have three gateways with 8 TRV’s in place and will probably order 8 more for the second floor.

I hope this helps some of you and brings some clarity to the topic.

Best regards

Eric

1 Like

We can simplify this… I created a script for the BLU Gateway to send TRV status automatically every 30 seconds:

let topic_prefix = null;

Shelly.call("MQTT.GetConfig", {}, function (config) {
    topic_prefix = config.topic_prefix;
});

function SendTrvStatus() {
    Shelly.call("BluTrv.GetRemoteStatus", {"id": 200}, function (status) {
        MQTT.publish(topic_prefix + "/status/blutrv:200/trv", JSON.stringify(status.status["trv:0"]));
    });
};


MQTT.setConnectHandler(SendTrvStatus);
let UpdateTimer = Timer.set(30000, true, SendTrvStatus);

And the climate entity configuration should look like this:

mqtt:
  - climate:
      name: "Shelly BLU TRV 112233445566"
      unique_id: "112233445566"
      current_temperature_topic: "shellyblugwg3-aabbccddeeff/status/bthomesensor:203"
      current_temperature_template: "{{ value_json.value }}"
      max_temp: 30
      min_temp: 4
      temp_step: 0.1
      temperature_state_topic: "shellyblugwg3-aabbccddeeff/status/bthomesensor:202"
      temperature_state_template: "{{ value_json.value }}"
      temperature_command_template: "{{ {'id': 0, 'src': 'homeassistant', 'method': 'BluTRV.Call', 'params': {'id': 200, 'method': 'TRV.SetTarget', 'params': {'id': 0, 'target_C': value | round(1)}}} | to_json }}"
      temperature_command_topic: "shellyblugwg3-aabbccddeeff/rpc"
      action_topic: "shellyblugwg3-aabbccddeeff/status/blutrv:200/trv"
      action_template: "{% if value_json.pos > 0 %}heating{% else %}idle{% endif %}"
      mode_state_topic: "shellyblugwg3-aabbccddeeff/status/bthomesensor:202"
      mode_state_template: "{{ 'off' if value_json.value == 4 else 'heat' }}"
      modes: ["heat", "off"]
      mode_command_topic: "shellyblugwg3-aabbccddeeff/rpc"
      mode_command_template: "{% set target = 4 if value == 'off' else 21 %}{{ {'id': 0, 'src': 'homeassistant', 'method': 'BluTRV.Call', 'params': {'id': 200, 'method': 'TRV.SetTarget', 'params': {'id': 0, 'target_C': target}}} | to_json }}"
      availability:
        - topic: "shellyblugwg3-aabbccddeeff/status/blutrv:200"
          value_template: "{%if value_json.rpc%}online{%else%}offline{%endif%}"
      device:
        connections:
          - - bluetooth
            - 11:22:33:44:55:66
        name: Shelly BLU TRV
        model: Shelly BLU TRV
        model_id: SBTR-EU867E
        manufacturer: Shelly

112233445566 - BLU TRV ID
aabbccddeeff - BLU Gateway ID

1 Like

That’s a great idea!
I’ve been using shelly scripts on my Shelly2PM relays to make a smart switch for smart light bulbs, that can act, if needed also as a clasic switch.

So, as I had some resources already at hand, I took your idea and made a script for BLU GW, to react on events and automatically push the status over mqtt as you did.

Each time a TRV has an event, the script will push to the appropriate MQTT topic for the TRV component. I’ve tested with target temperature changes and position changes done automatically by the TRV itself.

The script has a default topic defined “/remotestatus/{component}/trv”, but this can be overridden by adding a key in BLU GW KVS with name “publish_remotestatus_topic”.
Nonetheless the topic should have a {component} placeholder, as the script is replacing this with the appropriate value for the component that triggered the event.

An advantage would be that is a generic script, that pushes only when a change happens.
A disadvantage is the lack of an initial status if no event is happening on the TRV.

let publishRemoteStatusTopicKey = "publish_remotestatus_topic";
let publishRemoteStatusTopicDefaultValue = "/remotestatus/{component}/trv";
let topicPrefix = null;

// Create an object to act as a map, where key value settings for the Shelly BLU GW device are persisted in memory
var keyValueSettings = {};

/**
 * Gets the value of a key from the Shelly's KVS
 * @param {*} key the key for which the value will be retrieved
 * @param {*} defaultValue the default value for the key, in case no entry is found in KVS
 */
function kvsGet(key, defaultValue) {
    try {
        var userDataParam = {"key": key, "defaultValue": defaultValue};
        Shelly.call(
            "KVS.get",
            {"key": key},
            function (result, error_code, error_message, userData) {
                var keyValueSettingObj = null;
                if (error_code === 0) {
                    keyValueSettingObj = {"key": userData.key, "value": result.value, "etag": result.etag};
                } else {
                    keyValueSettingObj = {"key": userData.key, "value": userData.defaultValue, "etag": null};
                }
                keyValueSettings[key] = keyValueSettingObj;
            },
            userDataParam
        );
    } catch (e1) {
        console.log("kvsGet has failed for key '", key, "'. Error: ", e1);
    }
}

function getMqttConfigHandler(config) {
    topicPrefix = config.topic_prefix;
}

function getMqttConfig() {
    try {
        Shelly.call("MQTT.GetConfig", {}, getMqttConfigHandler);
    } catch (e) {
        console.log("getMqttConfig has failed. Error: ", e1);
    }
}

function bluTrvGetRemoteStatusHandler(status, errNo, errMsg, userData) {
    // console.log("bluTrvGetRemoteStatusHandler status: ", status, ", errNo: ", errNo, ", errMsg: ", errMsg, ", userData: ", userData)
    if (errNo !== 0) {
        console.log("bluTrvGetRemoteStatusHandler error no: ", errNo, ", errMsg: '", errMsg, "', userData: ", userData);
        return;
    }

    if (keyValueSettings[publishRemoteStatusTopicKey] && keyValueSettings[publishRemoteStatusTopicKey].value) {
        var topic = topicPrefix + keyValueSettings[publishRemoteStatusTopicKey].value.replace("{component}", userData.component);
        MQTT.publish(topic, JSON.stringify(status.status["trv:0"]));
    } else
        console.log("KVS missing and no default value for key: ", publishRemoteStatusTopicKey);
}

function sendTrvStatus(component, id) {
    if (topicPrefix !== null) {
        Shelly.call("BluTrv.GetRemoteStatus", {"id": id}, bluTrvGetRemoteStatusHandler, {component: component, id: id});
    } else
        console.log("sendTrvStatus topicPrefix is null");
};

function eventHandler(event) {
    try {
        // console.log("event=", event);
        if (event && event.info) {
            sendTrvStatus(event.info.component, event.info.id)
        } else
            console.log("eventHandler (event && event.info) is false");
    } catch (e1) {
        console.log("Shelly handle event '", event, "' has failed. Error: ", e1);
    }
}

function addEventHandler() {
    try {
        console.log("addEventHandler...");
        Shelly.addEventHandler(eventHandler);
        console.log("addEventHandler done.");
    } catch (e1) {
        console.log("addEventHandler has failed. Error: ", e1);
    }
}

function initScript() {
    try {
        kvsGet(publishRemoteStatusTopicKey, publishRemoteStatusTopicDefaultValue);
        getMqttConfig();
        addEventHandler();
    } catch (e) {
        console.log("initScript has failed. Error: ", e1);
    }
}

initScript();

This looks great! I play with it tomorrow.

I’ve noticed the following issue, on my BLU GW that has 5 TRVs attached:
“Error too many calls in progress.”

This behavior appears when I’ve triggered from HA a change of target temperature on all TRVs on that GW.

I’ll look into it and make a v2.
On the other two GWs, I didn’t notice any issues.

Best regards
Vlad

I managed to fix the bug “too many calls in progress”. It seems the Shelly devices have a limitation up to 5 concurrent RPC calls. I found a library that handles this, so I integrated and now the script works without problems, even on the BLU GW with 5 TRVs. I can share the updated script, for who wants.

@Bieniu
I’m interested if you checked if it would be possible to implement the boost feature for the MQTT climate configuration. I would really like to have such an option in HA. This is a mandatory feature to have a happy wife, happy life :slight_smile:

1 Like

The device does not inform whether the boost is active or not in any way (at least I did not find such information anywhere), so you cannot implement the boost as an HVAC mode. You can create start boost and stop boost buttons, however.

Hi,
is there a chance to set the valve dirctly, lets say 33% open? I want to use an external temp sensor and a PID controller. Just need to set the valve.
The biuld in sensor is useless, to near to the source of the heat.

With best regards

Gerhard

Yes, you need to use TRV.SetPosition service Shelly BLU TRV (Thermostatic Radiator Valve) | Shelly Technical Documentation

Hi there, I am not much familiar with all those stuff, but have a question:

My config is Blu GW Gen3 and 2 valves. GW is configured to send data to Mosquitto MQTT to which I have pointed MQTT integration in HA.

According to this thread, I added two climate entities into configuration.yaml. Everything seems to be working fine, but I realized, that current temperature in climate entity is different from current temperature in Shelly app. I did a test with GetRemoteStatus (http://blu_gw_ip/rpc/BluTrv.GetRemoteStatus?id=200) for first valve with following results:

{
  "v": 49,
  "ts": 1733053549,
  "status": {
    "sys": {
      "time": "12:45",
      "unixtime": 1733053548,
      "offset": 3600,
      "uptime": 85700,
      "ram_size": 25848,
      "ram_free": 10088,
      "cfg_rev": 6,
      "state_rev": 49
    },
    "temperature:0": {
      "id": 0,
      "tC": 24.17,
      "tF": 75.51,
      "errors": []
    },
    "trv:0": {
      "id": 0,
      "pos": 100,
      "steps": 23435,
      "current_C": 23.51,
      "target_C": 30,
      "schedule_rev": 0,
      "errors": []
    }
  }
}

and realized, that Shelly app shows following value as Current temperature:

"current_C": 23.51,

while climate entity in HA uses sensor ID 203 (for first valve) and sensor ID 206 (for second valve) which is in fact not “current_C” but this one:

 "tC": 24.17,

As you can see, there is a huge difference. I have another temperature sensor in same room and I am pretty sure sure that correct value is “current_C” and not “tC”

When I captured data sent from BLU GW to MQTT I did not see any other sensor ID which would contain “current_C” value. (For fist valve only ID 200,202,203 were reporting)

According to Shelly docs (Shelly BLU TRV (Thermostatic Radiator Valve) | Shelly Technical Documentation) “current_c” is in “trv” property, while “tC” is in “temperature” property

Would it be possible to get “current_C” value into climate entity?

OK, finally I have been able to sort it out. I used script for BLU GW created by @Bieniu (modified it for two valves) to send trv status to MQTT and then amended climate entity config as follows:

 current_temperature_topic: "shellyblugwg3-8cbfeaa67378/status/blutrv:200/trv"
      current_temperature_template: "{{ value_json.current_C }}"
current_temperature_topic: "shellyblugwg3-8cbfeaa67378/status/blutrv:201/trv"
      current_temperature_template: "{{ value_json.current_C }}"

I would be interested in your updated script! (Even if i just use 3 TRVs on one GW.)

And how does your mqtt yaml look like? Is this correct? (on first glance it seems to work ^^)

  - climate:
      name: "TRV-06"
      unique_id: "TRV-06"
      current_temperature_topic: "ShellyBLUGateway/status/bthomesensor:211"
      current_temperature_template: "{{ value_json.value }}"
      max_temp: 30
      min_temp: 4
      temp_step: 0.1
      temperature_state_topic: "ShellyBLUGateway/status/bthomesensor:210"
      temperature_state_template: "{{ value_json.value }}"
      temperature_command_template: "{{ {'id': 0, 'src': 'homeassistant', 'method': 'BluTRV.Call', 'params': {'id': 202, 'method': 'TRV.SetTarget', 'params': {'id': 0, 'target_C': value | round(1)}}} | to_json }}"
      temperature_command_topic: "ShellyBLUGateway/rpc"
      action_topic: "ShellyBLUGateway/remotestatus/blutrv:202/trv"
      action_template: "{% if value_json.pos > 0 %}heating{% else %}idle{% endif %}"
      mode_state_topic: "ShellyBLUGateway/status/bthomesensor:210"
      mode_state_template: "{{ 'off' if value_json.value == 4 else 'heat' }}"
      modes: ["heat", "off"]
      mode_command_topic: "ShellyBLUGateway/rpc"
      mode_command_template: "{% set target = 4 if value == 'off' else 21 %}{{ {'id': 0, 'src': 'homeassistant', 'method': 'BluTRV.Call', 'params': {'id': 202, 'method': 'TRV.SetTarget', 'params': {'id': 0, 'target_C': target}}} | to_json }}"
      availability:
        - topic: "ShellyBLUGateway/status/blutrv:202"
          value_template: "{%if value_json.rpc%}online{%else%}offline{%endif%}"
      device:
        connections:
          - - bluetooth
            - aa:bb:cc:dd:ee:ff
        name: ShellyBLUGateway
        model: Shelly BLU Gateway
        model_id: SBTR-EU867E
        manufacturer: Allterco Robotics

Hi,
just to be sure I do the right things next:

I installed a Shelly Gen 3 Gateway, its in my local WiFi and has a static IP (what else)

I added the Gateway to HA, cause HA discovered it.

I powered a TRV BLU and paired it successfully with my gateway.

I used the web interface of the gateway to play around with the TRV, do calibration successfully, just do be sure that this pair is a good team now.

HA discovered the TRV as a BT home device and after integration, I see the temperatures, actual and setpoint.

Nice. but I want to control the valve setting from HA.

I use the ‘Smart thermostat (PID)’ which can calculate a PWM signal, or valve settings in ‘%’.
Works also great, really good stuff.

All I need is a way to send this percentage values to my TRV(s).

Is there a step by step guide how to do?

With best regards

Gerhard
(Will also spent coffee)

There are several options available:

  • you can configure the device via ZHA integration
  • you can configure the device via MQTT integration (configuration above in this thread)
  • you can configure the device via MQTT using Shellies Discovery
  • you can control the device via API

Hi folks, I’m still just getting my set-up together, trying to look for devices without the mandatory cloud, do I understand correctly that these vents can be connected completely locally and controlled via HA? They cost quite a bit, wouldn’t want to make a mistake. Is this hub mandatory, or can I connect them directly to the Raspberry Pi5 module? Thanks!

Thanks.
ZHA isn’t a choice for me, cause I need the valves in a different flat and I connected that flat via ‘USBIP’, but the ZHA documentation includes a warning, not to do such things.
So I went on …