I just got my Hubitat C-8 Elevation, and I have quite a few devices for which there are no drivers available. So I'd like to learn how to write custom drivers.
The first device I want to have a go at, is an Eva Water Sensor. I thought this should be a fairly simple one to get started with.
So I started by pairing up with manual Zigbee pairing to get the following paring information:
And then started following the Zigbee driver guide, and browsed some posts on this forum.
The driver I have so far is:
metadata {
definition (name: "Eva Water Sensor Test Driver", namespace: "sivert", author: "Sivert Sliper") {
capability "Configuration"
capability "WaterSensor"
fingerprint inClusters: "0000,0001,0003,0500,FEED", manufacturer: "Eva", model: "Water Sensor", deviceJoinName: "Eva Water Sensor"
}
// Check https://github.com/hubitat/HubitatPublic/blob/3e9b86a19ab6bc0f6799019aef43b8220b739f60/examples/drivers/haloSmokeCoDetector.groovy#L6
preferences {
// None for now -- but for Zigbee devices that offer attributes that
// can be written to set preferences, they are often included here.
// Later, we will add conventional Hubitat logging preferences here.
}
}
def installed() {
log.debug "installed()"
}
def updated() {
log.debug "updated()"
}
def parse(String description) {
// Can be helpful for debugging; for now we'll just always log:
log.debug "parse description: ${description}"
def result = []
if (description?.startsWith("enroll request")) {
List cmds = zigbee.enrollResponse(1200)
result = cmds?.collect { new hubitat.device.HubAction(it, hubitat.device.Protocol.ZIGBEE) }
return result
}
// else {
// if (description?.startsWith("zone status")) {
// result = parseIasMessage(description)
// } else {
// result = parseReportAttributeMessage(description)
// }
// }
// return result
def descMap = zigbee.parseDescriptionAsMap(description)
def mapstring = descMap.toMapString()
log.debug "Parsed the description: ${mapstring}"
// Parses hex (base 16) string data to Integer -- perhaps easier to work with:
//def rawValue = Integer.parseInt(descMap.value, 16)
def rawValue = 0
switch (descMap.clusterInt) {
case 0x0006: // On/Off
if (descMap.attrInt == 0) {
String switchValue
// attribute value of 0 means off, 1 (only other valid value) means on
switchValue = (rawValue == 0) ? "off" : "on"
// for now, always log -- normally we would offer a preference:
log.info "${device.displayName} switch is ${switchValue}"
// this is what actually generates the event:
sendEvent(name: "switch", value: switchValue, descriptionText: "${device.displayName} switch is ${switchValue}")
}
else {
// some atribute besides 0, which we don't care about but will log
// for now -- could also leave out if you know it's not needed:
log.debug "0x0006:${descMap.attrId}:${rawValue}"
}
case 0x0500: // IAS cluster
if (descMap.attrInt == 0) {
String waterValue
waterValue = (rawValue == 0) ? "wet" : "dry"
// for now, always log -- normally we would offer a preference:
log.info "${device.displayName} is ${waterValue}"
// this is what actually generates the event:
sendEvent(name: "water", value: waterValue, descriptionText: "${device.displayName} is ${switchValue}")
}
else {
// some atribute besides 0, which we don't care about but will log
// for now -- could also leave out if you know it's not needed:
log.debug "0x0500:${descMap.attrId}:${rawValue}"
}
// In other drivers, you may have other cases here
// For example, case 0x0008 for level, etc.
default:
// Probably not needed in most drivers but might be helpful for
// debugging -- always logging for now:
log.debug "ignoring {descMap.clusterId}:${descMap.attrId}:${rawValue}"
break
}
}
def configure() {
List<String> cmds = []
cmds.add "zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 0x01 0x0500 {${device.zigbeeId}} {}"
return sendCommands(cmds) // or delayBetween(cmds)
}
def sendCommands(List<String> cmds) {
log.debug "Sending command(s): ${cmds}"
return cmds
}
def on() {
zigbee.on()
}
def off() {
zigbee.off()
}
As you may see, I've made an attempt at binding to the IAS Zone cluster, and to respond to the enroll request. (What's the parameter 1200 for in enrollResponse? I copied it from this thread.)
However, I don't seem to get any enrollment requests from the device after pairing. What I see in the log is the following:
dev:72023-06-04 06:26:15.793 PMdebugignoring {descMap.clusterId}:null:0
dev:72023-06-04 06:26:15.790 PMdebug0x0500:null:0
dev:72023-06-04 06:26:15.786 PMdebug0x0006:null:0
dev:72023-06-04 06:26:15.783 PMdebugParsed the description: [raw:catchall: 0000 0006 00 00 0040 00 9488 00 00 0000 00 00 02FDFF040101190000, profileId:0000, clusterId:0006, clusterInt:6, sourceEndpoint:00, destinationEndpoint:00, options:0040, messageType:00, dni:9488, isClusterSpecific:false, isManufacturerSpecific:false, manufacturerId:0000, command:00, direction:00, data:[02, FD, FF, 04, 01, 01, 19, 00, 00]]
dev:72023-06-04 06:26:15.776 PMdebugparse description: catchall: 0000 0006 00 00 0040 00 9488 00 00 0000 00 00 02FDFF040101190000
dev:72023-06-04 06:23:13.193 PMdebugignoring {descMap.clusterId}:null:0
dev:72023-06-04 06:23:13.189 PMdebugParsed the description: [raw:catchall: 0000 8005 00 00 0040 00 9488 00 00 0000 00 00 C7008894020102, profileId:0000, clusterId:8005, clusterInt:32773, sourceEndpoint:00, destinationEndpoint:00, options:0040, messageType:00, dni:9488, isClusterSpecific:false, isManufacturerSpecific:false, manufacturerId:0000, command:00, direction:00, data:[C7, 00, 88, 94, 02, 01, 02]]
dev:72023-06-04 06:23:13.184 PMdebugignoring {descMap.clusterId}:null:0
dev:72023-06-04 06:23:13.181 PMdebugParsed the description: [raw:catchall: 0000 8005 00 00 0040 00 9488 00 00 0000 00 00 CA008894020102, profileId:0000, clusterId:8005, clusterInt:32773, sourceEndpoint:00, destinationEndpoint:00, options:0040, messageType:00, dni:9488, isClusterSpecific:false, isManufacturerSpecific:false, manufacturerId:0000, command:00, direction:00, data:[CA, 00, 88, 94, 02, 01, 02]]
dev:72023-06-04 06:23:13.176 PMdebugparse description: catchall: 0000 8005 00 00 0040 00 9488 00 00 0000 00 00 C7008894020102
dev:72023-06-04 06:23:13.170 PMdebugparse description: catchall: 0000 8005 00 00 0040 00 9488 00 00 0000 00 00 CA008894020102
dev:72023-06-04 06:23:12.342 PMdebugignoring {descMap.clusterId}:null:0
dev:72023-06-04 06:23:12.338 PMdebugParsed the description: [raw:catchall: 0000 8021 00 00 0040 00 9488 00 00 0000 00 00 D100, profileId:0000, clusterId:8021, clusterInt:32801, sourceEndpoint:00, destinationEndpoint:00, options:0040, messageType:00, dni:9488, isClusterSpecific:false, isManufacturerSpecific:false, manufacturerId:0000, command:00, direction:00, data:[D1, 00]]
dev:72023-06-04 06:23:12.180 PMdebugparse description: catchall: 0000 8021 00 00 0040 00 9488 00 00 0000 00 00 D100
dev:72023-06-04 06:23:11.708 PMdebugSending command(s): [zdo bind 0x9488 0x01 0x01 0x0500 {680AE2FFFE6BA7CA} {}]
dev:72023-06-04 06:23:11.690 PMdebuginstalled()
Is anyone able to make some sense of this log? Or maybe point me to some resources so I can understand how to decode a message like catchall: 0000 8021 00 00 0040 00 9488 00 00 0000 00 00 D100