Auto setup of 433 Mhz Door/motion sensors

Like many, I have a tasmotized Sonoff RF Bridge, as well as a collection of door/window and motion sensors that beam a signal to the RF Bridge on various state changes. This signal usually consists of a unique ID and a state, run together in one string, along with some signal parameters:

rfbridge/tele/RESULT = {“Time”:“2020-08-24T22:08:05”,“RfReceived”:{“Sync”:14050,“Low”:470,“High”:1350,“Data”:“D3FD1A”,“RfKey”:“None”}}
rfbridge/tele/RESULT = {“Time”:“2020-08-24T22:08:10”,“RfReceived”:{“Sync”:14000,“Low”:470,“High”:1350,“Data”:“D3FD1E”,“RfKey”:“None”}}

Where D3FD1 is the unique ID, A means open and E means closed (6 means low battery).

As the number of sensors grows, trying to keep track of the codes using automations to set the value of binary sensors leads to a big pile of code that needs maintaining with any change. There have been a few solutions that used a python script to loop through the IDs, but it still means you need to kep the script updated.

To solve this I created a pair of automations that are triggered by the publication of data to the rfbridge/tele/RESULT topic, and then create and maintain MQTT devices in Home Assistant corresponding to each sensor.

Upon being triggered, the automations first filter the data through some conditions (since there is a surprising amount of RF garbage noise that looks like a signal but isn’t) for signal paramaters (Sync, Low and High values) as well as data structure (does it end with an E, A or 6).

One automation then takes data that manages to make it through the filters and publishes a pair of MQTT messages to the homeassistant/binary_sensor topic - one for battery OK or not, the other for open or closed. These messages include the unique ID of the sensor, the device class (door or battery), MQTT topic the device publishes on*, manufacturer (put what you want here), retain flag etc. If you have auto discover set up in HomeAssistant, it will receive the messages on the HomeAssistant topic and create a device under the unique ID of the door sensor, with a binary sensor entity each for battery and open/closed state of the sensor.

I also have an input boolean set up to control the triggering of this automation - it is set to off each time the automation runs so that you can control when it is listening for new devices. Even with the filtering I still found many new devices after a few days that were created by noise rather than new sensors. Just turn on the input boolean, trigger the sensor, and it will create a new device and then stop listening.

The second automation also publishes a pair of MQTT messages, but to the topic created in the first automation (see * above), one message each for battery and open/closed state. This updates the entities created by the first automation, relaying the state of the physical sensor through to the corresponding entity in HomeAssistant. While there is still noise received that causes nonsense MQTT messages to be generated, because they don’t correspond to anything HomeAssistant recognizes they are ignored.

While what I have posted below is targeted at door/window sensors, anything that produces a signal that an RF Bridge can receive should work, including motion sensors, moisture sensors, vibration sensors etc. You will have to edit the signal paramaters (hint: look at the console on your RF Bridge to see what it’s receiving) as well as the data bit (not everything uses the same values), but the principle remains the same.

Last note, I have the data automation set to retain= true. This means that the state of a door or window will survive a reboot, but it also means that the nonsense MQTT messages will pile up in the MQTT server. I dont know what to do about that…

Code:

