I have a regular Sears door opener with a button that either opens or closes the door.
Initially I got the solution that @johnconstantelo developed working with a magnetic switch that triggered when the door closed. See Mimolite for garage door and magnetic door contact
This worked find except sometimes the close/open event would get missed. I don’t know if this is linked to current reports of flakey ST service or not but it meant that sometimes the door didn’t report closed or open which made the whole project questionable.
MIMOlite supports a “multi-sensor” feature that reports a variable level, not just a contact sensor, and the bit that is really useful is that it can be programmed to send periodic updates with the value of the sensor, as fast as very 10 seconds. This means that even if the trigger event is lost you’ll still get a message indicating the door position no more than 10 seconds later and you can fix the state of the door in the device handler.
To do this you have to associate the hub with the MMIOlite’s group 2. You then have to add a handler for the SensorMultilevelReport. The code for this looks like this. The first line tells MIMOlite to send the reports to the hub, the second tells it to send a message every 10 seconds, it sends a report ever scaledConfigurationValue * 10 seconds. Add this to the configure() function.
zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]).format(),
zwave.configurationV1.configurationSet(scaledConfigurationValue: 1, parameterNumber: 9, size: 2).format(),
NOTE: The configurationSet() has a size:2, not size:1 like all the existing handlers use, this doesn’t work, all configure command for MIMOlite must use size:2.
You can then create this handler to test if the door is closed, this is what you’d do if you modified @johnconstantelo device handler.
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv1.SensorMultilevelReport cmd) {
// the cmd.scaledSensorValue goes to zero when the door is closed, well almost zero
// so treat anything less than 20 as zero
sensorValueEvent( (cmd.sensorValue < 20) ? 0 : 0xff )
}
Also in the parse function you need to tell the parser to use 0x31: 1 (multisensor version 1).
Look for a line like this in the parse() function and make sure it has 0x31: 1 in it. If if has 0x31: 3 or 0x31: 2 change it to 0x31: 1.
def cmd = zwave.parse(description, [ ... , 0x31: 1, ... ]
I then decided to change the design to only use the multisensor, it provides much more information about the door position that a contact sensor.
I removed the contact switch and replaced it with with a potentiometer that connects to the rotating bar at the top of the garage door. This means that instead of getting an on/off indication I get the exact position the door is at. Also the MIMOlite can be programmed to send out an alert when the door is closed and open. If the door gets stuck half way up you can actually tell how far open it is.
Another bonus about this is that it now shows opening/closing states on the ST app even when you use the button on the garage wall or the old fashioned garage door remote control.
I used a WXD3-13 2W 10K ohm Multi Turn Poteniometer from amazon ($6 for 2) and hooked it up like it is shown in this web site How-to-install-poteniometer.
The only difference is that you hook up the 2 wires from the potentiometer to the SIG1 input on the MIMOlite. If you’ve wired up the contact switch already you can use the same two wires you connected to it to connect to the potentiometer. (you can’t use both sensors at the same time, so disconnect the magnetic sensor if you connect a potentiometer).
The device handler for the code is below. It is a complete rewrite from the original one.
If you use this you do need to calibrate the door when you first install it. There are 2 configure buttons on the device screen, press the “closed” one when the door is closed and the “open” one when the door is open, this records the position at which your particular door is at when it is open and closed so it can tell when your door is open and closed. (It also shows what position value was recorded).
There is also a display of the raw position of the door that is only there for debugging and geek value.
The way I’ve written the hander it doesn’t matter which way the potentiometer is wired, it figures out if you’ve wired it “backwards” and should work.
/**
* MIMOlite as a Garage Door Control with Potentiometer Multi Sensor
*
* Copyright 2016 Simon Capper
*
* 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.
*
* Inspired by a similar handler written by https://community.smartthings.com/users/johnconstantelo
*/
metadata {
definition (name: "MIMOlite - Garage Door Control", namespace: "skyjunky", author: "Simon Capper") {
capability "Polling"
capability "Refresh"
capability "Momentary"
capability "Configuration"
capability "Garage Door Control"
attribute "powerSupply", "string"
attribute "doorPosition", "number"
attribute "doorPositionClosed", "number"
attribute "doorPositionOpen", "number"
command "setOpenPosition"
command "setClosedPosition"
fingerprint deviceId:"0x1000", inClusters:"0x72, 0x86, 0x71, 0x30, 0x31, 0x35, 0x70, 0x85, 0x25"
}
// UI tile definitions
tiles(scale:2) {
multiAttributeTile(name:"door", type: "generic", width: 6, height: 4){
tileAttribute ("device.door", key: "PRIMARY_CONTROL") {
attributeState "open", label: "Open", action: "push", icon: "st.doors.garage.garage-open", backgroundColor: "#ffa81e", nextState:"closing"
attributeState "opening", label: "Opening", action: "push", icon: "st.doors.garage.garage-opening", backgroundColor: "#ffffff", nextState:"closing"
attributeState "closed", label: "Closed", action: "push", icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821", nextState:"opening"
attributeState "closing", label: "Closing", action: "push", icon: "st.doors.garage.garage-closing", backgroundColor: "#ffffff", nextState:"opening"
attributeState "unknown", label: "Power Out", icon: "st.alarm.alarm.alarm", backgroundColor: "#bc2323"
}
tileAttribute ("powerSupply", key: "SECONDARY_CONTROL") {
attributeState "good", label: "Power Good"
attributeState "powerOutOpen", label: "Power Out - Door Open"
attributeState "powerOutClosed", label: "Power Out - Door Closed"
}
}
valueTile ("doorPositionOpen", "device.doorPositionOpen", width: 5, height: 1) {
state "doorPositionOpen", label: 'Configured Open Position ${currentValue}'
}
standardTile ("setOpenPos", "setOpenPos", decoration: "flat" ) {
state "setOpenPos", label: '', action: 'setOpenPosition', icon: "st.secondary.configure"
}
valueTile ("doorPositionClosed", "device.doorPositionClosed", width: 5, height: 1) {
state "doorPositionClosed", label: 'Configured Close Position ${currentValue}'
}
standardTile ("setClosedPos", "setClosedPos", decoration: "flat") {
state "setClosedPos", label: '', action: 'setClosedPosition', icon: "st.secondary.configure"
}
valueTile ("doorPosition", "device.doorPosition", width: 5, height: 1) {
state "doorPosition", label: 'Current Door Position ${currentValue}'
}
main "door"
details(["door", "doorPositionOpen", "setOpenPos",
"doorPositionClosed", "setClosedPos", "doorPosition"])
}
}
def parse(String description) {
def result = null
// supported classes
// 0x20 - BasicSet used to report when the sensor trigger level changes
// 0x25 - switch binary V1
// 0x30 - sensor binary V1
// 0x31 - sensor multilevel V1
// 0x35 - meter pulse (not tested)
// 0x70 - configuration V1
// 0x71 - alarm V1 (for supply voltage monitor, does not seem to respond to AlarmGet)
// 0x72 - manufacturer specific V1
// 0x85 - association V1
// 0x86 - version V1
def cmd = zwave.parse(description, [0x20: 1, 0x25: 1, 0x86: 1, 0x30: 1, 0x31: 1, 0x72: 1, 0x71: 1])
if (cmd) {
result = createEvent(zwaveEvent(cmd))
if (result) {
log.debug "Parsed command: ${result?.descriptionText} raw: ${description}"
} else {
log.debug "Unhandled command: ${description}"
}
} else {
log.debug "Unparsed command: ${description}"
}
return result
}
def getSensitivity() {
return 0x1
}
def getPotOpen() {
def rc = device.currentValue("doorPositionOpen")
if (rc == null) {
sendEvent(name: "doorPositionOpen", value: 158)
return 158
}
return rc
}
def getPotClosed() {
def rc = device.currentValue("doorPositionClosed")
if (rc == null) {
sendEvent(name: "doorPositionClosed", value: 0)
return 0
}
return rc
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
// trigger fires, request the door position so we can see what state it is in
poll().collect { sendHubCommand(new physicalgraph.device.HubAction(it)) }
return null
}
def zwaveEvent(physicalgraph.zwave.commands.alarmv1.AlarmReport cmd) {
if (cmd.alarmLevel) {
if (device.currentValue("door") == "closed") {
sendEvent(name: "powerSupply", value: "powerOutClosed")
} else {
sendEvent(name: "powerSupply", value: "powerOutOpen")
}
return [name: "door", value: "unknown"]
}
return null
}
def convertSensorValueToDoorState( BigDecimal sensorValue ) {
def upper = (potOpen < potClosed) ? potClosed : potOpen
def lower = (potOpen > potClosed) ? potClosed : potOpen
def upperState = (potOpen > potClosed) ? "open" : "closed"
def lowerState = (potOpen < potClosed) ? "open" : "closed"
if (sensorValue >= (upper - sensitivity)) {
return upperState
} else if (sensorValue <= (lower + sensitivity)) {
return lowerState
} else if (device.currentValue('door') == "open") {
return 'closing'
} else if (device.currentValue('door') == "closed") {
return 'opening'
}
return device.currentValue('door')
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv1.SensorMultilevelReport cmd) {
// if we are in a power fail state this is the first message we'll get after the power comes back
// so update the powerState
if (device.currentValue('powerSupply') != "good") {
sendEvent(name: "powerSupply", value: "good")
}
def adjustedValue = cmd.scaledSensorValue.intValue() >> 4
if (adjustedValue != device.currentValue('doorPosition')) {
def doorState = convertSensorValueToDoorState( adjustedValue )
sendEvent(name: "doorPosition", value: adjustedValue)
if (device.currentValue('door') != doorState) {
return [name: "door", value: doorState]
}
}
return null
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
return null
}
def setOpenPosition() {
sendEvent(name: "doorPositionOpen", value: device.currentValue('doorPosition'))
setTriggerLevels().collect { sendHubCommand(new physicalgraph.device.HubAction(it)) }
}
def setClosedPosition() {
sendEvent(name: "doorPositionClosed", value: device.currentValue('doorPosition'))
setTriggerLevels().collect { sendHubCommand(new physicalgraph.device.HubAction(it)) }
}
def open() {
if (device.currentValue("door") == "closed") {
push()
}
}
def close() {
if (device.currentValue("door") == "open") {
push()
}
}
def push() {
def cmds = delayBetween([
zwave.switchBinaryV1.switchBinarySet(switchValue: 0xff).format(),
],500)
return cmds
}
def poll() {
delayBetween([
zwave.sensorMultilevelV1.sensorMultilevelGet().format(),
],500)
}
def refresh() {
delayBetween([
zwave.sensorMultilevelV1.sensorMultilevelGet().format(),
],500)
}
def updated() {
// called when the device is updated
configure()
}
def installed() {
// called when the device is installed
configure()
}
def setTriggerLevels() {
def openVal = getPotOpen()
def closedVal = getPotClosed()
def slop = getSensitivity()
// if the potentiometer is installed backwards reverse the parameters
if (openVal < closedVal) {
def temp = closedVal
closedVal = openVal
openVal = temp
}
def lowerValHigh = closedVal + (2 * slop)
def lowerValLow = closedVal + slop
def upperValHigh = openVal - slop
def upperValLow = openVal - (2 * slop)
delayBetween([
// Lower Threshold, High (Default=0xBB)
zwave.configurationV1.configurationSet(scaledConfigurationValue: lowerValHigh, parameterNumber: 4, size: 2).format(),
// Lower Threshold, Low (Default=0xAB)
zwave.configurationV1.configurationSet(scaledConfigurationValue: lowerValLow, parameterNumber: 5, size: 2).format(),
// Upper Threshold, High (Default=0xFF)
zwave.configurationV1.configurationSet(scaledConfigurationValue: upperValHigh, parameterNumber: 6, size: 2).format(),
// Upper Threshold, Low (Default = 0xFE)
zwave.configurationV1.configurationSet(scaledConfigurationValue: upperValLow, parameterNumber: 7, size: 2).format(),
],500)
}
def configure() {
log.debug "configure"
def cmds = delayBetween([
// enable analog alert thresholds
zwave.configurationV1.configurationSet(scaledConfigurationValue: 0, parameterNumber: 8, size: 2).format(),
// turn on momentary button press, this presses it for 1 second
zwave.configurationV1.configurationSet(scaledConfigurationValue: 10, parameterNumber: 11, size: 2).format(),
// tell device to send multivalue sensor updates every 10 seconds
zwave.configurationV1.configurationSet(scaledConfigurationValue: 1, parameterNumber: 9, size: 2).format(),
// enable alarms to be sent to smartthings hub
zwave.associationV1.associationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]).format(),
// enable multivalue sensor updates to be sent to smartthings hub
zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]).format(),
],500)
cmds += setTriggerLevels()
return cmds
}