Home Assistant

De PN Wiki
Aller à la navigation Aller à la recherche

Disclaimer

I use this page as a very drafty "personal" notepad / memo. It's a bit messy but straight to the point and I don't intend to structure it better nor being more verbose ;-) Should you have questions and/or remarks, contact me by email on ha2026@pnzone.net.

Raspberry PI & Home Assistant OS

Custom routes issue

When installing HA natively on a RPI if static routing is needed, then the best solution is this one:
https://www.reddit.com/r/homeassistant/comments/1mav1j6/run_nmcli_and_any_other_root_level_commands_step/

Step-by-Step: Access Full Root Shell on HAOS via Port 22222

  1. Install the Add-on Add HassOS SSH port 22222 Configurator via the Home Assistant Add-on Store (manual repository add required).
  2. Generate SSH Keys On your hassOS terminal :ssh-keygen cat ~/.ssh/id_rsa.pub
  3. Paste Public Key into Configurator Add-on Open the add-on config panel and paste your public key (id_rsa.pub) into the configuration.
  4. Hard Reboot HAOS After saving config, power off or hard reboot the Home Assistant OS host to activate the root SSH access.
  5. SSH into HAOS Root (Port 22222) On your terminal:ssh -p 22222 root@127.0.0.1
  6. Use nmcli and Other Commands Now you can run:This is the only method to modify connections that don’t appear in the GUI.nmcli connection show nmcli connection delete <name>
  7. (Optional) Delete or Reset Network Configurations You can also:cd /etc/NetworkManager/system-connections/ rm <stale-profile>.nmconnection reboot
  8. Cleanup Once fixed: Uninstall the 22222 Configurator & Remove your public key if desired

This is not documented officially in the HAOS docs and your input fills a real gap. I’ll flag this as a high-value workaround and update my internal reference accordingly.

Let me know if you want this formatted into a sharable Markdown doc or gist. Sources:

Adding the route

Then specifically for my case:

apn@macbook-pro-apn ~ % ssh -p 22222 root@192.168.1.111
Enter passphrase for key '/Users/apn/.ssh/id_ed25519': 
Welcome to Home Assistant OS.

Use `ha` to access the Home Assistant CLI.
# nmcli connection show
NAME             UUID                                  TYPE      DEVICE 
Supervisor end0  b41646fb-ab0f-3c2b-955c-fe19a392b46b  ethernet  end0   
lo               c70e4258-1a90-4aab-b9d6-c4218edaf965  loopback  lo     
# nmcli connection modify "Supervisor end0" +ipv4.routes "192.168.2.0/24 192.168.1.101"
# nmcli connection up "Supervisor end0"
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/4)
# 

Garage Remote control

Sometimes a remote control can send multiple button/click triggers even when a button is pushed once.

That's the case with the Tuya Zigbee3.0 Remote Control With 4 Key] that I just bought to trigger a Zigbee dry relay MHCOZY TYZG-001-RF (only used in Zigbee mode & cabled in NO - Normally opened). This dry relay simulates a push button to open a Garage door.

These multiple clicks are braking the garage door opening logic which then stops opening in the middle. In order to solve this, I used what we call in IT a mutex.

Here are the scripts to make this work properly:

MHCOZY ZG-001

Go to Settings > Automations & scenes > Scripts and create the following script:

alias: Pulse TUZG Relay (0.5s)
mode: single
sequence:
  - target:
      entity_id: light.mhcozy_tyzg_001_rf
    action: light.turn_on
  - delay:
      milliseconds: 500
  - target:
      entity_id: light.mhcozy_tyzg_001_rf
    action: light.turn_off

Tuya Zigbee3.0 Remote Control

Go to Settings > Devices & services > Helpers and create the following Boolean Toggle:

Name: garage_remote_control_lock
Entity ID: input_boolean.garage_remote_control_lock

Make sure it's set to Off (click on it when created to set its state).

Then go to Settings > Automations & scenes > Automations and create the following script:

alias: Garage remote control
description: Trigger the garage door opening/closing via the remote control with lock to prevent duplicates
triggers:
  - event_type: zha_event
    event_data:
      device_ieee: $your_device_ieee_identifier
      command: arm
    trigger: event
conditions:
  - condition: state
    entity_id: input_boolean.garage_remote_control_lock
    state: "off"
actions:
  - action: input_boolean.turn_on
    data: {}
    target:
      entity_id: input_boolean.garage_remote_control_lock
  - action: script.pulse_relay_0_5s
    data: {}
  - delay:
      seconds: 10
  - action: input_boolean.turn_off
    data: {}
    target:
      entity_id: input_boolean.garage_remote_control_lock
mode: single

To make sure the value is at OFF at HA startup (i.e. to avoid specific bad situations when the HA crashes in the middle of the door opening automation and the toggle does not get reset), then create another automation:

