Compare commits

..

1 Commits

Author SHA1 Message Date
John Haines
c19da1b496 MSA-1494: control smart home 2016-09-26 00:03:26 -05:00
13 changed files with 1056 additions and 347 deletions

View File

@@ -87,27 +87,16 @@ def beep() {
up to this long from the time you send the message to the time you hear a sound. up to this long from the time you send the message to the time you hear a sound.
*/ */
// Used source endpoint of 0x02 because we are using smartthings manufacturer specific cluster.
[ [
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000", "delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000", "delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000", "delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000", "delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}"
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
] ]
} }

View File

@@ -105,21 +105,11 @@ def parseDescriptionAsMap(description) {
// Commands to device // Commands to device
def on() { def on() {
[ 'zcl on-off on'
'zcl on-off on',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
} }
def off() { def off() {
[ 'zcl on-off off'
'zcl on-off off',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
} }
def setLevel(value) { def setLevel(value) {

View File

@@ -31,13 +31,13 @@ metadata {
command "switchMode" command "switchMode"
command "switchFanMode" command "switchFanMode"
attribute "thermostatSetpoint", "number" attribute "thermostatSetpoint","number"
attribute "thermostatStatus", "string" attribute "thermostatStatus","string"
attribute "maxHeatingSetpoint", "number" attribute "maxHeatingSetpoint", "number"
attribute "minHeatingSetpoint", "number" attribute "minHeatingSetpoint", "number"
attribute "maxCoolingSetpoint", "number" attribute "maxCoolingSetpoint", "number"
attribute "minCoolingSetpoint", "number" attribute "minCoolingSetpoint", "number"
attribute "deviceTemperatureUnit", "string" attribute "deviceTemperatureUnit", "number"
} }
tiles { tiles {

View File

@@ -7,11 +7,9 @@
metadata { metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") { definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") {
capability "Health Check"
attribute "networkAddress", "string" attribute "networkAddress", "string"
// Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network // Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network
// Possible values "Online" or "Offline" // Possible values "Online" or "Offline"
attribute "status", "string" attribute "status", "string"
// Id is the number on the back of the hub, Hue uses last six digits of Mac address // Id is the number on the back of the hub, Hue uses last six digits of Mac address
// This is also used in the Hue application as ID // This is also used in the Hue application as ID
@@ -44,10 +42,6 @@ metadata {
} }
} }
void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes // parse events into attributes
def parse(description) { def parse(description) {
log.debug "Parsing '${description}'" log.debug "Parsing '${description}'"
@@ -76,8 +70,13 @@ def parse(description) {
def bulbs = new groovy.json.JsonSlurper().parseText(msg.body) def bulbs = new groovy.json.JsonSlurper().parseText(msg.body)
if (bulbs.state) { if (bulbs.state) {
log.info "Bridge response: $msg.body" log.info "Bridge response: $msg.body"
} else {
// Sending Bulbs List to parent"
if (parent.isInBulbDiscovery())
log.info parent.bulbListHandler(device.hub.id, msg.body)
} }
} else if (contentType?.contains("xml")) { }
else if (contentType?.contains("xml")) {
log.debug "HUE BRIDGE ALREADY PRESENT" log.debug "HUE BRIDGE ALREADY PRESENT"
parent.hubVerification(device.hub.id, msg.body) parent.hubVerification(device.hub.id, msg.body)
} }
@@ -86,7 +85,3 @@ def parse(description) {
} }
results results
} }
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -174,7 +174,7 @@ void setColorTemperature(value) {
void refresh() { void refresh() {
log.debug "Executing 'refresh'" log.debug "Executing 'refresh'"
parent?.manualRefresh() parent.manualRefresh()
} }
def verifyPercent(percent) { def verifyPercent(percent) {

View File

@@ -47,21 +47,9 @@ def parse(String description) {
// Commands to device // Commands to device
def on() { def on() {
[ 'zcl on-off on'
'zcl on-off on',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
} }
def off() { def off() {
[ 'zcl on-off off'
'zcl on-off off',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
} }

View File

@@ -180,9 +180,9 @@ private Map parseIasMessage(String description) {
def getTemperature(value) { def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100 def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){ if(getTemperatureScale() == "C"){
return Math.round(celsius) return celsius
} else { } else {
return Math.round(celsiusToFahrenheit(celsius)) return celsiusToFahrenheit(celsius) as Integer
} }
} }

View File

@@ -194,9 +194,9 @@ private Map parseIasMessage(String description) {
def getTemperature(value) { def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100 def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){ if(getTemperatureScale() == "C"){
return Math.round(celsius) return celsius
} else { } else {
return Math.round(celsiusToFahrenheit(celsius)) return celsiusToFahrenheit(celsius) as Integer
} }
} }

View File

@@ -261,9 +261,9 @@ def updated() {
def getTemperature(value) { def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100 def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){ if(getTemperatureScale() == "C"){
return Math.round(celsius) return celsius
} else { } else {
return Math.round(celsiusToFahrenheit(celsius)) return celsiusToFahrenheit(celsius) as Integer
} }
} }

View File

@@ -454,23 +454,17 @@ def sendStopEvent(source) {
eventData.value += "cancelled" eventData.value += "cancelled"
} }
// send 100% completion event
sendTimeRemainingEvent(100)
// send a non-displayed 0% completion to reset tiles
sendTimeRemainingEvent(0, false)
// send sessionStatus event last so the event feed is ordered properly
sendControllerEvent(eventData) sendControllerEvent(eventData)
sendTimeRemainingEvent(0)
} }
def sendTimeRemainingEvent(percentComplete, displayed = true) { def sendTimeRemainingEvent(percentComplete) {
log.trace "sendTimeRemainingEvent(${percentComplete})" log.trace "sendTimeRemainingEvent(${percentComplete})"
def percentCompleteEventData = [ def percentCompleteEventData = [
name: "percentComplete", name: "percentComplete",
value: percentComplete as int, value: percentComplete as int,
displayed: displayed, displayed: true,
isStateChange: true isStateChange: true
] ]
sendControllerEvent(percentCompleteEventData) sendControllerEvent(percentCompleteEventData)
@@ -480,7 +474,7 @@ def sendTimeRemainingEvent(percentComplete, displayed = true) {
def timeRemainingEventData = [ def timeRemainingEventData = [
name: "timeRemaining", name: "timeRemaining",
value: displayableTime(timeRemaining), value: displayableTime(timeRemaining),
displayed: displayed, displayed: true,
isStateChange: true isStateChange: true
] ]
sendControllerEvent(timeRemainingEventData) sendControllerEvent(timeRemainingEventData)
@@ -614,6 +608,8 @@ private completion() {
handleCompletionMessaging() handleCompletionMessaging()
handleCompletionModesAndPhrases() handleCompletionModesAndPhrases()
sendTimeRemainingEvent(100)
} }
private handleCompletionSwitches() { private handleCompletionSwitches() {

View File

@@ -83,7 +83,7 @@ def bridgeDiscovery(params=[:])
return dynamicPage(name:"bridgeDiscovery", title:"Discovery Started!", nextPage:"bridgeBtnPush", refreshInterval:refreshInterval, uninstall: true) { return dynamicPage(name:"bridgeDiscovery", title:"Discovery Started!", nextPage:"bridgeBtnPush", refreshInterval:refreshInterval, uninstall: true) {
section("Please wait while we discover your Hue Bridge. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { section("Please wait while we discover your Hue Bridge. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedHue", "enum", required:false, title:"Select Hue Bridge (${numFound} found)", multiple:false, options:options, submitOnChange: true input "selectedHue", "enum", required:false, title:"Select Hue Bridge (${numFound} found)", multiple:false, options:options
} }
} }
} }
@@ -131,7 +131,10 @@ def bulbDiscovery() {
def refreshInterval = 3 def refreshInterval = 3
state.inBulbDiscovery = true state.inBulbDiscovery = true
def bridge = null def bridge = null
if (selectedHue) {
bridge = getChildDevice(selectedHue)
subscribe(bridge, "bulbList", bulbListData)
}
state.bridgeRefreshCount = 0 state.bridgeRefreshCount = 0
def allLightsFound = bulbsDiscovered() ?: [:] def allLightsFound = bulbsDiscovered() ?: [:]
@@ -256,6 +259,10 @@ Map bulbsDiscovered() {
return bulbmap return bulbmap
} }
def bulbListData(evt) {
state.bulbs = evt.jsonData
}
Map getHueBulbs() { Map getHueBulbs() {
state.bulbs = state.bulbs ?: [:] state.bulbs = state.bulbs ?: [:]
} }
@@ -309,6 +316,29 @@ def uninstalled(){
state.username = null state.username = null
} }
// Handles events to add new bulbs
def bulbListHandler(hub, data = "") {
def msg = "Bulbs list not processed. Only while in settings menu."
def bulbs = [:]
if (state.inBulbDiscovery) {
def logg = ""
log.trace "Adding bulbs to state..."
state.bridgeProcessedLightList = true
def object = new groovy.json.JsonSlurper().parseText(data)
object.each { k,v ->
if (v instanceof Map)
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub, online: v.state?.reachable]
}
}
def bridge = null
if (selectedHue) {
bridge = getChildDevice(selectedHue)
}
bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
msg = "${bulbs.size()} bulbs found. ${bulbs}"
return msg
}
private upgradeDeviceType(device, newHueType) { private upgradeDeviceType(device, newHueType) {
def deviceType = getDeviceType(newHueType) def deviceType = getDeviceType(newHueType)
@@ -460,25 +490,24 @@ def ssdpBridgeHandler(evt) {
def host = ip + ":" + port def host = ip + ":" + port
log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host." 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 dniReceived = "${parsedEvent.mac}" def dni = "${parsedEvent.mac}"
def currentDni = dstate.mac def d = getChildDevice(dni)
def d = getChildDevice(dniReceived)
def networkAddress = null def networkAddress = null
if (!d) { if (!d) {
// There might be a mismatch between bridge DNI and the actual bridge mac address, correct that childDevices.each {
log.debug "Bridge with $dniReceived not found" if (it.getDeviceDataByName("mac")) {
def bridge = childDevices.find { it.deviceNetworkId == currentDni } def newDNI = "${it.getDeviceDataByName("mac")}"
if (bridge != null) { d = it
log.warn "Bridge is set to ${bridge.deviceNetworkId}, updating to $dniReceived" if (newDNI != it.deviceNetworkId) {
bridge.setDeviceNetworkId("${dniReceived}") def oldDNI = it.deviceNetworkId
dstate.mac = dniReceived log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
// Check to see if selectedHue is a valid bridge, otherwise update it it.setDeviceNetworkId("${newDNI}")
def isSelectedValid = bridges?.find {it.value?.mac == selectedHue} if (oldDNI == selectedHue) {
if (isSelectedValid == null) { app.updateSetting("selectedHue", newDNI)
log.warn "Correcting selectedHue in state" }
app.updateSetting("selectedHue", dniReceived) doDeviceSync()
}
} }
doDeviceSync()
} }
} else { } else {
updateBridgeStatus(d) updateBridgeStatus(d)
@@ -496,18 +525,6 @@ def ssdpBridgeHandler(evt) {
d.sendEvent(name:"networkAddress", value: host) d.sendEvent(name:"networkAddress", value: host)
d.updateDataValue("networkAddress", host) d.updateDataValue("networkAddress", host)
} }
if (dstate.mac != dniReceived) {
log.warn "Correcting bridge mac address in state"
dstate.mac = dniReceived
}
if (selectedHue != dniReceived) {
// Check to see if selectedHue is a valid bridge, otherwise update it
def isSelectedValid = bridges?.find {it.value?.mac == selectedHue}
if (isSelectedValid == null) {
log.warn "Correcting selectedHue in state"
app.updateSetting("selectedHue", dniReceived)
}
}
} }
} }
} }
@@ -540,8 +557,11 @@ void lightsHandler(physicalgraph.device.HubResponse hubResponse) {
if (isValidSource(hubResponse.mac)) { if (isValidSource(hubResponse.mac)) {
def body = hubResponse.json def body = hubResponse.json
if (!body?.state?.on) { //check if first time poll made it here by mistake if (!body?.state?.on) { //check if first time poll made it here by mistake
def bulbs = getHueBulbs()
log.debug "Adding bulbs to state!" log.debug "Adding bulbs to state!"
updateBulbState(body, hubResponse.hubId) body.each { k, v ->
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub: hubResponse.hubId]
}
} }
} }
} }
@@ -657,8 +677,11 @@ def locationHandler(evt) {
} else { } else {
//GET /api/${state.username}/lights response (application/json) //GET /api/${state.username}/lights response (application/json)
if (!body?.state?.on) { //check if first time poll made it here by mistake if (!body?.state?.on) { //check if first time poll made it here by mistake
def bulbs = getHueBulbs()
log.debug "Adding bulbs to state!" log.debug "Adding bulbs to state!"
updateBulbState(body, parsedEvent.hub) body.each { k,v ->
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:parsedEvent.hub]
}
} }
} }
} }
@@ -718,7 +741,7 @@ private void checkBridgeStatus() {
} }
if (it.value.lastActivity < time) { // it.value.lastActivity != null && if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
log.warn "Bridge $it.value.idNumber is Offline" log.warn "Bridge $it.key is Offline"
d.sendEvent(name: "status", value: "Offline") d.sendEvent(name: "status", value: "Offline")
state.bulbs?.each { state.bulbs?.each {
@@ -743,31 +766,6 @@ def isInBulbDiscovery() {
return state.inBulbDiscovery return state.inBulbDiscovery
} }
private updateBulbState(messageBody, hub) {
def bulbs = getHueBulbs()
// Copy of bulbs used to locate old lights in state that are no longer on bridge
def toRemove = [:]
toRemove << bulbs
messageBody.each { k,v ->
if (v instanceof Map) {
if (bulbs[k] == null) {
bulbs[k] = [:]
}
bulbs[k] << [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub, remove: false]
toRemove.remove(k)
}
}
// Remove bulbs from state that are no longer discovered
toRemove.each { k,v ->
log.warn "${bulbs[k].name} no longer exists on bridge, removing"
bulbs.remove(k)
}
}
///////////////////////////////////// /////////////////////////////////////
//CHILD DEVICE METHODS //CHILD DEVICE METHODS
///////////////////////////////////// /////////////////////////////////////
@@ -952,14 +950,6 @@ private handleCommandResponse(body) {
* @return empty array * @return empty array
*/ */
private handlePoll(body) { private handlePoll(body) {
// Used to track "unreachable" time
// Device is considered "offline" if it has been in the "unreachable" state for
// 11 minutes (e.g. two poll intervals)
// Note, Hue Bridge marks devices as "unreachable" often even when they accept commands
Calendar time11 = Calendar.getInstance()
time11.add(Calendar.MINUTE, -11)
Calendar currentTime = Calendar.getInstance()
def bulbs = getChildDevices() def bulbs = getChildDevices()
for (bulb in body) { for (bulb in body) {
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"} def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
@@ -969,10 +959,7 @@ private handlePoll(body) {
// light just came back online, notify device watch // light just came back online, notify device watch
def lastActivity = now() 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-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", 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 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 user just executed commands, then do not send events to avoid confusing the turning on/off state
@@ -982,18 +969,9 @@ private handlePoll(body) {
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode) sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
} }
} else { } else {
if (state.bulbs[bulb.key]?.unreachableSince == null) { state.bulbs[bulb.key]?.online = false
// Store the first time where device was reported as "unreachable" log.warn "$device is not reachable by Hue bridge"
state.bulbs[bulb.key]?.unreachableSince = currentTime.getTimeInMillis() device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
} else if (state.bulbs[bulb.key]?.online) {
// Check if device was "unreachable" for more than 11 minutes and mark "offline" if necessary
if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis()) {
log.warn "$device went Offline"
state.bulbs[bulb.key]?.online = false
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
}
}
log.warn "$device may not reachable by Hue bridge"
} }
} }
} }
@@ -1028,6 +1006,9 @@ def hubVerification(bodytext) {
def on(childDevice) { def on(childDevice) {
log.debug "Executing 'on'" log.debug "Executing 'on'"
def id = getId(childDevice) def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
createSwitchEvent(childDevice, "on") createSwitchEvent(childDevice, "on")
put("lights/$id/state", [on: true]) put("lights/$id/state", [on: true])
@@ -1037,6 +1018,9 @@ def on(childDevice) {
def off(childDevice) { def off(childDevice) {
log.debug "Executing 'off'" log.debug "Executing 'off'"
def id = getId(childDevice) def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
createSwitchEvent(childDevice, "off") createSwitchEvent(childDevice, "off")
put("lights/$id/state", [on: false]) put("lights/$id/state", [on: false])
@@ -1046,6 +1030,9 @@ def off(childDevice) {
def setLevel(childDevice, percent) { def setLevel(childDevice, percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
def id = getId(childDevice) def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
// 1 - 254 // 1 - 254
def level def level
@@ -1070,6 +1057,10 @@ def setLevel(childDevice, percent) {
def setSaturation(childDevice, percent) { def setSaturation(childDevice, percent) {
log.debug "Executing 'setSaturation($percent)'" log.debug "Executing 'setSaturation($percent)'"
def id = getId(childDevice) def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
// 0 - 254 // 0 - 254
def level = Math.min(Math.round(percent * 254 / 100), 254) def level = Math.min(Math.round(percent * 254 / 100), 254)
@@ -1082,6 +1073,9 @@ def setSaturation(childDevice, percent) {
def setHue(childDevice, percent) { def setHue(childDevice, percent) {
log.debug "Executing 'setHue($percent)'" log.debug "Executing 'setHue($percent)'"
def id = getId(childDevice) def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
// 0 - 65535 // 0 - 65535
def level = Math.min(Math.round(percent * 65535 / 100), 65535) def level = Math.min(Math.round(percent * 65535 / 100), 65535)
@@ -1094,6 +1088,9 @@ def setHue(childDevice, percent) {
def setColorTemperature(childDevice, huesettings) { def setColorTemperature(childDevice, huesettings) {
log.debug "Executing 'setColorTemperature($huesettings)'" log.debug "Executing 'setColorTemperature($huesettings)'"
def id = getId(childDevice) def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
// 153 (6500K) to 500 (2000K) // 153 (6500K) to 500 (2000K)
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings) def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
@@ -1105,6 +1102,9 @@ def setColorTemperature(childDevice, huesettings) {
def setColor(childDevice, huesettings) { def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'" log.debug "Executing 'setColor($huesettings)'"
def id = getId(childDevice) def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress() updateInProgress()
def value = [:] def value = [:]
@@ -1120,7 +1120,7 @@ def setColor(childDevice, huesettings) {
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535) value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
if (huesettings.saturation != null) if (huesettings.saturation != null)
value.sat = Math.min(Math.round(huesettings.saturation * 254 / 100), 254) value.sat = Math.min(Math.round(huesettings.saturation * 254 / 100), 254)
} else if (huesettings.hex != null) { } else if (huesettings.hex != null && false) {
// For now ignore model to get a consistent color if same color is set across multiple devices // For now ignore model to get a consistent color if same color is set across multiple devices
// def model = state.bulbs[getId(childDevice)]?.modelid // def model = state.bulbs[getId(childDevice)]?.modelid
// value.xy = calculateXY(huesettings.hex, model) // value.xy = calculateXY(huesettings.hex, model)
@@ -1162,14 +1162,7 @@ def setColor(childDevice, huesettings) {
} }
def ping(childDevice) { def ping(childDevice) {
if (childDevice.device?.deviceNetworkId?.equalsIgnoreCase(selectedHue)) { if (isOnline(getId(childDevice))) {
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) childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true)
return "Device is Online" return "Device is Online"
} else { } else {
@@ -1231,7 +1224,7 @@ private getBridgeIP() {
if (d) { if (d) {
if (d.getDeviceDataByName("networkAddress")) if (d.getDeviceDataByName("networkAddress"))
host = d.getDeviceDataByName("networkAddress") host = d.getDeviceDataByName("networkAddress")
else else
host = d.latestState('networkAddress').stringValue host = d.latestState('networkAddress').stringValue
} }
if (host == null || host == "") { if (host == null || host == "") {
@@ -1670,7 +1663,7 @@ private boolean checkPointInLampsReach(p, colorPoints) {
} }
/** /**
* Converts an RGB color in hex to HSV/HSB. * Converts an RGB color in hex to HSV.
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space. * Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
* *
* @param colorStr color value in hex (#ff03d3) * @param colorStr color value in hex (#ff03d3)
@@ -1680,32 +1673,32 @@ private boolean checkPointInLampsReach(p, colorPoints) {
def hexToHsv(colorStr){ def hexToHsv(colorStr){
def r = Integer.valueOf( colorStr.substring( 1, 3 ), 16 ) / 255 def r = Integer.valueOf( colorStr.substring( 1, 3 ), 16 ) / 255
def g = Integer.valueOf( colorStr.substring( 3, 5 ), 16 ) / 255 def g = Integer.valueOf( colorStr.substring( 3, 5 ), 16 ) / 255
def b = Integer.valueOf( colorStr.substring( 5, 7 ), 16 ) / 255 def b = Integer.valueOf( colorStr.substring( 5, 7 ), 16 ) / 255;
def max = Math.max(Math.max(r, g), b) def max = Math.max(Math.max(r, g), b)
def min = Math.min(Math.min(r, g), b) def min = Math.min(Math.min(r, g), b)
def h, s, v = max def h, s, v = max;
def d = max - min def d = max - min;
s = max == 0 ? 0 : d / max s = max == 0 ? 0 : d / max;
if(max == min){ if(max == min){
h = 0 h = 0;
}else{ }else{
switch(max){ switch(max){
case r: h = (g - b) / d + (g < b ? 6 : 0); break case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break case b: h = (r - g) / d + 4; break;
} }
h /= 6; h /= 6;
} }
return [Math.round(h * 100), Math.round(s * 100), Math.round(v * 100)] return [(h * 100).round(), (s * 100).round(), (v * 100).round()];
} }
/** /**
* Converts HSV/HSB color to RGB in hex. * Converts HSV color to RGB in hex.
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space. * Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
* *
* @param hue hue 0-100 * @param hue hue 0-100
@@ -1720,11 +1713,11 @@ def hsvToHex(hue, sat, value = 100){
def s = sat / 100 def s = sat / 100
def v = value / 100 def v = value / 100
def i = Math.floor(h * 6) def i = Math.floor(h * 6);
def f = h * 6 - i def f = h * 6 - i;
def p = v * (1 - s) def p = v * (1 - s);
def q = v * (1 - f * s) def q = v * (1 - f * s);
def t = v * (1 - (1 - f) * s) def t = v * (1 - (1 - f) * s);
switch (i % 6) { switch (i % 6) {
case 0: case 0:
@@ -1760,9 +1753,9 @@ def hsvToHex(hue, sat, value = 100){
} }
// Converting float components to int components. // Converting float components to int components.
def r1 = String.format("%02X", (int) (r * 255.0f)) def r1 = String.format("%02X", (int) (r * 255.0f));
def g1 = String.format("%02X", (int) (g * 255.0f)) def g1 = String.format("%02X", (int) (g * 255.0f));
def b1 = String.format("%02X", (int) (b * 255.0f)) def b1 = String.format("%02X", (int) (b * 255.0f));
return "#$r1$g1$b1" return "#$r1$g1$b1"
} }

View File

@@ -34,7 +34,6 @@
* locks | lock | lock, unlock | locked, unlocked * locks | lock | lock, unlock | locked, unlocked
* ---------------------+-------------------+-----------------------------+------------------------------------ * ---------------------+-------------------+-----------------------------+------------------------------------
*/ */
include 'asynchttp_v1'
definition( definition(
name: "Logitech Harmony (Connect)", name: "Logitech Harmony (Connect)",
@@ -110,28 +109,26 @@ def authPage() {
//device discovery request every 5 //25 seconds //device discovery request every 5 //25 seconds
int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int
state.deviceRefreshCount = deviceRefreshCount + 1 state.deviceRefreshCount = deviceRefreshCount + 1
def refreshInterval = 5 def refreshInterval = 3
def huboptions = state.HarmonyHubs ?: [] def huboptions = state.HarmonyHubs ?: []
def actoptions = state.HarmonyActivities ?: [] def actoptions = state.HarmonyActivities ?: []
def numFoundHub = huboptions.size() ?: 0 def numFoundHub = huboptions.size() ?: 0
def numFoundAct = actoptions.size() ?: 0 def numFoundAct = actoptions.size() ?: 0
if((deviceRefreshCount % 5) == 0) { if((deviceRefreshCount % 5) == 0) {
discoverDevices() discoverDevices()
} }
return dynamicPage(name:"Credentials", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) { return dynamicPage(name:"Credentials", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your Harmony Hubs and Activities. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { section("Please wait while we discover your Harmony Hubs and Activities. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedhubs", "enum", required:false, title:"Select Harmony Hubs (${numFoundHub} found)", multiple:true, submitOnChange: true, options:huboptions input "selectedhubs", "enum", required:false, title:"Select Harmony Hubs (${numFoundHub} found)", multiple:true, options:huboptions
} }
// Virtual activity flag // Virtual activity flag
if (numFoundHub > 0 && numFoundAct > 0 && true) if (numFoundHub > 0 && numFoundAct > 0 && true)
section("You can also add activities as virtual switches for other convenient integrations") { section("You can also add activities as virtual switches for other convenient integrations") {
input "selectedactivities", "enum", required:false, title:"Select Harmony Activities (${numFoundAct} found)", multiple:true, submitOnChange: true, options:actoptions input "selectedactivities", "enum", required:false, title:"Select Harmony Activities (${numFoundAct} found)", multiple:true, options:actoptions
} }
if (state.resethub) if (state.resethub)
section("Connection to the hub timed out. Please restart the hub and try again.") {} section("Connection to the hub timed out. Please restart the hub and try again.") {}
} }
} }
@@ -383,6 +380,8 @@ def discovery() {
log.debug "valid Token" log.debug "valid Token"
state.Harmonydevices = response.data state.Harmonydevices = response.data
state.resethub = false state.resethub = false
getActivityList()
poll()
} else { } else {
log.debug "Error: $response.status" log.debug "Error: $response.status"
} }
@@ -431,182 +430,142 @@ def addDevice() {
} }
def activity(dni,mode) { def activity(dni,mode) {
def tokenParam = [auth: state.HarmonyAccessToken] def Params = [auth: state.HarmonyAccessToken]
def url def msg = "Command failed"
def url = ''
if (dni == "all") { if (dni == "all") {
url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(tokenParam)}" url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(Params)}"
} else { } else {
def aux = dni.split('-') def aux = dni.split('-')
def hubId = aux[1] def hubId = aux[1]
if (mode == "hub" || (aux.size() <= 2) || (aux[2] == "off")){ if (mode == "hub" || (aux.size() <= 2) || (aux[2] == "off")){
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(tokenParam)}" url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(Params)}"
} else { } else {
def activityId = aux[2] def activityId = aux[2]
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/${activityId}/${mode}?${toQueryString(tokenParam)}" url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/${activityId}/${mode}?${toQueryString(Params)}"
} }
} }
def params = [ try {
uri: url, httpPostJson(uri: url) { response ->
contentType: 'application/json' if (response.data.code == 200 || dni == "all") {
] msg = "Command sent succesfully"
asynchttp_v1.post('activityResponse', params) state.aux = 0
return "Command Sent" } else {
} msg = "Command failed. Error: $response.data.code"
}
def activityResponse(response, data) { }
if (response.hasError()) { } catch (groovyx.net.http.HttpResponseException ex) {
log.error "Logitech Harmony - response has error: $response.errorMessage" log.error ex
if (response.status == 401) { // token is expired if (state.aux == 0) {
state.remove("HarmonyAccessToken") state.aux = 1
log.warn "Logitech Harmony - Access token has expired" activity(dni,mode)
} else {
msg = ex
state.aux = 0
}
} catch(Exception ex) {
msg = ex
} }
} else { runIn(10, "poll", [overwrite: true])
def ResponseValues return msg
try {
// json response already parsed into JSONElement object
ResponseValues = response.json
} catch (e) {
log.error "Logitech Harmony - error parsing json from response: $e"
}
if (ResponseValues) {
if (ResponseValues.code == 200) {
log.trace "Command sent succesfully"
poll()
} else {
log.trace "Command failed. Error: $response.data.code"
}
} else {
log.debug "Logitech Harmony - did not get json results from response body: $response.data"
}
}
} }
def poll() { def poll() {
// GET THE LIST OF ACTIVITIES // GET THE LIST OF ACTIVITIES
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
getActivityList() getActivityList()
def tokenParam = [auth: state.HarmonyAccessToken] def Params = [auth: state.HarmonyAccessToken]
def params = [ def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}"
uri: "https://home.myharmony.com/cloudapi/state?${toQueryString(tokenParam)}", try {
headers: ["Accept": "application/json"], httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
contentType: 'application/json' def map = [:]
] response.data.hubs.each {
asynchttp_v1.get('pollResponse', params) if (it.value.message == "OK") {
} else { map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}"
log.warn "Logitech Harmony - Access token has expired" def hub = getChildDevice("harmony-${it.key}")
} if (hub) {
} if (it.value.response.data.currentAvActivity == "-1") {
hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", display: false)
def pollResponse(response, data) { } else {
if (response.hasError()) { def currentActivity = getActivityName(it.value.response.data.currentAvActivity,it.key)
log.error "Logitech Harmony - response has error: $response.errorMessage" hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false)
if (response.status == 401) { // token is expired }
state.remove("HarmonyAccessToken") }
log.warn "Logitech Harmony - Access token has expired" } else {
} log.trace it.value.message
} else { }
def ResponseValues }
try { def activities = getChildDevices()
// json response already parsed into JSONElement object def activitynotrunning = true
ResponseValues = response.json activities.each { activity ->
} catch (e) { def act = activity.deviceNetworkId.split('-')
log.error "Logitech Harmony - error parsing json from response: $e" if (act.size() > 2) {
} def aux = map.find { it.key == act[1] }
if (ResponseValues) { if (aux) {
def map = [:] def aux2 = aux.value.split(',')
ResponseValues.hubs.each { def childDevice = getChildDevice(activity.deviceNetworkId)
if (it.value.message == "OK") { if ((act[2] == aux2[0]) && (aux2[1] == "1" || aux2[1] == "2")) {
map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}" childDevice?.sendEvent(name: "switch", value: "on")
def hub = getChildDevice("harmony-${it.key}") if (aux2[1] == "1")
if (hub) { runIn(5, "poll", [overwrite: true])
if (it.value.response.data.currentAvActivity == "-1") { } else {
hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", display: false) childDevice?.sendEvent(name: "switch", value: "off")
} else { if (aux2[1] == "3")
def currentActivity = getChildDevice("harmony-${it.key}-${it.value.response.data.currentAvActivity}").device.displayName runIn(5, "poll", [overwrite: true])
hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false) }
} }
}
} else {
log.trace "Logitech Harmony - error response: $it.value.message"
}
}
def activities = getChildDevices()
def activitynotrunning = true
activities.each { activity ->
def act = activity.deviceNetworkId.split('-')
if (act.size() > 2) {
def aux = map.find { it.key == act[1] }
if (aux) {
def aux2 = aux.value.split(',')
def childDevice = getChildDevice(activity.deviceNetworkId)
if ((act[2] == aux2[0]) && (aux2[1] == "1" || aux2[1] == "2")) {
childDevice?.sendEvent(name: "switch", value: "on")
if (aux2[1] == "1")
runIn(5, "poll", [overwrite: true])
} else {
childDevice?.sendEvent(name: "switch", value: "off")
if (aux2[1] == "3")
runIn(5, "poll", [overwrite: true])
} }
} }
return "Poll completed $map - $state.hubs"
} }
} catch (groovyx.net.http.HttpResponseException e) {
if (e.statusCode == 401) { // token is expired
state.remove("HarmonyAccessToken")
log.warn "Harmony Access token has expired"
}
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection to the hub timed out. Please restart the hub and try again."
state.resethub = true
} catch (e) {
log.info "Logitech Harmony - Error: $e"
} }
} else {
log.debug "Logitech Harmony - did not get json results from response body: $response.data"
}
}
}
def getActivityList() {
if (state.HarmonyAccessToken) {
def tokenParam = [auth: state.HarmonyAccessToken]
def params = [
uri: "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(tokenParam)}",
headers: ["Accept": "application/json"],
contentType: 'application/json'
]
asynchttp_v1.get('activityListResponse', params)
} else {
log.warn "Logitech Harmony - Access token has expired"
} }
} }
def activityListResponse(response, data) {
if (response.hasError()) { def getActivityList() {
log.error "Logitech Harmony - response has error: $response.errorMessage" // GET ACTIVITY'S NAME
if (response.status == 401) { // token is expired if (state.HarmonyAccessToken) {
state.remove("HarmonyAccessToken") def Params = [auth: state.HarmonyAccessToken]
log.warn "Logitech Harmony - Access token has expired" def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}"
} try {
} else { httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
def ResponseValues response.data.hubs.each {
try { def hub = getChildDevice("harmony-${it.key}")
// json response already parsed into JSONElement object if (hub) {
ResponseValues = response.json def hubname = getHubName("${it.key}")
} catch (e) { def activities = []
log.error "Logitech Harmony - error parsing json from response: $e" def aux = it.value.response.data.activities.size()
} if (aux >= 1) {
if (ResponseValues) { activities = it.value.response.data.activities.collect {
ResponseValues.hubs.each { [id: it.key, name: it.value['name'], type: it.value['type']]
def hub = getChildDevice("harmony-${it.key}") }
if (hub) { activities += [id: "off", name: "Activity OFF", type: "0"]
def hubname = getHubName("${it.key}") log.trace activities
def activities = [] }
def aux = it.value.response?.data.activities.size() hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
if (aux >= 1) { }
activities = it.value.response.data.activities.collect { }
[id: it.key, name: it.value['name'], type: it.value['type']] }
} } catch (groovyx.net.http.HttpResponseException e) {
activities += [id: "off", name: "Activity OFF", type: "0"] log.trace e
log.trace activities } catch (java.net.SocketTimeoutException e) {
} log.trace e
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false) } catch(Exception e) {
} log.trace e
} }
} else { }
log.debug "Logitech Harmony - did not get json results from response body: $response.data" return activity
}
}
} }
def getActivityName(activity,hubId) { def getActivityName(activity,hubId) {
@@ -787,7 +746,7 @@ def addSubscription() {
def attribute = data.attributeName def attribute = data.attributeName
def callbackUrl = data.callbackUrl def callbackUrl = data.callbackUrl
log.debug "Logitech Harmony - addSubscription, params: ${params}, request: ${data}" log.debug "addSubscription, params: ${params}, request: ${data}"
if (!attribute) { if (!attribute) {
render status: 400, data: '{"msg": "attributeName is required"}' render status: 400, data: '{"msg": "attributeName is required"}'
} else { } else {
@@ -849,7 +808,6 @@ def deviceHandler(evt) {
def deviceInfo = state[evt.deviceId] def deviceInfo = state[evt.deviceId]
if (state.harmonyHubs) { if (state.harmonyHubs) {
state.harmonyHubs.each { harmonyHub -> state.harmonyHubs.each { harmonyHub ->
log.trace "Logitech Harmony - Sending data to $harmonyHub.name"
sendToHarmony(evt, harmonyHub.callbackUrl) sendToHarmony(evt, harmonyHub.callbackUrl)
} }
} else if (deviceInfo) { } else if (deviceInfo) {

View File

@@ -0,0 +1,800 @@
/**
* Home Remote
*
* Copyright 2015 The Home Remote
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "Home Remote",
namespace: "thehomeremote.homeremote",
author: "The Home Remote",
description: "Web service that enables communication between the Home Remote app and a SmartThings hub.",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
oauth: [displayName: "The Home Remote", displayLink: "http://thehomeremote.com/"])
preferences {
section() {
input "accelerationSensors", "capability.accelerationSensor",title: "Acceleration Sensors", multiple: true, required: false
input "alarms", "capability.alarm",title: "Alarms", multiple: true, required: false
input "batteries", "capability.battery",title: "Batteries", multiple: true, required: false
input "beacons", "capability.beacon",title: "Beacons", multiple: true, required: false
input "buttonGroup", "capability.button",title: "Buttons", multiple: true, required: false
input "carbonMonoxideDetectors", "capability.carbonMonoxideDetector",title: "CO Detectors", multiple: true, required: false
input "colorControls", "capability.colorControl",title: "Color Lights", multiple: true, required: false
input "contactSensors", "capability.contactSensor",title: "Contact Sensors", multiple: true, required: false
input "doorControls", "capability.doorControl",title: "Door Controllers", multiple: true, required: false
input "energyMeters", "capability.energyMeter",title: "Energy Meters", multiple: true, required: false
input "illuminanceMeasurements", "capability.illuminanceMeasurement",title: "Illuminance Sensors", multiple: true, required: false
input "imageCaptures", "capability.imageCapture",title: "Cameras", multiple: true, required: false
input "locks", "capability.lock",title: "Locks", multiple: true, required: false
input "mediaControllers", "capability.mediaController",title: "Media Controllers", multiple: true, required: false
input "momentaries", "capability.momentary",title: "Momentary Buttons", multiple: true, required: false
input "motionSensors", "capability.motionSensor",title: "Motion Sensors", multiple: true, required: false
input "musicPlayers", "capability.musicPlayer",title: "Music Players", multiple: true, required: false
input "powerMeters", "capability.powerMeter",title: "Power Meters", multiple: true, required: false
input "presenceSensors", "capability.presenceSensor",title: "Presence Sensors", multiple: true, required: false
input "relativeHumidityMeasurements", "capability.relativeHumidityMeasurement",title: "Humidity Sensors", multiple: true, required: false
input "relaySwitches", "capability.relaySwitch",title: "Relays", multiple: true, required: false
input "signalStrengths", "capability.signalStrength",title: "Signal Strengths", multiple: true, required: false
input "sleepSensors", "capability.sleepSensor",title: "Sleep Sensors", multiple: true, required: false
input "smokeDetectors", "capability.smokeDetector",title: "Smoke Detectors", multiple: true, required: false
input "speechSyntheses", "capability.speechSynthesis",title: "Speech Syntheses", multiple: true, required: false
input "stepSensors", "capability.stepSensor",title: "Step Sensors", multiple: true, required: false
input "switches", "capability.switch",title: "Switches", multiple: true, required: false
input "switchLevels", "capability.switchLevel",title: "Dimmers", multiple: true, required: false
input "temperatureMeasurements", "capability.temperatureMeasurement",title: "Temperature Sensors", multiple: true, required: false
input "thermostats", "capability.thermostat",title: "Thermostats", multiple: true, required: false
input "threeAxes", "capability.threeAxis",title: "Three axis Sensors", multiple: true, required: false
input "tones", "capability.tone",title: "Tones", multiple: true, required: false
input "touchSensors", "capability.touchSensor",title: "Touch Sensors", multiple: true, required: false
input "valves", "capability.valve",title: "Valves", multiple: true, required: false
input "waterSensors", "capability.waterSensor",title: "Water Sensors", multiple: true, required: false
}
}
mappings {
path("/GetCurrentValues") {
action: [
GET: "getCurrentValues"
]
}
path("/GetCurrentValuesWithDisplayName") {
action: [
GET: "getCurrentValuesWithDisplayName"
]
}
path("/GetRoutines") {
action: [
GET: "getRoutines"
]
}
path("/ExecuteCommand") {
action: [
PUT: "executeCommand"
]
}
path("/ExecuteRoutine") {
action: [
PUT: "executeRoutine"
]
}
}
def getCurrentValues() {
def resp = []
accelerationSensors.each {
resp << [id: it.id, capability: "AccelerationSensor", attribute: "acceleration", value: it.currentValue("acceleration")]
}
alarms.each {
resp << [id: it.id, capability: "Alarm", attribute: "alarm", value: it.currentValue("alarm")]
}
batteries.each {
resp << [id: it.id, capability: "Battery", attribute: "battery", value: it.currentValue("battery")]
}
beacons.each {
resp << [id: it.id, capability: "Beacon", attribute: "presence", value: it.currentValue("presence")]
}
buttonGroup.each {
resp << [id: it.id, capability: "Button", attribute: "button", value: it.currentValue("button")]
}
carbonMonoxideDetectors.each {
resp << [id: it.id, capability: "CarbonMonoxideDetector", attribute: "carbonMonoxide", value: it.currentValue("carbonMonoxide")]
}
colorControls.each {
resp << [id: it.id, capability: "ColorControl", attribute: "hue", value: it.currentValue("hue")]
}
colorControls.each {
resp << [id: it.id, capability: "ColorControl", attribute: "saturation", value: it.currentValue("saturation")]
}
colorControls.each {
resp << [id: it.id, capability: "ColorControl", attribute: "color", value: it.currentValue("color")]
}
contactSensors.each {
resp << [id: it.id, capability: "ContactSensor", attribute: "contact", value: it.currentValue("contact")]
}
doorControls.each {
resp << [id: it.id, capability: "DoorControl", attribute: "door", value: it.currentValue("door")]
}
energyMeters.each {
resp << [id: it.id, capability: "EnergyMeter", attribute: "energy", value: it.currentValue("energy")]
}
illuminanceMeasurements.each {
resp << [id: it.id, capability: "IlluminanceMeasurement", attribute: "illuminance", value: it.currentValue("illuminance")]
}
imageCaptures.each {
resp << [id: it.id, capability: "ImageCapture", attribute: "image", value: it.currentValue("image")]
}
locks.each {
resp << [id: it.id, capability: "Lock", attribute: "lock", value: it.currentValue("lock")]
}
mediaControllers.each {
resp << [id: it.id, capability: "MediaController", attribute: "activities", value: it.currentValue("activities")]
}
mediaControllers.each {
resp << [id: it.id, capability: "MediaController", attribute: "currentActivity", value: it.currentValue("currentActivity")]
}
motionSensors.each {
resp << [id: it.id, capability: "MotionSensor", attribute: "motion", value: it.currentValue("motion")]
}
musicPlayers.each {
resp << [id: it.id, capability: "MusicPlayer", attribute: "status", value: it.currentValue("status")]
}
musicPlayers.each {
resp << [id: it.id, capability: "MusicPlayer", attribute: "level", value: it.currentValue("level")]
}
musicPlayers.each {
resp << [id: it.id, capability: "MusicPlayer", attribute: "trackDescription", value: it.currentValue("trackDescription")]
}
musicPlayers.each {
resp << [id: it.id, capability: "MusicPlayer", attribute: "trackData", value: it.currentValue("trackData")]
}
musicPlayers.each {
resp << [id: it.id, capability: "MusicPlayer", attribute: "mute", value: it.currentValue("mute")]
}
powerMeters.each {
resp << [id: it.id, capability: "PowerMeter", attribute: "power", value: it.currentValue("power")]
}
presenceSensors.each {
resp << [id: it.id, capability: "PresenceSensor", attribute: "presence", value: it.currentValue("presence")]
}
relativeHumidityMeasurements.each {
resp << [id: it.id, capability: "RelativeHumidityMeasurement", attribute: "humidity", value: it.currentValue("humidity")]
}
relaySwitches.each {
resp << [id: it.id, capability: "RelaySwitch", attribute: "switch", value: it.currentValue("switch")]
}
signalStrengths.each {
resp << [id: it.id, capability: "SignalStrength", attribute: "lqi", value: it.currentValue("lqi")]
}
signalStrengths.each {
resp << [id: it.id, capability: "SignalStrength", attribute: "rssi", value: it.currentValue("rssi")]
}
sleepSensors.each {
resp << [id: it.id, capability: "SleepSensor", attribute: "sleeping", value: it.currentValue("sleeping")]
}
smokeDetectors.each {
resp << [id: it.id, capability: "SmokeDetector", attribute: "smoke", value: it.currentValue("smoke")]
}
stepSensors.each {
resp << [id: it.id, capability: "StepSensor", attribute: "steps", value: it.currentValue("steps")]
}
stepSensors.each {
resp << [id: it.id, capability: "StepSensor", attribute: "goal", value: it.currentValue("goal")]
}
switches.each {
resp << [id: it.id, capability: "Switch", attribute: "switch", value: it.currentValue("switch")]
}
switchLevels.each {
resp << [id: it.id, capability: "SwitchLevel", attribute: "level", value: it.currentValue("level")]
}
temperatureMeasurements.each {
resp << [id: it.id, capability: "TemperatureMeasurement", attribute: "temperature", value: it.currentValue("temperature")]
}
thermostats.each {
resp << [id: it.id, capability: "Thermostat", attribute: "temperature", value: it.currentValue("temperature")]
}
thermostats.each {
resp << [id: it.id, capability: "Thermostat", attribute: "heatingSetpoint", value: it.currentValue("heatingSetpoint")]
}
thermostats.each {
resp << [id: it.id, capability: "Thermostat", attribute: "coolingSetpoint", value: it.currentValue("coolingSetpoint")]
}
//Commented out on 7/23/2016. This randomly started throwing number format exceptions with either my ecobee or Lyric thermostat.
//thermostats.each {
// resp << [id: it.id, capability: "Thermostat", attribute: "thermostatSetpoint", value: it.currentValue("thermostatSetpoint")]
//}
thermostats.each {
resp << [id: it.id, capability: "Thermostat", attribute: "thermostatMode", value: it.currentValue("thermostatMode")]
}
thermostats.each {
resp << [id: it.id, capability: "Thermostat", attribute: "thermostatFanMode", value: it.currentValue("thermostatFanMode")]
}
thermostats.each {
resp << [id: it.id, capability: "Thermostat", attribute: "thermostatOperatingState", value: it.currentValue("thermostatOperatingState")]
}
threeAxes.each {
resp << [id: it.id, capability: "ThreeAxis", attribute: "threeAxis", value: it.currentValue("threeAxis")]
}
touchSensors.each {
resp << [id: it.id, capability: "TouchSensor", attribute: "touch", value: it.currentValue("touch")]
}
valves.each {
resp << [id: it.id, capability: "Valve", attribute: "contact", value: it.currentValue("contact")]
}
waterSensors.each {
resp << [id: it.id, capability: "WaterSensor", attribute: "water", value: it.currentValue("water")]
}
//resp << [id: 0, capability: "Heartbeat", attribute: "heartbeat", value: String.valueOf(state.heartbeat)]
state.heartbeat = !state.heartbeat
return resp
}
def getCurrentValuesWithDisplayName() {
def resp = []
accelerationSensors.each {
resp << [id: it.id, displayName: it.displayName, capability: "AccelerationSensor", attribute: "acceleration", value: it.currentValue("acceleration")]
}
alarms.each {
resp << [id: it.id, displayName: it.displayName, capability: "Alarm", attribute: "alarm", value: it.currentValue("alarm")]
}
batteries.each {
resp << [id: it.id, displayName: it.displayName, capability: "Battery", attribute: "battery", value: it.currentValue("battery")]
}
beacons.each {
resp << [id: it.id, displayName: it.displayName, capability: "Beacon", attribute: "presence", value: it.currentValue("presence")]
}
buttonGroup.each {
resp << [id: it.id, displayName: it.displayName, capability: "Button", attribute: "button", value: it.currentValue("button")]
}
carbonMonoxideDetectors.each {
resp << [id: it.id, displayName: it.displayName, capability: "CarbonMonoxideDetector", attribute: "carbonMonoxide", value: it.currentValue("carbonMonoxide")]
}
colorControls.each {
resp << [id: it.id, displayName: it.displayName, capability: "ColorControl", attribute: "hue", value: it.currentValue("hue")]
}
colorControls.each {
resp << [id: it.id, displayName: it.displayName, capability: "ColorControl", attribute: "saturation", value: it.currentValue("saturation")]
}
colorControls.each {
resp << [id: it.id, displayName: it.displayName, capability: "ColorControl", attribute: "color", value: it.currentValue("color")]
}
contactSensors.each {
resp << [id: it.id, displayName: it.displayName, capability: "ContactSensor", attribute: "contact", value: it.currentValue("contact")]
}
doorControls.each {
resp << [id: it.id, displayName: it.displayName, capability: "DoorControl", attribute: "door", value: it.currentValue("door")]
}
energyMeters.each {
resp << [id: it.id, displayName: it.displayName, capability: "EnergyMeter", attribute: "energy", value: it.currentValue("energy")]
}
illuminanceMeasurements.each {
resp << [id: it.id, displayName: it.displayName, capability: "IlluminanceMeasurement", attribute: "illuminance", value: it.currentValue("illuminance")]
}
imageCaptures.each {
resp << [id: it.id, displayName: it.displayName, capability: "ImageCapture", attribute: "image", value: it.currentValue("image")]
}
locks.each {
resp << [id: it.id, displayName: it.displayName, capability: "Lock", attribute: "lock", value: it.currentValue("lock")]
}
mediaControllers.each {
resp << [id: it.id, displayName: it.displayName, capability: "MediaController", attribute: "activities", value: it.currentValue("activities")]
}
mediaControllers.each {
resp << [id: it.id, displayName: it.displayName, capability: "MediaController", attribute: "currentActivity", value: it.currentValue("currentActivity")]
}
motionSensors.each {
resp << [id: it.id, displayName: it.displayName, capability: "MotionSensor", attribute: "motion", value: it.currentValue("motion")]
}
musicPlayers.each {
resp << [id: it.id, displayName: it.displayName, capability: "MusicPlayer", attribute: "status", value: it.currentValue("status")]
}
musicPlayers.each {
resp << [id: it.id, displayName: it.displayName, capability: "MusicPlayer", attribute: "level", value: it.currentValue("level")]
}
musicPlayers.each {
resp << [id: it.id, displayName: it.displayName, capability: "MusicPlayer", attribute: "trackDescription", value: it.currentValue("trackDescription")]
}
musicPlayers.each {
resp << [id: it.id, displayName: it.displayName, capability: "MusicPlayer", attribute: "trackData", value: it.currentValue("trackData")]
}
musicPlayers.each {
resp << [id: it.id, displayName: it.displayName, capability: "MusicPlayer", attribute: "mute", value: it.currentValue("mute")]
}
powerMeters.each {
resp << [id: it.id, displayName: it.displayName, capability: "PowerMeter", attribute: "power", value: it.currentValue("power")]
}
presenceSensors.each {
resp << [id: it.id, displayName: it.displayName, capability: "PresenceSensor", attribute: "presence", value: it.currentValue("presence")]
}
relativeHumidityMeasurements.each {
resp << [id: it.id, displayName: it.displayName, capability: "RelativeHumidityMeasurement", attribute: "humidity", value: it.currentValue("humidity")]
}
relaySwitches.each {
resp << [id: it.id, displayName: it.displayName, capability: "RelaySwitch", attribute: "switch", value: it.currentValue("switch")]
}
signalStrengths.each {
resp << [id: it.id, displayName: it.displayName, capability: "SignalStrength", attribute: "lqi", value: it.currentValue("lqi")]
}
signalStrengths.each {
resp << [id: it.id, displayName: it.displayName, capability: "SignalStrength", attribute: "rssi", value: it.currentValue("rssi")]
}
sleepSensors.each {
resp << [id: it.id, displayName: it.displayName, capability: "SleepSensor", attribute: "sleeping", value: it.currentValue("sleeping")]
}
smokeDetectors.each {
resp << [id: it.id, displayName: it.displayName, capability: "SmokeDetector", attribute: "smoke", value: it.currentValue("smoke")]
}
stepSensors.each {
resp << [id: it.id, displayName: it.displayName, capability: "StepSensor", attribute: "steps", value: it.currentValue("steps")]
}
stepSensors.each {
resp << [id: it.id, displayName: it.displayName, capability: "StepSensor", attribute: "goal", value: it.currentValue("goal")]
}
switches.each {
resp << [id: it.id, displayName: it.displayName, capability: "Switch", attribute: "switch", value: it.currentValue("switch")]
}
switchLevels.each {
resp << [id: it.id, displayName: it.displayName, capability: "SwitchLevel", attribute: "level", value: it.currentValue("level")]
}
temperatureMeasurements.each {
resp << [id: it.id, displayName: it.displayName, capability: "TemperatureMeasurement", attribute: "temperature", value: it.currentValue("temperature")]
}
thermostats.each {
resp << [id: it.id, displayName: it.displayName, capability: "Thermostat", attribute: "temperature", value: it.currentValue("temperature")]
}
thermostats.each {
resp << [id: it.id, displayName: it.displayName, capability: "Thermostat", attribute: "heatingSetpoint", value: it.currentValue("heatingSetpoint")]
}
thermostats.each {
resp << [id: it.id, displayName: it.displayName, capability: "Thermostat", attribute: "coolingSetpoint", value: it.currentValue("coolingSetpoint")]
}
//Commented out on 7/23/2016. This randomly started throwing number format exceptions with either my ecobee or Lyric thermostat.
//thermostats.each {
// resp << [id: it.id, displayName: it.displayName, capability: "Thermostat", attribute: "thermostatSetpoint", value: it.currentValue("thermostatSetpoint")]
//}
thermostats.each {
resp << [id: it.id, displayName: it.displayName, capability: "Thermostat", attribute: "thermostatMode", value: it.currentValue("thermostatMode")]
}
thermostats.each {
resp << [id: it.id, displayName: it.displayName, capability: "Thermostat", attribute: "thermostatFanMode", value: it.currentValue("thermostatFanMode")]
}
thermostats.each {
resp << [id: it.id, displayName: it.displayName, capability: "Thermostat", attribute: "thermostatOperatingState", value: it.currentValue("thermostatOperatingState")]
}
threeAxes.each {
resp << [id: it.id, displayName: it.displayName, capability: "ThreeAxis", attribute: "threeAxis", value: it.currentValue("threeAxis")]
}
touchSensors.each {
resp << [id: it.id, displayName: it.displayName, capability: "TouchSensor", attribute: "touch", value: it.currentValue("touch")]
}
valves.each {
resp << [id: it.id, displayName: it.displayName, capability: "Valve", attribute: "contact", value: it.currentValue("contact")]
}
waterSensors.each {
resp << [id: it.id, displayName: it.displayName, capability: "WaterSensor", attribute: "water", value: it.currentValue("water")]
}
momentaries.each {
resp << [id: it.id, displayName: it.displayName, capability: "Momentary", attribute: "", value: ""]
}
//resp << [id: 0, displayName: "Heartbeat", capability: "Heartbeat", attribute: "heartbeat", value: state.heartbeat]
return resp
}
def getDevices(capability){
def result
switch (capability) {
case "Alarm":
result = alarms
break
case "ColorControl":
result = colorControls
break
case "DoorControl":
result = doorControls
break
case "ImageCapture":
result = imageCaptures
break
case "Lock":
result = locks
break
case "MediaController":
result = mediaControllers
break
case "Momentary":
result = momentaries
break
case "MusicPlayer":
result = musicPlayers
break
case "RelaySwitch":
result = relaySwitches
break
case "SpeechSynthesis":
result = speechSyntheses
break
case "Switch":
result = switches
break
case "SwitchLevel":
result = switchLevels
break
case "Thermostat":
result = thermostats
break
case "ThermostatCoolingSetpoint":
result = thermostatCoolingSetpoints
break
case "ThermostatFanMode":
result = thermostatFanModes
break
case "ThermostatHeatingSetpoint":
result = thermostatHeatingSetpoints
break
case "ThermostatMode":
result = thermostatModes
break
case "Tone":
result = tones
break
case "Valve":
result = valves
break
default:
result = valves
}
return result
}
def getDoorControlCommand(value){
def result
switch (value) {
case "closed":
result = "close"
break
case "open":
result = "open"
break
default:
result = value
}
return result
}
def getLockCommand(value){
def result
switch (value) {
case "locked":
result = "lock"
break
case "unlocked":
result = "unlock"
break
default:
result = value
}
return result
}
def getMuteCommand(value){
def result
switch (value) {
case "muted":
result = "mute"
break
case "unmuted":
result = "unmute"
break
default:
result = value
}
return result
}
def getContactCommand(value){
def result
switch (value) {
case "closed":
result = "close"
break
case "open":
result = "open"
break
default:
result = value
}
return result
}
def getRoutines() {
return location.helloHome?.getPhrases()*.label
}
def getThermostatFanModeCommand(value){
def result
switch (value) {
case "on":
result = "fanOn"
break
case "auto":
result = "fanAuto"
break
case "circulate":
result = "fanCirculate"
break
default:
result = value
}
return result
}
void executeCommand() {
def deviceId = request.JSON?.deviceId
def capability = request.JSON?.capability
def attribute = request.JSON?.attribute
def value = request.JSON?.value
if (deviceId) {
def devices = getDevices(capability)
def command
def valueIsParameter = false
def valueIsInteger = false
switch (attribute) {
case "hue":
command = "setHue"
valueIsParameter = true
valueIsInteger = true
break
case "saturation":
command = "setSaturation"
valueIsParameter = true
valueIsInteger = true
break
case "color":
command = "setColor"
def rgb = hexToRgb(value)
def hsl = rgbToHSL(rgb)
value = [hue:hsl.h.toInteger(), saturation:hsl.s.toInteger()]
valueIsParameter = true
break
case "level":
command = "setLevel"
valueIsParameter = true
valueIsInteger = true
break
case "heatingSetpoint":
command = "setHeatingSetpoint"
valueIsParameter = true
break
case "coolingSetpoint":
command = "setCoolingSetpoint"
valueIsParameter = true
break
case "currentActivity":
command = "startActivity"
valueIsParameter = true
break
case "door":
command = getDoorControlCommand(value)
break
case "lock":
command = getLockCommand(value)
break
case "mute":
command = getMuteCommand(value)
break
case "thermostatFanMode":
command = getThermostatFanModeCommand(value)
break
case "thermostatMode":
if (value == "emergency heat") {
command = "emergencyHeat"
}
else
{
command = value
}
break
case "contact":
command = getContactCommand(value)
break
default:
command = value
}
devices.each {
if (it.id == deviceId) {
// check that the device supports the specified command
// If not, return an error using httpError, providing a HTTP status code.
if (!it.hasCommand(command)) {
httpError(501, "$command is not a valid command for the device")
}
if(valueIsParameter){
if(valueIsInteger){
it."$command"(value as int)
}
else{
it."$command"(value)
}
}
else{
it."$command"()
}
}
}
}
}
void executeRoutine() {
def routine = request.JSON?.routine
location.helloHome?.execute(routine)
}
def rgbToHSL(rgb) {
def r = rgb.r / 255
def g = rgb.g / 255
def b = rgb.b / 255
def h = 0
def s = 0
def l = 0
def var_min = [r,g,b].min()
def var_max = [r,g,b].max()
def del_max = var_max - var_min
l = (var_max + var_min) / 2
if (del_max == 0) {
h = 0
s = 0
} else {
if (l < 0.5) { s = del_max / (var_max + var_min) }
else { s = del_max / (2 - var_max - var_min) }
def del_r = (((var_max - r) / 6) + (del_max / 2)) / del_max
def del_g = (((var_max - g) / 6) + (del_max / 2)) / del_max
def del_b = (((var_max - b) / 6) + (del_max / 2)) / del_max
if (r == var_max) { h = del_b - del_g }
else if (g == var_max) { h = (1 / 3) + del_r - del_b }
else if (b == var_max) { h = (2 / 3) + del_g - del_r }
if (h < 0) { h += 1 }
if (h > 1) { h -= 1 }
}
def hsl = [:]
hsl = [h: h * 100, s: s * 100, l: l]
hsl
}
def hexToRgb(colorHex) {
def rrInt = Integer.parseInt(colorHex.substring(1,3),16)
def ggInt = Integer.parseInt(colorHex.substring(3,5),16)
def bbInt = Integer.parseInt(colorHex.substring(5,7),16)
def colorData = [:]
colorData = [r: rrInt, g: ggInt, b: bbInt]
colorData
}
def installed() {
state.heartbeat = false
}
def updated() {
state.heartbeat = false
}