EcoLink (DW-ZWAVE2) Door/Window sensor as a water sensor?

Hi All,
I’m trying to use an EcoLink (DW-ZWAVE2) Door/Window sensor as a water sensor in my basement but having problems. I’m obviously using wires attached to the internal terminals but the device handlers that I’ve tried (see below) either don’t work or report the opposite status [“wet” when wires not touching (open circuit) and “dry” when touching (closed circuit)].

  • Z-Wave Water Sensor – detects state changes but reports opposite “wet <-> dry”
  • SmartSense Moisture – detects state changes but reports opposite “wet <-> dry”
  • SmartSense Moisture Sensor – does not detect state changes
  • Fibaro Flood Sensor – does not detect state changes
  • Fibaro Flood Sensor ZW5 – does not detect state changes
  • Everspring Flood Sensor – does not detect state changes
  • Water Sensor Capability – does not detect state changes
  • Fortrezz Wetness Sensor – does not detect state changes
  • Utilitech Flood Sensor (by tosa68) – does not detect state changes

It works flawlessly, with magnet or wires, when configuring as door/window sensor using the standard “Z-Wave Door/Window Sensor” device handler.

Is there a specific device handler that I should be using to get this working properly as a water sensor or do I need to modify one of the above handlers that “sort-of” work?


That’s what you have to do. Another community member has already done that, and I believe made a DH for this purpose:


Thanks for the link John! Unfortunately the DH written by @eradicatore didn’t work for me. It does not detect a state change at all. His (“Z-Wave ~Water Sensor”) DH appears to be a modification to the default “Z-Wave Water Sensor” DH so that it works with a “Monoprice” sensor. I’m actually using the “EcoLink” door sensor and it apparently doesn’t work with that custom DH. I’d love to find a solution as I have several of these sensors that I’d like to deploy as water sensors…

Hi @Limulus64, try this one:

Change the label on line 34 to Wet and the label on line 35 to Dry.

Lines 98 and 100 could use a tweak, and the DH name on line 20. Give that a try?

EDIT: I created this one this past Christmas to let me know when my tree needed watering.

Thanks again @johnconstantelo… but still no go. It doesn’t recognize state change and just reads “Wet” (I changed line 34) whether the circuit is open or closed.

On a side note… what sensor did you have paired with that DH because I love the idea of an Xmas tree water monitor! :slight_smile:

I have two set up that way using the same DH. One is a Monoprice and the other is the Ecolink (which is why I recommended trying mine).

Thanks @johnconstantelo, i am still able to use your code for DH and switch the water level to dry/wet label.

1 Like


  • Copyright 2016 Stuart Buchanan
  • Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except
  • in compliance with the License. You may obtain a copy of the License at:
  • Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
  • on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
  • for the specific language governing permissions and limitations under the License.
  • Ecolink Water Sensor (DSB45-ZWEU/DSB45-ZWUS)
  • Author: Stuart Buchanan, Based on original work by Tosa with thanks
  • Date: 2019-01-12

