/**
* SmartThings Lightify Dimmer Switch support
* Copyright (C) 2016 Adam Outler
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* Command reference
* on 'catchall: 0104 0006 01 01 0140 00 3A68 01 00 0000 01 00 '
* off 'catchall: 0104 0006 01 01 0140 00 3A68 01 00 0000 00 00 '
* held up 'catchall: 0104 0008 01 01 0140 00 3A68 01 00 0000 05 00 0032'
* held down 'catchall: 0104 0008 01 01 0140 00 3A68 01 00 0000 01 00 0132'
* released 'catchall: 0104 0008 01 01 0140 00 3A68 01 00 0000 03 00 '
*/
/**
*sets up fingerprint for autojoin
*sets up commands for polling and others
*sets up capabilities
*sets up variables
*/
metadata {
definition(name: "Lightify Dimming Switch- Zigbee", namespace: "adamoutler", author: "Adam Amber House") {
capability "Battery"
capability "Button"
capability "Switch Level"
attribute "State Array", "string"
attribute 'Awesomeness Level', "string"
attribute 'state', "string"
command "refresh"
command "poll"
command "toggle"
command "configure"
command "installed"
fingerprint profileId: "0104", deviceId: "0001", inClusters: "0000, 0001, 0003, 0020, 0402, 0B05", outClusters: "0003, 0006, 0008, 0019", manufacturer: "OSRAM", model: "LIGHTIFY Dimming Switch", deviceJoinName: "OSRAM Lightify Dimming Switch"
}
simulator {
// Simulations are for loosers, work in production :D
}
preferences {
section("Device1") {
input
("device1",
"string", title:
"Device Network ID 1", description:
"The Device Network Id", defaultValue:
"", type:
"capability.switch", required:
false, displayDuringSetup:
false)
input
("end1",
"string", title:
"Device Endpoint ID 1", description:
"endpointId from Data Section of device", defaultValue:
"", required:
false, displayDuringSetup:
false)
}
section("Device2") {
input
("device2",
"string", title:
"Device Network ID 2", description:
"The Device Network Id", defaultValue:
"", type:
"capability.switch", required:
false, displayDuringSetup:
false)
input
("end2",
"string", title:
"Device Endpoint ID 2", description:
"endpointId from Data Section of device", defaultValue:
"", required:
false, displayDuringSetup:
false)
}
section("Device3") {
input
("device3",
"string", title:
"Device Network ID 3", description:
"The Device Network Id", defaultValue:
"", type:
"capability.switch", required:
false, displayDuringSetup:
false)
input
("end3",
"string", title:
"Device Endpoint ID 3", description:
"endpointId from Data Section of device", defaultValue:
"", required:
false, displayDuringSetup:
false)
}
section("Device4") {
input
("device4",
"string", title:
"Device Network ID 4", description:
"The Device Network Id", defaultValue:
"", type:
"capability.switch", required:
false, displayDuringSetup:
false)
input
("end4",
"string", title:
"Device Endpoint ID 4", description:
"endpointId from Data Section of device", defaultValue:
"", required:
false, displayDuringSetup:
false)
}
section("Device5") {
input
("device5",
"string", title:
"Device Network ID 5", description:
"The Device Network Id", defaultValue:
"", type:
"capability.switch", required:
false, displayDuringSetup:
false)
input
("end5",
"string", title:
"Device Endpoint ID 5", description:
"endpointId from Data Section of device", defaultValue:
"", required:
false, displayDuringSetup:
false)
}
}
/**
*UI tile definitions
*/
tiles(scale: 2) {
standardTile("button", "device.state", width: 6, height: 4) {
state "off", label: 'Off', action: "toggle", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
state "on", label: "On", action: "toggle", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
state "turningOn", label: 'Turning on', icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
state "turningOff", label: 'Turning off', icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
}
valueTile
("battery",
"device.battery", decoration:
"flat", inactiveLabel:
false, width:
2, height:
2) {
state "battery", label: 'battery ${currentValue}%'
}
valueTile
("brightness",
"device.level", decoration:
"flat", inactiveLabel:
false, width:
2, height:
2) {
state "brightness", label: 'brightnessn${currentValue}%'
}
standardTile("refresh", "device.button", decoration: "flat", width: 2, height: 2) {
state "default", label: "", action: "refresh", icon: "st.secondary.refresh"
}
controlTile
("levelSliderControl",
"device.level",
"slider", height:
2, width:
6, inactiveLabel:
false, range:
"(1..100)") {
state "level", action:"setLevel"
}
main "button"
details(["button", "levelSliderControl", "switch", "battery", "brightness", "refresh"])
}
}
/**
*returns a map representing important states
*/
brightness: state.brightness,
battery: state.battery,
value: state.value,
level: state.level,
lastAction: state.lastAction,
on: state.on,
data: '',
descriptionText: "$device.displayName button $state.buttonNumber was $value"
]
}
/**
*Gets a list of devices in [address,endpoint] format compiled from inputs above
*returns: Arraylist<[address,endpoint]>
*/
String[] devs
= [settings.
device1, device2, device3, device4, device5
]
if (devs
== [null,
null,
null,
null,
null]) log.
info("------No devices configured in $device preferences--------")
String[] ends
= [end1, end2, end3, end4, end5
]
list.add([devs[i], ends[i]])
}
}
}
/**
*Parse events into attributes.
*/
log.debug(msgFromST)
if (msgFromST
?.
startsWith('catchall:')) {
def value
= handleMessage
(msgFromST
)
fireCommands(value.command)
} else if (msgFromST.
startsWith("read")) {
if (msgFromST.
contains("attrId: 0020,")) return batteryHandler
(zigbee.
parseDescriptionAsMap(msgFromST
))
log.error('unrecognized command:' + msgFromST)
log.error('unrecognized command:' + msgFromST)
}
}
/**
*fire commands into the hub
*commands - an array of string commands to be fired
*/
log.trace("Executing commands-- state:" + state + " commands:" + commands)
sendHubCommand
([value
].
collect {new physicalgraph.
device.
HubAction(it
)
})
}
}
}
Map msg
= zigbee.
parseDescriptionAsMap(msgFromST
)
def returnval
= handleButtonPress
(msg
)
handleButtonHeld(msg)
log.info("Networking Bind Response received!!!")
updateButtonState("Network binding complete!")
log.info("Network managment Leave Response!!!")
log.error("Unhandled message: " + msg)
}
}
/**
* This is the handler for button presses. Map is routed here once a button press has been detected
*/
updateButtonState("on")
bothButtonsPressed()
updateButtonState("off")
log.info("Button Press Bind Response!!!")
log.error(getLinkText(device)+" got unknown button press command: " + msg.command)
return [name:
"error",value:
"unknown button press command"]
}
}
/**
*this is the handler for button held events. Map is routed here once a button held event has been detected
*/
log.debug("Button held- Lowering brightness commanded")
updateButtonState("lowering brightness")
state.lastHeld = "down"
log.debug("stop brightness commanded")
updateButtonState(state.lastHeld + " released")
log.debug("Button held- raising brightness commanded")
state.lastHeld = "up"
updateButtonState("raising brightness")
log.info("Button Held Bind Response - 7!!!")
log.info("Button Held Bind Response - 8!!!")
log.error("Unhandled button held event: " + msg)
}
}
/**
* adjusts brightness up/down depending on the value of the up boolean true is up, false is down
* continues to adjust until state.dimming is changed
* up- true if we are adjusting brightness up, false if down
* level - first level commanded
*/
log.debug("adjusting brightness" + (up?"up" : "down") + " from current " + state.brightness)
//increase or decrease brightness
state.
brightening = false
}
state.
brightness = (int) level
executeBrightnessAdjustmentUntilButtonReleased()
log.debug("Final brightness adjusted to " + state.brightness)
}
sendEvent(name: "brightness", value: state.brightness)
}
/**
* performs a recursive brightness adjustment based on state.brightening while state.dimming is true
*/
void executeBrightnessAdjustmentUntilButtonReleased
() {
state.brightness = state.brightness + 20
state.brightness = state.brightness - 20
}
if (state.
brightness > 100) {
state.brightness = 100
}
if (state.
brightness < 1){
state.brightness = 1
}
setLevel
((double) state.
brightness,
1000)
reportOnState
(true) //Manage and report states
runIn(1, executeBrightnessAdjustmentUntilButtonReleased)
}
}
/**
* gets the current brightness in hex format
* length - the length of the number after leading 0's have been applied. This will likely be "2"
*/
sb.insert(0, '0')
}
}
/**
*formats a decimal value with leading 0's
*value - the value to be formatted
*length - the size of the number after leading 0's have been added
*/
}
/**
*performs configuration and bindings
*/
if (state.
boundnetwork && state.
bounddimmer && state.
boundonoff){
log.debug("configuration complete")
sendEvent(name:"Configured", value:"true")
}
log.debug "Confuguring Reporting and Bindings."
fireCommands(zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(0x0001, 0x0020))
configureDaemon()
}
/*
* handles binding if switch doesn't pay attention the first time.
*/
}
}
}
/**
*Refresh support. Causes battery status update and others
*/
fireCommands(zigbee.readAttribute(0x0001, 0x0020) +zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.readAttribute(0x0001, 0x0020) )
}
/**
*actions to be taken once the device is installed
*/
log.info(device.name + " installed!!!")
sendEvent(name:"Configured", value:"false")
state.lastAction = 0
state.brightness = 0
state.buttonNumber = 1
state.value = "unknown"
state.lastHeld = "none"
state.battery = 100
return reportOnState
(getOnState
())
}
log.info("updated")
}
/**
*handles battery messages
*/
def linkText
= getLinkText
(device
)
def result
= [ name:
'battery', value: state.
battery ]
def volts
= Integer.
valueOf(rawValue.
value,
16) /
10
state.battery = "unknown"
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
state.battery = "overvoltage"
def pct
= (volts
- minVolts
) /
(maxVolts
- minVolts
)
result.battery = state.battery
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
sendEvent(name: 'battery', value: state.battery, units: "%")
log.debug "${result?.descriptionText}"
}
/**
* handle level commands,
* level=desired level
* duration=desired time-to-level
*/
if (level
> 100 || level
< 0 || duration
< 0 || duration
> 9999) {
log.debug("Maximum parameters (level 0-100, duration 0-9999)-- commanded level:" + level + " commanded duration:" + duration)
}
log.info("Brightness commanded to " + level + "%")
state.
brightness = (int) level
def result
= createStCommand
(" 8 4 {" + getBrightnessHex
(2) + " " + formatNumber
((int) duration,
4) + "}")
if (state.
on != "on") on
()
fireCommands(result.command) //send it to the hub for processing
}
/**
*sets level using a 1-second duration
*level= percent
*/
setLevel(level, 1000)
}
/**
* Returns true if the switch is on. false if off.
*/
}
/**
*poll support, forces an update of states
*/
sendEvent(name: 'brightness', value: state.brightness, units: "%")
reportOnState(getOnState())
}
/**
*takes a command and data, generates an "st cmd" array
*command- string representing the command and data to be send to the device
*returns the command for all devices on the switch
*/
List<String> output = []
LinkedHashMap result = getStatus()
for (item
in getDevices
()) {
output.add("st cmd 0x" + item[0] + " 0x" + item[1] + " " + command)
}
}
/**
* Handles event updates. All updates go here.
*/
String onValue
= (on
? "on" :
"off")
sendEvent(name: 'state', type:"thing", value:onValue)
sendEvent(name: 'battery', value: state.battery)
sendEvent(name: 'level', value: state.brightness)
sendEvent(name: 'button', value: state.value)
sendEvent(name: 'numberOfButtons', value: 5)
sendEvent(name: 'State Array', value: state)
sendEvent(name: 'Awesomeness Level', value: "over 9000")
state.on = onValue
}
def updateButtonState
(def value
) {
state.value = value
sendEvent(name: "button", value: value, unit: "")
}
/**
*commands the opposite of the current state
*if on, turn off, if off, turn on.
*/
log.debug(device.displayName + " toggled on")
command = off()
command = on()
log.debug(device.displayName + " toggled off")
}
log.debug(command)
fireCommands(command.command)
}
/**
*detects the current state versus commanded
*if curret is off, and commanded is off, returns true and same for inverse
*/
}
/**
*This switch does not support pressing the same button in rapid sucession
*this check finds out if user tapped one button and then the other within 1.5 seconds.
*/
return (state.
lastAction < now
()+1500)
}
//TODO create a up-down tapped mechanism.
/**
*turns light on.
*if already on, commands max
*/
log.debug(device.displayName + " commanded on" )
}
state.lastAction=now()
if (doubleTapped
(true)) setLevel
(100,
1000)
return createStCommand
("6 1 {}")
}
/**
*turns light off
*if already off, commands on to minimum
*/
log.debug(device.displayName + " commanded off")
}
state.lastAction=now()
setLevel(1, 1000)
}
return createStCommand
("6 0 {}")
}
/**
*Action to be taken when both buttons are pressed at the same time
*/
def bothButtonsPressed
() {
log.error("Both Buttons Pressed" + state)
installed()
state.lastAction = 100
updateButtonState("Initiating configuration routines. Please stand by.")
configure()
}