Reverse Contact State in Aeotec Dry Contact Sensor?

Recent ST convert here... just learning how everything works. With ST I was using a couple of Aeotec Dry Contact sensors to monitor a gate and a mailbox with external switches. I had rewrite the DTH because the default handler would show an open contact as closed and a closed contact as open. I can't seem to figure out how to rewrite the driver in Hubitat because I'm using a built-in driver. Is there another driver that would show the contact correctly, or one that I could rewrite to make it work? Thanks!

If you can get the groovy driver from ST it will probably convert over pretty easy. Then just switch to that driver, it will be just as it was before. There is a thread on here about converting ST code to HE. A few minor tweaks but for the most part it's the same.

The above advice is good, and if you're not sure how to do it yourself, someone (including me) could probably also do it for you if you post the original source, assuming it's not reliant on something odd that Hubitat doesn't support (unlikley, but who knows).

You won't be able to "rewrite" a Hubitat stock driver because they are not open-source, though they do have a few examples (not this one) published on their public GitHub as examples for developers.

An alternative to any of the above: if you don't mind creating a second device, you can create a virtual contact sensor, then use a rule (or custom app or whatever automation works for you) to set that sensor state to the opposite of the "real" one whenever that changes. Then, use the virtual sensor instead of the real one in all your automations. For example, in Rule Machine, this would look like:

Trigger: Real Sensor *changes* (select the "Contact" capability for this)

Actions:

IF (Real Sensor is open) THEN
  Virtual Sensor: close()
ELSE
  Virtual sensor: open()
END-IF

(The "open()" and "close()" things above are custom commands, which sound scary but are just a type of action you can choose in Rule Machine, and they're necessary here since I don't think any of RM's built-in actions can run these commands on virtual devices--this is a bit of an outlying case since, of course, normally you'd only read values from sensors, not set them.)

1 Like

Yeah, I considered using a virtual sensor and a webCoRE piston to flip the states around but I'd prefer a proper driver if it's possible.

I was able to find a DTH that someone rewrote for ST that reverses the contacts.

https://raw.githubusercontent.com/constjs/jcdevhandlers/master/devicetypes/jscgs350/my-zwave-reversed-contact-sensor.src/my-zwave-reversed-contact-sensor.groovy

If you want to point me in the right direction I could take a shot at it. I'm pretty familiar with ST DTH but I've never modified a Hubitat driver before. Or feel free to edit it yourself if it's something that could be done quickly and easily. Thanks!

OK, so I probably did a little more than I needed to, but I couldn't stand how non-Hubitat-ty the logging looked, so I added options and cleaned it up a bit (and added info logging they were lacking). I also fixed everything I could that looks like it would have broken if the sensor supports (and was included with) S2 security. I left a lot of oddities that might be normal in ST DTHs (been a while...) but should be harmless on Hubitat.

Most of the other modifications I did were just the usual: the physicalgraph namespace becomes hubitat, the tiles and simulator sections are not used (so can be removed), and whatnot: App and driver porting to Hubitat

If this was too much, I can try again and not try as hard in case that messed something up. I haven't tested this with any real sensors. Hope it works for you!

/**

 *  Copyright 2015 SmartThings

 *

 *  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:

 *

 *      http://www.apache.org/licenses/LICENSE-2.0

 *

 *  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.

 *

 *  Z-Wave Reversed Contact Sensor.  Does the opposite of the regular door/window sensor.  Built specifically for the

 *  Aeotec Aeon Dry Contact Sensor for @Boatman (John Lee).

 *

 *  Author: SmartThings

 *  Date: 2013-11-3

 *  Version: v2

 *

 *  Updates:

 *  -------

 *  04-17-2016 : Initial commit

 *  12-07-2020 : Modified for Hubitat

 *

 */

