[First driver] Eva Water Sensor

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

Did you also look at the publicly released source for a few of Hubitat's internal drivers?

You can ignore all ZDO messages (profileId:0000) for now; these are mostly related to the device connection status to the Zigbee coordinator (the HE hub). They are not directly related to the enrollment of the IAS device.

Probably it inserts a delay of 1200 ms before sending the enroll response, but I may be wrong.

Try adding

cmds.add zigbee.enrollResponse() + zigbee.readAttribute(0x0500, 0x0000)

in the configure() method after the bind command.

For most of the sleepy Zigbee devices, this enrollment code needs to be executed while the device is still in pairing mode, so you must pair it to HE again. Sending the initialization commands later usually has no effect.

Thanks, both! I've now got communication with the sensor. It sends messages when I put it in some water!

Had a look at the haloSmokeCoDetector example, and saw that they did the enrollResponse at configure time there, so I added it to my code.

Didn't know that it needed to be sent during pairing though, so thanks for the tip, kkossev!

ps. the argument to zigbee.enrollResponse(1200) does seem to be a millisecond delay, as it shows up in the log as he wattr 0x9488 0x01 0x0500 0x0010 0xF0 {ADCC6FFEFF445B38}, delay 1200, he raw 0x9488 1 0x01 0x0500 {01 23 00 00 00}, delay 1200,

Now I can get on with writing the actual driver. Thanks again!

1 Like

By the way did you try the generic watersensor driver? After you change to it, click save then click configure.

I did try it earlier, but I suppose I didn't try it while pairing.

Tried again now with a fresh sensor. Did add device -> By brand -> Generic/Other -> Generic Zigbee Moisture Sensor then add. Then paired. But the sensor switched to my driver automatically (guess the fingerprint overrides my selection?). So I changed the driver to "Generic Component Water Sensor" (is this the same as the Generic Zigbee Moisture Sensor above? The driver list doesn't seem to match the add devices list).

So after it paired with my driver, and I changed to the generic one, then dipped the sensor in some water, all I get from the logs is:

Whats it say on the device page attributes? And when you switched drivers did you click Configure?

Not the same driver. The component driver is for a device that has multiple components, one of which is a water sensor. The component driver will not work with your device.

@rlithgow1 is asking you to try the "Generic Zigbee Water Sensor" driver. To do so, after pairing, change to that driver and then click "Configure" on the device page.

Oops looks like I switched up the screenshots yesterday. Apologies!

The screenshot I sent was one I took to show that "Generic Component Water Sensor" isn't available as an option when pairing. Is that because it's a "Component" driver?

I did try changing the driver to "Generic Zigbee Moisture Sensor" (with and without temperature), but nothing happens when I dip the sensor. I did 'save' and 'configure'. When changing back to my driver. the sensor works again.

@aaiyar : There is no such thing as "Generic Zigbee Water Sensor" in the list of available drivers. The closest option is "Generic Zigbee Moisture Sensor" (which doesn't sound like a leak detector to me). How do we find documentation for that driver? Do we pick it just based on the driver name?

Try pairing the sensor again, after changing the driver to the inbuilt Generic Zigbee Moisture Sensor.

Or try clicking on the Confugure button at the same time, when you activate the sensor by wetting it.

1 Like

That worked (configure while wetting it). Nice trick to avoid repairing!

Seems like the driver is working.

For the future though, are these built in drivers documented somewhere?

Normally the Hub will find a matching driver using a "fingerprint". You can help the next Eva Water Sensor user by gathering the fingerprint data and giving it to Hubitat. They will incorporate that into the next build and anyone with the same device will have the driver auto selected.

  1. On that Device Info page, change the driver to "Device",
  2. Click Save Device.
  3. Click Get Info.
  4. Click Logs on the left.
  5. Click Past Logs.

There at the top will be a few lines related to that device's fingerprint:

dev:1996 2023-06-06 06:41:57.256 AM info  fingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0003,0004,0005,0006,0702,0B04,0B05,FC03", outClusters:"0019", model:"3210-L", manufacturer:"CentraLite"
dev:1996 2023-06-06 06:41:57.043 AM trace ZCL version:01
dev:1996 2023-06-06 06:41:57.035 AM trace Software Build Id:unknown
dev:1996 2023-06-06 06:41:57.033 AM trace Model:3210-L
dev:1996 2023-06-06 06:41:57.031 AM trace Manufacturer:CentraLite
dev:1996 2023-06-06 06:41:56.786 AM debug getting info for unknown Zigbee device...

Then set the Type back to the Generic Zigbee Moisture Sensor.

The above is NOT for the Eva Water Sensor.. please reply here with the Fingerprint line and include (using the @ symbol) Mike.Maxwell. --> @mike.maxwell

I guess that's a no to the documentation then :sweat_smile: But thanks for the info on how to add devices!

@mike.maxwell , here's the fingerprint from Device Info:

dev:82023-06-06 05:30:18.328 PMinfofingerprint profileId:"0104", endpointId:"01", inClusters:"0000,0001,0003,0500,FEED", outClusters:"0003,0019", model:"Water Sensor", manufacturer:"Eva"
dev:82023-06-06 05:30:17.382 PMtraceZCL version:03
dev:82023-06-06 05:30:17.370 PMtraceSoftware Build Id:0.2
dev:82023-06-06 05:30:17.367 PMtraceModel:Water Sensor
dev:82023-06-06 05:30:17.358 PMtraceManufacturer:Eva
dev:82023-06-06 05:30:16.665 PMdebuggetting info for unknown Zigbee device...
1 Like