Zunzunbee 8-Button Slate Switch [BETA}

I just got the Zunzunbee 8-Button Slate Switch knowing that it works with Home Assistant but not Hubitat. HOWEVER, in less than 30-minutes, my friend Claude and I landed on this driver if using 8 buttons. It does short press and long press for each of 8 buttons and has temperature & battery sensor. It can be setup for less buttons. If you want less, I don’t know if that changes anything in driver. The number of buttons used is set on the device itself, not in Device settings in Home Assistant or Hubitat. It also includes stickers to place on the button controller’s face.

The driver is below if anyone else ends up buying one. I just got it so I can’t recommend or discourage purchase yet.

If you have issues, I can try to help but it will probably be Claude running the debug. If any experts have suggestions for improvement, they are more than welcome.

/**
 * Hubitat Zigbee Driver: zunzunbee Slate Switch (SSWZ8T)
 * 8-button battery-powered Zigbee 3.0 scene controller
 *
 * Button actions decoded from IAS Zone commandStatusChangeNotification
 * Zone status values map directly to button number and press type.
 *
 * Buttons 1-8, each supporting: short_press, long_press
 * Also exposes: battery %, temperature (°F)
 *
 * Author: Stephen Nutt (device testing & reverse engineering) 
 *         with Claude (code generation)
 *         Based on zigbee-herdsman-converters PR #11298
 * Date: 2026-05-26
 */

import hubitat.zigbee.clusters.iaszone.ZoneStatus
import groovy.transform.Field

metadata {
    definition(
        name: "zunzunbee Slate Switch (SSWZ8T)",
        namespace: "zunzunbee",
        author: "Claude",
        importUrl: ""
    ) {
        capability "PushableButton"
        capability "HoldableButton"
        capability "Battery"
        capability "TemperatureMeasurement"
        capability "Refresh"
        capability "Configuration"

        attribute "lastAction", "string"

        fingerprint profileId: "0104",
                    inClusters: "0000,0001,0003,0402,0500",
                    outClusters: "0019",
                    model: "SSWZ8T",
                    manufacturer: "zunzunbee"
    }

    preferences {
        input name: "tempOffset", type: "decimal", title: "Temperature Offset (°F)", defaultValue: 0, range: "-10..10"
        input name: "logEnable",  type: "bool",    title: "Enable Debug Logging",    defaultValue: true
    }
}

@Field static final Map<Integer, String> ZONE_STATUS_MAP = [
    0x2002: "button_1_short_press",
    0x2003: "button_1_long_press",
    0x2004: "button_2_short_press",
    0x2005: "button_2_long_press",
    0x2008: "button_3_short_press",
    0x2009: "button_3_long_press",
    0x2010: "button_4_short_press",
    0x2011: "button_4_long_press",
    0x2020: "button_5_short_press",
    0x2021: "button_5_long_press",
    0x2040: "button_6_short_press",
    0x2041: "button_6_long_press",
    0x2080: "button_7_short_press",
    0x2081: "button_7_long_press",
    0x2100: "button_8_short_press",
    0x2101: "button_8_long_press"
]

def installed() {
    log.info "zunzunbee SSWZ8T installed"
    sendEvent(name: "numberOfButtons", value: 8)
    initialize()
}

def updated() {
    log.info "zunzunbee SSWZ8T updated"
    if (logEnable) runIn(1800, logsOff)
    initialize()
}

def initialize() {
    sendEvent(name: "numberOfButtons", value: 8)
}

def configure() {
    log.info "Configuring zunzunbee SSWZ8T"
    def cmds = []
    cmds += zigbee.enrollResponse()
    cmds += zigbee.configureReporting(0x0001, 0x0021, DataType.UINT8, 30, 3600, 5)
    cmds += zigbee.configureReporting(0x0402, 0x0000, DataType.INT16, 60, 1800, 50)
    return cmds
}

def refresh() {
    log.info "Refreshing zunzunbee SSWZ8T"
    def cmds = []
    cmds += zigbee.readAttribute(0x0001, 0x0021)
    cmds += zigbee.readAttribute(0x0402, 0x0000)
    return cmds
}