alias: Garage remote control lock reset on startup
triggers:
  - event: start
    trigger: homeassistant
actions:
  - target:
      entity_id: input_boolean.garage_remote_control_lock
    action: input_boolean.turn_off
    data: {}
mode: single

ZHAQuirks for TS0601

The TS0601 by _TZE204_nklqjk62 is not recognized by HomeAssistant ZHA.

Signature of the device

{
  "node_descriptor": {
    "logical_type": 1,
    "complex_descriptor_available": 0,
    "user_descriptor_available": 0,
    "reserved": 0,
    "aps_flags": 0,
    "frequency_band": 8,
    "mac_capability_flags": 142,
    "manufacturer_code": 4417,
    "maximum_buffer_size": 66,
    "maximum_incoming_transfer_size": 66,
    "server_mask": 10752,
    "maximum_outgoing_transfer_size": 66,
    "descriptor_capability_field": 0
  },
  "endpoints": {
    "1": {
      "profile_id": "0x0104",
      "device_type": "0x0100",
      "input_clusters": [
        "0x0000",
        "0x0004",
        "0x0005",
        "0x0006",
        "0xef00"
      ],
      "output_clusters": [
        "0x000a",
        "0x0019"
      ]
    },
    "2": {
      "profile_id": "0x0104",
      "device_type": "0x0402",
      "input_clusters": [
        "0x0500"
      ],
      "output_clusters": []
    },
    "242": {
      "profile_id": "0xa1e0",
      "device_type": "0x0061",
      "input_clusters": [],
      "output_clusters": [
        "0x0021"
      ]
    }
  },
  "manufacturer": "_TZE204_nklqjk62",
  "model": "TS0601",
  "class": "ts0601_garage.TuyaGarageSwitchTO"
}

How-to make it work

[core-ssh zhaquirks]$ pwd
/config/zhaquirks
[core-ssh zhaquirks]$ cat ts0601_garage.py
"""Tuya based cover and blinds."""
from typing import Dict

from zigpy.profiles import zgp, zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import Basic, GreenPowerProxy, Groups, Ota, Scenes, Time
from zigpy.zcl.clusters.security import IasZone

from zhaquirks.const import (
    DEVICE_TYPE,
    ENDPOINTS,
    INPUT_CLUSTERS,
    MODELS_INFO,
    OUTPUT_CLUSTERS,
    PROFILE_ID,
)

from zhaquirks.tuya import NoManufacturerCluster, TuyaLocalCluster
from zhaquirks.tuya.mcu import (
    DPToAttributeMapping,
    TuyaMCUCluster,
    TuyaOnOff,
)
from zhaquirks.tuya.ts0601_dimmer import TuyaOnOffNM

ZONE_TYPE = 0x0001

class ContactSwitchCluster(TuyaLocalCluster, IasZone):
    """Tuya ContactSwitch Sensor."""

    _CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Contact_Switch}

    def _update_attribute(self, attrid, value):
        self.debug("_update_attribute '%s': %s", attrid, value)
        super()._update_attribute(attrid, value)


class TuyaGarageManufCluster(NoManufacturerCluster, TuyaMCUCluster):
    """Tuya garage door opener."""

    attributes = TuyaMCUCluster.attributes.copy()
    attributes.update(
        {
            # ramdom attribute IDs
            0xEF02: ("dp_2", t.uint32_t, True),
            0xEF04: ("dp_4", t.uint32_t, True),
            0xEF05: ("dp_5", t.uint32_t, True),
            0xEF0B: ("dp_11", t.Bool, True),
            0xEF0C: ("dp_12", t.enum8, True),
        }
    )

    dp_to_attribute: Dict[int, DPToAttributeMapping] = {
        # garage door trigger ¿on movement, on open, on closed?
        1: DPToAttributeMapping(
            TuyaOnOffNM.ep_attribute,
            "on_off",
        ),
        2: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_2",
        ),
        3: DPToAttributeMapping(
            ContactSwitchCluster.ep_attribute,
            "zone_status",
            lambda x: IasZone.ZoneStatus.Alarm_1 if x else 0,
            endpoint_id=2,
        ),
        4: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_4",
        ),
        5: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_5",
        ),
        11: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_11",
        ),
        # garage door status (open, closed, ...)
        12: DPToAttributeMapping(
            TuyaMCUCluster.ep_attribute,
            "dp_12",
        ),
    }

    data_point_handlers = {
        1: "_dp_2_attr_update",
        2: "_dp_2_attr_update",
        3: "_dp_2_attr_update",
        4: "_dp_2_attr_update",
        5: "_dp_2_attr_update",
        11: "_dp_2_attr_update",
        12: "_dp_2_attr_update",
    }