- alias: door_sensor_device_config
  trigger:
    platform: mqtt
    topic: rfbridge/tele/RESULT
  condition:
    condition: and
    conditions:
    - condition: template
      value_template: '{{trigger.payload_json.RfReceived.Sync | int > 13800 }}'
    - condition: state
      entity_id: input_boolean.door_sensor_discovery
      state: 'on'
    - condition: or
      conditions:
      - condition: template
        value_template: '{{trigger.payload_json.RfReceived.Data[-1] == "A" }}'
      - condition: template
        value_template: '{{trigger.payload_json.RfReceived.Data[-1] == "E" }}'
      - condition: template
        value_template: '{{trigger.payload_json.RfReceived.Data[-1] == "6" }}'
  action:
  - service: mqtt.publish
    data_template:
      topic: '{{ ''homeassistant/binary_sensor/batt_state'' + trigger.payload_json.RfReceived.Data[:5]
        + ''/config'' }}'
      payload: "{\n  \"name\": \"{{ 'Door/Window Battery ' + trigger.payload_json.RfReceived.Data[:5]}}\"\
        ,\n  \"stat_t\": \"~BATT_STATE\",\n  \"device_class\": \"battery\",\n  \"\
        uniq_id\": \"{{ 'Batt-' + trigger.payload_json.RfReceived.Data[:5]}}\",\n\
        \  \"device\": {\n    \"identifiers\": [\n      \"{{trigger.payload_json.RfReceived.Data[:5]}}\"\
        \n    ],\n  \"name\": \"{{ 'Door/Window ' + trigger.payload_json.RfReceived.Data[:5]}}\"\
        ,\n  \"model\": \"Door/Window Sensor\"\n,\n  \"manufacturer\": \"CompanyName\"\
        \n  },\n  \"~\": \"{{ trigger.payload_json.RfReceived.Data[:5] + '/tele/'\
        \ }}\"\n}"
      retain: true
  - service: mqtt.publish
    data_template:
      topic: '{{ ''homeassistant/binary_sensor/door_state'' + trigger.payload_json.RfReceived.Data[:5]
        + ''/config'' }}'
      payload: "{\n  \"name\": \"{{ 'Door/Window State ' + trigger.payload_json.RfReceived.Data[:5]}}\"\
        ,\n  \"stat_t\": \"~DOOR_STATE\",\n  \"device_class\": \"door\",\n  \"uniq_id\"\
        : \"{{ 'State-' + trigger.payload_json.RfReceived.Data[:5]}}\",\n  \"device\"\
        : {\n    \"identifiers\": [\n      \"{{trigger.payload_json.RfReceived.Data[:5]}}\"\
        \n    ]\n  },\n  \"~\": \"{{ trigger.payload_json.RfReceived.Data[:5] + '/tele/'\
        \ }}\"\n}"
      retain: true
  - service: input_boolean.turn_off
    entity_id: input_boolean.door_sensor_discovery
  id: 2535483654
- alias: door_sensor_data
  trigger:
    platform: mqtt
    topic: rfbridge/tele/RESULT
  condition:
    condition: and
    conditions:
    - condition: template
      value_template: '{{trigger.payload_json.RfReceived.Sync | int > 13800 }}'
    - condition: or
      conditions:
      - condition: template
        value_template: '{{trigger.payload_json.RfReceived.Data[-1] == "A" }}'
      - condition: template
        value_template: '{{trigger.payload_json.RfReceived.Data[-1] == "E" }}'
      - condition: template
        value_template: '{{trigger.payload_json.RfReceived.Data[-1] == "6" }}'
  action:
  - service: mqtt.publish
    data_template:
      topic: '{{ trigger.payload_json.RfReceived.Data[:5] + ''/tele/DOOR_STATE'' }}'
      payload: "\n{% if trigger.payload_json.RfReceived.Data[-1] == \"A\" %}\n  ON\n\
        {% elif trigger.payload_json.RfReceived.Data[-1] == \"E\" %}\n  OFF\n{% endif\
        \ %}"
      retain: true
  - service: mqtt.publish
    data_template:
      topic: '{{ trigger.payload_json.RfReceived.Data[:5] + ''/tele/BATT_STATE'' }}'
      payload: "\n{% if trigger.payload_json.RfReceived.Data[-1] == \"6\" %}\n  ON\n\
        {% else %}\n  OFF\n{% endif %}"
      retain: true
  id: 8648374648

Hope this is useful! I’d love to hear any comments or ways to improve upon it!

7 Likes

Genius!! Thanks for sharing.

@Brandon_Beierle, great automation! I’ve made a few changes and wanted to share with the community. I believe the addresses for the sensors are only 16 bit as the last 2 values in the data field are always 0x0A (D or 6) and verified with 40 different sensors (though all from the same vendor). As such, I added another conditional filter to help avoid bogus 433MHz sensors. The other change I did was to the device naming (4 digit vs 5 - addressing etc) and the topic structure in MQTT to avoid far too many root level topics. All sensors are now under rfbridge/‘address’. I’m also using 2x Tasmota bridges so the trigger condition is slightly different as well.

Discovery Automation:

alias: Window Sensor Device Config
description: ''
trigger:
  - platform: mqtt
    topic: rfbridge/rfbridge1/tele/RESULT
  - platform: mqtt
    topic: rfbridge/rfbridge2/tele/RESULT