metadata {

   definition (name: "My Z-Wave Reversed Contact Sensor", namespace: "jscgs350", author: "jscgs350") {

      capability "ContactSensor"

      capability "Sensor"

      capability "Battery"

      capability "Configuration"

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

//    fingerprint deviceId: "0x07", inClusters: "0x30"

//    fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82"

   }

   preferences {

      input name: "enableDebug", type: "bool", title: "Enable debug logging", defaultValue: true

      input name: "enableDesc", type: "bool", title: "Enable descriptionText logging", defaultValue: true

   }

}

void logsOff(){

   log.warn "Auto-disabling debug logging..."

   device.updateSetting("enableDebug", [value:"false", type:"bool"])

}

def parse(String description) {

   if (enableDebug) log.debug "parse: $description"

   def result = null

   if (description.startsWith("Err 106")) {

      if (getDataValue("S2")) {

         if (enableDebug) log.debug description

      } else {

         log.warn "This sensor failed to complete the network security key exchange. If you are unable to control it, 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, 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)

      }

   }

   if (enableDebug) log.debug "parsed '$description' to $result"

   return result

}

def updated() {

   if (enableDebug != false) runIn(1800, logsOff)

   def cmds = []

   if (!state.MSR) {

      cmds = [

         zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),

         "delay 1200",

         zwaveSecureEncap(zwave.wakeUpV1.wakeUpNoMoreInformation())

      ]

   } else if (!state.lastbat) {

      cmds = []

   } else {

      cmds = [zwaveSecureEncap(zwave.wakeUpV1.wakeUpNoMoreInformation())]

   }

   response(cmds)

}

def configure() {

   delayBetween([

      zwaveSecureEncap(zwave.manufacturerSpecificV2.manufacturerSpecificGet()),

      batteryGetCommand()

   ], 6000)

}

def sensorValueEvent(value) {

   if (enableDesc && device.currentValue("contact") != (value ? "closed" : "open")) log.info "$device.displayName is ${value ? 'closed' : 'open'}"

   if (value) {

      createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed")

   } else {

      createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open")

   }

}

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

{

   sensorValueEvent(cmd.value)

}

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

{

   sensorValueEvent(cmd.value)

}

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

{

   sensorValueEvent(cmd.value)

}

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

{

   sensorValueEvent(cmd.sensorValue)

}

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

{

   sensorValueEvent(cmd.sensorState)

}

def zwaveEvent(hubitat.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) {

         if (enableDesc) log.info "$device.displayName covering was removed"

         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)

   }

   result

}

def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd)

{

   def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)

   def cmds = []

   if (!state.MSR) {

      cmds << zwaveSecureEncap(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))

      cmds << zwaveSecureEncap(zwave.manufacturerSpecificV2.manufacturerSpecificGet())

      cmds << "delay 1200"

   }

   if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) {

      cmds << batteryGetCommand()

   } else {

      cmds << zwaveSecureEncap(zwave.wakeUpV1.wakeUpNoMoreInformation())

   }

   [event, response(cmds)]

}

def zwaveEvent(hubitat.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()

   if (enableDesc) log.info "$device.displayName battery is ${map.value}%"

   [createEvent(map), response(zwave.wakeUpV1.wakeUpNoMoreInformation())]

}

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {

   def result = []

   def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)

   if (enableDebug) 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())

      }

   }

   result

}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {

   hubitat.zwave.Command encapCmd = cmd.encapsulatedCommand([0x20: 1, 0x85: 2, 0x70: 1])

   if (encapCmd) {

      zwaveEvent(encapCmd)

   }

}

def zwaveEvent(hubitat.zwave.Command cmd) {

   createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)

}

def batteryGetCommand() {

   zwaveSecureEncap(zwave.batteryV1.batteryGet())

}

Wow, this is great! As soon as it stops snowing I will test this out and confirm that it works. Thanks for that link as well.

Worked perfectly. Thanks again!

1 Like

Just a note to say thanks for this code. ST user in transition to Hubitat and had exactly the same issue. Seems to work great.

1 Like