|
|
|
|
@@ -8,7 +8,7 @@
|
|
|
|
|
* 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
|
|
|
|
|
* 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
|
|
|
|
|
@@ -24,7 +24,7 @@ definition(
|
|
|
|
|
category: "SmartThings Labs",
|
|
|
|
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
|
|
|
|
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
|
|
|
|
|
singleInstance: true
|
|
|
|
|
singleInstance: true
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
preferences {
|
|
|
|
|
@@ -110,7 +110,7 @@ def bridgeLinking() {
|
|
|
|
|
if (state.refreshUsernameNeeded) {
|
|
|
|
|
paragraphText = "The current Hue username is invalid.\n\nPlease press the button on your Hue Bridge to re-link. "
|
|
|
|
|
} else {
|
|
|
|
|
paragraphText = "Press the button on your Hue Bridge to setup a link. "
|
|
|
|
|
paragraphText = "Press the button on your Hue Bridge to setup a link. "
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
|
|
|
|
|
@@ -198,7 +198,7 @@ void ssdpSubscribe() {
|
|
|
|
|
|
|
|
|
|
private sendDeveloperReq() {
|
|
|
|
|
def token = app.id
|
|
|
|
|
def host = getBridgeIP()
|
|
|
|
|
def host = getBridgeIP()
|
|
|
|
|
sendHubCommand(new physicalgraph.device.HubAction([
|
|
|
|
|
method: "POST",
|
|
|
|
|
path: "/api",
|
|
|
|
|
@@ -209,7 +209,7 @@ private sendDeveloperReq() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private discoverHueBulbs() {
|
|
|
|
|
def host = getBridgeIP()
|
|
|
|
|
def host = getBridgeIP()
|
|
|
|
|
sendHubCommand(new physicalgraph.device.HubAction([
|
|
|
|
|
method: "GET",
|
|
|
|
|
path: "/api/${state.username}/lights",
|
|
|
|
|
@@ -231,8 +231,8 @@ private verifyHueBridge(String deviceNetworkId, String host) {
|
|
|
|
|
private verifyHueBridges() {
|
|
|
|
|
def devices = getHueBridges().findAll { it?.value?.verified != true }
|
|
|
|
|
devices.each {
|
|
|
|
|
def ip = convertHexToIP(it.value.networkAddress)
|
|
|
|
|
def port = convertHexToInt(it.value.deviceAddress)
|
|
|
|
|
def ip = convertHexToIP(it.value.networkAddress)
|
|
|
|
|
def port = convertHexToInt(it.value.deviceAddress)
|
|
|
|
|
verifyHueBridge("${it.value.mac}", (ip + ":" + port))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -261,7 +261,7 @@ Map bulbsDiscovered() {
|
|
|
|
|
bulbs.each {
|
|
|
|
|
def value = "${it.name}"
|
|
|
|
|
def key = app.id +"/"+ it.id
|
|
|
|
|
logg += "$value - $key, "
|
|
|
|
|
logg += "$value - $key, "
|
|
|
|
|
bulbmap["${key}"] = value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -288,22 +288,22 @@ def installed() {
|
|
|
|
|
def updated() {
|
|
|
|
|
log.trace "Updated with settings: ${settings}"
|
|
|
|
|
unsubscribe()
|
|
|
|
|
unschedule()
|
|
|
|
|
unschedule()
|
|
|
|
|
initialize()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def initialize() {
|
|
|
|
|
log.debug "Initializing"
|
|
|
|
|
unsubscribe(bridge)
|
|
|
|
|
state.inBulbDiscovery = false
|
|
|
|
|
state.bridgeRefreshCount = 0
|
|
|
|
|
state.bulbRefreshCount = 0
|
|
|
|
|
unsubscribe(bridge)
|
|
|
|
|
state.inBulbDiscovery = false
|
|
|
|
|
state.bridgeRefreshCount = 0
|
|
|
|
|
state.bulbRefreshCount = 0
|
|
|
|
|
state.updating = false
|
|
|
|
|
if (selectedHue) {
|
|
|
|
|
addBridge()
|
|
|
|
|
addBulbs()
|
|
|
|
|
doDeviceSync()
|
|
|
|
|
runEvery5Minutes("doDeviceSync")
|
|
|
|
|
addBridge()
|
|
|
|
|
addBulbs()
|
|
|
|
|
doDeviceSync()
|
|
|
|
|
runEvery5Minutes("doDeviceSync")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -369,7 +369,6 @@ def addBulbs() {
|
|
|
|
|
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"
|
|
|
|
|
@@ -399,23 +398,23 @@ def addBridge() {
|
|
|
|
|
if(vbridge) {
|
|
|
|
|
def d = getChildDevice(selectedHue)
|
|
|
|
|
if(!d) {
|
|
|
|
|
// compatibility with old devices
|
|
|
|
|
def newbridge = true
|
|
|
|
|
childDevices.each {
|
|
|
|
|
if (it.getDeviceDataByName("mac")) {
|
|
|
|
|
def newDNI = "${it.getDeviceDataByName("mac")}"
|
|
|
|
|
if (newDNI != it.deviceNetworkId) {
|
|
|
|
|
def oldDNI = it.deviceNetworkId
|
|
|
|
|
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
|
|
|
|
it.setDeviceNetworkId("${newDNI}")
|
|
|
|
|
// compatibility with old devices
|
|
|
|
|
def newbridge = true
|
|
|
|
|
childDevices.each {
|
|
|
|
|
if (it.getDeviceDataByName("mac")) {
|
|
|
|
|
def newDNI = "${it.getDeviceDataByName("mac")}"
|
|
|
|
|
if (newDNI != it.deviceNetworkId) {
|
|
|
|
|
def oldDNI = it.deviceNetworkId
|
|
|
|
|
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
|
|
|
|
it.setDeviceNetworkId("${newDNI}")
|
|
|
|
|
if (oldDNI == selectedHue) {
|
|
|
|
|
app.updateSetting("selectedHue", newDNI)
|
|
|
|
|
}
|
|
|
|
|
newbridge = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (newbridge) {
|
|
|
|
|
newbridge = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (newbridge) {
|
|
|
|
|
// Hue uses last 6 digits of MAC address as ID number, this number is shown on the bottom of the bridge
|
|
|
|
|
def idNumber = getBridgeIdNumber(selectedHue)
|
|
|
|
|
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub, ["label": "Hue Bridge ($idNumber)"])
|
|
|
|
|
@@ -426,9 +425,11 @@ def addBridge() {
|
|
|
|
|
d.completedSetup = true
|
|
|
|
|
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
|
|
|
|
def childDevice = getChildDevice(d.deviceNetworkId)
|
|
|
|
|
childDevice?.sendEvent(name: "status", value: "Online")
|
|
|
|
|
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
|
|
|
|
|
updateBridgeStatus(childDevice)
|
|
|
|
|
childDevice.sendEvent(name: "idNumber", value: idNumber)
|
|
|
|
|
|
|
|
|
|
childDevice?.sendEvent(name: "idNumber", value: idNumber)
|
|
|
|
|
if (vbridge.value.ip && vbridge.value.port) {
|
|
|
|
|
if (vbridge.value.ip.contains(".")) {
|
|
|
|
|
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
|
|
|
|
|
@@ -580,47 +581,47 @@ void usernameHandler(physicalgraph.device.HubResponse hubResponse) {
|
|
|
|
|
@Deprecated
|
|
|
|
|
def locationHandler(evt) {
|
|
|
|
|
def description = evt.description
|
|
|
|
|
log.trace "Location: $description"
|
|
|
|
|
log.trace "Location: $description"
|
|
|
|
|
|
|
|
|
|
def hub = evt?.hubId
|
|
|
|
|
def parsedEvent = parseLanMessage(description)
|
|
|
|
|
parsedEvent << ["hub":hub]
|
|
|
|
|
|
|
|
|
|
if (parsedEvent?.ssdpTerm?.contains("urn:schemas-upnp-org:device:basic:1")) {
|
|
|
|
|
//SSDP DISCOVERY EVENTS
|
|
|
|
|
//SSDP DISCOVERY EVENTS
|
|
|
|
|
log.trace "SSDP DISCOVERY EVENTS"
|
|
|
|
|
def bridges = getHueBridges()
|
|
|
|
|
log.trace bridges.toString()
|
|
|
|
|
if (!(bridges."${parsedEvent.ssdpUSN.toString()}")) {
|
|
|
|
|
//bridge does not exist
|
|
|
|
|
//bridge does not exist
|
|
|
|
|
log.trace "Adding bridge ${parsedEvent.ssdpUSN}"
|
|
|
|
|
bridges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
|
|
|
|
} else {
|
|
|
|
|
// update the values
|
|
|
|
|
def ip = convertHexToIP(parsedEvent.networkAddress)
|
|
|
|
|
def port = convertHexToInt(parsedEvent.deviceAddress)
|
|
|
|
|
def host = ip + ":" + port
|
|
|
|
|
def ip = convertHexToIP(parsedEvent.networkAddress)
|
|
|
|
|
def port = convertHexToInt(parsedEvent.deviceAddress)
|
|
|
|
|
def host = ip + ":" + port
|
|
|
|
|
log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host."
|
|
|
|
|
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
|
|
|
|
|
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
|
|
|
|
|
def dni = "${parsedEvent.mac}"
|
|
|
|
|
def d = getChildDevice(dni)
|
|
|
|
|
def networkAddress = null
|
|
|
|
|
if (!d) {
|
|
|
|
|
childDevices.each {
|
|
|
|
|
if (it.getDeviceDataByName("mac")) {
|
|
|
|
|
def newDNI = "${it.getDeviceDataByName("mac")}"
|
|
|
|
|
def d = getChildDevice(dni)
|
|
|
|
|
def networkAddress = null
|
|
|
|
|
if (!d) {
|
|
|
|
|
childDevices.each {
|
|
|
|
|
if (it.getDeviceDataByName("mac")) {
|
|
|
|
|
def newDNI = "${it.getDeviceDataByName("mac")}"
|
|
|
|
|
d = it
|
|
|
|
|
if (newDNI != it.deviceNetworkId) {
|
|
|
|
|
def oldDNI = it.deviceNetworkId
|
|
|
|
|
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
|
|
|
|
it.setDeviceNetworkId("${newDNI}")
|
|
|
|
|
if (newDNI != it.deviceNetworkId) {
|
|
|
|
|
def oldDNI = it.deviceNetworkId
|
|
|
|
|
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
|
|
|
|
it.setDeviceNetworkId("${newDNI}")
|
|
|
|
|
if (oldDNI == selectedHue) {
|
|
|
|
|
app.updateSetting("selectedHue", newDNI)
|
|
|
|
|
}
|
|
|
|
|
doDeviceSync()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
doDeviceSync()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
updateBridgeStatus(d)
|
|
|
|
|
if (d.getDeviceDataByName("networkAddress")) {
|
|
|
|
|
@@ -628,22 +629,22 @@ def locationHandler(evt) {
|
|
|
|
|
} else {
|
|
|
|
|
networkAddress = d.latestState('networkAddress').stringValue
|
|
|
|
|
}
|
|
|
|
|
log.trace "Host: $host - $networkAddress"
|
|
|
|
|
if(host != networkAddress) {
|
|
|
|
|
log.debug "Device's port or ip changed for device $d..."
|
|
|
|
|
dstate.ip = ip
|
|
|
|
|
dstate.port = port
|
|
|
|
|
dstate.name = "Philips hue ($ip)"
|
|
|
|
|
d.sendEvent(name:"networkAddress", value: host)
|
|
|
|
|
d.updateDataValue("networkAddress", host)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.trace "Host: $host - $networkAddress"
|
|
|
|
|
if(host != networkAddress) {
|
|
|
|
|
log.debug "Device's port or ip changed for device $d..."
|
|
|
|
|
dstate.ip = ip
|
|
|
|
|
dstate.port = port
|
|
|
|
|
dstate.name = "Philips hue ($ip)"
|
|
|
|
|
d.sendEvent(name:"networkAddress", value: host)
|
|
|
|
|
d.updateDataValue("networkAddress", host)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (parsedEvent.headers && parsedEvent.body) {
|
|
|
|
|
log.trace "HUE BRIDGE RESPONSES"
|
|
|
|
|
def headerString = parsedEvent.headers.toString()
|
|
|
|
|
if (headerString?.contains("xml")) {
|
|
|
|
|
log.trace "description.xml response (application/xml)"
|
|
|
|
|
log.trace "description.xml response (application/xml)"
|
|
|
|
|
def body = new XmlSlurper().parseText(parsedEvent.body)
|
|
|
|
|
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
|
|
|
|
|
def bridges = getHueBridges()
|
|
|
|
|
@@ -655,7 +656,7 @@ def locationHandler(evt) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if(headerString?.contains("json") && isValidSource(parsedEvent.mac)) {
|
|
|
|
|
log.trace "description.xml response (application/json)"
|
|
|
|
|
log.trace "description.xml response (application/json)"
|
|
|
|
|
def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body)
|
|
|
|
|
if (body.success != null) {
|
|
|
|
|
if (body.success[0] != null) {
|
|
|
|
|
@@ -692,7 +693,7 @@ def doDeviceSync(){
|
|
|
|
|
poll()
|
|
|
|
|
ssdpSubscribe()
|
|
|
|
|
discoverBridges()
|
|
|
|
|
checkBridgeStatus()
|
|
|
|
|
checkBridgeStatus()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -705,10 +706,14 @@ def doDeviceSync(){
|
|
|
|
|
private void updateBridgeStatus(childDevice) {
|
|
|
|
|
// Update activity timestamp if child device is a valid bridge
|
|
|
|
|
def vbridges = getVerifiedHueBridges()
|
|
|
|
|
def vbridge = vbridges.find {"${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase()}
|
|
|
|
|
def vbridge = vbridges.find {
|
|
|
|
|
"${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase()
|
|
|
|
|
}
|
|
|
|
|
vbridge?.value?.lastActivity = now()
|
|
|
|
|
if(vbridge) {
|
|
|
|
|
if (vbridge && childDevice?.device?.currentValue("status") == "Offline") {
|
|
|
|
|
log.debug "$childDevice is back Online"
|
|
|
|
|
childDevice?.sendEvent(name: "status", value: "Online")
|
|
|
|
|
childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -717,29 +722,37 @@ private void updateBridgeStatus(childDevice) {
|
|
|
|
|
* for the bridge and all connected lights. Also, set ID number on bridge if not done previously.
|
|
|
|
|
*/
|
|
|
|
|
private void checkBridgeStatus() {
|
|
|
|
|
def bridges = getHueBridges()
|
|
|
|
|
// Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer)
|
|
|
|
|
def time = now() - (1000 * 60 * 11)
|
|
|
|
|
bridges.each {
|
|
|
|
|
def d = getChildDevice(it.value.mac)
|
|
|
|
|
if(d) {
|
|
|
|
|
// Set id number on bridge if not done
|
|
|
|
|
if (it.value.idNumber == null) {
|
|
|
|
|
it.value.idNumber = getBridgeIdNumber(it.value.serialNumber)
|
|
|
|
|
def bridges = getHueBridges()
|
|
|
|
|
// Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer)
|
|
|
|
|
def time = now() - (1000 * 60 * 11)
|
|
|
|
|
bridges.each {
|
|
|
|
|
def d = getChildDevice(it.value.mac)
|
|
|
|
|
if (d) {
|
|
|
|
|
// Set id number on bridge if not done
|
|
|
|
|
if (it.value.idNumber == null) {
|
|
|
|
|
it.value.idNumber = getBridgeIdNumber(it.value.serialNumber)
|
|
|
|
|
d.sendEvent(name: "idNumber", value: it.value.idNumber)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
|
|
|
|
|
log.warn "Bridge $it.value.idNumber is Offline"
|
|
|
|
|
d.sendEvent(name: "status", value: "Offline")
|
|
|
|
|
if (d.currentStatus == "Online") {
|
|
|
|
|
log.warn "$d is Offline"
|
|
|
|
|
d.sendEvent(name: "status", value: "Offline")
|
|
|
|
|
d.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
|
|
|
|
|
|
|
|
|
|
state.bulbs?.each {
|
|
|
|
|
it.value.online = false
|
|
|
|
|
Calendar currentTime = Calendar.getInstance()
|
|
|
|
|
getChildDevices().each {
|
|
|
|
|
def id = getId(it)
|
|
|
|
|
if (state.bulbs[id]?.online == true) {
|
|
|
|
|
state.bulbs[id]?.online = false
|
|
|
|
|
state.bulbs[id]?.unreachableSince = currentTime.getTimeInMillis()
|
|
|
|
|
it.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
getChildDevices().each {
|
|
|
|
|
it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", isStateChange: true, displayed: false)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
} else if (d.currentStatus == "Offline") {
|
|
|
|
|
log.debug "$d is back Online"
|
|
|
|
|
d.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
|
|
|
|
|
d.sendEvent(name: "status", value: "Online")//setOnline(false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -791,24 +804,24 @@ def parse(childDevice, description) {
|
|
|
|
|
def parsedEvent = parseLanMessage(description)
|
|
|
|
|
if (parsedEvent.headers && parsedEvent.body) {
|
|
|
|
|
def headerString = parsedEvent.headers.toString()
|
|
|
|
|
def bodyString = parsedEvent.body.toString()
|
|
|
|
|
def bodyString = parsedEvent.body.toString()
|
|
|
|
|
if (headerString?.contains("json")) {
|
|
|
|
|
def body
|
|
|
|
|
try {
|
|
|
|
|
body = new groovy.json.JsonSlurper().parseText(bodyString)
|
|
|
|
|
} catch (all) {
|
|
|
|
|
log.warn "Parsing Body failed - trying again..."
|
|
|
|
|
poll()
|
|
|
|
|
}
|
|
|
|
|
if (body instanceof java.util.Map) {
|
|
|
|
|
// get (poll) reponse
|
|
|
|
|
return handlePoll(body)
|
|
|
|
|
def body
|
|
|
|
|
try {
|
|
|
|
|
body = new groovy.json.JsonSlurper().parseText(bodyString)
|
|
|
|
|
} catch (all) {
|
|
|
|
|
log.warn "Parsing Body failed - trying again..."
|
|
|
|
|
poll()
|
|
|
|
|
}
|
|
|
|
|
if (body instanceof java.util.Map) {
|
|
|
|
|
// get (poll) reponse
|
|
|
|
|
return handlePoll(body)
|
|
|
|
|
} else {
|
|
|
|
|
//put response
|
|
|
|
|
return handleCommandResponse(body)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
log.debug "parse - got something other than headers,body..."
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
@@ -829,19 +842,19 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) {
|
|
|
|
|
device.sendEvent([name: "colorTemperature", value: temp, descriptionText: "Color temperature has changed"])
|
|
|
|
|
// Return because color temperature change is not counted as a color change in SmartThings so no hex update necessary
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hue != null) {
|
|
|
|
|
// 0-65535
|
|
|
|
|
def value = Math.min(Math.round(hue * 100 / 65535), 65535) as int
|
|
|
|
|
events["hue"] = [name: "hue", value: value, descriptionText: "Color has changed", displayed: false]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sat != null) {
|
|
|
|
|
// 0-254
|
|
|
|
|
def value = Math.round(sat * 100 / 254) as int
|
|
|
|
|
events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false]
|
|
|
|
|
}
|
|
|
|
|
events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Following is used to decide what to base hex calculations on since it is preferred to return a colorchange in hex
|
|
|
|
|
if (xy != null && colormode != "hs") {
|
|
|
|
|
@@ -943,12 +956,9 @@ private handleCommandResponse(body) {
|
|
|
|
|
updates.each { childDeviceNetworkId, params ->
|
|
|
|
|
def device = getChildDevice(childDeviceNetworkId)
|
|
|
|
|
def id = getId(device)
|
|
|
|
|
// If device is offline, then don't send events which will update device watch
|
|
|
|
|
if (isOnline(id)) {
|
|
|
|
|
sendBasicEvents(device, "on", params.on)
|
|
|
|
|
sendBasicEvents(device, "bri", params.bri)
|
|
|
|
|
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
|
|
|
|
|
}
|
|
|
|
|
sendBasicEvents(device, "on", params.on)
|
|
|
|
|
sendBasicEvents(device, "bri", params.bri)
|
|
|
|
|
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
|
|
|
|
|
}
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
@@ -981,39 +991,38 @@ private handlePoll(body) {
|
|
|
|
|
|
|
|
|
|
def bulbs = getChildDevices()
|
|
|
|
|
for (bulb in body) {
|
|
|
|
|
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
|
|
|
|
|
def device = bulbs.find { it.deviceNetworkId == "${app.id}/${bulb.key}" }
|
|
|
|
|
if (device) {
|
|
|
|
|
if (bulb.value.state?.reachable) {
|
|
|
|
|
if (state.bulbs[bulb.key]?.online == false) {
|
|
|
|
|
if (state.bulbs[bulb.key]?.online == false || state.bulbs[bulb.key]?.online == null) {
|
|
|
|
|
// light just came back online, notify device watch
|
|
|
|
|
def lastActivity = now()
|
|
|
|
|
device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true)
|
|
|
|
|
device.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true)
|
|
|
|
|
log.debug "$device is Online"
|
|
|
|
|
}
|
|
|
|
|
// Mark light as "online"
|
|
|
|
|
state.bulbs[bulb.key]?.unreachableSince = null
|
|
|
|
|
state.bulbs[bulb.key]?.online = true
|
|
|
|
|
|
|
|
|
|
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
|
|
|
|
|
if (!state.updating) {
|
|
|
|
|
sendBasicEvents(device, "on", bulb.value?.state?.on)
|
|
|
|
|
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
|
|
|
|
|
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (state.bulbs[bulb.key]?.unreachableSince == null) {
|
|
|
|
|
// Store the first time where device was reported as "unreachable"
|
|
|
|
|
state.bulbs[bulb.key]?.unreachableSince = currentTime.getTimeInMillis()
|
|
|
|
|
} else if (state.bulbs[bulb.key]?.online) {
|
|
|
|
|
}
|
|
|
|
|
if (state.bulbs[bulb.key]?.online || state.bulbs[bulb.key]?.online == null) {
|
|
|
|
|
// Check if device was "unreachable" for more than 11 minutes and mark "offline" if necessary
|
|
|
|
|
if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis()) {
|
|
|
|
|
if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis() || state.bulbs[bulb.key]?.online == null) {
|
|
|
|
|
log.warn "$device went Offline"
|
|
|
|
|
state.bulbs[bulb.key]?.online = false
|
|
|
|
|
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
|
|
|
|
|
device.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.warn "$device may not reachable by Hue bridge"
|
|
|
|
|
}
|
|
|
|
|
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
|
|
|
|
|
if (!state.updating) {
|
|
|
|
|
sendBasicEvents(device, "on", bulb.value?.state?.on)
|
|
|
|
|
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
|
|
|
|
|
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return []
|
|
|
|
|
@@ -1032,16 +1041,16 @@ def updateHandler() {
|
|
|
|
|
|
|
|
|
|
def hubVerification(bodytext) {
|
|
|
|
|
log.trace "Bridge sent back description.xml for verification"
|
|
|
|
|
def body = new XmlSlurper().parseText(bodytext)
|
|
|
|
|
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
|
|
|
|
|
def bridges = getHueBridges()
|
|
|
|
|
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
|
|
|
|
|
if (bridge) {
|
|
|
|
|
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"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
def body = new XmlSlurper().parseText(bodytext)
|
|
|
|
|
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
|
|
|
|
|
def bridges = getHueBridges()
|
|
|
|
|
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
|
|
|
|
|
if (bridge) {
|
|
|
|
|
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"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def on(childDevice) {
|
|
|
|
|
@@ -1050,7 +1059,7 @@ def on(childDevice) {
|
|
|
|
|
updateInProgress()
|
|
|
|
|
createSwitchEvent(childDevice, "on")
|
|
|
|
|
put("lights/$id/state", [on: true])
|
|
|
|
|
return "Bulb is turning On"
|
|
|
|
|
return "Bulb is turning On"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def off(childDevice) {
|
|
|
|
|
@@ -1059,15 +1068,15 @@ def off(childDevice) {
|
|
|
|
|
updateInProgress()
|
|
|
|
|
createSwitchEvent(childDevice, "off")
|
|
|
|
|
put("lights/$id/state", [on: false])
|
|
|
|
|
return "Bulb is turning Off"
|
|
|
|
|
return "Bulb is turning Off"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def setLevel(childDevice, percent) {
|
|
|
|
|
log.debug "Executing 'setLevel'"
|
|
|
|
|
def id = getId(childDevice)
|
|
|
|
|
updateInProgress()
|
|
|
|
|
updateInProgress()
|
|
|
|
|
// 1 - 254
|
|
|
|
|
def level
|
|
|
|
|
def level
|
|
|
|
|
if (percent == 1)
|
|
|
|
|
level = 1
|
|
|
|
|
else
|
|
|
|
|
@@ -1101,7 +1110,7 @@ def setSaturation(childDevice, percent) {
|
|
|
|
|
def setHue(childDevice, percent) {
|
|
|
|
|
log.debug "Executing 'setHue($percent)'"
|
|
|
|
|
def id = getId(childDevice)
|
|
|
|
|
updateInProgress()
|
|
|
|
|
updateInProgress()
|
|
|
|
|
// 0 - 65535
|
|
|
|
|
def level = Math.min(Math.round(percent * 65535 / 100), 65535)
|
|
|
|
|
// TODO should this be done by app only or should we default to on?
|
|
|
|
|
@@ -1113,7 +1122,7 @@ def setHue(childDevice, percent) {
|
|
|
|
|
def setColorTemperature(childDevice, huesettings) {
|
|
|
|
|
log.debug "Executing 'setColorTemperature($huesettings)'"
|
|
|
|
|
def id = getId(childDevice)
|
|
|
|
|
updateInProgress()
|
|
|
|
|
updateInProgress()
|
|
|
|
|
// 153 (6500K) to 500 (2000K)
|
|
|
|
|
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
|
|
|
|
|
createSwitchEvent(childDevice, "on")
|
|
|
|
|
@@ -1122,14 +1131,14 @@ def setColorTemperature(childDevice, huesettings) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def setColor(childDevice, huesettings) {
|
|
|
|
|
log.debug "Executing 'setColor($huesettings)'"
|
|
|
|
|
log.debug "Executing 'setColor($huesettings)'"
|
|
|
|
|
def id = getId(childDevice)
|
|
|
|
|
updateInProgress()
|
|
|
|
|
updateInProgress()
|
|
|
|
|
|
|
|
|
|
def value = [:]
|
|
|
|
|
def hue = null
|
|
|
|
|
def sat = null
|
|
|
|
|
def xy = null
|
|
|
|
|
def value = [:]
|
|
|
|
|
def hue = null
|
|
|
|
|
def sat = null
|
|
|
|
|
def xy = null
|
|
|
|
|
|
|
|
|
|
// Prefer hue/sat over hex to make sure it works with the majority of the smartapps
|
|
|
|
|
if (huesettings.hue != null || huesettings.sat != null) {
|
|
|
|
|
@@ -1154,48 +1163,32 @@ def setColor(childDevice, huesettings) {
|
|
|
|
|
// value.xy = calculateXY(hex, model)
|
|
|
|
|
// Once groups, or scenes are introduced it might be a good idea to use unique models again
|
|
|
|
|
value.xy = calculateXY(hex)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Default behavior is to turn light on
|
|
|
|
|
value.on = true
|
|
|
|
|
// Default behavior is to turn light on
|
|
|
|
|
value.on = true
|
|
|
|
|
|
|
|
|
|
if (huesettings.level != null) {
|
|
|
|
|
if (huesettings.level <= 0)
|
|
|
|
|
value.on = false
|
|
|
|
|
else if (huesettings.level == 1)
|
|
|
|
|
value.bri = 1
|
|
|
|
|
else
|
|
|
|
|
if (huesettings.level != null) {
|
|
|
|
|
if (huesettings.level <= 0)
|
|
|
|
|
value.on = false
|
|
|
|
|
else if (huesettings.level == 1)
|
|
|
|
|
value.bri = 1
|
|
|
|
|
else
|
|
|
|
|
value.bri = Math.min(Math.round(huesettings.level * 254 / 100), 254)
|
|
|
|
|
}
|
|
|
|
|
value.alert = huesettings.alert ? huesettings.alert : "none"
|
|
|
|
|
value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4
|
|
|
|
|
}
|
|
|
|
|
value.alert = huesettings.alert ? huesettings.alert : "none"
|
|
|
|
|
value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4
|
|
|
|
|
|
|
|
|
|
// Make sure to turn off light if requested
|
|
|
|
|
if (huesettings.switch == "off")
|
|
|
|
|
value.on = false
|
|
|
|
|
// Make sure to turn off light if requested
|
|
|
|
|
if (huesettings.switch == "off")
|
|
|
|
|
value.on = false
|
|
|
|
|
|
|
|
|
|
createSwitchEvent(childDevice, value.on ? "on" : "off")
|
|
|
|
|
put("lights/$id/state", value)
|
|
|
|
|
put("lights/$id/state", value)
|
|
|
|
|
return "Setting color to $value"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def ping(childDevice) {
|
|
|
|
|
if (childDevice.device?.deviceNetworkId?.equalsIgnoreCase(selectedHue)) {
|
|
|
|
|
if (childDevice.device?.currentValue("status")?.equalsIgnoreCase("Online")) {
|
|
|
|
|
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Bridge is reachable", displayed: false, isStateChange: true)
|
|
|
|
|
return "Bridge is Online"
|
|
|
|
|
} else {
|
|
|
|
|
return "Bridge is Offline"
|
|
|
|
|
}
|
|
|
|
|
} else if (isOnline(getId(childDevice))) {
|
|
|
|
|
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true)
|
|
|
|
|
return "Device is Online"
|
|
|
|
|
} else {
|
|
|
|
|
return "Device is Offline"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getId(childDevice) {
|
|
|
|
|
if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) {
|
|
|
|
|
return childDevice.device?.deviceNetworkId[3..-1]
|
|
|
|
|
@@ -1246,30 +1239,30 @@ private getBridgeIdNumber(serialNumber) {
|
|
|
|
|
private getBridgeIP() {
|
|
|
|
|
def host = null
|
|
|
|
|
if (selectedHue) {
|
|
|
|
|
def d = getChildDevice(selectedHue)
|
|
|
|
|
if (d) {
|
|
|
|
|
if (d.getDeviceDataByName("networkAddress"))
|
|
|
|
|
host = d.getDeviceDataByName("networkAddress")
|
|
|
|
|
else
|
|
|
|
|
host = d.latestState('networkAddress').stringValue
|
|
|
|
|
}
|
|
|
|
|
if (host == null || host == "") {
|
|
|
|
|
def serialNumber = selectedHue
|
|
|
|
|
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
|
|
|
|
|
if (!bridge) {
|
|
|
|
|
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
|
|
|
|
|
}
|
|
|
|
|
if (bridge?.ip && bridge?.port) {
|
|
|
|
|
if (bridge?.ip.contains("."))
|
|
|
|
|
host = "${bridge?.ip}:${bridge?.port}"
|
|
|
|
|
else
|
|
|
|
|
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
|
|
|
|
|
} else if (bridge?.networkAddress && bridge?.deviceAddress)
|
|
|
|
|
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
|
|
|
|
|
}
|
|
|
|
|
log.trace "Bridge: $selectedHue - Host: $host"
|
|
|
|
|
}
|
|
|
|
|
return host
|
|
|
|
|
def d = getChildDevice(selectedHue)
|
|
|
|
|
if (d) {
|
|
|
|
|
if (d.getDeviceDataByName("networkAddress"))
|
|
|
|
|
host = d.getDeviceDataByName("networkAddress")
|
|
|
|
|
else
|
|
|
|
|
host = d.latestState('networkAddress').stringValue
|
|
|
|
|
}
|
|
|
|
|
if (host == null || host == "") {
|
|
|
|
|
def serialNumber = selectedHue
|
|
|
|
|
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
|
|
|
|
|
if (!bridge) {
|
|
|
|
|
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
|
|
|
|
|
}
|
|
|
|
|
if (bridge?.ip && bridge?.port) {
|
|
|
|
|
if (bridge?.ip.contains("."))
|
|
|
|
|
host = "${bridge?.ip}:${bridge?.port}"
|
|
|
|
|
else
|
|
|
|
|
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
|
|
|
|
|
} else if (bridge?.networkAddress && bridge?.deviceAddress)
|
|
|
|
|
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
|
|
|
|
|
}
|
|
|
|
|
log.trace "Bridge: $selectedHue - Host: $host"
|
|
|
|
|
}
|
|
|
|
|
return host
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Integer convertHexToInt(hex) {
|
|
|
|
|
@@ -1309,7 +1302,7 @@ private List getRealHubFirmwareVersions() {
|
|
|
|
|
* @param childDevice device to send event for
|
|
|
|
|
* @param setSwitch The new switch state, "on" or "off"
|
|
|
|
|
* @param setLevel Optional, switchLevel between 0-100, used if you set level to 0 for example since
|
|
|
|
|
* that should generate "off" instead of level change
|
|
|
|
|
* that should generate "off" instead of level change
|
|
|
|
|
*/
|
|
|
|
|
private void createSwitchEvent(childDevice, setSwitch, setLevel = null) {
|
|
|
|
|
|
|
|
|
|
@@ -1452,8 +1445,8 @@ private float[] calculateXY(colorStr, model = null) {
|
|
|
|
|
xy[0] = closestPoint.x;
|
|
|
|
|
xy[1] = closestPoint.y;
|
|
|
|
|
}
|
|
|
|
|
// xy[0] = PHHueHelper.precision(4, xy[0]);
|
|
|
|
|
// xy[1] = PHHueHelper.precision(4, xy[1]);
|
|
|
|
|
// xy[0] = PHHueHelper.precision(4, xy[0]);
|
|
|
|
|
// xy[1] = PHHueHelper.precision(4, xy[1]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO needed, assume it just sets number of decimals?
|
|
|
|
|
@@ -1471,7 +1464,7 @@ private float[] calculateXY(colorStr, model = null) {
|
|
|
|
|
*
|
|
|
|
|
* @param points the float array contain x and the y value. [x,y]
|
|
|
|
|
* @param model the model of the lamp, example: "LCT001" for hue bulb. Used to calculate the color gamut.
|
|
|
|
|
* If this value is empty the default gamut values are used.
|
|
|
|
|
* If this value is empty the default gamut values are used.
|
|
|
|
|
* @return the color value in hex (#ff03d3). If xy is null OR xy is not an array of size 2, Color. BLACK will be returned
|
|
|
|
|
*/
|
|
|
|
|
private String colorFromXY(points, model ) {
|
|
|
|
|
@@ -1785,3 +1778,4 @@ def hsvToHex(hue, sat, value = 100){
|
|
|
|
|
|
|
|
|
|
return "#$r1$g1$b1"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|