Categories
Uncategorized

Getting a Tuya ZY-N1 Sound Detector Working in Zigbee2MQTT (and Using It as a Smoke Alarm Listener)

IMPORTANT NOTE:  If you wait a few weeks this should be supported by Z2M by default, so you might want to just wait.

The Tuya ZY-N1 sound detector works in Zigbee2MQTT today with a community external converter, and it makes a handy smoke alarm listener for Home Assistant. Getting there took one non-obvious setting change, so here is the whole route.

The ZY-N1 is a small USB-powered Zigbee sound sensor that reports how loud a room is. It shows up as TS0601 with manufacturer _TZE204_r6kfl9ta. At the time of writing there is no built-in support in Zigbee2MQTT: the device request is still an open issue, Koenkk/zigbee2mqtt #33195, and that issue is where the working converter comes from.

My setup: Home Assistant with the Zigbee2MQTT add-on (version 2.x). The converter was tested by its author on Zigbee2MQTT 2.14.1, and automatic loading of external converters needs 2.x.

Step 1: Pair it and check the fingerprint

Pair the sensor first, before adding any converter. Turn on Permit Join in Zigbee2MQTT and plug the sensor in. It joins as a router, because it is mains powered, and appears as unsupported with an “Automatically generated definition”.

Open the device’s About tab and check two values. The Zigbee model must be TS0601 and the manufacturer must be exactly _TZE204_r6kfl9ta. The converter only matches that exact pair, so a unit with a different manufacturer string needs a different converter.

Step 2: Add the external converter

The converter is a single JavaScript file that tells Zigbee2MQTT how to read the sensor. Its author posted it in the “External definition” section of issue #33195, along with detailed notes on what each datapoint does.

  1. Using the File Editor or Studio Code Server add-on, find the folder that holds Zigbee2MQTT’s own configuration.yaml and database.db. On a standard add-on install that is /homeassistant/zigbee2mqtt/.
  2. Inside it, create a folder called exactly external_converters.
  3. In that folder, create a file called zy-n1.mjs. The .mjs extension matters, because the file uses import statements.
  4. Paste in the whole code block from the issue, from the first import line to the final ];. Leave out the triple backticks GitHub shows around it.

The two import lines at the top need no setup. They borrow code from zigbee-herdsman-converters, the device library that ships inside Zigbee2MQTT.

What the converter gives you:

Entity What it does Updates
sound_class Loudness class: 0 quiet, 1 medium, 2 loud Live, pushed by the device
noise_detected Noise detected, yes or no Live, pushed by the device
sound_level Sound level in dB Only when asked for (see Step 3)
dpNN_raw Unmapped settings, read-only When asked for

The sensitivity and timing settings from the printed manual are not mapped yet. That is why the converter exposes them only as raw, read-only values.

The gotcha: external JavaScript was switched off

My converter was correct and in the right place, but Zigbee2MQTT ignored it because the enable_external_js setting was off. The symptoms were confusing:

  • The device kept showing “Automatically generated definition”, even after deleting and re-pairing it.
  • Clicking Reconfigure gave the error Device "..." cannot be configured. That message means the device’s definition has no configure step, which is true of the generic fallback but not of the converter.
  • The startup log said nothing at all about external converters.

The setting is documented in the Zigbee2MQTT settings reference. It allows external extensions and converters to run, which is why it can be switched off. You can turn it on in the Zigbee2MQTT web UI under Settings, Advanced, or add it to Zigbee2MQTT’s configuration.yaml:

advanced:
  enable_external_js: true

If an advanced: section already exists, add the line under it rather than creating a second one. The change needs a restart of the Zigbee2MQTT add-on. After that, the device page showed Tuya ZY-N1 instead of the generated definition.

Step 3: Reconfigure and poll for the dB reading

With the converter loaded, click Reconfigure on the device page. This runs the converter’s setup, which also asks the sensor for all its values, including the dB level.

The sensor never sends the dB level on its own; it only answers when asked. The converter’s author works around this by re-running the configure step once a minute from Home Assistant, and I did the same. Replace SoundDetector with your device’s friendly name:

alias: Poll ZY-N1 sound level
triggers:
  - trigger: time_pattern
    minutes: "/1"
actions:
  - action: mqtt.publish
    data:
      topic: zigbee2mqtt/bridge/request/device/configure
      payload: '{"id": "SoundDetector"}'
mode: single

This step is optional. The smoke alarm alert below uses sound_class, which updates live without any polling.

Step 4: Listen for the smoke alarm

The alert fires when the sensor hears loud noise for 10 seconds in a row, allowing for the gaps between beeps. A smoke alarm beeps in bursts, so reacting to a single loud reading would be triggered by a door slam, while requiring perfectly continuous noise would miss the alarm.

First, a template binary sensor in Home Assistant’s main configuration.yaml (not the Zigbee2MQTT one). The delay_off keeps it on through short pauses:

template:
  - binary_sensor:
      - name: "Loud noise sustained"
        unique_id: loud_noise_sustained
        state: "{{ is_state('sensor.sounddetector_sound_class', '2') }}"
        delay_off: "00:00:05"

Reload it from Developer Tools, YAML, Template entities. Then the automation. I send alerts through a WhatsApp gateway and ntfy; swap in your own notify actions:

alias: Smoke alarm heard
triggers:
  - trigger: state
    entity_id: binary_sensor.loud_noise_sustained
    to: "on"
    for: "00:00:10"
actions:
  - action: rest_command.whatsapp_send
    continue_on_error: true
    data:
      chatid: "YOUR_CHAT_ID"
      message: "Smoke alarm sounding at home ({{ now().strftime('%H:%M') }})"
  - action: shell_command.notify
    continue_on_error: true
    data:
      device: "Smoke alarm"
      state: "sounding"
  - delay: "00:05:00"
mode: single

Two details matter here. continue_on_error: true means a failed WhatsApp call does not stop the second alert. The 5-minute delay with mode: single stops a continuous alarm from sending a message every few seconds.

Testing, and the 5-minute trap

My first real test failed, and the cause was the automation’s own cooldown. I had used “Run actions” to check the notifications a few minutes earlier. That run was still sitting in its 5-minute delay, so mode: single quietly ignored the new trigger. The second test worked, and both the WhatsApp message and the ntfy alert arrived.

For testing, I used a siren near the sensor. In quiet conditions sound_class read 0; with the siren going it jumped to 2. If a test does not trigger, check these in History:

  • sound_class never reaches 2: the sound is not loud enough at the sensor. Move it closer.
  • sound_class reaches 2 but the helper flickers: raise delay_off to about 8 seconds.
  • The helper stays on for over 10 seconds: open the automation’s Traces to see whether a previous run was still in its delay. Restart the automation from its menu to clear it.

Finally, test with the real smoke alarm’s test button, held for 15 to 20 seconds, from where the sensor actually sits.

Wrap-up

The ZY-N1 now alerts me within about 10 seconds of a sustained alarm, for the price of one converter file and one setting. A few limitations are worth knowing:

  • Any loud, sustained sound can trigger it, including loud music, a TV or another siren. Mine also fires when my SOS siren sounds, which I am happy with.
  • The sensitivity and timing settings cannot be changed from Home Assistant until someone maps them.
  • It is an extra alert for when you are away, not a replacement for proper smoke alarms. Zigbee or interconnected smoke detectors are the more dependable route to fire alerts.

When official support lands in a Zigbee2MQTT release, delete the external converter file so it does not override the built-in definition.

Credit to GitHub user HACucoo, who wrote the converter and documented the device’s datapoints in the issue.

Sources

Leave a Reply