Thirdreality Smart Presence Sensor R2

Does the Thirdreality Smart Presence Sensor R2 work with C-8? I have the Zigbee pairing but not luck with it working as a motion or presence sensor. Any suggestions appreciated.

What driver are you using?

I have not installed additional drivers. Using the options for the device. Tried options on the Device Info tab for Generic Zigbee Motion Sensor (no temp), as indicated in the hardware documentation. Tried several others including Generic Component Motion Sensor. Hope this answers the question.

Many thanks if the response

As an experiment, you might want to try this driver that I created for the Thirdreality R3 product:

Many thanks. This did not seem to be what is needed. I had a discussion with GROK and found a Thirdreality Motion R1 driver and it did not work. Interesting the Hubitat delivered Compatible Devices for Thirdreality are not available in the “Type” drop down on the Device Info tab after Zigbee pairing.

Again, thanks for your reply and assistance.

This one seems to work for my R2 that I just bought, nothing else seemed to have.

/**

ThirdReality Presence Sensor R2 (24GHz mmWave)

Hubitat driver



Handles BOTH reporting paths because the R2 fingerprint isn't documented:

IAS Zone (cluster 0x0500)        -> motion via zone status

Occupancy Sensing (cluster 0x0406) -> motion via occupancy bitmap

Whichever the device actually uses will drive the "motion" attribute.



Enable "debug logging", trip the sensor, and check Logs to see which

cluster fires. That confirms the real reporting path.
*/

import hubitat.zigbee.zcl.DataType

metadata {
definition(name: "ThirdReality Presence Sensor R2",
namespace: "heph",
author: "heph") {
capability "Motion Sensor"
capability "Battery"
capability "Configuration"
capability "Refresh"
capability "Sensor"

    attribute "lastUpdate", "string"

    // Generic 24GHz radar fingerprint. inClusters list is permissive:
    // 0000 basic, 0001 power, 0003 identify, 0406 occupancy, 0500 IAS.
    fingerprint profileId: "0104", endpointId: "01",
        inClusters: "0000,0001,0003,0406,0500",
        outClusters: "0019",
        manufacturer: "Third Reality, Inc", model: "3RPR"
    fingerprint profileId: "0104", endpointId: "01",
        inClusters: "0000,0001,0003,0500",
        outClusters: "0019",
        manufacturer: "Third Reality, Inc", model: "3RPR"
}

preferences {
    input name: "resetSeconds", type: "number",
        title: "Auto-reset motion after N seconds of no report (0 = trust device)",
        defaultValue: 0, range: "0..3600"
    input name: "logEnable", type: "bool",
        title: "Enable debug logging", defaultValue: true
    input name: "txtEnable", type: "bool",
        title: "Enable description text logging", defaultValue: true
}

}

/* ---------------- lifecycle ---------------- */

def installed() {
log.info "installed"
configure()
}

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

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

/* ---------------- parse ---------------- */

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

// IAS zone status can arrive as a plain string, not a descMap
if (description?.startsWith("zone status") || description?.startsWith("zone report")) {
    def zs = description.contains("0x") ?
        Integer.decode((description =~ /0x[0-9A-Fa-f]+/)[0]) : 0
    handleMotion((zs & 0x01) != 0)
    return
}

Map d = zigbee.parseDescriptionAsMap(description)
if (logEnable) log.debug "descMap: ${d}"

// IAS Zone Status Change Notification (cluster-specific cmd 0x00)
// payload: zoneStatus(uint16 LE) + extStatus + zoneId + delay
if (d.clusterInt == 0x0500 && d.isClusterSpecific && d.command == "00" && d.data?.size() >= 2) {
    Integer zs = zigbee.convertHexToInt("${d.data[1]}${d.data[0]}")
    handleMotion((zs & 0x01) != 0)
    return
}

// collect main attr + any nested additional attrs into one list
List<Map> attrs = []
if (d.attrInt != null) attrs << [cluster: d.clusterInt, attr: d.attrInt, value: d.value]
d.additionalAttrs?.each {
    attrs << [cluster: d.clusterInt, attr: Integer.parseInt(it.attrId, 16), value: it.value]
}

attrs.each { a -> handleAttr(a.cluster, a.attr, a.value) }

}

private handleAttr(Integer cluster, Integer attr, String value) {
if (value == null) return
switch (cluster) {
case 0x0406: // Occupancy Sensing
if (attr == 0x0000) {
handleMotion((Integer.parseInt(value, 16) & 0x01) != 0)
}
break
case 0x0500: // IAS Zone status attribute
if (attr == 0x0002) {
handleMotion((Integer.parseInt(value, 16) & 0x01) != 0)
}
break
case 0x0001: // Power Configuration
if (attr == 0x0021) {        // battery percentage (0-200)
Integer pct = Math.round(Integer.parseInt(value, 16) / 2)
pct = Math.min(100, Math.max(0, pct))
sendBattery(pct)
} else if (attr == 0x0020) { // battery voltage (100mV units)
Integer mv = Integer.parseInt(value, 16) * 100
// 2 AA ~ 3.0V full, 2.1V empty (rough fallback only)
Integer pct = Math.round(((mv - 2100) / (3000 - 2100)) * 100)
pct = Math.min(100, Math.max(0, pct))
if (device.currentValue("battery") == null) sendBattery(pct)
}
break
}
}

/* ---------------- helpers ---------------- */

private handleMotion(boolean active) {
String v = active ? "active" : "inactive"
if (device.currentValue("motion") != v) {
if (txtEnable) log.info "motion is ${v}"
sendEvent(name: "motion", value: v)
}
sendEvent(name: "lastUpdate", value: new Date().format("yyyy-MM-dd HH:mm:ss"))

unschedule(resetMotion)
if (active && (resetSeconds as Integer) > 0) {
    runIn(resetSeconds as Integer, resetMotion)
}

}

def resetMotion() {
if (txtEnable) log.info "motion auto-reset to inactive"
sendEvent(name: "motion", value: "inactive")
}

private sendBattery(Integer pct) {
if (txtEnable) log.info "battery ${pct}%"
sendEvent(name: "battery", value: pct, unit: "%")
}

/* ---------------- commands ---------------- */

def refresh() {
if (logEnable) log.debug "refresh"
List cmds = 

cmds += zigbee.readAttribute(0x0406, 0x0000)   // occupancy
cmds += zigbee.readAttribute(0x0500, 0x0002)   // IAS zone status
cmds += zigbee.readAttribute(0x0001, 0x0021)   // battery %
cmds += zigbee.readAttribute(0x0001, 0x0020)   // battery voltage
return cmds
}

def configure() {
if (logEnable) log.debug "configure"
List cmds = 


// IAS enrollment (harmless if device uses 0x0406 instead)
cmds += zigbee.enrollResponse()
cmds += zigbee.writeAttribute(0x0500, 0x0010, DataType.IEEE_ADDRESS,
                              "${device.hub.zigbeeEui}")

// bind + reporting for both possible paths
cmds += zigbee.configureReporting(0x0406, 0x0000, DataType.BITMAP8, 0, 3600, null)
cmds += zigbee.configureReporting(0x0500, 0x0002, DataType.BITMAP16, 0, 3600, null)
cmds += zigbee.configureReporting(0x0001, 0x0021, DataType.UINT8, 3600, 21600, 1)

cmds += refresh()
return cmds

}

Heph,

Many thanks. Updated the driver and look great. Will test and see results.

John