I made a Custom Component for my Sec-Touch Ventilation Central controller

Hi, I updated the ESP with the latest code but still I don’t seem to get any data from the SEV. This is what the (wireless) logs tell me:

INFO Successfully connected to sec-touch @ 192.168.178.185 in 0.005s
INFO Successful handshake with sec-touch @ 192.168.178.185 in 0.117s
[16:24:16][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:18][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:20][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:22][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:24][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:26][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:28][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:30][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:32][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:34][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:36][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:38][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:40][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:44][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:46][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:48][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:50][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:52][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:54][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:24:59][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:25:01][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:25:02][I][safe_mode:041]: Boot seems successful; resetting boot loop counter
[16:25:03][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup
[16:25:05][W][sec-touch:061]: [watchdog] Task of type GET_DATA timed out, forcing cleanup

That logs means that the SEC is not returning any data at all.

Since you were trying a wrong version before I would maybe recommend to change the TX ↔ RX cables. If that does not work, then maybe your device SEC has a disabled UART. That I can not know :frowning_face:

I also handled the random reboot, you can safely enable debug logs now.
I am using the component for a couple of weeks now to automate the night “full ventilation” when I am away from home and until now it worked as expected without any crashes.

At least there we could see if the device is sending the heartbeat or something.

:scream: is there any way to verify that? I used a powermeter (Multimeter) to measure TX ↔ GND / RX ↔ GND of the SEC interface and there was some „movement“ at least. Don’t know if that’s any indication that UART is enabled.

I would assume the manufacturer not to turn that port off since they themselves are selling some kind of overpriced WLAN dongle to plug in there in order to make this thing “smart” which otherwise also would not work

AFAIK the SEC does not send data unless you ask for it specifically. So this could also be noise BUT could also be the heartbeat.

this is true!

So lets check the possible issues I could think of:

  1. You were using the default RX/TX pins on the board, those are also used for the UART Logging (see docs) but you have disable it with baud_rate: 0 in the logger as I remember. Did you tried with the 16 and 17 PINs and changed the configuration?

  2. The Cable could be inverted. Changing RX to TX would be enough.

  3. The GND pin of the board is faulty? I had once a problem in a random board where a PIN was marked as GND but it was not that (this can be check with the multimeter and check the continuity between the GND pin and others GND Pins and also with the shield of the chip in the board itself).

  4. The Jumper cables are not doing correct contact with the SEC-Touch or are faulty.

I am inclined to believe the cable connection is the problem. I got my connector as gift from the man that did the first pure C++ version. I will try to find out which exact model it is.

Hi again,

thank you for your advice - but unfortunately it is just not working for me :sob:

Here’s what I tried:

  • I restarted the SEC
  • updated ESP Home
  • used two different NodeMCU boards
  • tried all possible combinations with the pins and the jumper cables RX0/TX0/D16/D17/RXD/TXD/G16/G17
  • tried different jumper cables and measured their output with the multimeter to factor out connection issues
  • I am using these “Klemmenblock Stecker” I ordered from Amazon (DE) a while ago which fit the SEC perfectly

Here’s my YAML - just to make sure:

esphome:
  name: esphome-web-014144
  friendly_name: AZ Wroom 32
  min_version: 2025.5.0
  name_add_mac_suffix: false

esp32:
  board: esp32dev
  framework:
    type: esp-idf

# Enable Home Assistant API
api:
  encryption:
    key: "TOP-SECRET"

ota:
  - platform: esphome
    password: "TOP-SECRET"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Sec-Touch Fallback Hotspot"
    password: "TOP-SECRET"

captive_portal:

logger:
  level: DEBUG


################################ SEC-Touch ################################


# I am using a fork of your repo just to be in control of accidental changes - it is synced and up to date 😉
external_components:
  - source:
      type: git
      url: https://github.com/LorenzKahl/esphome-components
      ref: main

web_server:
  port: 80
  local: true
  log: false
  version: 3
  sorting_groups:
    - id: group_1
      name: Fan Group 1
      sorting_weight: -100
    - id: group_2
      name: Fan Group 2
      sorting_weight: -99
    - id: group_3
      name: Fan Group 3
      sorting_weight: -98
    - id: group_4
      name: Fan Group 4
      sorting_weight: -97
    - id: group_5
      name: Fan Group 5
      sorting_weight: -96
    - id: group_6
      name: Fan Group 6
      sorting_weight: -95
    - id: group_settings
      name: Configuration
      sorting_weight: -94
  
uart:
  id: sec_touch_uart
  tx_pin: 
    number: GPIO17
  rx_pin:
    number: GPIO16
  baud_rate: 28800

sec_touch:
  uart_id: sec_touch_uart
  update_interval: 5s # 5s is the default

fan:
  - platform: sec_touch
    icon: "mdi:fan"
    fan_number: 1
    name: "Fan 1"
    web_server:
      sorting_group_id: group_1
  - platform: sec_touch
    icon: "mdi:fan"
    fan_number: 2
    name: "Fan 2"
    web_server:
      sorting_group_id: group_2
  - platform: sec_touch
    icon: "mdi:fan"
    fan_number: 3
    name: "Fan 3"
    web_server:
      sorting_group_id: group_3
  - platform: sec_touch
    icon: "mdi:fan"
    fan_number: 4
    name: "Fan 4"
    web_server:
      sorting_group_id: group_4
  - platform: sec_touch
    icon: "mdi:fan"
    fan_number: 5
    name: "Fan 5"
    web_server:
      sorting_group_id: group_5
  - platform: sec_touch
    icon: "mdi:fan"
    fan_number: 6
    name: "Fan 6"
    web_server:
      sorting_group_id: group_6


button:
  - platform: sec_touch
    program_text_update:
      name: "Program Labels Update"
      icon: "mdi:book-refresh"
  - platform: restart
    name: "Restart"
    
text_sensor:
  - platform: sec_touch
    fan_number: 1
    label_text:
      name: "Label Fan 1"
      web_server:
        sorting_group_id: group_1
    mode_text:
      name: "Mode Fan 1"
      web_server:
        sorting_group_id: group_1
  - platform: sec_touch
    fan_number: 2
    label_text:
      name: "Label Fan 2"
      web_server:
        sorting_group_id: group_2
    mode_text:
      name: "Mode Fan 2"
      web_server:
        sorting_group_id: group_2
  - platform: sec_touch
    fan_number: 3
    label_text:
      name: "Label Fan 3"
      web_server:
        sorting_group_id: group_3
    mode_text:
      name: "Mode Fan 3"
      web_server:
        sorting_group_id: group_3
  - platform: sec_touch
    fan_number: 4
    label_text:
      name: "Label Fan 4"
      web_server:
        sorting_group_id: group_4
    mode_text:
      name: "Mode Fan 4"
      web_server:
        sorting_group_id: group_4
  - platform: sec_touch
    fan_number: 5
    label_text:
      name: "Label Fan 5"
      web_server:
        sorting_group_id: group_5
    mode_text:
      name: "Mode Fan 5"
      web_server:
        sorting_group_id: group_5
  - platform: sec_touch
    fan_number: 6
    label_text:
      name: "Label Fan 6"
      web_server:
        sorting_group_id: group_6
    mode_text:
      name: "Mode Fan 6"
      web_server:
        sorting_group_id: group_6

With the presumably correct wiring:

  • ESP32 G17 ↔ RX SEC
  • ESP32 G16 ↔ TX SEC
    …here’s what I get from the logs - right after I switch on Fan 6 using the web UI:
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA
[17:56:26][D][fan:021]: 'Fan 6' - Setting:
[17:56:26][D][fan:024]:   State: ON
[17:56:26][D][fan:030]:   Speed: 11
[17:56:26][D][SecTouchFan:107]: Control called
[17:56:26][D][SecTouchFan:121]: New state to 1
[17:56:26][D][SecTouchFan:126]: Setting speed to 11
[17:56:26][I][SecTouchFan:149]: [Update for 178] - [] speed: 11
[17:56:26][D][sec-touch:242]: add_set_task
[17:56:26][I][SecTouchFan:153]: Publishing state of FAN
[17:56:26][D][fan:120]: 'Fan 6' - Sending state:
[17:56:26][D][fan:121]:   State: ON
[17:56:26][D][fan:123]:   Speed: 11
[17:56:26][D][text_sensor:064]: 'Mode Fan 6': Sending state 'Unknown'
[17:56:26][W][component:257]: Component web_server took a long time for an operation (54 ms).
[17:56:26][W][component:258]: Components should block for at most 30 ms.
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA
[17:56:26][D][sec-touch:065]: [loop] We are waiting for the response after a task of type GET_DATA

When I flip the wiring

  • ESP32 G16 ↔ RX SEC
  • ESP32 G17 ↔ TX SEC
    …interestingly I get this instead:
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 224?)
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 13)
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 224?)
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 13)
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 224?)
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 13)
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 224?)
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 13)
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 224?)
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 13)
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 224?)
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 13)
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 0?)
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 13)
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 224?)
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 13)
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 224?)
[18:04:34][D][fan:021]: 'Fan 6' - Setting:
[18:04:34][D][fan:024]:   State: OFF
[18:04:34][D][SecTouchFan:107]: Control called
[18:04:34][D][SecTouchFan:121]: New state to 0
[18:04:34][I][SecTouchFan:149]: [Update for 178] - [] speed: 0
[18:04:34][D][sec-touch:242]: add_set_task
[18:04:34][I][SecTouchFan:153]: Publishing state of FAN
[18:04:34][D][fan:120]: 'Fan 6' - Sending state:
[18:04:34][D][fan:121]:   State: OFF
[18:04:34][D][fan:123]:   Speed: 0
[18:04:34][D][text_sensor:064]: 'Mode Fan 6': Sending state 'Off'
[18:04:34][W][component:257]: Component web_server took a long time for an operation (55 ms).
[18:04:34][W][component:258]: Components should block for at most 30 ms.
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 14)
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 0?)
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 14)
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 224?)
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 14)
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 224?)
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 14)
[18:04:34][D][sec-touch:083]: [loop]  Discarding noise byte (or maybe a Heartbeat with 28?)
[18:04:34][D][sec-touch:075]: [loop] Data available (Current buffer size: 0, Task queue size: 14)