def parse(String description) {
    if (logEnable) log.debug "parse: ${description}"

    if (description?.startsWith("zone status")) {
        return parseIasZone(description)
    }

    def descMap = zigbee.parseDescriptionAsMap(description)
    if (!descMap) return

    if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0021) {
        def battPct = Integer.parseInt(descMap.value, 16)
        if (logEnable) log.debug "Battery: ${battPct}%"
        sendEvent(name: "battery", value: battPct, unit: "%")
        return
    }

    if (descMap.clusterInt == 0x0402 && descMap.attrInt == 0x0000) {
        def rawTemp = Integer.parseInt(descMap.value, 16)
        if (rawTemp > 32767) rawTemp -= 65536
        def tempC = rawTemp / 100.0
        def tempF = (tempC * 9 / 5 + 32) + (tempOffset ?: 0)
        tempF = Math.round(tempF *10)/ 10.0
        if (logEnable) log.debug "Temperature: ${tempF}°F"
        sendEvent(name: "temperature", value: tempF, unit: "°F")
        return
    }

    if (descMap.clusterInt == 0x0500) {
        if (logEnable) log.debug "IAS Zone cluster message: ${descMap}"
    }
}

private parseIasZone(String description) {
    def matcher = description =~ /zone status (0x[0-9A-Fa-f]+)/
    if (!matcher) {
        if (logEnable) log.debug "Could not parse zone status from: ${description}"
        return
    }

def zoneStatusHex = matcher[0][1]
    def zoneStatus    = Integer.parseInt(zoneStatusHex.replace("0x",""), 16)
    if (logEnable) log.debug "IAS zoneStatus: ${zoneStatusHex}"
    def action = ZONE_STATUS_MAP[zoneStatus]
    if (!action) {
        if (logEnable) log.debug "No action mapped for zoneStatus: ${zoneStatusHex}"
        return
    }

    if (logEnable) log.debug "Action: ${action}"
    sendEvent(name: "lastAction", value: action)

    def parts     = action.tokenize("_")
    def buttonNum = parts[1].toInteger()
    def pressType = parts[2]

    if (pressType == "short") {
        sendEvent(name: "pushed", value: buttonNum, isStateChange: true,
                  descriptionText: "Button ${buttonNum} short press")
    } else if (pressType == "long") {
        sendEvent(name: "held", value: buttonNum, isStateChange: true,
                  descriptionText: "Button ${buttonNum} long press")
    }
}

def logsOff() {
    log.warn "Debug logging disabled"
    device.updateSetting("logEnable", [value: "false", type: "bool"])
}

Dang, that is pretty cute, and the stickers are a nice touch.

Are you using the temperature sensing?

Also curious what the Device pages (Commands and Preferences) look like. Can you post screen shots (so we're sure this happened). :wink:

I am not sure that I trust the battery since it says 200 right now. It said 100 originally.

It just came today so I am not sure where I am going to use it yet so no plans for temperature sensor yet. When first paired with initial version of driver, Hubitat did not automatically select the correct driver.

Looks good. Looking forward to your reports on it's functionality/reliability, in particular of course, button presses on the device promptly responded to. :slight_smile:

Does it require a switch to mount on? I assume you can do a plain wall-mount as well, right?

Snap On Wall Mounted Smart Controller: Magnetically snaps over existing paddle or toggle wall plate screws. Slate Switch does NOT physically flip or actuate the underlying switch.

Ok, first negative is that it beeps. It makes one beep for short press and another for long. It also makes noise when pairing. I don’t think it’s a changeable preference.

That might mean I won’t use in Master Bedroom as planned.

I had a problem with my Deebot vacuum cleaner beeping way too much. Solve by opening it up and a quick cut to the speaker wire.

Maybe your device has some surgery in its future? Curious if it looks like it's openable at all without breaking it. Cuz the beep would be a game-over for me. I personally really hate beeping buttons.

Wall Mount without putting it on an existing light switch look possible? Thinking I'll use double sided tape if I had to.

It has magnets so you could probably just put a couple of screws in the sheetrock the right distance apart. :grin:

The beeps probably wouldn’t wake up your better half but………

I would open it, find the beeper on the board, and de-solder it from the board. That’s just me though.

There were a number of reviews on Amazon saying that button presses weren't consistently registered, so maybe the beeps are the manuf way of ensuring it's clear when the button press was actually received? Be interested to see how it seems to you after you/family use it for a bit.