condition:
  - condition: and
    conditions:
      - condition: and
        conditions:
          - condition: template
            value_template: '{{trigger.payload_json.RfReceived.Sync | int > 13800 }}'
          - condition: template
            value_template: '{{trigger.payload_json.RfReceived.Data[-2] == "0" }}'
      - condition: state
        entity_id: input_boolean.window_sensor_discovery
        state: 'on'
      - condition: or
        conditions:
          - condition: template
            value_template: '{{trigger.payload_json.RfReceived.Data[-1] == "A" }}'
          - condition: template
            value_template: '{{trigger.payload_json.RfReceived.Data[-1] == "E" }}'
          - condition: template
            value_template: '{{trigger.payload_json.RfReceived.Data[-1] == "6" }}'
action:
  - service: mqtt.publish
    data_template:
      topic: >-
        {{ 'homeassistant/binary_sensor/batt_state' +
        trigger.payload_json.RfReceived.Data[:4] + '/config' }}
      payload: |-
        {
          "name": "{{ 'Window Battery ' + trigger.payload_json.RfReceived.Data[:4]}}",
          "stat_t": "~BATT_STATE",
          "device_class": "battery",
          "uniq_id": "{{ 'Batt-' + trigger.payload_json.RfReceived.Data[:4]}}",
          "device": {
            "identifiers": [
              "{{trigger.payload_json.RfReceived.Data[:4]}}"
            ],
          "name": "{{ 'Window ' + trigger.payload_json.RfReceived.Data[:4]}}",
          "model": "Door/Window Sensor"
        ,
          "manufacturer": "CompanyName"
          },
          "~": "{{ 'rfbridge/' + trigger.payload_json.RfReceived.Data[:4] + '/tele/' }}"
        }
      retain: true
  - service: mqtt.publish
    data_template:
      topic: >-
        {{ 'homeassistant/binary_sensor/window_state' +
        trigger.payload_json.RfReceived.Data[:4] + '/config' }}
      payload: |-
        {
          "name": "{{ 'Window State ' + trigger.payload_json.RfReceived.Data[:4]}}",
          "stat_t": "~WINDOW_STATE",
          "device_class": "window",
          "uniq_id": "{{ 'State-' + trigger.payload_json.RfReceived.Data[:4]}}",
          "device": {
            "identifiers": [
              "{{trigger.payload_json.RfReceived.Data[:4]}}"
            ]
          },
          "~": "{{ 'rfbridge/' + trigger.payload_json.RfReceived.Data[:4] + '/tele/' }}"
        }
      retain: true
  - service: input_boolean.turn_off
    entity_id: input_boolean.window_sensor_discovery
mode: single

Data Automation:

alias: Window Sensor Data
trigger:
  - platform: mqtt
    topic: rfbridge/rfbridge1/tele/RESULT
  - platform: mqtt
    topic: rfbridge/rfbridge2/tele/RESULT
condition:
  - condition: and
    conditions:
      - condition: and
        conditions:
          - condition: template
            value_template: '{{trigger.payload_json.RfReceived.Sync | int > 13800 }}'
          - condition: template
            value_template: '{{trigger.payload_json.RfReceived.Data[-2] == "0" }}'
  - condition: or
    conditions:
      - condition: template
        value_template: '{{trigger.payload_json.RfReceived.Data[-1] == "A" }}'
      - condition: template
        value_template: '{{trigger.payload_json.RfReceived.Data[-1] == "E" }}'
      - condition: template
        value_template: '{{trigger.payload_json.RfReceived.Data[-1] == "6" }}'
action:
  - service: mqtt.publish
    data_template:
      topic: >-
        {{ 'rfbridge/' + trigger.payload_json.RfReceived.Data[:4] +
        '/tele/WINDOW_STATE' }}
      payload: |
        {% if trigger.payload_json.RfReceived.Data[-1] == "A" %}
          ON
        {% elif trigger.payload_json.RfReceived.Data[-1] == "E" %}
          OFF
        {% endif %}
      retain: true
  - service: mqtt.publish
    data_template:
      topic: >-
        {{ 'rfbridge/' +  trigger.payload_json.RfReceived.Data[:4] +
        '/tele/BATT_STATE' }}
      payload: |
        {% if trigger.payload_json.RfReceived.Data[-1] == "6" %}
          ON
        {% else %}
          OFF
        {% endif %}
      retain: true
mode: single

1 Like

@LorDHaZarD Glad it was useful! It’s been so long since I wrote it I’ll have to look at the original to understand your changes…

FWIW you can use as many rfbridges as you want on the same mqtt topic…I just set all of mine to rfbridge. If they all receive the RF signal you get multiple but identical messages so you don’t need to differentiate the bridges.

Thanks for the new ideas!