I really don’t know what else I can do and pretty much feel like giving it up - on the other hand, I really REALLY want this to work. I also want to have a sleep mode, a cooking mode, an away mode and most of all a barbecue mode :blush:

If there’s anything more that I could do please let me know. Is there anybody beside you know of having gotten this to work?

Cheers, Lorenz

I do not know anyone else using this directly to be honest. My neighbor (who does not have HA) tested the original pure c++ lib. Maybe you could tested it to discard UART Problems?

Your config.yml looks correct. The first log, like the logs before, show that the component sends the request and it is waiting for response and it never comes.

The second one looks more promising as the UART input got something but it could also be noise due wrong connection. There should be no need for you to do anything from the web ui since at the start the component asks the SEC-Touch for the status of all the existent fans. And then it update that on each update loop (5s)

One thing that we have different is that I do not have a Fan 6 pair. It should not really change anything but could you maybe just try with one single Fan in your config.yml? just let the logs run and see if it always times out after each update loop. Maybe the 6 Fan ID is not an 178 and that is the reason why it does not work.

Sadly I am too far away from your location (from github) otherwise I would come to test gladly :smile:

Update: The crashing problems are completely resolved.

The basic functionality is working as expected.

Beside that, turning on a fan now will go to speed 1 if no other speed was set before (instead of go to automatic)