metadata {
definition (name: “Ecolink Water Sensor”, namespace: “Joeyski”, author: “Joey Estrella”) {
capability “Water Sensor”
capability “Battery”
capability “Configuration”

    fingerprint deviceId: "0x2001", inClusters: "0x30,0x80,0x84,0x71,0x70,0x85,0x86,0x72"

simulator {
	status "dry": "command: 2001, payload: 00"
	status "wet": "command: 2001, payload: FF"

tiles {
standardTile(“water”, “device.water”, width: 2, height: 2) {
state “dry”, icon:“st.alarm.water.dry”, backgroundColor:"#ffffff"
state “wet”, icon:“st.alarm.water.wet”, backgroundColor:"#53a7c0"
valueTile(“battery”, “device.battery”, inactiveLabel: false, canChangeBackground: true) {
state “battery”, label:’${currentValue}% Battery’, unit:"",
[value: 19, color: “#BC2323”],
[value: 20, color: “#D04E00”],
[value: 30, color: “#D04E00”],
[value: 40, color: “#DAC400”],
[value: 41, color: “#79b821”]
standardTile(“configure”, “device.configure”, inactiveLabel: false, decoration: “flat”) {
state “configure”, label:’’, action:“configuration.configure”, icon:“st.secondary.configure”
main “water”
details([“water”, “battery”, “configure”])

def parse(String description) {
def result = null
if (description.startsWith(“Err 106”)) {
if (state.sec) {
log.debug description
} else {
log.debug “This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.”
result = createEvent(
descriptionText: “This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.”,
eventType: “ALERT”,
name: “secureInclusion”,
value: “failed”,
isStateChange: true,
} else if (description != “updated”) {
def cmd = zwave.parse(description, [0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1])
if (cmd) {
result = zwaveEvent(cmd)
log.debug “parsed '{description}' to {result}”
return result

def updated() {
def cmds =
if (!state.MSR) {
cmds = [
“delay 1200”,
} else if (!state.lastbat) {
cmds =
} else {
cmds = [zwave.wakeUpV1.wakeUpNoMoreInformation().format()]

def configure() {
], 6000)

def sensorValueEvent(value) {
if (value) {
log.debug “Sensor it Dry”
createEvent(name: “water”, value: “dry”, descriptionText: “$device.displayName is dry”)
} else {
log.debug “Sensor it Wet”
createEvent(name: “water”, value: “wet”, descriptionText: “$device.displayName is wet”)

def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd)

def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd)

def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd)

def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd)

def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd)

def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd)
def result =
if (cmd.notificationType == 0x06 && cmd.event == 0x16) {
result << sensorValueEvent(1)
} else if (cmd.notificationType == 0x06 && cmd.event == 0x17) {
result << sensorValueEvent(0)
} else if (cmd.notificationType == 0x07) {
if (cmd.v1AlarmType == 0x07) { // special case for nonstandard messages from Monoprice door/window sensors
result << sensorValueEvent(cmd.v1AlarmLevel)
} else if (cmd.event == 0x01 || cmd.event == 0x02) {
result << sensorValueEvent(1)
} else if (cmd.event == 0x03) {
result << createEvent(descriptionText: “$device.displayName covering was removed”, isStateChange: true)
result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
result << createEvent(descriptionText: “$device.displayName detected glass breakage”, isStateChange: true)
} else if (cmd.event == 0x07) {
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
result << createEvent(name: “motion”, value: “active”, descriptionText:"$device.displayName detected motion")
} else if (cmd.notificationType) {
def text = “Notification cmd.notificationType: event {([cmd.event] + cmd.eventParameter).join(”, “)}”
result << createEvent(name: “notification$cmd.notificationType”, value: “$cmd.event”, descriptionText: text, displayed: false)
} else {
def value = cmd.v1AlarmLevel == 255 ? “active” : cmd.v1AlarmLevel ?: “inactive”
result << createEvent(name: “alarm $cmd.v1AlarmType”, value: value, displayed: false)

def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
def event = createEvent(descriptionText: “${device.displayName} woke up”, isStateChange: false)
def cmds =
if (!state.MSR) {
cmds << zwave.wakeUpV1.wakeUpIntervalSet(seconds:43600, nodeid:zwaveHubNodeId).format()
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()
cmds << “delay 1200”
if (!state.lastbat || now() - state.lastbat > 53
60601000) {
cmds << batteryGetCommand()
} else {
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
[event, response(cmds)]

def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [ name: “battery”, unit: “%” ]
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = “${device.displayName} has a low battery”
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
state.lastbat = now()
[createEvent(map), response(zwave.wakeUpV1.wakeUpNoMoreInformation())]

def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
def result =

def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
log.debug "msr: $msr"
updateDataValue("MSR", msr)

result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false)

if (msr == "011A-0601-0901") {  // Enerwave motion doesn't always get the associationSet that the hub sends on join
	result << response(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))
} else if (!device.currentState("battery")) {
	if (msr == "0086-0102-0059") {
		result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format())
	} else {
		result << response(batteryGetCommand())



def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x85: 2, 0x70: 1])
// log.debug “encapsulated: $encapsulatedCommand”
if (encapsulatedCommand) {
state.sec = 1

def zwaveEvent(physicalgraph.zwave.Command cmd) {
createEvent(descriptionText: “$device.displayName: $cmd”, displayed: false)

def batteryGetCommand() {
def cmd = zwave.batteryV1.batteryGet()
if (state.sec) {
cmd = zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd)