mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-10 05:11:51 +00:00
Compare commits
1 Commits
MSA-1372-2
...
MSA-1333-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca5443d4dc |
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
Osram Tunable White 60 A19 bulb
|
||||
|
||||
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
|
||||
that issue by using state variables
|
||||
*/
|
||||
|
||||
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
|
||||
|
||||
metadata {
|
||||
definition (name: "Kudled white bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Color Temperature"
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
attribute "colorName", "string"
|
||||
|
||||
// indicates that device keeps track of heartbeat (in state.heartbeat)
|
||||
attribute "heartbeat", "string"
|
||||
|
||||
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
// status messages
|
||||
status "on": "on/off: 1"
|
||||
status "off": "on/off: 0"
|
||||
|
||||
// reply messages
|
||||
reply "zcl on-off on": "on/off: 1"
|
||||
reply "zcl on-off off": "on/off: 0"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles {
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821"
|
||||
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
|
||||
state "colorTemperature", label: '${currentValue} K'
|
||||
}
|
||||
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat") {
|
||||
state "colorName", label: '${currentValue}'
|
||||
}
|
||||
|
||||
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
|
||||
state "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "refresh", "colorName", "levelSliderControl", "level", "colorTempSliderControl", "colorTemp"])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
//log.trace description
|
||||
|
||||
// save heartbeat (i.e. last time we got a message from device)
|
||||
state.heartbeat = Calendar.getInstance().getTimeInMillis()
|
||||
|
||||
if (description?.startsWith("catchall:")) {
|
||||
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
|
||||
{
|
||||
def result = createEvent(name: "switch", value: "on")
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
}
|
||||
else if(description?.endsWith("0000") || description?.endsWith("1000") || description?.matches("on/off\\s*:\\s*0"))
|
||||
{
|
||||
def result = createEvent(name: "switch", value: "off")
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
else if (description?.startsWith("read attr -")) {
|
||||
def descMap = parseDescriptionAsMap(description)
|
||||
log.trace "descMap : $descMap"
|
||||
|
||||
if (descMap.cluster == "0300") {
|
||||
log.debug descMap.value
|
||||
def tempInMired = convertHexToInt(descMap.value)
|
||||
def tempInKelvin = Math.round(1000000/tempInMired)
|
||||
log.trace "temp in kelvin: $tempInKelvin"
|
||||
sendEvent(name: "colorTemperature", value: tempInKelvin, displayed:false)
|
||||
}
|
||||
else if(descMap.cluster == "0008"){
|
||||
def dimmerValue = Math.round(convertHexToInt(descMap.value) * 100 / 255)
|
||||
log.debug "dimmer value is $dimmerValue"
|
||||
sendEvent(name: "level", value: dimmerValue)
|
||||
}
|
||||
}
|
||||
else {
|
||||
def name = description?.startsWith("on/off: ") ? "switch" : null
|
||||
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
|
||||
def result = createEvent(name: name, value: value)
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
def on() {
|
||||
log.debug "on()"
|
||||
sendEvent(name: "switch", value: "on")
|
||||
setLevel(state?.levelValue)
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "off()"
|
||||
sendEvent(name: "switch", value: "off")
|
||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
sendEvent(name: "heartbeat", value: "alive", displayed:false)
|
||||
[
|
||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 7"
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
def configure() {
|
||||
state.levelValue = 100
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
def configCmds = [
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x0300 {${device.zigbeeId}} {}", "delay 500"
|
||||
]
|
||||
return onOffConfig() + levelConfig() + configCmds + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def onOffConfig() {
|
||||
[
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 6 0 0x10 0 300 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
|
||||
]
|
||||
}
|
||||
|
||||
//level config for devices with min reporting interval as 5 seconds and reporting interval if no activity as 1hour (3600s)
|
||||
//min level change is 01
|
||||
def levelConfig() {
|
||||
[
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 8 0 0x20 5 3600 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
|
||||
]
|
||||
}
|
||||
|
||||
def setColorTemperature(value) {
|
||||
if(value<101){
|
||||
value = (value*38) + 2700 //Calculation of mapping 0-100 to 2700-6500
|
||||
}
|
||||
|
||||
def tempInMired = Math.round(1000000/value)
|
||||
def finalHex = swapEndianHex(hex(tempInMired, 4))
|
||||
def genericName = getGenericName(value)
|
||||
log.debug "generic name is : $genericName"
|
||||
|
||||
def cmds = []
|
||||
sendEvent(name: "colorTemperature", value: value, displayed:false)
|
||||
sendEvent(name: "colorName", value: genericName)
|
||||
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x0300 0x0a {${finalHex} 2000}"
|
||||
|
||||
cmds
|
||||
}
|
||||
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
state.levelValue = (value==null) ? 100 : value
|
||||
log.trace "setLevel($value)"
|
||||
def cmds = []
|
||||
|
||||
if (value == 0) {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
|
||||
}
|
||||
else if (device.latestValue("switch") == "off") {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
|
||||
sendEvent(name: "level", value: state.levelValue)
|
||||
def level = hex(state.levelValue * 254 / 100)
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
|
||||
|
||||
//log.debug cmds
|
||||
cmds
|
||||
}
|
||||
|
||||
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
|
||||
private getGenericName(value){
|
||||
def genericName = "White"
|
||||
if(value < 3300){
|
||||
genericName = "Soft White"
|
||||
} else if(value < 4150){
|
||||
genericName = "Moonlight"
|
||||
} else if(value < 5000){
|
||||
genericName = "Cool White"
|
||||
} else if(value <= 6500){
|
||||
genericName = "Daylight"
|
||||
}
|
||||
|
||||
genericName
|
||||
}
|
||||
|
||||
private getEndpointId() {
|
||||
//new BigInteger(device.endpointId, 16).toString()
|
||||
"0x0B"
|
||||
}
|
||||
|
||||
private hex(value, width=2) {
|
||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
||||
while (s.size() < width) {
|
||||
s = "0" + s
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
//Need to reverse array of size 2
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
byte tmp;
|
||||
tmp = array[1];
|
||||
array[1] = array[0];
|
||||
array[0] = tmp;
|
||||
return array
|
||||
}
|
||||
@@ -403,21 +403,39 @@ def refresh() {
|
||||
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
||||
/* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276)
|
||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||
as of now.
|
||||
*/
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode])
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
|
||||
refreshCmds = refreshCmds + [
|
||||
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
|
||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||
as of now.
|
||||
*/
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
} else {
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
|
||||
refreshCmds = refreshCmds + [
|
||||
/* sensitivity - default value (8) */
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
}
|
||||
|
||||
//Common refresh commands
|
||||
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
|
||||
zigbee.readAttribute(0x0001, 0x0020) +
|
||||
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
|
||||
refreshCmds = refreshCmds + [
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global read 0xFC02 0x0010",
|
||||
"send 0x${device.deviceNetworkId} 1 1","delay 400"
|
||||
]
|
||||
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
@@ -425,15 +443,38 @@ def refresh() {
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting"
|
||||
|
||||
def configCmds = enrollResponse() +
|
||||
zigbee.batteryConfig() +
|
||||
zigbee.temperatureConfig() +
|
||||
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
|
||||
def configCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
|
||||
return configCmds + refresh()
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ metadata {
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82"
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x80,0x71,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20" // Philio multi+
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x72,0x5A,0x80,0x73,0x86,0x84,0x85,0x59,0x71,0x70,0x7A,0x98" // Vision door/window
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98,0x86,0x72,0x5A,0x85,0x59,0x73,0x80,0x71,0x31,0x70,0x84,0x7A" // Vision Motion
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -264,9 +263,5 @@ def retypeBasedOnMSR() {
|
||||
log.debug "Changing device type to Door / Window Sensor Plus (SG)"
|
||||
setDeviceType("Door / Window Sensor Plus (SG)")
|
||||
break
|
||||
case "0109-2002-0205": // Vision Motion
|
||||
log.debug "Changing device type to Vision Motion Sensor Plus (SG)"
|
||||
setDeviceType("Vision Motion Sensor Plus (SG)")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,6 @@ metadata {
|
||||
capability "Motion Sensor"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
|
||||
fingerprint mfr: "011F", prod: "0001", model: "0001", deviceJoinName: "Schlage Motion Sensor" // Schlage motion
|
||||
fingerprint mfr: "014A", prod: "0001", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion
|
||||
fingerprint mfr: "014A", prod: "0004", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion +
|
||||
fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814
|
||||
fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02
|
||||
fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -132,9 +125,9 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
||||
}
|
||||
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
|
||||
result << response(zwave.batteryV1.batteryGet())
|
||||
} else {
|
||||
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
result << response("delay 1200")
|
||||
}
|
||||
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
@@ -24,24 +24,17 @@ definition(
|
||||
iconX3Url: "http://www.gidjit.com/appicon@3x.png",
|
||||
oauth: [displayName: "Gidjit", displayLink: "www.gidjit.com"])
|
||||
|
||||
preferences(oauthPage: "deviceAuthorization") {
|
||||
// deviceAuthorization page is simply the devices to authorize
|
||||
page(name: "deviceAuthorization", title: "Device Authorization", nextPage: "instructionPage",
|
||||
install: false, uninstall: true) {
|
||||
section ("Allow Gidjit to have access, thereby allowing you to quickly control and monitor your following devices. Privacy Policy can be found at http://priv.gidjit.com/privacy.html") {
|
||||
input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false
|
||||
input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade
|
||||
}
|
||||
|
||||
}
|
||||
page(name: "instructionPage", title: "Device Discovery", install: true) {
|
||||
section() {
|
||||
paragraph "Now the process is complete return to the Devices section of the Detected Screen. From there and you can add actions to each of your device panels, including launching SmartThings routines."
|
||||
}
|
||||
}
|
||||
|
||||
preferences {
|
||||
section ("Allow Gidjit to have access, there by allowing you to quickly control and monitor the following devices") {
|
||||
input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false
|
||||
input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade
|
||||
//input "bulbs", "capability.colorControl", title: "Control your lights", multiple: true, required: false //windowShade
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
mappings {
|
||||
path("/structureinfo") {
|
||||
action: [
|
||||
|
||||
@@ -337,10 +337,10 @@ def initialize() {
|
||||
|
||||
settings.devices.each {
|
||||
def deviceId = it
|
||||
def detail = state?.deviceDetail[deviceId]
|
||||
def detail = state.deviceDetail[deviceId]
|
||||
|
||||
try {
|
||||
switch(detail?.type) {
|
||||
switch(detail.type) {
|
||||
case 'NAMain':
|
||||
log.debug "Base station"
|
||||
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
|
||||
@@ -487,12 +487,12 @@ def poll() {
|
||||
log.debug "State: ${state.deviceState}"
|
||||
|
||||
settings.devices.each { deviceId ->
|
||||
def detail = state?.deviceDetail[deviceId]
|
||||
def data = state?.deviceState[deviceId]
|
||||
def child = children?.find { it.deviceNetworkId == deviceId }
|
||||
def detail = state.deviceDetail[deviceId]
|
||||
def data = state.deviceState[deviceId]
|
||||
def child = children.find { it.deviceNetworkId == deviceId }
|
||||
|
||||
log.debug "Update: $child";
|
||||
switch(detail?.type) {
|
||||
switch(detail.type) {
|
||||
case 'NAMain':
|
||||
log.debug "Updating NAMain $data"
|
||||
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())
|
||||
|
||||
@@ -39,7 +39,6 @@ preferences {
|
||||
page(name: "completionPage")
|
||||
page(name: "numbersPage")
|
||||
page(name: "controllerExplanationPage")
|
||||
page(name: "unsupportedDevicesPage")
|
||||
}
|
||||
|
||||
def rootPage() {
|
||||
@@ -48,9 +47,6 @@ def rootPage() {
|
||||
section("What to dim") {
|
||||
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
|
||||
if (dimmers) {
|
||||
if (dimmersContainUnsupportedDevices()) {
|
||||
href(name: "toUnsupportedDevicesPage", page: "unsupportedDevicesPage", title: "Some of your selected dimmers don't seem to be supported", description: "Tap here to fix it", required: true)
|
||||
}
|
||||
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
|
||||
}
|
||||
}
|
||||
@@ -75,31 +71,6 @@ def rootPage() {
|
||||
}
|
||||
}
|
||||
|
||||
def unsupportedDevicesPage() {
|
||||
|
||||
def unsupportedDimmers = dimmers.findAll { !hasSetLevelCommand(it) }
|
||||
|
||||
dynamicPage(name: "unsupportedDevicesPage") {
|
||||
if (unsupportedDimmers) {
|
||||
section("These devices do not support the setLevel command") {
|
||||
unsupportedDimmers.each {
|
||||
paragraph deviceLabel(it)
|
||||
}
|
||||
}
|
||||
section {
|
||||
input(name: "dimmers", type: "capability.sensor", title: "Please remove the above devices from this list.", submitOnChange: true, multiple: true)
|
||||
}
|
||||
section {
|
||||
paragraph "If you think there is a mistake here, please contact support."
|
||||
}
|
||||
} else {
|
||||
section {
|
||||
paragraph "You're all set. You can hit the back button, now. Thanks for cleaning up your settings :)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def controllerExplanationPage() {
|
||||
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
|
||||
|
||||
@@ -557,16 +528,14 @@ def updateDimmers(percentComplete) {
|
||||
} else {
|
||||
|
||||
def shouldChangeColors = (colorize && colorize != "false")
|
||||
def canChangeColors = hasSetColorCommand(dimmer)
|
||||
|
||||
if (shouldChangeColors && hasSetColorCommand(dimmer)) {
|
||||
def hue = getHue(dimmer, nextLevel)
|
||||
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel} and hue to ${hue}"
|
||||
dimmer.setColor([hue: hue, saturation: 100, level: nextLevel])
|
||||
} else if (hasSetLevelCommand(dimmer)) {
|
||||
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel}"
|
||||
dimmer.setLevel(nextLevel)
|
||||
log.debug "Setting ${deviceLabel(dimmer)} to ${nextLevel}"
|
||||
|
||||
if (shouldChangeColors && canChangeColors) {
|
||||
dimmer.setColor([hue: getHue(dimmer, nextLevel), saturation: 100, level: nextLevel])
|
||||
} else {
|
||||
log.warn "${deviceLabel(dimmer)} does not have setColor or setLevel commands."
|
||||
dimmer.setLevel(nextLevel)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -848,21 +817,24 @@ private getRedHue(level) {
|
||||
if (level >= 96) return 17
|
||||
}
|
||||
|
||||
private dimmersContainUnsupportedDevices() {
|
||||
def found = dimmers.find { hasSetLevelCommand(it) == false }
|
||||
return found != null
|
||||
}
|
||||
|
||||
private hasSetLevelCommand(device) {
|
||||
return hasCommand(device, "setLevel")
|
||||
def isDimmer = false
|
||||
device.supportedCommands.each {
|
||||
if (it.name.contains("setLevel")) {
|
||||
isDimmer = true
|
||||
}
|
||||
}
|
||||
return isDimmer
|
||||
}
|
||||
|
||||
private hasSetColorCommand(device) {
|
||||
return hasCommand(device, "setColor")
|
||||
}
|
||||
|
||||
private hasCommand(device, String command) {
|
||||
return (device.supportedCommands.find { it.name == command } != null)
|
||||
def hasColor = false
|
||||
device.supportedCommands.each {
|
||||
if (it.name.contains("setColor")) {
|
||||
hasColor = true
|
||||
}
|
||||
}
|
||||
return hasColor
|
||||
}
|
||||
|
||||
private dimmersWithSetColorCommand() {
|
||||
@@ -1101,4 +1073,4 @@ def hasStartLevel() {
|
||||
|
||||
def hasEndLevel() {
|
||||
return (endLevel != null && endLevel != "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ definition(
|
||||
preferences {
|
||||
page(name:"mainPage", title:"Hue Device Setup", content:"mainPage", refreshTimeout:5)
|
||||
page(name:"bridgeDiscovery", title:"Hue Bridge Discovery", content:"bridgeDiscovery", refreshTimeout:5)
|
||||
page(name:"bridgeDiscoveryFailed", title:"Bridge Discovery Failed", content:"bridgeDiscoveryFailed", refreshTimeout:0)
|
||||
page(name:"bridgeBtnPush", title:"Linking with your Hue", content:"bridgeLinking", refreshTimeout:5)
|
||||
page(name:"bulbDiscovery", title:"Hue Device Setup", content:"bulbDiscovery", refreshTimeout:5)
|
||||
}
|
||||
@@ -54,21 +53,12 @@ def bridgeDiscovery(params=[:])
|
||||
def options = bridges ?: []
|
||||
def numFound = options.size() ?: 0
|
||||
|
||||
if (numFound == 0) {
|
||||
if (state.bridgeRefreshCount == 25) {
|
||||
log.trace "Cleaning old bridges memory"
|
||||
state.bridges = [:]
|
||||
app.updateSetting("selectedHue", "")
|
||||
} else if (state.bridgeRefreshCount > 100) {
|
||||
// five minutes have passed, give up
|
||||
// there seems to be a problem going back from discovey failed page in some instances (compared to pressing next)
|
||||
// however it is probably a SmartThings settings issue
|
||||
state.bridges = [:]
|
||||
app.updateSetting("selectedHue", "")
|
||||
state.bridgeRefreshCount = 0
|
||||
return bridgeDiscoveryFailed()
|
||||
}
|
||||
}
|
||||
if (numFound == 0 && state.bridgeRefreshCount > 25) {
|
||||
log.trace "Cleaning old bridges memory"
|
||||
state.bridges = [:]
|
||||
state.bridgeRefreshCount = 0
|
||||
app.updateSetting("selectedHue", "")
|
||||
}
|
||||
|
||||
ssdpSubscribe()
|
||||
|
||||
@@ -89,13 +79,6 @@ def bridgeDiscovery(params=[:])
|
||||
}
|
||||
}
|
||||
|
||||
def bridgeDiscoveryFailed() {
|
||||
return dynamicPage(name:"bridgeDiscoveryFailed", title: "Bridge Discovery Failed", nextPage: "bridgeDiscovery") {
|
||||
section("Failed to discover any Hue Bridges. Please confirm that the Hue Bridge is connected to the same network as your SmartThings Hub, and that it has power.") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def bridgeLinking()
|
||||
{
|
||||
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
|
||||
@@ -105,15 +88,19 @@ def bridgeLinking()
|
||||
def nextPage = ""
|
||||
def title = "Linking with your Hue"
|
||||
def paragraphText
|
||||
def hueimage = null
|
||||
if (selectedHue) {
|
||||
paragraphText = "Press the button on your Hue Bridge to setup a link. "
|
||||
hueimage = "http://huedisco.mediavibe.nl/wp-content/uploads/2013/09/pair-bridge.png"
|
||||
} else {
|
||||
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
|
||||
hueimage = null
|
||||
}
|
||||
if (state.username) { //if discovery worked
|
||||
nextPage = "bulbDiscovery"
|
||||
title = "Success!"
|
||||
paragraphText = "Linking to your hub was a success! Please click 'Next'!"
|
||||
hueimage = null
|
||||
}
|
||||
|
||||
if((linkRefreshcount % 2) == 0 && !state.username) {
|
||||
@@ -123,6 +110,8 @@ def bridgeLinking()
|
||||
return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) {
|
||||
section("") {
|
||||
paragraph """${paragraphText}"""
|
||||
if (hueimage != null)
|
||||
image "${hueimage}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,14 +135,13 @@ def bulbDiscovery() {
|
||||
if((bulbRefreshCount % 5) == 0) {
|
||||
discoverHueBulbs()
|
||||
}
|
||||
def selectedBridge = state.bridges.find { key, value -> value?.serialNumber?.equalsIgnoreCase(selectedHue) }
|
||||
def title = selectedBridge?.value?.name ?: "Find bridges"
|
||||
|
||||
return dynamicPage(name:"bulbDiscovery", title:"Bulb Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||
section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||
input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions
|
||||
}
|
||||
section {
|
||||
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
|
||||
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||
|
||||
}
|
||||
@@ -360,19 +348,18 @@ def addBulbs() {
|
||||
def newHueBulb
|
||||
if (bulbs instanceof java.util.Map) {
|
||||
newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
|
||||
if (newHueBulb != null) {
|
||||
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
||||
if (newHueBulb != null) {
|
||||
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
||||
if (d) {
|
||||
log.debug "created ${d.displayName} with id $dni"
|
||||
d.completedSetup = true
|
||||
d.refresh()
|
||||
}
|
||||
} else {
|
||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||
}
|
||||
} else {
|
||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||
}
|
||||
} else {
|
||||
//backwards compatable
|
||||
//backwards compatable
|
||||
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
|
||||
d = addChildBulb(dni, "Extended Color Light", newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
||||
d?.completedSetup = true
|
||||
@@ -382,7 +369,7 @@ def addBulbs() {
|
||||
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
|
||||
if (bulbs instanceof java.util.Map) {
|
||||
// Update device type if incorrect
|
||||
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
upgradeDeviceType(d, newHueBulb?.value?.type)
|
||||
}
|
||||
}
|
||||
@@ -502,21 +489,7 @@ void bridgeDescriptionHandler(physicalgraph.device.HubResponse hubResponse) {
|
||||
def bridges = getHueBridges()
|
||||
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
|
||||
if (bridge) {
|
||||
// serialNumber from API is in format of 0017882413ad (mac address), however on the actual bridge only last six
|
||||
// characters are printed on the back so using that to identify bridge
|
||||
def idNumber = body?.device?.serialNumber?.text()
|
||||
if (idNumber?.size() >= 6)
|
||||
idNumber = idNumber[-6..-1].toUpperCase()
|
||||
|
||||
// usually in form of bridge name followed by (ip), i.e. defaults to Philips Hue (192.168.1.2)
|
||||
// replace IP with serial number to make it easier for user to identify
|
||||
def name = body?.device?.friendlyName?.text()
|
||||
def index = name?.indexOf('(')
|
||||
if (index != -1) {
|
||||
name = name.substring(0,index)
|
||||
name += " ($idNumber)"
|
||||
}
|
||||
bridge.value << [name:name, serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
||||
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
||||
} else {
|
||||
log.error "/description.xml returned a bridge that didn't exist"
|
||||
}
|
||||
|
||||
@@ -658,73 +658,29 @@ def updateDevice() {
|
||||
def data = request.JSON
|
||||
def command = data.command
|
||||
def arguments = data.arguments
|
||||
|
||||
log.debug "updateDevice, params: ${params}, request: ${data}"
|
||||
if (!command) {
|
||||
render status: 400, data: '{"msg": "command is required"}'
|
||||
} else {
|
||||
def device = allDevices.find { it.id == params.id }
|
||||
if (device) {
|
||||
if (validateCommand(device, command)) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
} else {
|
||||
render status: 403, data: '{"msg": "Access denied. This command is not supported by current capability."}'
|
||||
}
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, command) {
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
def currentDeviceCapability = getCapabilityName(device)
|
||||
if (currentDeviceCapability != "" && capabilityCommands[currentDeviceCapability]) {
|
||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
if (device) {
|
||||
if (device.hasCommand("$command")) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Command not supported by this Device"}'
|
||||
}
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(device) {
|
||||
def capName = ""
|
||||
if (switches.find{it.id == device.id})
|
||||
capName = "Switch"
|
||||
else if (alarms.find{it.id == device.id})
|
||||
capName = "Alarm"
|
||||
else if (locks.find{it.id == device.id})
|
||||
capName = "Lock"
|
||||
log.trace "Device: $device - Capability Name: $capName"
|
||||
return capName
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
def listSubscriptions() {
|
||||
log.debug "listSubscriptions()"
|
||||
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
|
||||
@@ -824,51 +780,17 @@ def deviceHandler(evt) {
|
||||
|
||||
def sendToHarmony(evt, String callbackUrl) {
|
||||
def callback = new URI(callbackUrl)
|
||||
if(isIP(callback.host)){
|
||||
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
|
||||
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
|
||||
sendHubCommand(new physicalgraph.device.HubAction(
|
||||
method: "POST",
|
||||
path: path,
|
||||
headers: [
|
||||
"Host": host,
|
||||
"Content-Type": "application/json"
|
||||
],
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
))
|
||||
} else {
|
||||
def params = [
|
||||
uri: callbackUrl,
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
]
|
||||
try {
|
||||
log.debug "Sending data to Harmony Cloud: $params"
|
||||
httpPostJson(params) { resp ->
|
||||
log.debug "Harmony Cloud - Response: ${resp.status}"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "Harmony Cloud - Something went wrong: $e"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isIP(String str) {
|
||||
try {
|
||||
String[] parts = str.split("\\.");
|
||||
if (parts.length != 4) return false;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int p
|
||||
try {
|
||||
p = Integer.parseInt(parts[i]);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
if (p > 255 || p < 0) return false;
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
|
||||
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
|
||||
sendHubCommand(new physicalgraph.device.HubAction(
|
||||
method: "POST",
|
||||
path: path,
|
||||
headers: [
|
||||
"Host": host,
|
||||
"Content-Type": "application/json"
|
||||
],
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
))
|
||||
}
|
||||
|
||||
def listHubs() {
|
||||
|
||||
@@ -92,89 +92,24 @@ void updateLock() {
|
||||
|
||||
private void updateAll(devices) {
|
||||
def command = request.JSON?.command
|
||||
def type = params.param1
|
||||
if (!devices) {
|
||||
httpError(404, "Devices not found")
|
||||
}
|
||||
|
||||
if (command){
|
||||
devices.each { device ->
|
||||
executeCommand(device, type, command)
|
||||
}
|
||||
if (command) {
|
||||
devices."$command"()
|
||||
}
|
||||
}
|
||||
|
||||
private void update(devices) {
|
||||
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
|
||||
def command = request.JSON?.command
|
||||
def type = params.param1
|
||||
def device = devices?.find { it.id == params.id }
|
||||
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
}
|
||||
|
||||
if (command) {
|
||||
executeCommand(device, type, command)
|
||||
def device = devices.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, deviceType, command) {
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
def currentDeviceCapability = getCapabilityName(deviceType)
|
||||
if (capabilityCommands[currentDeviceCapability]) {
|
||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(type) {
|
||||
switch(type) {
|
||||
case "switches":
|
||||
return "Switch"
|
||||
case "locks":
|
||||
return "Lock"
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and executes the command
|
||||
* on the device or devices
|
||||
*/
|
||||
def executeCommand(device, type, command) {
|
||||
if (validateCommand(device, type, command)) {
|
||||
device."$command"()
|
||||
} else {
|
||||
httpError(403, "Access denied. This command is not supported by current capability.")
|
||||
}
|
||||
}
|
||||
|
||||
private show(devices, name) {
|
||||
def device = devices.find { it.id == params.id }
|
||||
if (!device) {
|
||||
|
||||
Reference in New Issue
Block a user