Each patch. has a tag in the github repo, so you can just ping one of them if you want it keep it on some specific version.

Added Example of Buttons with decluttering card into the repo:

Maybe it’s way too late or maybe it’s in over even remotely close to the ax delivery boards issue; but I use those for quite some time now, in over a dozen projects. By reading their e-books you can Maybe I misunderstood what they meant by the find out lots of things about everything they

And: “Also, the Bluetooth Radio has support for the following communication

interface protocols:

● UART HCI interface, up to 4 Mbps

● SDIO / SPI HCI interface

● I2C interface

● PCM / I2S audio interface”

So hopefully this will help configuring things in case someone might be needing some extra information.

Updated for compatibility with ESPHOME 2025.11.0

Hi @distante,
thanks for sharing your work so far — this is a really impressive project and exactly the kind of integration I love seeing in the Home Assistant ecosystem. I also very much appreciate the focus on local control and avoiding the vendor cloud-brick.

I have a question regarding summer ventilation mode without heat recovery (Wärmerückgewinnung). I’m fairly sure this feature is not currently integrated, but I was wondering whether, from a technical point of view, this mode is generally accessible via the interface you’re using and therefore could be integrated in principle.

My use case would be switching behavior based on time and temperatures: during the day I’d like to run the normal mode to benefit from heat recovery (effectively keeping the house cooler by recovering the “cold” indoors), and during the night switch to summer ventilation (no heat recovery) to pull in cooler outside air directly. That kind of day/night automation would be very easy to set up in Home Assistant using indoor and outdoor temperature sensors.

This would be an alternative to the storm ventilation mode, which already bypasses heat recovery but is simply too loud for a nightly use case.

Any ideas or info worth sharing?

It should be possible, the problem is find out which command code is send by the original screen controller so it can be reproduced.