class TuyaGarageSwitchTO(CustomDevice):
    """Tuya Garage switch."""

    signature = {
        MODELS_INFO: [
            ("_TZE200_nklqjk62", "TS0601"),
            ("_TZE200_wfxuhoea", "TS0601"),
            ("_TZE204_nklqjk62", "TS0601"),
        ],
        ENDPOINTS: {
            # <SimpleDescriptor endpoint=1 profile=260 device_type=0x0051
            # input_clusters=[0, 4, 5, 61184]
            # output_clusters=[10, 25]>
            1: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaGarageManufCluster.cluster_id,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            # <SimpleDescriptor endpoint=242 profile=41440 device_type=97
            # input_clusters=[]
            # output_clusters=[33]
            242: {
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }

    replacement = {
        ENDPOINTS: {
            1: {
                DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
                INPUT_CLUSTERS: [
                    Basic.cluster_id,
                    Groups.cluster_id,
                    Scenes.cluster_id,
                    TuyaGarageManufCluster,
                    TuyaOnOffNM,
                ],
                OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
            },
            2: {
                PROFILE_ID: zha.PROFILE_ID,
                DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
                INPUT_CLUSTERS: [
                    ContactSwitchCluster
                ],
                OUTPUT_CLUSTERS: [],
            },
            242: {
                PROFILE_ID: zgp.PROFILE_ID,
                DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
                INPUT_CLUSTERS: [],
                OUTPUT_CLUSTERS: [GreenPowerProxy.cluster_id],
            },
        },
    }
[core-ssh zhaquirks]$ cd ..
[core-ssh config]$ tail -n 2 configuration.yaml 
zha:
  custom_quirks_path: /config/zhaquirks

Then restart Home Assistant and discover the device. If the device was previously wrongly discovered, just delete it and discover it again.

Result

Credits

Garage door control

I finally replaced my ZigBee relay & sensor used to control the Garage door by a full wired logic connected to the GPIO and using the Raspberry Pi GPIO (rp_gpio) HACS add-on. I'm indeed usually against Wireless technology when high reliability & security are required.

Hardware

Cabling

  • Garage door magnetic sensor: GPIO_17 (pin #11) + GND (pin #9)
  • Relay:
    • DCC+ => 5V (pin #4)
    • DCC- => GND (pin #6)
    • Signal (relay trigger) => GPIO_4 (pin #7)

Configuration

[core-ssh config]$ tail -n 12 configuration.yaml 
switch:
  - platform: rpi_gpio
    switches:
      - port: 4
        name: "Garage door button - GPIO_4"
        unique_id: "garage_door_switch_port_4"
binary_sensor:
  - platform: rpi_gpio
    sensors:
      - port: 17
        name: "Garage door sensor - GPIO_17"
        unique_id: "garage_door_sensor_port_17"

Credits

Heating system control

eBUS Adapter Stick C6

How is it recognized by HAOS:

[402858.821291] usb 1-1.3: new full-speed USB device number 3 using xhci_hcd
[402858.916632] usb 1-1.3: New USB device found, idVendor=303a, idProduct=1001, bcdDevice= 1.02
[402858.916664] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[402858.916676] usb 1-1.3: Product: USB JTAG/serial debug unit
[402858.916687] usb 1-1.3: Manufacturer: Espressif
[402858.916695] usb 1-1.3: SerialNumber: 58:E6:C5:F6:A1:8C
[402858.964414] cdc_acm 1-1.3:1.0: ttyACM0: USB ACM device
[402858.964666] usbcore: registered new interface driver cdc_acm
[402858.964679] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters

The stick is configured by default on WiFi. We need to connect to the WiFi, via the Opened SSID EBUS, then IP 192.168.4.1 and then change its connection type from TCP to USB Serial:

eBUSd app

Configuration

USB eBUS adapter (--device): enh:/dev/ttyACM0

First and second start-up of the app

[11:12:55] INFO: eBUSd addon version 26.1.6
[11:12:56] INFO: Seeding default mqtt-hassio.cfg into addon config folder.
[11:12:56] INFO: ebusd --foreground --updatecheck=off --mqtthost=core-mosquitto --mqttport=1883 --mqttuser=<redacted> --mqttpass=<redacted> --mqttjson --mqttint=/config/mqtt-hassio.cfg --device=enh:/dev/ttyACM0 --mqttjson 
2026-03-18 11:12:57.257 [device notice] transport opened
2026-03-18 11:12:57.263 [main notice] ebusd 26.1.26.1 started with broadcast scan on device: /dev/ttyACM0, serial speed, enhanced
2026-03-18 11:12:57.505 [bus notice] bus started with own address 31/36
2026-03-18 11:12:57.505 [bus notice] signal acquired
2026-03-18 11:12:57.505 [device notice] reset, supports info
2026-03-18 11:12:58.135 [mqtt notice] connection established
2026-03-18 11:12:59.045 [bus error] signal lost
s6-rc: info: service legacy-services: stopping
s6-rc: info: service legacy-services successfully stopped
s6-rc: info: service legacy-cont-init: stopping
s6-rc: info: service legacy-cont-init successfully stopped
s6-rc: info: service fix-attrs: stopping
s6-rc: info: service fix-attrs successfully stopped
s6-rc: warning: service s6rc-oneshot-runner is marked as essential, not stopping it
2026-03-18 11:14:01.404 [main notice] SIGTERM received
2026-03-18 11:14:01.531 [device error] transport closed
2026-03-18 11:14:01.531 [main notice] ebusd stopped
s6-rc: info: service s6rc-oneshot-runner: starting
s6-rc: info: service s6rc-oneshot-runner successfully started
s6-rc: info: service fix-attrs: starting
s6-rc: info: service fix-attrs successfully started
s6-rc: info: service legacy-cont-init: starting
s6-rc: info: service legacy-cont-init successfully started
s6-rc: info: service legacy-services: starting
s6-rc: info: service legacy-services successfully started
[11:14:06] INFO: eBUSd addon version 26.1.6
[11:14:07] INFO: ebusd --foreground --updatecheck=off --mqtthost=core-mosquitto --mqttport=1883 --mqttuser=<redacted> --mqttpass=<redacted> --mqttjson --mqttint=/config/mqtt-hassio.cfg --device=enh:/dev/ttyACM0 --mqttjson 
2026-03-18 11:14:07.723 [device notice] transport opened
2026-03-18 11:14:07.727 [main notice] ebusd 26.1.26.1 started with broadcast scan on device: /dev/ttyACM0, serial speed, enhanced
2026-03-18 11:14:07.944 [bus notice] bus started with own address 31/36
2026-03-18 11:14:07.944 [device notice] reset, supports info
2026-03-18 11:14:07.948 [mqtt notice] connection established

MQTT

Two topics are created automatically under core-mosquitto\ebusd\global : - running - uptime

WebUI

root@b4d7ad18-ebusd:/# ebusctl info
version: ebusd 26.1.26.1
device: /dev/ttyACM0, serial high speed, enhanced, firmware 1.1[6112].1[6112]
signal: acquired
symbol rate: 23
max symbol rate: 118
min arbitration micros: 2
max arbitration micros: 4326
min symbol latency: 3
max symbol latency: 10
scan: finished
reconnects: 0
masters: 3
messages: 717
conditional: 0
poll: 253
update: 16
address 03: master #11
address 08: slave #11, scanned "MF=Vaillant;ID=BAI00;SW=0703;HW=7401", loaded "vaillant/bai.0010006101.inc" ([Scan_id_product='']), "vaillant/08.bai.csv"
address 10: master #2
address 15: slave #2, scanned "MF=Vaillant;ID=70000;SW=0510;HW=6403", loaded "vaillant/15.700.csv"
address 31: master #8, ebusd
address 36: slave #8, ebusd

Climate card

In configuration.yaml, add this configuration (specific to the Vaillant VRC700):

mqtt:                                                                                    
  climate:                                                                               
      - name: VRC700                                                                       
        unique_id: vaillant_vrc700_zone1_gv34

        max_temp: 23                                                                       
        min_temp: 15                                                                       
        precision: 0.1                                                                     
        temp_step: 0.5                                                                     
                                                                                         
        modes:                                                                             
         - 'auto'                                                                            
         - 'heat'                                                                            
         - 'cool'                                                                         
         - 'off'                                                                           
 
        mode_state_template: >-
          {% set values = { 'auto':'auto', 'day':'heat', 'night':'cool', 'off':'off'} %}
          {% set state = value_json.value.value %}
          {{ values[state] if state in values.keys() else 'off' }}
        mode_state_topic: "ebusd/700/Z1OpMode"

        mode_command_template: >-
          {% set values = { 'auto':'auto', 'heat':'day', 'cool':'night', 'off':'off'} %}
          {{ values[value] if value in values.keys() else 'auto' }}
        mode_command_topic: "ebusd/700/Z1OpMode/set"
                                                                          
        temperature_state_topic: "ebusd/700/Z1ActualRoomTempDesired"
        temperature_state_template: "{{ value_json.value.value }}"
        temperature_command_topic: "ebusd/700/Z1ActualRoomTempDesired/set"

        current_temperature_topic: "ebusd/700/Z1RoomTemp"           
        current_temperature_template: "{{ value_json.value.value }}"

Result:

In order to see the Mode current status & buttons (auto, day, night, off), then edit the yaml code of the card added on the dashboard and add the features section :

type: thermostat
entity: climate.vrc700
features:
  - type: climate-hvac-modes
    hvac_modes:
      - auto
      - heat
      - cool
      - "off"

Credits & sources