That was my initial intent - to have both rfbridges write to the same MQTT topic… I noticed that Tasmota doesnt break apart the ‘online’ status from the rest of the telemetry so if I wanted to know the status of each bridge I needed separate topics. Or at least that’s how it seemed to shape out in the Tasmota config, I’m all ears if there’s a better way to configure Tas for this kind of role. Thanks!

Home Assistant still assigns a separate device number, and keeps them as separate devices. My HA shows me each bridge’s status, including IP address, uptime etc. (all the info in the tele/rfbridge/hass_state line you can see in the console). You are correct that the online topic is the same as far as the broker is concerned, so if you have an external entity consuming that topic it could create a problem I suppose. Equally, if there is an LWT (like when you reboot) it shows up under the MQTT traffic (in the HA device screen) attributed by to all rfbridges using that topic, and HA will show all your bridges as being offline until they are all back up. For me, this hasn’t been an issue, but I suppose it’s not ideal.
However, assuming one of your bridges gets unplugged or loses it’s wifi signal, it won’t send an LWT, so it won’t make the others look offline, and thats when the HA device status will let you know which one is down.

So I did a complete overhaul of how I’m handling these 433 MHz sensors in HA…

I ditched Tasmota on the Sonoff RF bridges in favor of ESPHome with a bespoke configuration for these sensors - it also removes one of the automations from earlier. The idea here again is that I have 2 or 3x of the Sonoff bridges in various locations throughout my house all publishing to the same state topic per RF sensor which improves reliability if one of the bridges missed the single packet from a window sensor. Other note, I’m using the tamper switch (not physically populated on my sensors) to clear the battery low flag as it seems that once set this flag it might not get cleared otherwise. When I do go to replace a battery I’ll just short the pads where the tamper switch would have been and that will clear the battery low state. The discovery automation will also pre-set this to cleared as well.

ESPHome Configuration (modify as required for your configuration):
[bssid preferentially set to closest AP, multiple APs broadcasting same SSID]

substitutions:
  device_name: rf-bridge_attic
  device_description: Sonoff 433 MHz RF Bridge for window sensors
  friendly_name: RF Bridge - Attic

esphome:
  name: ${device_name}
  comment: ${device_description}
  platform: ESP8266
  board: esp01_1m

wifi:
  networks:
  - ssid: !secret wifi_ssid
    password: !secret wifi_pwd
    bssid: 86:2a:a8:94:fc:b0
  fast_connect: true
  
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ${device_name}
    password: !secret esphome_pwd

captive_portal:

# Enable logging
logger:
  baud_rate: 0

# Enable Home Assistant API
api:
  password: !secret esphome_pwd

ota:
  password: !secret esphome_pwd

# Enable web server
web_server:
  port: 80

time:
  - platform: homeassistant
    id: homeassistant_time

uart:
  tx_pin: GPIO01
  rx_pin: GPIO03
  baud_rate: 19200

rf_bridge:
  on_code_received:
    - homeassistant.event:
        event: esphome.rf-bridge_code_received
        data:
          sync: !lambda 'char buffer [10];return itoa(data.sync,buffer,16);'
          low: !lambda 'char buffer [10];return itoa(data.low,buffer,16);'
          high: !lambda 'char buffer [10];return itoa(data.high,buffer,16);'
          code: !lambda 'char buffer [10];return itoa(data.code,buffer,16);'
    - mqtt.publish:
        topic: !lambda |-
          char topic[] = "rfbridge/";
          char buffer [10];
          if ((data.code % 256) == 6 || (data.code % 256) == 7){
            return strcat(strncat(topic, itoa(data.code,buffer,16), 4), "/state/battery");
          }
          else if( (data.code % 256) == 10 || (data.code % 256) == 14) {
            return strcat(strncat(topic, itoa(data.code,buffer,16), 4), "/state/window");
          }
          else {
            return strcat(strncat(topic, itoa(data.code,buffer,16), 4), "/state/unknown");
          }
        payload: !lambda |-
          if ((data.code % 256) == 6) {
            return "ON";
          }
          if ((data.code % 256) == 7) {
            return "OFF";
          }
          else if((data.code % 256) == 10) {
            return "ON";
          }
          else if((data.code % 256) == 14) {
            return "OFF";
          }
          else {
            return "error";
          }
        retain: true
        
mqtt:
  broker: 192.168.1.5
  username: !secret mqtt_user
  password: !secret mqtt_pass
  topic_prefix: 'esphome/${device_name}'
  discovery: false
          
