Sonoff Zigbee (SNZB) Sensors & Switches with Zigbee2MQTT

Tags: #<Tag:0x00007efebde63210> #<Tag:0x00007efebde63080> #<Tag:0x00007efebde62f68>

With the recent release of the affordable Sonoff Zigbee range, I decided to upgrade some sensors around the house from RF to Zigbee. Here is how I added them to my openHAB installation.

There are (at least) two ways to go about adding Zigbee things:

  1. The Zigbee Binding
  2. Using a Zigbee to MQTT bridge

I chose the second option.


This guide assumes that you already have a working MQTT Broker such as the embedded openHAB MQTT Broker Moquette or Mosquitto.

Note: This guide contains references to a Bridge to the embedded MQTT broker (mqtt:broker:bridge). Replace that with a reference to your own broker bridge, where necessary.

ZigBee Hardware

Note: * = Not covered yet as I don’t have one.




  1. Prepare the CC2531

    Follow the guide in the Zigbee2MQTT documentation.

  2. Setup Zigbee2MQTT

    The software can be installed in various ways that are all well documented. I chose Docker.

  3. Add Map Transformations






    device_connected=DEVICE CONNECTED
    device_ban=DEVICE BAN
    device_ban_failed=DEVICE BAN FAILED
    device_announced=DEVICE ANNOUNCED
    device_removed=DEVICE REMOVE
    device_removed_failed=DEVICE REMOVE FAILED
    device_force_removed=DEVICE FORCE REMOVE
    device_force_removed_failed=DEVICE FORCE REMOVE FAILED
    device_banned=DEVICE BANNED
    device_whitelisted=DEVICE WHITELISTED
    device_renamed=DEVICE RENAMED
    group_renamed=GROUP RENAMED
    group_added=GROUP ADDED
    group_removed=GROUP REMOVED
    device_bind=DEVICE BOUND
    device_unbind=DEVICE UNBOUND
    device_group_add=DEVICE GROUP ADD
    device_group_add_failed=DEVICE GROUP ADD FAILED
    device_group_remove=DEVICE GROUP REMOVE
    device_group_remove_failed=DEVICE GROUP REMOVE FAILED
    device_group_remove_all=DEVICE GROUP REMOVE ALL
    device_group_remove_all_failed=DEVICE GROUP REMOVE ALL FAILED
    devices=LIST ALL DEVICES
    groups=LIST ALL GROUPS
    zigbee_publish_error=ZIGBEE PUBLISH ERROR
    ota_update=OTA UPDATE


  4. Add Zigbee2MQTT Thing

    Create a new Things file and add the following:

    Thing mqtt:topic:zigbee2mqtt "Zigbee 2 MQTT" (mqtt:broker:bridge) {
            Type contact : state       "State"              [ stateTopic="zigbee2mqtt/bridge/state", off="offline", on="online"]
            Type string : version      "Version"            [ stateTopic="zigbee2mqtt/bridge/config", transformationPattern="JSONPATH:$.version"]
            Type string : commit       "Commit"             [ stateTopic="zigbee2mqtt/bridge/config", transformationPattern="JSONPATH:$.commit"]
            Type string : type         "Type"               [ stateTopic="zigbee2mqtt/bridge/config", transformationPattern="REGEX:(.*type.*)‚ą©JSONPATH:$.coordinator.type"]
            Type string : transportrev "Transport Revision" [ stateTopic="zigbee2mqtt/bridge/config", transformationPattern="REGEX:(.*meta.*)‚ą©JSONPATH:$.coordinator.meta.transportrev"]
            Type string : product      "Product"            [ stateTopic="zigbee2mqtt/bridge/config", transformationPattern="REGEX:(.*meta.*)‚ą©JSONPATH:$.coordinator.meta.product"]
            Type string : majorrel     "Major Release"      [ stateTopic="zigbee2mqtt/bridge/config", transformationPattern="REGEX:(.*meta.*)‚ą©JSONPATH:$.coordinator.meta.majorrel"]
            Type string : minorrel     "Minor Release"      [ stateTopic="zigbee2mqtt/bridge/config", transformationPattern="REGEX:(.*meta.*)‚ą©JSONPATH:$.coordinator.meta.minorrel"]
            Type string : maintrel     "Main Release"       [ stateTopic="zigbee2mqtt/bridge/config", transformationPattern="REGEX:(.*meta.*)‚ą©JSONPATH:$.coordinator.meta.maintrel"]
            Type string : revision     "Revision"           [ stateTopic="zigbee2mqtt/bridge/config", transformationPattern="REGEX:(.*meta.*)‚ą©JSONPATH:$.coordinator.meta.revision"]
            Type number : log          "Log Level"          [ stateTopic="zigbee2mqtt/bridge/config", commandTopic="zigbee2mqtt/bridge/config/log_level", transformationPattern="REGEX:(.*log_level.*)‚ą©JSONPATH:$.log_level"]
            Type switch : permit       "Permit Join"        [ stateTopic="zigbee2mqtt/bridge/config", commandTopic="zigbee2mqtt/bridge/config/permit_join", transformationPattern="REGEX:(.*permit_join.*)‚ą©JSONPATH:$.permit_join", off="false", on="true"]
            Type string : logtype      "Log type"           [ stateTopic="zigbee2mqtt/bridge/log", transformationPattern="JSONPATH:$.type"]
            Type string : message      "Message"            [ stateTopic="zigbee2mqtt/bridge/log", transformationPattern="JSONPATH:$.message"]
            Type string : friendlyname "Friendly name"      [ stateTopic="zigbee2mqtt/bridge/log", transformationPattern="REGEX:(.*friendly_name.*)‚ą©JSONPATH:$.meta.friendly_name"]
            Type string : model        "Model"              [ stateTopic="zigbee2mqtt/bridge/log", transformationPattern="REGEX:(.*model.*)‚ą©JSONPATH:$.meta.model"]
            Type string : vendor       "Vendor"             [ stateTopic="zigbee2mqtt/bridge/log", transformationPattern="REGEX:(.*vendor.*)‚ą©JSONPATH:$.meta.vendor"]
            Type string : description  "Description"        [ stateTopic="zigbee2mqtt/bridge/log", transformationPattern="REGEX:(.*description.*)‚ą©JSONPATH:$.meta.description"]
            Type contact : supported   "Supported"          [ stateTopic="zigbee2mqtt/bridge/log", transformationPattern="REGEX:(.*supported.*)‚ą©JSONPATH:$.meta.supported", off="false", on="true"]
  5. Add Zigbee2MQTT Item

    Put this in an Items file:

    Group  Zigbee_2_MQTT "Zigbee2MQTT"    <zigbee2mqtt>     (ZigbeeBridges)
    Contact Zigbee_2_MQTT_State       "State: [MAP(]" <contactable> (Zigbee_2_MQTT) { channel="mqtt:topic:zigbee2mqtt:state" }
    String  Zigbee_2_MQTT_Version     "Version: [%s]"                  <zigbee2mqtt> (Zigbee_2_MQTT) { channel="mqtt:topic:zigbee2mqtt:version" }
    String  Zigbee_2_MQTT_Commit      "Commit: [%s]"                   <zigbee2mqtt> (Zigbee_2_MQTT) { channel="mqtt:topic:zigbee2mqtt:commit" }
    String  Zigbee_2_MQTT_Type        "Type: [%s]"                     <zigbee2mqtt> (Zigbee_2_MQTT) { channel="mqtt:topic:zigbee2mqtt:type" }
    String  Zigbee_2_MQTT_Transport   "Transport rev: [%s]"            <zigbee2mqtt> (Zigbee_2_MQTT) { channel="mqtt:topic:zigbee2mqtt:transportrev" }
    String  Zigbee_2_MQTT_Product     "Product: [%s]"                  <zigbee2mqtt> (Zigbee_2_MQTT) { channel="mqtt:topic:zigbee2mqtt:product" }
    String  Zigbee_2_MQTT_Major_Rel   "Major rel: [%s]"                <zigbee2mqtt> (Zigbee_2_MQTT) { channel="mqtt:topic:zigbee2mqtt:majorrel" }
    String  Zigbee_2_MQTT_Minor_Rel   "Minor rel: [%s]"                <zigbee2mqtt> (Zigbee_2_MQTT) { channel="mqtt:topic:zigbee2mqtt:minorrel" }
    String  Zigbee_2_MQTT_Main_Rel    "Main rel: [%s]"                 <zigbee2mqtt> (Zigbee_2_MQTT) { channel="mqtt:topic:zigbee2mqtt:maintrel" }
    String  Zigbee_2_MQTT_Revision    "Revision: [%s]"                 <zigbee2mqtt> (Zigbee_2_MQTT) { channel="mqtt:topic:zigbee2mqtt:revision" }
    Switch  Zigbee_2_MQTT_Permit_Join "Permit Join: []"                <zigbee2mqtt> (Zigbee_2_MQTT) { channel="mqtt:topic:zigbee2mqtt:permit" } 
    Group  Zigbee_2_MQTT_Logs "Zigbee2MQTT Logs" <text> (Zigbee_2_MQTT)
    Number  Zigbee_2_MQTT_Log_Level   "Log level: [MAP(]" <text> (Zigbee_2_MQTT_Logs) { channel="mqtt:topic:zigbee2mqtt:log" } 
    String  Zigbee_2_MQTT_Log_Type    "Log type: [MAP(]"   <text> (Zigbee_2_MQTT_Logs) { channel="mqtt:topic:zigbee2mqtt:logtype" } 
    String  Zigbee_2_MQTT_Log_Message "Log message: [%s]"                              <text> (Zigbee_2_MQTT_Logs) { channel="mqtt:topic:zigbee2mqtt:message" } 
    String  Zigbee_2_MQTT_Friendly    "Friendly name: [%s]"                            <text> (Zigbee_2_MQTT_Logs) { channel="mqtt:topic:zigbee2mqtt:friendlyname" } 
    String  Zigbee_2_MQTT_Model       "Model: [%s]"                                    <text> (Zigbee_2_MQTT_Logs) { channel="mqtt:topic:zigbee2mqtt:model" } 
    String  Zigbee_2_MQTT_Vendor      "Vendor: [%s]"                                   <text> (Zigbee_2_MQTT_Logs) { channel="mqtt:topic:zigbee2mqtt:vendor" } 
    String  Zigbee_2_MQTT_Description "Description: [%s]"                              <text> (Zigbee_2_MQTT_Logs) { channel="mqtt:topic:zigbee2mqtt:description" } 
    Contact Zigbee_2_MQTT_Supported   "Supported: [MAP(]"  <text> (Zigbee_2_MQTT_Logs) { channel="mqtt:topic:zigbee2mqtt:supported" } 
  6. Discover Zigbee Things and IDs

    I refer you to the Zigbee2MQTT documentation (last time, swear :crossed_fingers:)

  7. Add Zigbee Things

    Some of the Sonoff Zigbee things have payload values in common - such as battery_low and tamper between the Motion and Wireless Door/Window sensors.

    If you are adding either or both of those devices, create the following Map transformation:


    1: Wireless Switches

             Reports actions. Can be one of the following three values: single, double and long.

    1. Add a new Thing to a file

      Thing mqtt:topic:snzb01a "My Awesome Button" (mqtt:broker:bridge) @ "Some Fancy Room" {
           Type string : action       "Action"       [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="REGEX:(.*action.*)‚ą©JSONPATH:$.action"]
           Type number : linkquality  "Link Quality" [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="JSONPATH:$.linkquality"]
           Type number : battery      "Battery"      [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="JSONPATH:$.battery"]
           Type number : voltage      "Voltage"      [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="JSONPATH:$.voltage"]
    2. Add a new Item to a file

      Group ZigbeeButtonActions "Zigbee Buttons" <button>
      Group  SNZB_01_1_Button "My Awesome Button" <sonoff_snzb01> (GF_SomeRoom, Sonoff_SNZB_01)
      String  SNZB_01_1_Button_Action  "Action: [%s]"      <none> (SNZB_01_1_Button, ZigbeeButtonActions) { channel="mqtt:topic:snzb01a:action" } 
      Number  SNZB_01_1_Button_Link    "Link quality [%d]" <none> (SNZB_01_1_Button)                      { channel="mqtt:topic:snzb01a:linkquality" }
      Number  SNZB_01_1_Button_Battery "Battery [%d]"      <none> (SNZB_01_1_Button)                      { channel="mqtt:topic:snzb01a:battery" }
      Number  SNZB_01_1_Button_Voltage "Voltage [%d]"      <none> (SNZB_01_1_Button)                      { channel="mqtt:topic:snzb01a:voltage" }
    3. Create a Rule

      val String filename = "my_super_cool_zigbee_rule_file.rules"
      rule "Zigbee button received update"
         Member of ZigbeeButtonActions received update
         logInfo(filename, "Zigbee button received update: {}",
         switch (triggeringItem) {
            case SNZB_01_1_Button_Action: {
               switch (triggeringItem.state) {
                  case 'single': {
                     // Do stuff
                  case 'double': {
                     // Do other stuff
                  case 'long': {
                     // Do super secret stuff
    2: Temperature and Humidity Sensors

             Not covered yet.

    1. Add a new Thing to a file

    2. Add a new Item to a file

    3. Create a Rule

    3: Motion Sensors

             Reports occupancy.

    1. Create Map Transformations

    2. Add a new Thing to a file

      Thing mqtt:topic:snzb03a "Important Motion Sensor" (mqtt:broker:bridge) @ "Important Room" {
           Type contact : occupancy   "Occupancy"    [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="REGEX:(.*occupancy.*)‚ą©JS:snzb03_occupancy.js"]
           Type contact : tamper      "Tamper"       [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="REGEX:(.*tamper.*)‚ą©JS:tamper.js"]
           Type contact : battery_low "Battery Low"  [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="REGEX:(.*battery_low.*)‚ą©JS:battery_low.js"]
           Type number : linkquality  "Link Quality" [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="JSONPATH:$.linkquality"]
           Type number : battery      "Battery"      [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="JSONPATH:$.battery"]
           Type number : voltage      "Voltage"      [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="JSONPATH:$.voltage"]
    3. Add a new Item to a file:

      Group MotionSensors "Motion Sensors" <motion>
      Group  SNZB_03_1_Motion_Sensor "Important Room Motion Sensor" <sonoff_snzb03> (GF_ImportantRoom, Sonoff_SNZB_03)
      Contact SNZB_03_1_Motion_Sensor_Occupancy   "Important Room [MAP(]" <motion> (SNZB_03_1_Motion_Sensor, MotionSensors) { channel="mqtt:topic:snzb03a:occupancy" }
      Contact SNZB_03_1_Motion_Sensor_Tamper      "Tamper [MAP(]"              <none>   (SNZB_03_1_Motion_Sensor)                { channel="mqtt:topic:snzb03a:tamper" }
      Contact SNZB_03_1_Motion_Sensor_Battery_Low "Battery low [MAP(]"    <none>   (SNZB_03_1_Motion_Sensor)                { channel="mqtt:topic:snzb03a:battery_low" }
      Number  SNZB_03_1_Motion_Sensor_Link        "Link quality [%d]"                             <none>   (SNZB_03_1_Motion_Sensor)                { channel="mqtt:topic:snzb03a:linkquality" }
      Number  SNZB_03_1_Motion_Sensor_Battery     "Battery [%d]"                                  <none>   (SNZB_03_1_Motion_Sensor)                { channel="mqtt:topic:snzb03a:battery" }
      Number  SNZB_03_1_Motion_Sensor_Voltage     "Voltage [%d]"                                  <none>   (SNZB_03_1_Motion_Sensor)                { channel="mqtt:topic:snzb03a:voltage" }
    • Create a new Rule:
      val String filename = "best_rule_file_ever.rules"
      rule "Motion sensor status"
         Member of MotionSensors changed
         logInfo(filename, + ": " + triggeringItem.state)
         switch (triggeringItem.state) {
            case OPEN: {
               switch (triggeringItem) {
                  case SNZB_03_1_Motion_Sensor_Occupancy: {
                     // Motion detected, let the dogs loose!
            case CLOSED: {
               switch (triggeringItem) {
                  case SNZB_03_1_Motion_Sensor_Occupancy: {
                     // No more motion detected
    4: Wireless Door/Window Sensors

             Reports contact.

    1. Add a new Thing to a file

      Thing mqtt:topic:snzb04a "Hidden Door Contact Sensor" (mqtt:broker:bridge) @ "Hidden Room" {
            Type contact : sensor      "Contact"      [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="REGEX:(.*contact.*)‚ą©JSONPATH:$.contact", on="false", off="true"]
            Type contact : tamper      "Tamper"       [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="REGEX:(.*tamper.*)‚ą©JSONPATH:$.tamper", on="true", off="false"]
            Type contact : battery_low "Battery Low"  [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="REGEX:(.*battery_low.*)‚ą©JSONPATH:$.battery_low", on="true", off="false"]
            Type number : linkquality  "Link Quality" [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="JSONPATH:$.linkquality"]
            Type number : battery      "Battery"      [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="JSONPATH:$.battery"]
            Type number : voltage      "Voltage"      [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="JSONPATH:$.voltage"]
    2. Add a new Item to a file

      Group DoorSensors   "Door Sensors"   <door>  
      Group  SNZB_04_1_Contact_Sensor "Hidden Door Contact Sensor" <sonoff_snzb04> (GF_HiddenRoom, Sonoff_SNZB_04)
      Contact SNZB_04_1_Contact_Sensor_Contact     "Contact: [%s]"                              <door> (SNZB_04_1_Contact_Sensor, DoorSensors) { channel="mqtt:topic:snzb04a:sensor" } 
      Contact SNZB_04_1_Contact_Sensor_Tamper      "Tamper [MAP(]"           <none> (SNZB_04_1_Contact_Sensor)              { channel="mqtt:topic:snzb04a:tamper" }
      Contact SNZB_04_1_Contact_Sensor_Battery_Low "Battery low [MAP(]" <none> (SNZB_04_1_Contact_Sensor)              { channel="mqtt:topic:snzb04a:battery_low" }
      Number  SNZB_04_1_Contact_Sensor_Link        "Link quality [%d]"                          <none> (SNZB_04_1_Contact_Sensor)              { channel="mqtt:topic:snzb04a:linkquality" }
      Number  SNZB_04_1_Contact_Sensor_Battery     "Battery [%d]"                               <none> (SNZB_04_1_Contact_Sensor)              { channel="mqtt:topic:snzb04a:battery" }
      Number  SNZB_04_1_Contact_Sensor_Voltage     "Voltage [%d]"                               <none> (SNZB_04_1_Contact_Sensor)              { channel="mqtt:topic:snzb04a:voltage" }
    3. Create a Rule

      val String filename = "doors_and_windows_and_tings.rules"
      rule "Door sensor status"
          Member of DoorSensors changed
          logInfo(filename, + ": " + triggeringItem.state)
          switch (triggeringItem.state) {
             case OPEN: {
                 switch (triggeringItem) {
                     case SNZB_04_1_Contact_Sensor_Contact: {
                        // The ***ting door is open!
             case CLOSED: {
                 switch (triggeringItem) {
                     case SNZB_04_1_Contact_Sensor_Contact: {
                        // Closed now

Done. :sunglasses:


Thanks for this, looks great!

You can do at least this one, and probably a few more directly in the channel without the JS transformation:


Will have to re-evaluate the JS transformations as they are probably not all necessary, I know the contact sensor one is useful though as it flips the OPEN = True mapping to the opposite of the rest. They helped me get through implementation without errors so I included them here.

Bring on the improvements though. Let’s trim-the-fat/shed-some-weight/less-is-more this approach!

I hear you - I went on the same journey recently with WLED stuff!

I think you can trim this down very easily already. For example, on your motion sensor, you could get rid of all the JS transforms by changing your things file as follows:

Type contact : occupancy   "Occupancy"    [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="JSONPATH:$.data.occupancy", on="true", off="false"]
Type contact : tamper      "Tamper"       [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="JSONPATH:$.data.tamper", on="true", off="false"]
Type contact : battery_low "Battery Low"  [ stateTopic="zigbee2mqtt/UNIQUE_ZIGBEE_ID", transformationPattern="JSONPATH:$.data.battery_low", on="true", off="false"]

The above will use the JSONPATH transform to extract the required values, then map true and false to on and off (which for a contact channel actually means true and false will map to open and closed). If you want to switch it, you can map on="false" and off="true"…

(I’ve made an assumption here on what the device actually returns on these topics, so you may need to tweak)

1 Like

Good idea. Done! Replaced with a few simple maps. Much cleaner now.

Also improved my Zigbee2MQTT item by leaning on what I picked up from your post on the topic. Muchas gracias :+1:

1 Like

I have this setup in place, but the range is not that good. Sonoff also released a ZigBee bridge which can be flashed with Tasmota. I saw some tutorials to connect it directly with home assistant through their ZigBee binding. Is this also possible with the Openhab ZigBee binding?
Connecting everything via mqtt is possible, but it can be easier :smile:

Hey there,
I know you decided to go the MQTT way, but was wondering if anyone has any idea if it will just work with Zigbee Binding directly!
I’m thinking of buying some of those, but would be nice if someone had a positive experience already.

Yes, I was able to get SONOFF SNZB-03 motion sensor working with standard ZigBee binding directly. The key to discovering is to change the ZigBee USB stick channel to 15. See this thread. I believe it is a ZigBee 3.0 strangeness. (Zigbee 3.0 Installation Code )