/**
* MIT License
* Copyright 2022 Jonathan Bradshaw (jb@nrgup.net)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import com.hubitat.app.DeviceWrapper
metadata {
definition(
name: 'ESPHome ratGDO',
namespace: 'esphome',
author: 'Jonathan Bradshaw, Adrian Caramaliu',
singleThreaded: true,
importUrl: 'https://raw.githubusercontent.com/bradsjm/hubitat-drivers/main/ESPHome/ESPHome-ratGDO.groovy') {
capability 'Actuator'
capability 'Sensor'
capability 'Refresh'
capability 'Initialize'
capability 'Signal Strength'
capability 'Door Control'
capability 'Garage Door Control'
capability "Contact Sensor"
capability 'Switch'
capability 'Lock'
capability 'MotionSensor'
capability 'Pushable Button'
// attribute populated by ESPHome API Library automatically
attribute 'dryContactLight', 'enum', [ 'open', 'closed' ]
attribute 'dryContactOpen', 'enum', [ 'open', 'closed' ]
attribute 'dryContactClose', 'enum', [ 'open', 'closed' ]
attribute 'learn', 'enum', [ 'on', 'off' ]
attribute 'motor', 'enum', [ 'idle', 'running' ]
attribute 'networkStatus', 'enum', [ 'connecting', 'online', 'offline' ]
attribute 'obstruction', 'enum', [ 'present', 'not present' ]
attribute 'openings', 'number'
attribute 'position', 'number'
command 'learnOn'
command 'learnOff'
command 'restart'
command 'stop'
command 'sync'
command 'toggle'
}
preferences {
input name: 'ipAddress', // required setting for API library
type: 'text',
title: 'Device IP Address',
required: true
input name: 'password', // optional setting for API library
type: 'text',
title: 'Device Password (if required)',
required: false
input name: 'childrenEnable',
type: 'bool',
title: "Enable Child Devices?",
defaultValue: false,
required: false
input name: 'logEnable', // if enabled the library will log debug details
type: 'bool',
title: 'Enable Debug Logging',
required: false,
defaultValue: false
input name: 'logTextEnable',
type: 'bool',
title: 'Enable descriptionText logging',
required: false,
defaultValue: true
}
}
public void initialize() {
// API library command to open socket to device, it will automatically reconnect if needed
openSocket()
if (logEnable) {
runIn(1800, 'logsOff')
}
}
public void installed() {
log.info "${device} driver installed"
}
public void logsOff() {
espHomeSubscribeLogs(LOG_LEVEL_INFO, false) // disable device logging
device.updateSetting('logEnable', false)
log.info "${device} debug logging disabled"
}
public void updated() {
if( !settings.childrenEnable ){
getChildDevices().each{
log.info "Children disabled, deleting ${ it.deviceNetworkId }"
deleteChildDevice( it.deviceNetworkId )
}
}
log.info "${device} driver configuration updated"
initialize()
}
public void uninstalled() {
closeSocket('driver uninstalled') // make sure the socket is closed when uninstalling
log.info "${device} driver uninstalled"
}
// the parse method is invoked by the API library when messages are received
public void parse(Map message) {
if (logEnable) { log.debug "ESPHome received: ${message}" }
switch (message.type) {
case 'device':
// Device information
break
case 'entity':
doParseEntity(message)
break
case 'state':
doParseState(message)
break
}
}
private void doParseEntity(Map message) {
if (message.platform == 'cover') {
if (message.name == "Door") {
state.doorKey = message.key as Long
}
return
}
if (message.platform == 'binary') {
if (message.name == "Motion") {
state.motionKey = message.key as Long
getMotionDevice(message.key)
}
if (message.name == "Obstruction") {
state.obstructionKey = message.key as Long
getObstructionDevice(message.key)
}
if (message.name == "Button") {
state.buttonKey = message.key as Long
getButtonDevice(message.key)
}
if (message.name == "Motor") {
state.motorKey = message.key as Long
}
if (message.name == "Dry contact light") {
state.dryContactLightKey = message.key as Long
getDryContactLightDevice(message.key)
}
if (message.name == "Dry contact open") {
state.dryContactOpenKey = message.key as Long
getDryContactOpenDevice(message.key)
}
if (message.name == "Dry contact close") {
state.dryContactOpenKey = message.key as Long
getDryContactCloseDevice(message.key)
}
return
}
if (message.platform == 'light') {
if (message.name == "Light") {
state.lightKey = message.key as Long
getLightDevice(message.key)
}
return
}
if (message.platform == 'switch') {
if (message.name == "Learn") {
state.learnKey = message.key as Long
getLearnDevice(message.key)
}
return
}
if (message.platform == 'lock') {
if (message.name == "Lock remotes") {
state.lockKey = message.key as Long
getLockDevice(message.key)
}
return
}
if (message.platform == 'sensor') {
if (message.name == "Openings") {
state.openingsKey = message.key as Long
}
if (message.deviceClass == 'signal_strength') {
state.signalStrengthKey = message.key
}
return
}
if (message.platform == 'button') {
if (message.name == "Restart") {
state.restartKey = message.key
}
if (message.name == "Sync") {
state.syncKey = message.key
}
if (message.name == "Toggle door") {
state.toggleKey = message.key
}
return
}
}
private void doParseState(Map message) {
String type = message.isDigital ? 'digital' : 'physical'
// Check if the entity key matches the message entity key received to update device state
if (state.doorKey as Long == message.key) {
String value
String contact
switch (message.currentOperation) {
case COVER_OPERATION_IDLE:
value = message.position > 0 ? 'open' : 'closed'
contact = value
break
case COVER_OPERATION_IS_OPENING:
value = 'opening'
break
case COVER_OPERATION_IS_CLOSING:
value = 'closing'
break
}
sendDeviceEvent("door", value, type, "Door")
sendDeviceEvent("contact", contact, type, "Contact")
int position = Math.round(message.position * 100) as int
sendDeviceEvent("position", position, type, "Position")
return
}
if (state.motionKey as Long == message.key) {
String value = message.state ? "active" : "inactive"
sendDeviceEvent("motion", value, type, "Motion", getMotionDevice(message.key))
return
}
if (state.obstructionKey as Long == message.key) {
String value = message.state ? "present" : "not present"
sendDeviceEvent("obstruction", value, type, "Obstruction", getObstructionDevice(message.key), "presence")
return
}
if (state.motorKey as Long == message.key) {
String value = message.state ? "running" : "idle"
sendDeviceEvent("motor", value, type, "Motor")
return
}
if (state.learnKey as Long == message.key) {
String value = message.state ? "on" : "off"
sendDeviceEvent("learn", value, type, "Learn", getLearnDevice(message.key), "switch")
return
}
if (state.lightKey as Long == message.key) {
String value = message.state ? "on" : "off"
sendDeviceEvent("switch", value, type, "Light", getLightDevice(message.key))
return
}
if (state.lockKey as Long == message.key) {
String value = message.state == 1 ? "locked" : "unlocked"
sendDeviceEvent("lock", value, type, "Remotes lock", getLockDevice(message.key))
return
}
if (state.openingsKey as Long == message.key) {
int value = message.state as int
sendDeviceEvent("openings", value, type, "Openings")
return
}
if (state.dryContactLightKey as Long == message.key) {
String value = message.state == 1 ? "closed" : "open"
sendDeviceEvent("dryContactLight", value, type, "Dry Contact Light", getDryContactLightDevice(message.key))
return
}
if (state.dryContactOpenKey as Long == message.key) {
String value = message.state == 1 ? "closed" : "open"
sendDeviceEvent("dryContactOpen", value, type, "Dry Contact Open", getDryContactOpenDevice(message.key))
return
}
if (state.dryContactCloseKey as Long == message.key) {
String value = message.state == 1 ? "closed" : "open"
sendDeviceEvent("dryContactClose", value, type, "Dry Contact Close", getDryContactCloseDevice(message.key))
return
}
if (state.buttonKey as Long == message.key && message.hasState) {
if (message.state) {
sendDeviceEvent("pushed", 1, type, "Button", getButtonDevice(message.key), null, true)
}
return
}
if (state.signalStrengthKey as Long == message.key && message.hasState) {
Integer rssi = Math.round(message.state as Float)
String unit = "dBm"
if (device.currentValue("rssi") != rssi) {
descriptionText = "${device} rssi is ${rssi}"
sendEvent(name: "rssi", value: rssi, unit: unit, type: type, descriptionText: descriptionText)
if (logTextEnable) { log.info descriptionText }
}
return
}
}
// child device management
private void sendDeviceEvent(name, value, type, description, child = null, childEventName = null, isStateChange = null) {
String descriptionText = "${description} is ${value} (${type})"
def event = [name: name, value: value, type: type, descriptionText: descriptionText]
if (isStateChange) {
event.isStateChange = true
}
if (isStateChange || device.currentValue(name) != value) {
if (logTextEnable) { log.info descriptionText }
sendEvent(event)
}
if (child) {
if (isStateChange || child.currentValue(name) != value) {
if (childEventName) {
event.name = childEventName
}
child.parse([event])
}
}
}
private DeviceWrapper getOrCreateDevice(key, deviceType, label = null) {
if (key == null || !settings.childrenEnable) {
return null
}
String dni = "${device.id}-${key}" as String
def d = getChildDevice(dni)
if (!d) {
d = addChildDevice(
"hubitat",
"Generic Component ${deviceType}",
dni
)
d.name = "${device.label} ${label?:deviceType}"
d.label = "${device.label} ${label?:deviceType}"
}
return d
}
private DeviceWrapper getMotionDevice(key) {
return getOrCreateDevice(key, "Motion Sensor")
}
private DeviceWrapper getButtonDevice(key) {
return getOrCreateDevice(key, "Button Controller", "Button")
}
private DeviceWrapper getLearnDevice(key) {
return getOrCreateDevice(key, "Switch", "Learn")
}
private DeviceWrapper getLightDevice(key) {
return getOrCreateDevice(key, "Switch", "Light")
}
private DeviceWrapper getLockDevice(key) {
return getOrCreateDevice(key, "Lock")
}
private DeviceWrapper getObstructionDevice(key) {
return getOrCreateDevice(key, "Presence Sensor", "Obstruction")
}
private DeviceWrapper getDryContactLightDevice(key) {
return getOrCreateDevice(key, "Contact Sensor", "Light Dry Contact")
}
private DeviceWrapper getDryContactOpenDevice(key) {
return getOrCreateDevice(key, "Contact Sensor", "Open Dry Contact")
}
private DeviceWrapper getDryContactCloseDevice(key) {
return getOrCreateDevice(key, "Contact Sensor", "Close Dry Contact")
}
// driver commands
public void open() {
if (state.doorKey) {
// API library cover command, entity key for the cover is required
if (logTextEnable) { log.info "${device} open" }
espHomeCoverCommand(key: state.doorKey as Long, position: 1.0)
}
}
public void close() {
if (state.doorKey) {
// API library cover command, entity key for the cover is required
if (logTextEnable) { log.info "${device} close" }
espHomeCoverCommand(key: state.doorKey as Long, position: 0.0)
}
}
public void stop() {
if (state.doorKey) {
// API library cover command, entity key for the cover is required
if (logTextEnable) { log.info "${device} close" }
espHomeCoverCommand(key: state.doorKey as Long, stop: 1)
}
}
public void on() {
if (state.lightKey) {
if (logTextEnable) { log.info "${device} on" }
espHomeLightCommand(key: state.lightKey as Long, state: true)
}
}
public void off() {
if (state.lightKey) {
if (logTextEnable) { log.info "${device} off" }
espHomeLightCommand(key: state.lightKey as Long, state: false)
}
}
public void lock() {
if (state.lockKey) {
if (logTextEnable) { log.info "${device} locked" }
espHomeLockCommand(key: state.lockKey as Long, lockCommand: LOCK_LOCK)
}
}
public void unlock() {
if (state.lockKey) {
if (logTextEnable) { log.info "${device} unlocked" }
espHomeLockCommand(key: state.lockKey as Long, lockCommand: LOCK_UNLOCK)
}
}
public void learnOn() {
if (logTextEnable) { log.info "${device} on" }
espHomeSwitchCommand(key: settings.learn as Long, state: true)
}
public void learnOff() {
if (logTextEnable) { log.info "${device} off" }
espHomeSwitchCommand(key: settings.learn as Long, state: false)
}
public void refresh() {
log.info "${device} refresh"
state.clear()
state.requireRefresh = true
espHomeDeviceInfoRequest()
}
public void restart() {
if (state.restartKey) {
log.info "${device} restart"
espHomeButtonCommand(key: state.restartKey as Long)
}
}
public void sync() {
if (state.syncKey) {
log.info "${device} sync"
espHomeButtonCommand(key: state.syncKey as Long)
}
}
public void toggle() {
if (state.toggleKey) {
log.info "${device} toggle"
espHomeButtonCommand(key: state.toggleKey as Long)
}
}
// child component commands
public void componentOn(DeviceWrapper dw) {
Long key = dw.getDeviceNetworkId().minus("${device.id}-") as Long
if (state.lightKey as Long == key) {
on()
}
if (state.learnKey as Long == key) {
learnOn()
}
}
public void componentOff(DeviceWrapper dw) {
Long key = dw.getDeviceNetworkId().minus("${device.id}-") as Long
if (state.lightKey as Long == key) {
off()
}
if (state.learnKey as Long == key) {
learnOff()
}
}
public void componentLock(DeviceWrapper dw) {
Long key = dw.getDeviceNetworkId().minus("${device.id}-") as Long
if (state.lockKey as Long == key) {
lock()
}
}
public void componentUnlock(DeviceWrapper dw) {
Long key = dw.getDeviceNetworkId().minus("${device.id}-") as Long
if (state.lockKey as Long == key) {
unlock()
}
}
public void componentRefresh(DeviceWrapper dw) {
refresh()
}
// Put this line at the end of the driver to include the ESPHome API library helper
#include esphome.espHomeApiHelper