sensor:
 # Reports the WiFi signal strength
  - platform: wifi_signal
    name: ${device_name} Signal
    update_interval: 10s
    filters:
      - sliding_window_moving_average:
          window_size: 6
          send_every: 6

# Reports how long the device has been powered (in minutes)
  - platform: uptime
    name: ${device_name} Uptime
    id: uptime_seconds
    filters:
      - lambda: return x / 60.0;
    unit_of_measurement: minutes
    icon: mdi:clock-start

binary_sensor:
  - platform: status
    name: "${device_name} Status"
    id: bridge_status
    on_state:
      then:
        - if:
            condition: 
              binary_sensor.is_on: bridge_status
            then: 
              light.turn_on: wifi_led
            else:
              light.turn_off: wifi_led
          
  - platform: gpio
    pin: GPIO00
    name: "${device_name} Button"
    filters:
      - invert:
    internal: true

light:
  - platform: binary
    name:  "${device_name} WiFi LED"
    id: wifi_led
    output: output_wifi_led
    internal: True

output:
  - platform: gpio
    pin:
      number: GPIO13
    id: output_wifi_led
    inverted: true

#switch:
#  - platform: restart
#    name: "${device_name} Restart"

On the HA side I only have one automation now for the initial configuration of these sensors which uses the esphome event triggers rather than MQTT. I also removed the helper boolean to turn on/off this MQTT discovery feature - I just enable and disable the automation when I’m adding new devices. Also did a few tweaks on the MQTT tree side of things in order to keep devices/states a bit more nested logically.

MQTT Discovery Automation:

alias: Window Sensor Device Config
description: ''
trigger:
  - platform: event
    event_type: esphome.rf-bridge_code_received
condition:
  - condition: and
    conditions:
      - condition: or
        conditions:
          - condition: template
            value_template: '{{ trigger.event.data.code[-1] == "a" }}'
          - condition: template
            value_template: '{{ trigger.event.data.code[-1] == "e" }}'
      - condition: template
        value_template: '{{ trigger.event.data.code[-2] == "0" }}'
action:
  - service: mqtt.publish
    data_template:
      topic: >-
        {{ 'homeassistant/binary_sensor/' + trigger.event.data.code[:4] +
        '/batt_state/config' }}
      payload: |-
        {
          "name": "{{ 'Window ' + trigger.event.data.code[:4] + ' Battery' }}",
          "state_topic": "~battery",
          "device_class": "battery",
          "uniq_id": "{{ trigger.event.data.code[:4] + '-batt' }}",
          "device": {
            "identifiers": [
              "{{ trigger.event.data.code[:4] }}"
            ],
          "name": "{{ 'Window ' + trigger.event.data.code[:4] }}",
          "model": "{{'Door/Window Sensor: ' + trigger.event.data.code[:4]}}",
          "manufacturer": "ESPHome 433 MHz RF Bridge"
          },
          "~": "{{ 'rfbridge/' + trigger.event.data.code[:4] + '/state/' }}"
        }
      retain: true
  - service: mqtt.publish
    data_template:
      topic: >-
        {{ 'homeassistant/binary_sensor/' + trigger.event.data.code[:4] +
        '/window_state/config' }}
      payload: |-
        {
          "name": "{{ 'Window ' + trigger.event.data.code[:4] + ' State' }}",
          "state_topic": "~window",
          "device_class": "window",
          "uniq_id": "{{ trigger.event.data.code[:4] + '-state' }}",
          "device": {
            "identifiers": [
              "{{ trigger.event.data.code[:4] }}"
            ]
          },
          "~": "{{ 'rfbridge/' + trigger.event.data.code[:4] + '/state/' }}"
        }
      retain: true
  - service: mqtt.publish
    data:
      topic: '{{''rfbridge/'' +  trigger.event.data.code[:4] + ''/state/battery'' }}'
      payload: 'OFF'
      retain: true
mode: single

Hey man, just wanted to say thanks to you and @Brandon_Beierle , this resolved an issue I was having with retain status and makes life so much easier for bringing in these GS-WDS07 sensors into my network.

looking at this… Can I upload a esphome bin file using tasmota to reflash the sonoff bridge? Its messy doing this with a serial adapter.

Thank you. FYI there is a project where the author has hacked the microcontroller used in these sensors (8051) to add more features.

I am also using sdr_433 to monitor these sensors using a sdr dongle and weighing up which method to adopt long term…