mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-10 05:11:51 +00:00
Compare commits
37 Commits
MSA-1488-1
...
PROD_2016.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f5eb7b85a | ||
|
|
64e4ccc517 | ||
|
|
aa890ae3d5 | ||
|
|
8d701b9fea | ||
|
|
719b24ecd6 | ||
|
|
9d5ab3bfc8 | ||
|
|
aae7f23a22 | ||
|
|
218cc43520 | ||
|
|
5b0ca4b815 | ||
|
|
9ddc020f04 | ||
|
|
aab3b8d7f8 | ||
|
|
a0ccf35eaa | ||
|
|
9fbbaec8f6 | ||
|
|
e4c1824afd | ||
|
|
797a58cb68 | ||
|
|
c428267d63 | ||
|
|
02f30cf425 | ||
|
|
fea802ffce | ||
|
|
6400d26f4a | ||
|
|
5e3aaa3270 | ||
|
|
f5c3997679 | ||
|
|
81cf1179ef | ||
|
|
79d20b0edb | ||
|
|
b6d862fdd4 | ||
|
|
d58084c438 | ||
|
|
dbfaef3e69 | ||
|
|
40ed88e7fd | ||
|
|
1d6e22dc16 | ||
|
|
30993aa218 | ||
|
|
2f8ed277ff | ||
|
|
1d180ac487 | ||
|
|
230541a145 | ||
|
|
8c4f7edc83 | ||
|
|
4f188581df | ||
|
|
0b7bb40474 | ||
|
|
e373b6f92e | ||
|
|
4ad0a6fd9d |
@@ -1,320 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016 Eric Maycock
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Sonoff Wifi Switch 2.0
|
||||
*
|
||||
* Author: Eric Maycock (erocm123)
|
||||
* Date: 2016-01-27
|
||||
*/
|
||||
|
||||
import groovy.json.JsonSlurper
|
||||
import groovy.util.XmlSlurper
|
||||
|
||||
metadata {
|
||||
definition (name: "Sonoff Wifi Switch 2.0", namespace: "erocm123", author: "Eric Maycock") {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Configuration"
|
||||
|
||||
command "reboot"
|
||||
}
|
||||
|
||||
simulator {
|
||||
}
|
||||
|
||||
preferences {
|
||||
input("ip", "string", title:"IP Address", description: "192.168.1.150" ,required: true, displayDuringSetup: true)
|
||||
// Port should always be 80
|
||||
//input("port", "string", title:"Port", description: "80" , required: true, displayDuringSetup: true)
|
||||
}
|
||||
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("configure", "device.configure", inactiveLabel: false, width: 2, height: 2, decoration: "flat") {
|
||||
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||
}
|
||||
valueTile("reboot", "device.reboot", decoration: "flat", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) {
|
||||
state "default", label:"Reboot", action:"reboot", icon:"", backgroundColor:"#FFFFFF"
|
||||
}
|
||||
valueTile("hubInfo", "device.hubInfo", decoration: "flat", height: 2, width: 6, inactiveLabel: false, canChangeIcon: false) {
|
||||
state "hubInfo", label:'${currentValue}' //backgroundColor:"#FFFFFF"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch",
|
||||
"refresh","configure","reboot",
|
||||
"hubInfo"])
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "installed()"
|
||||
configure()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "updated()"
|
||||
configure()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "configure()"
|
||||
log.debug "Configuring Device For SmartThings Use"
|
||||
state.ruleConfigured = false
|
||||
state.switchConfigured = false
|
||||
state.buttonConfigured = false
|
||||
sendEvent(name:"hubInfo", value:"Sonoff switch still being configured")
|
||||
if (state.MAC != null) state.dni = setDeviceNetworkId(state.MAC)
|
||||
else
|
||||
if (ip != null) state.dni = setDeviceNetworkId(ip, "80")
|
||||
state.hubIP = device.hub.getDataValue("localIP")
|
||||
response(setupDevices() + setupRules() + refresh())
|
||||
}
|
||||
|
||||
def setupDevices() {
|
||||
def cmds = []
|
||||
cmds << postAction("/devices?index=1&page=1", "taskdevicenumber=1&edit=1&page=1")
|
||||
cmds << postAction("/devices?index=1&page=1", "taskdevicenumber=1&taskdevicename=BUTTON&taskdevicetimer=0&taskdeviceid=1&taskdevicepin1=0&taskdevicepin1pullup=on&plugin_001_type=1&plugin_001_button=2&taskdevicevaluename1=Switch&edit=1&page=1")
|
||||
cmds << postAction("/devices?index=2&page=1", "taskdevicenumber=1&edit=1&page=1")
|
||||
cmds << postAction("/devices?index=2&page=1", "taskdevicenumber=1&taskdevicename=SWITCH&taskdevicetimer=0&taskdeviceid=2&taskdevicepin1=12&taskdevicepin1pullup=on&plugin_001_type=1&plugin_001_button=0&taskdevicesenddata=on&taskdevicevaluename1=Switch&edit=1&page=1")
|
||||
return delayBetween(cmds, 1000)
|
||||
}
|
||||
|
||||
def setupRules() {
|
||||
def cmds = []
|
||||
cmds << postAction("/advanced", "mqttsubscribe=&mqttpublish=&messagedelay=1000&ip=0&ntphost=&timezone=0&syslogip=0.0.0.0&sysloglevel=0&udpport=0&useserial=on&serialloglevel=0&webloglevel=0&baudrate=115200&wdi2caddress=0&wireclockstretchlimit=0&userules=on&edit=1")
|
||||
cmds << postAction("/rules", "rules=On+BUTTON%23Switch+do%0D%0A++if+%5BSWITCH%23Switch%5D%3D0%0D%0A++++gpio%2C12%2C1%0D%0A++else%0D%0A++++gpio%2C12%2C0%0D%0A++endif%0D%0Aendon%0D%0A%0D%0AOn+SWITCH%23Switch+do%0D%0A+if+%5BSWITCH%23Switch%5D%3D1%0D%0A++++gpio%2C13%2C0%0D%0A++else%0D%0A++++gpio%2C13%2C1%0D%0A++endif%0D%0Aendon")
|
||||
return delayBetween(cmds, 1000)
|
||||
}
|
||||
|
||||
def setupConfig() {
|
||||
// Automatic config page submit not working at this time. Has to be done manually
|
||||
/*def hubIP = device.hub.getDataValue("localIP")
|
||||
log.debug "Hub IP: ${hubIP}"
|
||||
def cmds = []
|
||||
//cmds << postAction("/config", "protocol=1&usedns=0&controllerip=$hubIP&controllerport=39500&controlleruser=&controllerpassword=&delay=60")
|
||||
return delayBetween(cmds, 1000)*/
|
||||
}
|
||||
|
||||
def parse(description) {
|
||||
//log.debug "Parsing: ${description}"
|
||||
def events = []
|
||||
def cmds
|
||||
def descMap = parseDescriptionAsMap(description)
|
||||
def body
|
||||
log.debug "descMap: ${descMap}"
|
||||
|
||||
|
||||
if (!state.MAC || state.MAC != descMap["mac"]) {
|
||||
log.debug "Mac address of device found ${descMap["mac"]}"
|
||||
updateDataValue("MAC", descMap["mac"])
|
||||
}
|
||||
|
||||
if (state.MAC != null && state.dni != state.MAC) state.dni = setDeviceNetworkId(state.MAC)
|
||||
if (descMap["body"]) body = new String(descMap["body"].decodeBase64())
|
||||
|
||||
if (body && body != "") {
|
||||
|
||||
|
||||
if(body.startsWith("{") || body.startsWith("[")) {
|
||||
def slurper = new JsonSlurper()
|
||||
def result = slurper.parseText(body)
|
||||
|
||||
//log.debug "result: ${result}"
|
||||
|
||||
if (result.containsKey("Sensors")) {
|
||||
def mySwitch = result.Sensors.find { it.TaskName == "SWITCH" }
|
||||
def myButton = result.Sensors.find { it.TaskName == "BUTTON" }
|
||||
def myLED = result.Sensors.find { it.TaskName == "LED" }
|
||||
if (mySwitch) {
|
||||
events << createEvent(name:"switch", value: (mySwitch.Switch.toInteger() == 0 ? 'off' : 'on'))
|
||||
state.switchConfigured = true
|
||||
}
|
||||
if (myButton) state.buttonConfigured = true
|
||||
//if (myLED) log.debug "LED is ${(myLED.Switch.toInteger() == 0 ? 'off' : 'on')}"
|
||||
}
|
||||
if (result.containsKey("pin")) {
|
||||
if (result.pin == 12) events << createEvent(name:"switch", value: (result.state.toInteger() == 0 ? 'off' : 'on'))
|
||||
}
|
||||
|
||||
if (result.containsKey("System")) {
|
||||
if (result.System.containsKey("Uptime")) log.debug "System has been up ${result.System.Uptime.toInteger() / 60} hours"
|
||||
}
|
||||
} else {
|
||||
//log.debug "Response is not JSON: $body"
|
||||
def ruleSearch = "OnBUTTONSwitchdoifSWITCHSwitch0gpio121elsegpio120endifendonOnSWITCHSwitchdoifSWITCHSwitch1gpio130elsegpio131endifendon"
|
||||
if (body.replaceAll("\\W", "").indexOf(ruleSearch) > 0) state.ruleConfigured = true
|
||||
|
||||
}
|
||||
} else {
|
||||
cmds = refresh()
|
||||
}
|
||||
|
||||
if (settings.ip) {
|
||||
//log.debug "switch: $state.switchConfigured, button: $state.buttonConfigured, rule: $state.ruleConfigured"
|
||||
if (state.switchConfigured == true && state.buttonConfigured == true && state.ruleConfigured == true) {
|
||||
events << createEvent(name:"hubInfo", value:"For instant status updates, configure switch at http://$settings.ip/config | Hub Info - IP: ${device.hub.getDataValue("localIP")}, Port: 39500")
|
||||
} else {
|
||||
events << createEvent(name:"hubInfo", value:"Sonoff switch still being configured")
|
||||
}
|
||||
}
|
||||
else {
|
||||
events << createEvent(name:"hubInfo", value:"IP address of the switch not entered. Please do so in device preferences.")
|
||||
}
|
||||
|
||||
if (cmds) return cmds else return events
|
||||
|
||||
}
|
||||
|
||||
def parseDescriptionAsMap(description) {
|
||||
description.split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
|
||||
if (nameAndValue.length == 2) map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
else map += [(nameAndValue[0].trim()):""]
|
||||
}
|
||||
}
|
||||
|
||||
private parseHTML(html) {
|
||||
//log.debug html
|
||||
def myHtml = html.split("<")
|
||||
def result = []
|
||||
switch (myHtml[0]) {
|
||||
case "GPIO 12 Set to 0":
|
||||
result = [name: "switch", value: "off"]
|
||||
break
|
||||
case "GPIO 12 Set to 1":
|
||||
result = [name: "switch", value: "on"]
|
||||
break
|
||||
case "GPIO 13 Set to 0":
|
||||
log.debug "LED is on"
|
||||
break
|
||||
case "GPIO 13 Set to 1":
|
||||
log.debug "LED is off"
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private ledOn() {
|
||||
return getAction("/control?cmd=GPIO,13,0")
|
||||
}
|
||||
|
||||
private ledOff() {
|
||||
return getAction("/control?cmd=GPIO,13,1")
|
||||
}
|
||||
|
||||
def on() {
|
||||
log.debug "on()"
|
||||
def cmds = []
|
||||
cmds << getAction("/control?cmd=GPIO,12,1")
|
||||
return cmds
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "off()"
|
||||
def cmds = []
|
||||
cmds << getAction("/control?cmd=GPIO,12,0")
|
||||
return cmds
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh()"
|
||||
def cmds = []
|
||||
cmds << getAction("/json")
|
||||
return cmds
|
||||
}
|
||||
|
||||
private getAction(uri){
|
||||
updateDNI()
|
||||
def headers = getHeader()
|
||||
def hubAction = new physicalgraph.device.HubAction(
|
||||
method: "GET",
|
||||
path: uri,
|
||||
headers: headers
|
||||
)
|
||||
return hubAction
|
||||
}
|
||||
|
||||
private postAction(uri, data){
|
||||
updateDNI()
|
||||
def headers = getHeader()
|
||||
def hubAction = new physicalgraph.device.HubAction(
|
||||
method: "POST",
|
||||
path: uri,
|
||||
headers: headers,
|
||||
body: data
|
||||
)
|
||||
return hubAction
|
||||
}
|
||||
|
||||
private setDeviceNetworkId(ip, port = null){
|
||||
def myDNI
|
||||
if (port == null) {
|
||||
myDNI = ip
|
||||
} else {
|
||||
def iphex = convertIPtoHex(ip)
|
||||
def porthex = convertPortToHex(port)
|
||||
myDNI = "$iphex:$porthex"
|
||||
}
|
||||
log.debug "Device Network Id set to ${myDNI}"
|
||||
return myDNI
|
||||
}
|
||||
|
||||
private updateDNI() {
|
||||
if (device.deviceNetworkId != state.dni) {
|
||||
device.deviceNetworkId = state.dni
|
||||
}
|
||||
}
|
||||
|
||||
private getHostAddress() {
|
||||
return "${ip}:80"
|
||||
}
|
||||
|
||||
private String convertIPtoHex(ipAddress) {
|
||||
String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join()
|
||||
return hex
|
||||
}
|
||||
|
||||
private String convertPortToHex(port) {
|
||||
String hexport = port.toString().format( '%04x', port.toInteger() )
|
||||
return hexport
|
||||
}
|
||||
|
||||
private getHeader(){
|
||||
def headers = [:]
|
||||
headers.put("Host", getHostAddress())
|
||||
headers.put("Content-Type", "application/x-www-form-urlencoded")
|
||||
return headers
|
||||
}
|
||||
|
||||
def reboot() {
|
||||
log.debug "reboot()"
|
||||
def uri = "/?cmd=reboot"
|
||||
getAction(uri)
|
||||
}
|
||||
@@ -87,16 +87,27 @@ def beep() {
|
||||
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}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
"delay 7000",
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
"delay 7000",
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
"delay 7000",
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
"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",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -105,11 +105,21 @@ def parseDescriptionAsMap(description) {
|
||||
|
||||
// Commands to device
|
||||
def on() {
|
||||
'zcl on-off on'
|
||||
[
|
||||
'zcl on-off on',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 500'
|
||||
]
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -31,13 +31,13 @@ metadata {
|
||||
command "switchMode"
|
||||
command "switchFanMode"
|
||||
|
||||
attribute "thermostatSetpoint","number"
|
||||
attribute "thermostatStatus","string"
|
||||
attribute "thermostatSetpoint", "number"
|
||||
attribute "thermostatStatus", "string"
|
||||
attribute "maxHeatingSetpoint", "number"
|
||||
attribute "minHeatingSetpoint", "number"
|
||||
attribute "maxCoolingSetpoint", "number"
|
||||
attribute "minCoolingSetpoint", "number"
|
||||
attribute "deviceTemperatureUnit", "number"
|
||||
attribute "deviceTemperatureUnit", "string"
|
||||
}
|
||||
|
||||
tiles {
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Health Check"
|
||||
|
||||
attribute "networkAddress", "string"
|
||||
// Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network
|
||||
// Possible values "Online" or "Offline"
|
||||
// Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network
|
||||
// Possible values "Online" or "Offline"
|
||||
attribute "status", "string"
|
||||
// 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
|
||||
@@ -42,6 +44,10 @@ metadata {
|
||||
}
|
||||
}
|
||||
|
||||
void installed() {
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
@@ -70,13 +76,8 @@ def parse(description) {
|
||||
def bulbs = new groovy.json.JsonSlurper().parseText(msg.body)
|
||||
if (bulbs.state) {
|
||||
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"
|
||||
parent.hubVerification(device.hub.id, msg.body)
|
||||
}
|
||||
@@ -85,3 +86,7 @@ def parse(description) {
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
def ping() {
|
||||
log.debug "${parent.ping(this)}"
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ void setColorTemperature(value) {
|
||||
|
||||
void refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
parent?.manualRefresh()
|
||||
}
|
||||
|
||||
def verifyPercent(percent) {
|
||||
|
||||
@@ -47,9 +47,21 @@ def parse(String description) {
|
||||
|
||||
// Commands to device
|
||||
def on() {
|
||||
'zcl on-off on'
|
||||
[
|
||||
'zcl on-off on',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 500'
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
def off() {
|
||||
'zcl on-off off'
|
||||
[
|
||||
'zcl on-off off',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 500'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -180,9 +180,9 @@ private Map parseIasMessage(String description) {
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return celsius
|
||||
return Math.round(celsius)
|
||||
} else {
|
||||
return celsiusToFahrenheit(celsius) as Integer
|
||||
return Math.round(celsiusToFahrenheit(celsius))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -194,9 +194,9 @@ private Map parseIasMessage(String description) {
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return celsius
|
||||
return Math.round(celsius)
|
||||
} else {
|
||||
return celsiusToFahrenheit(celsius) as Integer
|
||||
return Math.round(celsiusToFahrenheit(celsius))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -261,9 +261,9 @@ def updated() {
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return celsius
|
||||
return Math.round(celsius)
|
||||
} else {
|
||||
return celsiusToFahrenheit(celsius) as Integer
|
||||
return Math.round(celsiusToFahrenheit(celsius))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
|
||||
}
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD226/246 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"toggle", type:"generic", width:6, height:4){
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Smart Windows
|
||||
* Compares two temperatures – indoor vs outdoor, for example – then sends an alert if windows are open (or closed!).
|
||||
*
|
||||
*
|
||||
* Copyright 2014 Eric Gideon
|
||||
*
|
||||
* Based in part on the "When it's going to rain" SmartApp by the SmartThings team,
|
||||
@@ -21,13 +21,18 @@ definition(
|
||||
name: "Smart Windows",
|
||||
namespace: "egid",
|
||||
author: "Eric Gideon",
|
||||
description: "Compares two temperatures – indoor vs outdoor, for example – then sends an alert if windows are open (or closed!). If you don't use an external temperature device, your zipcode will be used instead.",
|
||||
description: "Compares two temperatures – indoor vs outdoor, for example – then sends an alert if windows are open (or closed!). If you don't use an external temperature device, your location will be used instead.",
|
||||
iconUrl: "https://s3.amazonaws.com/smartthings-device-icons/Home/home9-icn.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartthings-device-icons/Home/home9-icn@2x.png"
|
||||
)
|
||||
|
||||
|
||||
preferences {
|
||||
|
||||
if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') {
|
||||
section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." }
|
||||
}
|
||||
|
||||
section( "Set the temperature range for your comfort zone..." ) {
|
||||
input "minTemp", "number", title: "Minimum temperature"
|
||||
input "maxTemp", "number", title: "Maximum temperature"
|
||||
@@ -39,9 +44,11 @@ preferences {
|
||||
input "inTemp", "capability.temperatureMeasurement", title: "Indoor"
|
||||
input "outTemp", "capability.temperatureMeasurement", title: "Outdoor (optional)", required: false
|
||||
}
|
||||
section( "Set your location" ) {
|
||||
input "zipCode", "text", title: "Zip code"
|
||||
}
|
||||
|
||||
if (location.channelName != 'samsungtv') {
|
||||
section( "Set your location" ) { input "zipCode", "text", title: "Zip code" }
|
||||
}
|
||||
|
||||
section( "Notifications" ) {
|
||||
input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes","No"]], required:false
|
||||
input "retryPeriod", "number", title: "Minutes between notifications:"
|
||||
@@ -72,7 +79,7 @@ def temperatureHandler(evt) {
|
||||
|
||||
def currentInTemp = evt.doubleValue
|
||||
def openWindows = sensors.findAll { it?.latestValue("contact") == 'open' }
|
||||
|
||||
|
||||
log.trace "Temp event: $evt"
|
||||
log.info "In: $currentInTemp; Out: $currentOutTemp"
|
||||
|
||||
@@ -98,7 +105,7 @@ def temperatureHandler(evt) {
|
||||
if ( currentOutTemp < maxTemp && !openWindows ) {
|
||||
send( "Open some windows to cool down the house! Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." )
|
||||
} else if ( currentOutTemp > maxTemp && openWindows ) {
|
||||
send( "It's gotten warmer outside! You should close these windows: ${openWindows.join(', ')}. Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." )
|
||||
send( "It's gotten warmer outside! You should close these windows: ${openWindows.join(', ')}. Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." )
|
||||
} else {
|
||||
log.debug "No notifications sent. Everything is in the right place."
|
||||
}
|
||||
@@ -125,7 +132,11 @@ def temperatureHandler(evt) {
|
||||
}
|
||||
|
||||
def weatherCheck() {
|
||||
def json = getWeatherFeature("conditions", zipCode)
|
||||
def json
|
||||
if (location.channelName != 'samsungtv')
|
||||
json = getWeatherFeature("conditions", zipCode)
|
||||
else
|
||||
json = getWeatherFeature("conditions")
|
||||
def currentTemp = json?.current_observation?.temp_f
|
||||
|
||||
if ( currentTemp ) {
|
||||
@@ -150,4 +161,4 @@ private send(msg) {
|
||||
}
|
||||
|
||||
log.info msg
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,348 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016 Eric Maycock
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Sonoff (Connect)
|
||||
*
|
||||
* Author: Eric Maycock (erocm123)
|
||||
* Date: 2016-06-02
|
||||
*/
|
||||
|
||||
definition(
|
||||
name: "Sonoff (Connect)",
|
||||
namespace: "erocm123",
|
||||
author: "Eric Maycock (erocm123)",
|
||||
description: "Service Manager for Sonoff switches",
|
||||
category: "Convenience",
|
||||
iconUrl: "https://raw.githubusercontent.com/erocm123/SmartThingsPublic/master/smartapps/erocm123/sonoff-connect.src/sonoff-connect-icon.png",
|
||||
iconX2Url: "https://raw.githubusercontent.com/erocm123/SmartThingsPublic/master/smartapps/erocm123/sonoff-connect.src/sonoff-connect-icon-2x.png",
|
||||
iconX3Url: "https://raw.githubusercontent.com/erocm123/SmartThingsPublic/master/smartapps/erocm123/sonoff-connect.src/sonoff-connect-icon-3x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
page(name: "mainPage")
|
||||
page(name: "configurePDevice")
|
||||
page(name: "deletePDevice")
|
||||
page(name: "changeName")
|
||||
page(name: "discoveryPage", title: "Device Discovery", content: "discoveryPage", refreshTimeout:5)
|
||||
page(name: "addDevices", title: "Add Sonoff Switches", content: "addDevices")
|
||||
page(name: "deviceDiscovery")
|
||||
}
|
||||
|
||||
def mainPage() {
|
||||
dynamicPage(name: "mainPage", title: "Manage your Sonoff switches", nextPage: null, uninstall: true, install: true) {
|
||||
section("Configure"){
|
||||
href "deviceDiscovery", title:"Discover Sonoff Devices", description:""
|
||||
}
|
||||
section("Installed Devices"){
|
||||
getChildDevices().sort({ a, b -> a["deviceNetworkId"] <=> b["deviceNetworkId"] }).each {
|
||||
href "configurePDevice", title:"$it.label", description:"", params: [did: it.deviceNetworkId]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def configurePDevice(params){
|
||||
def currentDevice
|
||||
getChildDevices().each {
|
||||
if(it.deviceNetworkId == params.did){
|
||||
state.currentDeviceId = it.deviceNetworkId
|
||||
state.currentDisplayName = it.displayName
|
||||
}
|
||||
}
|
||||
dynamicPage(name: "configurePDevice", title: "Configure Sonoff Switches created with this app", nextPage: null) {
|
||||
section {
|
||||
app.updateSetting("${state.currentDeviceId}_label", getChildDevice(state.currentDeviceId).label)
|
||||
input "${state.currentDeviceId}_label", "text", title:"Device Name", description: "", required: false
|
||||
href "changeName", title:"Change Device Name", description: "Edit the name above and click here to change it", params: [did: state.currentDeviceId]
|
||||
}
|
||||
section {
|
||||
href "deletePDevice", title:"Delete $state.currentDisplayName", description: "", params: [did: state.currentDeviceId]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def deletePDevice(params){
|
||||
try {
|
||||
unsubscribe()
|
||||
getChildDevices().each {
|
||||
if(it.deviceNetworkId.startsWith("${params.did}/")) deleteChildDevice(it.deviceNetworkId)
|
||||
}
|
||||
deleteChildDevice(params.did)
|
||||
dynamicPage(name: "deletePDevice", title: "Deletion Summary", nextPage: "mainPage") {
|
||||
section {
|
||||
paragraph "The device has been deleted. Press next to continue"
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
dynamicPage(name: "deletePDevice", title: "Deletion Summary", nextPage: "mainPage") {
|
||||
section {
|
||||
paragraph "Error: ${(e as String).split(":")[1]}."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def changeName(params){
|
||||
def thisDevice = getChildDevice(params.did)
|
||||
thisDevice.label = settings["${params.did}_label"]
|
||||
|
||||
dynamicPage(name: "changeName", title: "Change Name Summary", nextPage: "mainPage") {
|
||||
section {
|
||||
paragraph "The device has been renamed. Press \"Next\" to continue"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def discoveryPage(){
|
||||
return deviceDiscovery()
|
||||
}
|
||||
|
||||
def deviceDiscovery(params=[:])
|
||||
{
|
||||
def devices = devicesDiscovered()
|
||||
|
||||
int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int
|
||||
state.deviceRefreshCount = deviceRefreshCount + 1
|
||||
def refreshInterval = 3
|
||||
|
||||
def options = devices ?: []
|
||||
def numFound = options.size() ?: 0
|
||||
|
||||
if ((numFound == 0 && state.deviceRefreshCount > 25) || params.reset == "true") {
|
||||
log.trace "Cleaning old device memory"
|
||||
state.devices = [:]
|
||||
state.deviceRefreshCount = 0
|
||||
app.updateSetting("selectedDevice", "")
|
||||
}
|
||||
|
||||
ssdpSubscribe()
|
||||
|
||||
//sonoff discovery request every 15 //25 seconds
|
||||
if((deviceRefreshCount % 5) == 0) {
|
||||
discoverDevices()
|
||||
}
|
||||
|
||||
//setup.xml request every 3 seconds except on discoveries
|
||||
if(((deviceRefreshCount % 3) == 0) && ((deviceRefreshCount % 5) != 0)) {
|
||||
verifyDevices()
|
||||
}
|
||||
|
||||
return dynamicPage(name:"deviceDiscovery", title:"Discovery Started!", nextPage:"addDevices", refreshInterval:refreshInterval, uninstall: true) {
|
||||
section("Please wait while we discover your Sonoff devices. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||
input "selectedDevices", "enum", required:false, title:"Select Sonoff Switch (${numFound} found)", multiple:true, options:options
|
||||
}
|
||||
section("Options") {
|
||||
href "deviceDiscovery", title:"Reset list of discovered devices", description:"", params: ["reset": "true"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map devicesDiscovered() {
|
||||
def vdevices = getVerifiedDevices()
|
||||
def map = [:]
|
||||
vdevices.each {
|
||||
def value = "${it.value.name}"
|
||||
def key = "${it.value.mac}"
|
||||
map["${key}"] = value
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
def getVerifiedDevices() {
|
||||
getDevices().findAll{ it?.value?.verified == true }
|
||||
}
|
||||
|
||||
private discoverDevices() {
|
||||
sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:Basic:1", physicalgraph.device.Protocol.LAN))
|
||||
}
|
||||
|
||||
def configured() {
|
||||
|
||||
}
|
||||
|
||||
def buttonConfigured(idx) {
|
||||
return settings["lights_$idx"]
|
||||
}
|
||||
|
||||
def isConfigured(){
|
||||
if(getChildDevices().size() > 0) return true else return false
|
||||
}
|
||||
|
||||
def isVirtualConfigured(did){
|
||||
def foundDevice = false
|
||||
getChildDevices().each {
|
||||
if(it.deviceNetworkId != null){
|
||||
if(it.deviceNetworkId.startsWith("${did}/")) foundDevice = true
|
||||
}
|
||||
}
|
||||
return foundDevice
|
||||
}
|
||||
|
||||
private virtualCreated(number) {
|
||||
if (getChildDevice(getDeviceID(number))) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private getDeviceID(number) {
|
||||
return "${state.currentDeviceId}/${app.id}/${number}"
|
||||
}
|
||||
|
||||
def installed() {
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
unsubscribe()
|
||||
unschedule()
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
ssdpSubscribe()
|
||||
runEvery5Minutes("ssdpDiscover")
|
||||
}
|
||||
|
||||
void ssdpSubscribe() {
|
||||
subscribe(location, "ssdpTerm.urn:schemas-upnp-org:device:Basic:1", ssdpHandler)
|
||||
}
|
||||
|
||||
void ssdpDiscover() {
|
||||
sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:Basic:1", physicalgraph.device.Protocol.LAN))
|
||||
}
|
||||
|
||||
def ssdpHandler(evt) {
|
||||
def description = evt.description
|
||||
def hub = evt?.hubId
|
||||
def parsedEvent = parseLanMessage(description)
|
||||
parsedEvent << ["hub":hub]
|
||||
|
||||
def devices = getDevices()
|
||||
|
||||
String ssdpUSN = parsedEvent.ssdpUSN.toString()
|
||||
|
||||
if (devices."${ssdpUSN}") {
|
||||
def d = devices."${ssdpUSN}"
|
||||
def child = getChildDevice(parsedEvent.mac)
|
||||
def childIP
|
||||
def childPort
|
||||
if (child) {
|
||||
childIP = child.getDeviceDataByName("ip")
|
||||
childPort = child.getDeviceDataByName("port").toString()
|
||||
log.debug "Device data: ($childIP:$childPort) - reporting data: (${convertHexToIP(parsedEvent.networkAddress)}:${convertHexToInt(parsedEvent.deviceAddress)})."
|
||||
if(childIP != convertHexToIP(parsedEvent.networkAddress) || childPort != convertHexToInt(parsedEvent.deviceAddress).toString()){
|
||||
log.debug "Device data (${child.getDeviceDataByName("ip")}) does not match what it is reporting(${convertHexToIP(parsedEvent.networkAddress)}). Attempting to update."
|
||||
child.sync(convertHexToIP(parsedEvent.networkAddress), convertHexToInt(parsedEvent.deviceAddress).toString())
|
||||
}
|
||||
}
|
||||
|
||||
if (d.networkAddress != parsedEvent.networkAddress || d.deviceAddress != parsedEvent.deviceAddress) {
|
||||
d.networkAddress = parsedEvent.networkAddress
|
||||
d.deviceAddress = parsedEvent.deviceAddress
|
||||
}
|
||||
} else {
|
||||
devices << ["${ssdpUSN}": parsedEvent]
|
||||
}
|
||||
}
|
||||
|
||||
void verifyDevices() {
|
||||
def devices = getDevices().findAll { it?.value?.verified != true }
|
||||
devices.each {
|
||||
def ip = convertHexToIP(it.value.networkAddress)
|
||||
def port = convertHexToInt(it.value.deviceAddress)
|
||||
String host = "${ip}:${port}"
|
||||
sendHubCommand(new physicalgraph.device.HubAction("""GET ${it.value.ssdpPath} HTTP/1.1\r\nHOST: $host\r\n\r\n""", physicalgraph.device.Protocol.LAN, host, [callback: deviceDescriptionHandler]))
|
||||
}
|
||||
}
|
||||
|
||||
def getDevices() {
|
||||
state.devices = state.devices ?: [:]
|
||||
}
|
||||
|
||||
void deviceDescriptionHandler(physicalgraph.device.HubResponse hubResponse) {
|
||||
log.trace "description.xml response (application/xml)"
|
||||
def body = hubResponse.xml
|
||||
if (body?.device?.modelName?.text().startsWith("Sonoff Wifi Switch")) {
|
||||
def devices = getDevices()
|
||||
def device = devices.find {it?.key?.contains(body?.device?.UDN?.text())}
|
||||
if (device) {
|
||||
device.value << [name:body?.device?.friendlyName?.text() + " (" + convertHexToIP(hubResponse.ip) + ")", serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
||||
} else {
|
||||
log.error "/description.xml returned a device that didn't exist"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def addDevices() {
|
||||
def devices = getDevices()
|
||||
def sectionText = ""
|
||||
|
||||
selectedDevices.each { dni ->bridgeLinking
|
||||
def selectedDevice = devices.find { it.value.mac == dni }
|
||||
def d
|
||||
if (selectedDevice) {
|
||||
d = getChildDevices()?.find {
|
||||
it.deviceNetworkId == selectedDevice.value.mac
|
||||
}
|
||||
}
|
||||
|
||||
if (!d) {
|
||||
log.debug "Creating Sonoff Switch with dni: ${selectedDevice.value.mac}"
|
||||
log.debug Integer.parseInt(selectedDevice.value.deviceAddress,16)
|
||||
addChildDevice("erocm123", "Sonoff Wifi Switch", selectedDevice.value.mac, selectedDevice?.value.hub, [
|
||||
"label": selectedDevice?.value?.name ?: "Sonoff Wifi Switch",
|
||||
"data": [
|
||||
"mac": selectedDevice.value.mac,
|
||||
"ip": convertHexToIP(selectedDevice.value.networkAddress),
|
||||
"port": "" + Integer.parseInt(selectedDevice.value.deviceAddress,16)
|
||||
]
|
||||
])
|
||||
sectionText = sectionText + "Succesfully added Sonoff Wifi Switch with ip address ${convertHexToIP(selectedDevice.value.networkAddress)} \r\n"
|
||||
}
|
||||
|
||||
}
|
||||
log.debug sectionText
|
||||
return dynamicPage(name:"addDevices", title:"Devices Added", nextPage:"mainPage", uninstall: true) {
|
||||
if(sectionText != ""){
|
||||
section("Add Sonoff Results:") {
|
||||
paragraph sectionText
|
||||
}
|
||||
}else{
|
||||
section("No devices added") {
|
||||
paragraph "All selected devices have previously been added"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def uninstalled() {
|
||||
unsubscribe()
|
||||
getChildDevices().each {
|
||||
deleteChildDevice(it.deviceNetworkId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String convertHexToIP(hex) {
|
||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
def log(message){
|
||||
|
||||
}
|
||||
253
smartapps/gideon-api/gideon.src/gideon.groovy
Normal file
253
smartapps/gideon-api/gideon.src/gideon.groovy
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Gideon
|
||||
*
|
||||
* Copyright 2016 Nicola Russo
|
||||
*
|
||||
* 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: "Gideon",
|
||||
namespace: "gideon.api",
|
||||
author: "Braindrain Solutions",
|
||||
description: "Gideon AI Smart app allows you to connect and control all of your SmartThings devices through the Gideon AI app, making your SmartThings devices even smarter.",
|
||||
category: "Family",
|
||||
iconUrl: "http://s33.postimg.org/t77u7y7v3/logo.png",
|
||||
iconX2Url: "http://s33.postimg.org/t77u7y7v3/logo.png",
|
||||
iconX3Url: "http://s33.postimg.org/t77u7y7v3/logo.png",
|
||||
oauth: [displayName: "Gideon AI API", displayLink: "gideon.ai"])
|
||||
|
||||
|
||||
preferences {
|
||||
section("Control these switches...") {
|
||||
input "switches", "capability.switch", multiple:true
|
||||
}
|
||||
section("Control these motion sensors...") {
|
||||
input "motions", "capability.motionSensor", multiple:true
|
||||
}
|
||||
section("Control these presence sensors...") {
|
||||
input "presence_sensors", "capability.presenceSensor", multiple:true
|
||||
}
|
||||
section("Control these outlets...") {
|
||||
input "outlets", "capability.switch", multiple:true
|
||||
}
|
||||
section("Control these locks...") {
|
||||
input "locks", "capability.lock", multiple:true
|
||||
}
|
||||
section("Control these locks...") {
|
||||
input "temperature_sensors", "capability.temperatureMeasurement"
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
// TODO: subscribe to attributes, devices, locations, etc.
|
||||
subscribe(outlet, "energy", outletHandler)
|
||||
subscribe(outlet, "switch", outletHandler)
|
||||
}
|
||||
|
||||
// TODO: implement event handlers
|
||||
def outletHandler(evt) {
|
||||
log.debug "$outlet.currentEnergy"
|
||||
//TODO call G API
|
||||
}
|
||||
|
||||
|
||||
private device(it, type) {
|
||||
it ? [id: it.id, label: it.label, type: type] : null
|
||||
}
|
||||
|
||||
//API Mapping
|
||||
mappings {
|
||||
path("/getalldevices") {
|
||||
action: [
|
||||
GET: "getAllDevices"
|
||||
]
|
||||
}
|
||||
path("/doorlocks/:id/:command") {
|
||||
action: [
|
||||
GET: "updateDoorLock"
|
||||
]
|
||||
}
|
||||
path("/doorlocks/:id") {
|
||||
action: [
|
||||
GET: "getDoorLockStatus"
|
||||
]
|
||||
}
|
||||
path("/tempsensors/:id") {
|
||||
action: [
|
||||
GET: "getTempSensorsStatus"
|
||||
]
|
||||
}
|
||||
path("/presences/:id") {
|
||||
action: [
|
||||
GET: "getPresenceStatus"
|
||||
]
|
||||
}
|
||||
path("/motions/:id") {
|
||||
action: [
|
||||
GET: "getMotionStatus"
|
||||
]
|
||||
}
|
||||
path("/outlets/:id") {
|
||||
action: [
|
||||
GET: "getOutletStatus"
|
||||
]
|
||||
}
|
||||
path("/outlets/:id/:command") {
|
||||
action: [
|
||||
GET: "updateOutlet"
|
||||
]
|
||||
}
|
||||
path("/switches/:command") {
|
||||
action: [
|
||||
PUT: "updateSwitch"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
//API Methods
|
||||
def getAllDevices() {
|
||||
def locks_list = locks.collect{device(it,"Lock")}
|
||||
def presences_list = presence_sensors.collect{device(it,"Presence")}
|
||||
def motions_list = motions.collect{device(it,"Motion")}
|
||||
def outlets_list = outlets.collect{device(it,"Outlet")}
|
||||
def switches_list = switches.collect{device(it,"Switch")}
|
||||
def temp_list = temperature_sensors.collect{device(it,"Temperature")}
|
||||
return [Locks: locks_list, Presences: presences_list, Motions: motions_list, Outlets: outlets_list, Switches: switches_list, Temperatures: temp_list]
|
||||
}
|
||||
|
||||
//LOCKS
|
||||
def getDoorLockStatus() {
|
||||
def device = locks.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
return [Device_state: device.currentValue('lock')]
|
||||
}
|
||||
}
|
||||
|
||||
def updateDoorLock() {
|
||||
def command = params.command
|
||||
def device = locks.find { it.id == params.id }
|
||||
if (command){
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
if(command == "toggle")
|
||||
{
|
||||
if(device.currentValue('lock') == "locked")
|
||||
device.unlock();
|
||||
else
|
||||
device.lock();
|
||||
|
||||
return [Device_id: params.id, result_action: "200"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//PRESENCE
|
||||
def getPresenceStatus() {
|
||||
|
||||
def device = presence_sensors.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
return [Device_state: device.currentValue('presence')]
|
||||
}
|
||||
}
|
||||
|
||||
//MOTION
|
||||
def getMotionStatus() {
|
||||
|
||||
def device = motions.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
return [Device_state: device.currentValue('motion')]
|
||||
}
|
||||
}
|
||||
|
||||
//OUTLET
|
||||
def getOutletStatus() {
|
||||
|
||||
def device = outlets.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
return [Device_state: device.currentSwitch, Current_watt: device.currentValue("energy")]
|
||||
}
|
||||
}
|
||||
|
||||
def updateOutlet() {
|
||||
|
||||
def command = params.command
|
||||
def device = outlets.find { it.id == params.id }
|
||||
if (command){
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
if(command == "toggle")
|
||||
{
|
||||
if(device.currentSwitch == "on")
|
||||
device.off();
|
||||
else
|
||||
device.on();
|
||||
|
||||
return [Device_id: params.id, result_action: "200"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//SWITCH
|
||||
def updateSwitch() {
|
||||
def command = params.command
|
||||
def device = switches.find { it.id == params.id }
|
||||
if (command){
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
if(command == "toggle")
|
||||
{
|
||||
if(device.currentSwitch == "on")
|
||||
device.off();
|
||||
else
|
||||
device.on();
|
||||
|
||||
return [Device_id: params.id, result_action: "200"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TEMPERATURE
|
||||
def getTempSensorsStatus() {
|
||||
|
||||
def device = temperature_sensors.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
return [Device_state: device.currentValue('temperature')]
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,13 @@ definition(
|
||||
)
|
||||
|
||||
preferences {
|
||||
section("Zip code?") {
|
||||
input "zipcode", "text", title: "Zipcode?"
|
||||
|
||||
if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') {
|
||||
section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." }
|
||||
}
|
||||
|
||||
if (location.channelName != 'samsungtv') {
|
||||
section( "Set your location" ) { input "zipCode", "text", title: "Zip code" }
|
||||
}
|
||||
|
||||
section("Things to check?") {
|
||||
@@ -60,7 +65,11 @@ def scheduleCheck(evt) {
|
||||
// Only need to poll if we haven't checked in a while - and if something is left open.
|
||||
if((now() - (30 * 60 * 1000) > state.lastCheck["time"]) && open) {
|
||||
log.info("Something's open - let's check the weather.")
|
||||
def response = getWeatherFeature("forecast", zipcode)
|
||||
def response
|
||||
if (location.channelName != 'samsungtv')
|
||||
response = getWeatherFeature("forecast", zipCode)
|
||||
else
|
||||
response = getWeatherFeature("forecast")
|
||||
def weather = isStormy(response)
|
||||
|
||||
if(weather) {
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
* Author: Juan Risso
|
||||
* Date: 2013-12-19
|
||||
*/
|
||||
|
||||
include 'asynchttp_v1'
|
||||
|
||||
definition(
|
||||
name: "Jawbone UP (Connect)",
|
||||
namespace: "juano2310",
|
||||
@@ -303,7 +306,8 @@ def setup() {
|
||||
def childDevice = addChildDevice('juano2310', "Jawbone User", "${app.id}.${member.xid}",null,[name:"Jawbone UP - " + member.first, completedSetup: true])
|
||||
if (childDevice) {
|
||||
log.debug "Child Device Successfully Created"
|
||||
generateInitialEvent (member, childDevice)
|
||||
childDevice?.generateSleepingEvent(false)
|
||||
pollChild(childDevice)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,67 +353,67 @@ def uninstalled() {
|
||||
}
|
||||
|
||||
def pollChild(childDevice) {
|
||||
def member = state.member
|
||||
generatePollingEvents (member, childDevice)
|
||||
def childMap = [ value: "$childDevice.device.deviceNetworkId}"]
|
||||
|
||||
def params = [
|
||||
uri: 'https://jawbone.com',
|
||||
path: '/nudge/api/users/@me/goals',
|
||||
headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ],
|
||||
contentType: 'application/json'
|
||||
]
|
||||
|
||||
asynchttp_v1.get('responseGoals', params, childMap)
|
||||
|
||||
def params2 = [
|
||||
uri: 'https://jawbone.com',
|
||||
path: '/nudge/api/users/@me/moves',
|
||||
headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ],
|
||||
contentType: 'application/json'
|
||||
]
|
||||
|
||||
asynchttp_v1.get('responseMoves', params2, childMap)
|
||||
}
|
||||
|
||||
def generatePollingEvents (member, childDevice) {
|
||||
// lets figure out if the member is currently "home" (At the place)
|
||||
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
|
||||
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
|
||||
def urlsleeps = "https://jawbone.com/nudge/api/users/@me/sleeps"
|
||||
def goals = null
|
||||
def moves = null
|
||||
def sleeps = null
|
||||
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||
goals = response.data.data
|
||||
}
|
||||
httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||
moves = response.data.data.items[0]
|
||||
}
|
||||
|
||||
try { // we are going to just ignore any errors
|
||||
log.debug "Member = ${member.first}"
|
||||
log.debug "Moves Goal = ${goals.move_steps} Steps"
|
||||
log.debug "Moves = ${moves.details.steps} Steps"
|
||||
|
||||
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
|
||||
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
|
||||
//setColor(moves.details.steps,goals.move_steps,childDevice)
|
||||
}
|
||||
catch (e) {
|
||||
// eat it
|
||||
}
|
||||
def responseGoals(response, dni) {
|
||||
if (response.hasError()) {
|
||||
log.error "response has error: $response.errorMessage"
|
||||
} else {
|
||||
def goals
|
||||
try {
|
||||
// json response already parsed into JSONElement object
|
||||
goals = response.json.data
|
||||
} catch (e) {
|
||||
log.error "error parsing json from response: $e"
|
||||
}
|
||||
if (goals) {
|
||||
def childDevice = getChildDevice(dni.value)
|
||||
log.debug "Goal = ${goals.move_steps} Steps"
|
||||
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
|
||||
} else {
|
||||
log.debug "did not get json results from response body: $response.data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def generateInitialEvent (member, childDevice) {
|
||||
// lets figure out if the member is currently "home" (At the place)
|
||||
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
|
||||
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
|
||||
def urlsleeps = "https://jawbone.com/nudge/api/users/@me/sleeps"
|
||||
def goals = null
|
||||
def moves = null
|
||||
def sleeps = null
|
||||
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||
goals = response.data.data
|
||||
}
|
||||
httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||
moves = response.data.data.items[0]
|
||||
}
|
||||
|
||||
try { // we are going to just ignore any errors
|
||||
log.debug "Member = ${member.first}"
|
||||
log.debug "Moves Goal = ${goals.move_steps} Steps"
|
||||
log.debug "Moves = ${moves.details.steps} Steps"
|
||||
log.debug "Sleeping state = false"
|
||||
childDevice?.generateSleepingEvent(false)
|
||||
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
|
||||
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
|
||||
//setColor(moves.details.steps,goals.move_steps,childDevice)
|
||||
}
|
||||
catch (e) {
|
||||
// eat it
|
||||
}
|
||||
def responseMoves(response, dni) {
|
||||
if (response.hasError()) {
|
||||
log.error "response has error: $response.errorMessage"
|
||||
} else {
|
||||
def moves
|
||||
try {
|
||||
// json response already parsed into JSONElement object
|
||||
moves = response.json.data.items[0]
|
||||
} catch (e) {
|
||||
log.error "error parsing json from response: $e"
|
||||
}
|
||||
if (moves) {
|
||||
def childDevice = getChildDevice(dni.value)
|
||||
log.debug "Moves = ${moves.details.steps} Steps"
|
||||
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
|
||||
} else {
|
||||
log.debug "did not get json results from response body: $response.data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def setColor (steps,goal,childDevice) {
|
||||
@@ -433,7 +437,7 @@ def hookEventHandler() {
|
||||
// get some stuff we need
|
||||
def userId = json.events.user_xid[0]
|
||||
def json_type = json.events.type[0]
|
||||
def json_action = json.events.action[0]
|
||||
def json_action = json.events.action[0]
|
||||
|
||||
//log.debug json
|
||||
log.debug "Userid = ${userId}"
|
||||
|
||||
@@ -454,17 +454,23 @@ def sendStopEvent(source) {
|
||||
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)
|
||||
sendTimeRemainingEvent(0)
|
||||
}
|
||||
|
||||
def sendTimeRemainingEvent(percentComplete) {
|
||||
def sendTimeRemainingEvent(percentComplete, displayed = true) {
|
||||
log.trace "sendTimeRemainingEvent(${percentComplete})"
|
||||
|
||||
def percentCompleteEventData = [
|
||||
name: "percentComplete",
|
||||
value: percentComplete as int,
|
||||
displayed: true,
|
||||
displayed: displayed,
|
||||
isStateChange: true
|
||||
]
|
||||
sendControllerEvent(percentCompleteEventData)
|
||||
@@ -474,7 +480,7 @@ def sendTimeRemainingEvent(percentComplete) {
|
||||
def timeRemainingEventData = [
|
||||
name: "timeRemaining",
|
||||
value: displayableTime(timeRemaining),
|
||||
displayed: true,
|
||||
displayed: displayed,
|
||||
isStateChange: true
|
||||
]
|
||||
sendControllerEvent(timeRemainingEventData)
|
||||
@@ -608,8 +614,6 @@ private completion() {
|
||||
handleCompletionMessaging()
|
||||
|
||||
handleCompletionModesAndPhrases()
|
||||
|
||||
sendTimeRemainingEvent(100)
|
||||
}
|
||||
|
||||
private handleCompletionSwitches() {
|
||||
|
||||
@@ -83,7 +83,7 @@ def bridgeDiscovery(params=[:])
|
||||
|
||||
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.") {
|
||||
input "selectedHue", "enum", required:false, title:"Select Hue Bridge (${numFound} found)", multiple:false, options:options
|
||||
input "selectedHue", "enum", required:false, title:"Select Hue Bridge (${numFound} found)", multiple:false, options:options, submitOnChange: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,10 +131,7 @@ def bulbDiscovery() {
|
||||
def refreshInterval = 3
|
||||
state.inBulbDiscovery = true
|
||||
def bridge = null
|
||||
if (selectedHue) {
|
||||
bridge = getChildDevice(selectedHue)
|
||||
subscribe(bridge, "bulbList", bulbListData)
|
||||
}
|
||||
|
||||
state.bridgeRefreshCount = 0
|
||||
def allLightsFound = bulbsDiscovered() ?: [:]
|
||||
|
||||
@@ -259,10 +256,6 @@ Map bulbsDiscovered() {
|
||||
return bulbmap
|
||||
}
|
||||
|
||||
def bulbListData(evt) {
|
||||
state.bulbs = evt.jsonData
|
||||
}
|
||||
|
||||
Map getHueBulbs() {
|
||||
state.bulbs = state.bulbs ?: [:]
|
||||
}
|
||||
@@ -316,29 +309,6 @@ def uninstalled(){
|
||||
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) {
|
||||
def deviceType = getDeviceType(newHueType)
|
||||
|
||||
@@ -490,24 +460,25 @@ def ssdpBridgeHandler(evt) {
|
||||
def host = ip + ":" + port
|
||||
log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host."
|
||||
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
|
||||
def dni = "${parsedEvent.mac}"
|
||||
def d = getChildDevice(dni)
|
||||
def dniReceived = "${parsedEvent.mac}"
|
||||
def currentDni = dstate.mac
|
||||
def d = getChildDevice(dniReceived)
|
||||
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 (oldDNI == selectedHue) {
|
||||
app.updateSetting("selectedHue", newDNI)
|
||||
}
|
||||
doDeviceSync()
|
||||
}
|
||||
// There might be a mismatch between bridge DNI and the actual bridge mac address, correct that
|
||||
log.debug "Bridge with $dniReceived not found"
|
||||
def bridge = childDevices.find { it.deviceNetworkId == currentDni }
|
||||
if (bridge != null) {
|
||||
log.warn "Bridge is set to ${bridge.deviceNetworkId}, updating to $dniReceived"
|
||||
bridge.setDeviceNetworkId("${dniReceived}")
|
||||
dstate.mac = 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)
|
||||
}
|
||||
doDeviceSync()
|
||||
}
|
||||
} else {
|
||||
updateBridgeStatus(d)
|
||||
@@ -525,6 +496,18 @@ def ssdpBridgeHandler(evt) {
|
||||
d.sendEvent(name:"networkAddress", value: 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -557,11 +540,8 @@ void lightsHandler(physicalgraph.device.HubResponse hubResponse) {
|
||||
if (isValidSource(hubResponse.mac)) {
|
||||
def body = hubResponse.json
|
||||
if (!body?.state?.on) { //check if first time poll made it here by mistake
|
||||
def bulbs = getHueBulbs()
|
||||
log.debug "Adding bulbs to state!"
|
||||
body.each { k, v ->
|
||||
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub: hubResponse.hubId]
|
||||
}
|
||||
updateBulbState(body, hubResponse.hubId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -677,11 +657,8 @@ def locationHandler(evt) {
|
||||
} else {
|
||||
//GET /api/${state.username}/lights response (application/json)
|
||||
if (!body?.state?.on) { //check if first time poll made it here by mistake
|
||||
def bulbs = getHueBulbs()
|
||||
log.debug "Adding bulbs to state!"
|
||||
body.each { k,v ->
|
||||
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:parsedEvent.hub]
|
||||
}
|
||||
updateBulbState(body, parsedEvent.hub)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -741,7 +718,7 @@ private void checkBridgeStatus() {
|
||||
}
|
||||
|
||||
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
|
||||
log.warn "Bridge $it.key is Offline"
|
||||
log.warn "Bridge $it.value.idNumber is Offline"
|
||||
d.sendEvent(name: "status", value: "Offline")
|
||||
|
||||
state.bulbs?.each {
|
||||
@@ -766,6 +743,31 @@ def isInBulbDiscovery() {
|
||||
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
|
||||
/////////////////////////////////////
|
||||
@@ -950,6 +952,14 @@ private handleCommandResponse(body) {
|
||||
* @return empty array
|
||||
*/
|
||||
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()
|
||||
for (bulb in body) {
|
||||
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
|
||||
@@ -959,7 +969,10 @@ private handlePoll(body) {
|
||||
// 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)
|
||||
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
|
||||
@@ -969,9 +982,18 @@ 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)
|
||||
}
|
||||
} else {
|
||||
state.bulbs[bulb.key]?.online = false
|
||||
log.warn "$device is not reachable by Hue bridge"
|
||||
device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true)
|
||||
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) {
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1006,9 +1028,6 @@ def hubVerification(bodytext) {
|
||||
def on(childDevice) {
|
||||
log.debug "Executing 'on'"
|
||||
def id = getId(childDevice)
|
||||
if (!isOnline(id)) {
|
||||
return "Bulb is unreachable"
|
||||
}
|
||||
updateInProgress()
|
||||
createSwitchEvent(childDevice, "on")
|
||||
put("lights/$id/state", [on: true])
|
||||
@@ -1018,9 +1037,6 @@ def on(childDevice) {
|
||||
def off(childDevice) {
|
||||
log.debug "Executing 'off'"
|
||||
def id = getId(childDevice)
|
||||
if (!isOnline(id)) {
|
||||
return "Bulb is unreachable"
|
||||
}
|
||||
updateInProgress()
|
||||
createSwitchEvent(childDevice, "off")
|
||||
put("lights/$id/state", [on: false])
|
||||
@@ -1030,9 +1046,6 @@ def off(childDevice) {
|
||||
def setLevel(childDevice, percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
def id = getId(childDevice)
|
||||
if (!isOnline(id)) {
|
||||
return "Bulb is unreachable"
|
||||
}
|
||||
updateInProgress()
|
||||
// 1 - 254
|
||||
def level
|
||||
@@ -1057,10 +1070,6 @@ def setLevel(childDevice, percent) {
|
||||
def setSaturation(childDevice, percent) {
|
||||
log.debug "Executing 'setSaturation($percent)'"
|
||||
def id = getId(childDevice)
|
||||
if (!isOnline(id)) {
|
||||
return "Bulb is unreachable"
|
||||
}
|
||||
|
||||
updateInProgress()
|
||||
// 0 - 254
|
||||
def level = Math.min(Math.round(percent * 254 / 100), 254)
|
||||
@@ -1073,9 +1082,6 @@ def setSaturation(childDevice, percent) {
|
||||
def setHue(childDevice, percent) {
|
||||
log.debug "Executing 'setHue($percent)'"
|
||||
def id = getId(childDevice)
|
||||
if (!isOnline(id)) {
|
||||
return "Bulb is unreachable"
|
||||
}
|
||||
updateInProgress()
|
||||
// 0 - 65535
|
||||
def level = Math.min(Math.round(percent * 65535 / 100), 65535)
|
||||
@@ -1088,9 +1094,6 @@ def setHue(childDevice, percent) {
|
||||
def setColorTemperature(childDevice, huesettings) {
|
||||
log.debug "Executing 'setColorTemperature($huesettings)'"
|
||||
def id = getId(childDevice)
|
||||
if (!isOnline(id)) {
|
||||
return "Bulb is unreachable"
|
||||
}
|
||||
updateInProgress()
|
||||
// 153 (6500K) to 500 (2000K)
|
||||
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
|
||||
@@ -1102,9 +1105,6 @@ def setColorTemperature(childDevice, huesettings) {
|
||||
def setColor(childDevice, huesettings) {
|
||||
log.debug "Executing 'setColor($huesettings)'"
|
||||
def id = getId(childDevice)
|
||||
if (!isOnline(id)) {
|
||||
return "Bulb is unreachable"
|
||||
}
|
||||
updateInProgress()
|
||||
|
||||
def value = [:]
|
||||
@@ -1120,7 +1120,7 @@ def setColor(childDevice, huesettings) {
|
||||
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
||||
if (huesettings.saturation != null)
|
||||
value.sat = Math.min(Math.round(huesettings.saturation * 254 / 100), 254)
|
||||
} else if (huesettings.hex != null && false) {
|
||||
} else if (huesettings.hex != null) {
|
||||
// For now ignore model to get a consistent color if same color is set across multiple devices
|
||||
// def model = state.bulbs[getId(childDevice)]?.modelid
|
||||
// value.xy = calculateXY(huesettings.hex, model)
|
||||
@@ -1162,7 +1162,14 @@ def setColor(childDevice, huesettings) {
|
||||
}
|
||||
|
||||
def ping(childDevice) {
|
||||
if (isOnline(getId(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 {
|
||||
@@ -1224,7 +1231,7 @@ private getBridgeIP() {
|
||||
if (d) {
|
||||
if (d.getDeviceDataByName("networkAddress"))
|
||||
host = d.getDeviceDataByName("networkAddress")
|
||||
else
|
||||
else
|
||||
host = d.latestState('networkAddress').stringValue
|
||||
}
|
||||
if (host == null || host == "") {
|
||||
@@ -1663,7 +1670,7 @@ private boolean checkPointInLampsReach(p, colorPoints) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an RGB color in hex to HSV.
|
||||
* Converts an RGB color in hex to HSV/HSB.
|
||||
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
|
||||
*
|
||||
* @param colorStr color value in hex (#ff03d3)
|
||||
@@ -1673,32 +1680,32 @@ private boolean checkPointInLampsReach(p, colorPoints) {
|
||||
def hexToHsv(colorStr){
|
||||
def r = Integer.valueOf( colorStr.substring( 1, 3 ), 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 min = Math.min(Math.min(r, g), b)
|
||||
|
||||
def h, s, v = max;
|
||||
def h, s, v = max
|
||||
|
||||
def d = max - min;
|
||||
s = max == 0 ? 0 : d / max;
|
||||
def d = max - min
|
||||
s = max == 0 ? 0 : d / max
|
||||
|
||||
if(max == min){
|
||||
h = 0;
|
||||
h = 0
|
||||
}else{
|
||||
switch(max){
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break
|
||||
case g: h = (b - r) / d + 2; break
|
||||
case b: h = (r - g) / d + 4; break
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return [(h * 100).round(), (s * 100).round(), (v * 100).round()];
|
||||
return [Math.round(h * 100), Math.round(s * 100), Math.round(v * 100)]
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts HSV color to RGB in hex.
|
||||
* Converts HSV/HSB color to RGB in hex.
|
||||
* Algorithm based on http://en.wikipedia.org/wiki/HSV_color_space.
|
||||
*
|
||||
* @param hue hue 0-100
|
||||
@@ -1713,11 +1720,11 @@ def hsvToHex(hue, sat, value = 100){
|
||||
def s = sat / 100
|
||||
def v = value / 100
|
||||
|
||||
def i = Math.floor(h * 6);
|
||||
def f = h * 6 - i;
|
||||
def p = v * (1 - s);
|
||||
def q = v * (1 - f * s);
|
||||
def t = v * (1 - (1 - f) * s);
|
||||
def i = Math.floor(h * 6)
|
||||
def f = h * 6 - i
|
||||
def p = v * (1 - s)
|
||||
def q = v * (1 - f * s)
|
||||
def t = v * (1 - (1 - f) * s)
|
||||
|
||||
switch (i % 6) {
|
||||
case 0:
|
||||
@@ -1753,9 +1760,9 @@ def hsvToHex(hue, sat, value = 100){
|
||||
}
|
||||
|
||||
// Converting float components to int components.
|
||||
def r1 = String.format("%02X", (int) (r * 255.0f));
|
||||
def g1 = String.format("%02X", (int) (g * 255.0f));
|
||||
def b1 = String.format("%02X", (int) (b * 255.0f));
|
||||
def r1 = String.format("%02X", (int) (r * 255.0f))
|
||||
def g1 = String.format("%02X", (int) (g * 255.0f))
|
||||
def b1 = String.format("%02X", (int) (b * 255.0f))
|
||||
|
||||
return "#$r1$g1$b1"
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
* locks | lock | lock, unlock | locked, unlocked
|
||||
* ---------------------+-------------------+-----------------------------+------------------------------------
|
||||
*/
|
||||
include 'asynchttp_v1'
|
||||
|
||||
definition(
|
||||
name: "Logitech Harmony (Connect)",
|
||||
@@ -109,26 +110,28 @@ def authPage() {
|
||||
//device discovery request every 5 //25 seconds
|
||||
int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int
|
||||
state.deviceRefreshCount = deviceRefreshCount + 1
|
||||
def refreshInterval = 3
|
||||
def refreshInterval = 5
|
||||
|
||||
def huboptions = state.HarmonyHubs ?: []
|
||||
def actoptions = state.HarmonyActivities ?: []
|
||||
|
||||
def numFoundHub = huboptions.size() ?: 0
|
||||
def numFoundAct = actoptions.size() ?: 0
|
||||
def numFoundAct = actoptions.size() ?: 0
|
||||
|
||||
if((deviceRefreshCount % 5) == 0) {
|
||||
discoverDevices()
|
||||
}
|
||||
|
||||
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.") {
|
||||
input "selectedhubs", "enum", required:false, title:"Select Harmony Hubs (${numFoundHub} found)", multiple:true, options:huboptions
|
||||
input "selectedhubs", "enum", required:false, title:"Select Harmony Hubs (${numFoundHub} found)", multiple:true, submitOnChange: true, options:huboptions
|
||||
}
|
||||
// Virtual activity flag
|
||||
if (numFoundHub > 0 && numFoundAct > 0 && true)
|
||||
// Virtual activity flag
|
||||
if (numFoundHub > 0 && numFoundAct > 0 && true)
|
||||
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, options:actoptions
|
||||
input "selectedactivities", "enum", required:false, title:"Select Harmony Activities (${numFoundAct} found)", multiple:true, submitOnChange: true, options:actoptions
|
||||
}
|
||||
if (state.resethub)
|
||||
if (state.resethub)
|
||||
section("Connection to the hub timed out. Please restart the hub and try again.") {}
|
||||
}
|
||||
}
|
||||
@@ -380,8 +383,6 @@ def discovery() {
|
||||
log.debug "valid Token"
|
||||
state.Harmonydevices = response.data
|
||||
state.resethub = false
|
||||
getActivityList()
|
||||
poll()
|
||||
} else {
|
||||
log.debug "Error: $response.status"
|
||||
}
|
||||
@@ -430,142 +431,182 @@ def addDevice() {
|
||||
}
|
||||
|
||||
def activity(dni,mode) {
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def msg = "Command failed"
|
||||
def url = ''
|
||||
def tokenParam = [auth: state.HarmonyAccessToken]
|
||||
def url
|
||||
if (dni == "all") {
|
||||
url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(Params)}"
|
||||
url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(tokenParam)}"
|
||||
} else {
|
||||
def aux = dni.split('-')
|
||||
def hubId = aux[1]
|
||||
if (mode == "hub" || (aux.size() <= 2) || (aux[2] == "off")){
|
||||
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(Params)}"
|
||||
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(tokenParam)}"
|
||||
} else {
|
||||
def activityId = aux[2]
|
||||
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/${activityId}/${mode}?${toQueryString(Params)}"
|
||||
def activityId = aux[2]
|
||||
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/${activityId}/${mode}?${toQueryString(tokenParam)}"
|
||||
}
|
||||
}
|
||||
try {
|
||||
httpPostJson(uri: url) { response ->
|
||||
if (response.data.code == 200 || dni == "all") {
|
||||
msg = "Command sent succesfully"
|
||||
state.aux = 0
|
||||
} else {
|
||||
msg = "Command failed. Error: $response.data.code"
|
||||
}
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException ex) {
|
||||
log.error ex
|
||||
if (state.aux == 0) {
|
||||
state.aux = 1
|
||||
activity(dni,mode)
|
||||
} else {
|
||||
msg = ex
|
||||
state.aux = 0
|
||||
}
|
||||
} catch(Exception ex) {
|
||||
msg = ex
|
||||
def params = [
|
||||
uri: url,
|
||||
contentType: 'application/json'
|
||||
]
|
||||
asynchttp_v1.post('activityResponse', params)
|
||||
return "Command Sent"
|
||||
}
|
||||
|
||||
def activityResponse(response, data) {
|
||||
if (response.hasError()) {
|
||||
log.error "Logitech Harmony - response has error: $response.errorMessage"
|
||||
if (response.status == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Logitech Harmony - Access token has expired"
|
||||
}
|
||||
runIn(10, "poll", [overwrite: true])
|
||||
return msg
|
||||
} else {
|
||||
def ResponseValues
|
||||
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() {
|
||||
// GET THE LIST OF ACTIVITIES
|
||||
if (state.HarmonyAccessToken) {
|
||||
getActivityList()
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}"
|
||||
try {
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
def map = [:]
|
||||
response.data.hubs.each {
|
||||
if (it.value.message == "OK") {
|
||||
map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}"
|
||||
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)
|
||||
} else {
|
||||
def currentActivity = getActivityName(it.value.response.data.currentAvActivity,it.key)
|
||||
hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.trace 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])
|
||||
}
|
||||
}
|
||||
def tokenParam = [auth: state.HarmonyAccessToken]
|
||||
def params = [
|
||||
uri: "https://home.myharmony.com/cloudapi/state?${toQueryString(tokenParam)}",
|
||||
headers: ["Accept": "application/json"],
|
||||
contentType: 'application/json'
|
||||
]
|
||||
asynchttp_v1.get('pollResponse', params)
|
||||
} else {
|
||||
log.warn "Logitech Harmony - Access token has expired"
|
||||
}
|
||||
}
|
||||
|
||||
def pollResponse(response, data) {
|
||||
if (response.hasError()) {
|
||||
log.error "Logitech Harmony - response has error: $response.errorMessage"
|
||||
if (response.status == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Logitech Harmony - Access token has expired"
|
||||
}
|
||||
} else {
|
||||
def ResponseValues
|
||||
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) {
|
||||
def map = [:]
|
||||
ResponseValues.hubs.each {
|
||||
if (it.value.message == "OK") {
|
||||
map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}"
|
||||
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)
|
||||
} else {
|
||||
def currentActivity = getChildDevice("harmony-${it.key}-${it.value.response.data.currentAvActivity}").device.displayName
|
||||
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 getActivityList() {
|
||||
// GET ACTIVITY'S NAME
|
||||
if (state.HarmonyAccessToken) {
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}"
|
||||
try {
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
response.data.hubs.each {
|
||||
def hub = getChildDevice("harmony-${it.key}")
|
||||
if (hub) {
|
||||
def hubname = getHubName("${it.key}")
|
||||
def activities = []
|
||||
def aux = it.value.response.data.activities.size()
|
||||
if (aux >= 1) {
|
||||
activities = it.value.response.data.activities.collect {
|
||||
[id: it.key, name: it.value['name'], type: it.value['type']]
|
||||
}
|
||||
activities += [id: "off", name: "Activity OFF", type: "0"]
|
||||
log.trace activities
|
||||
}
|
||||
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
log.trace e
|
||||
} catch (java.net.SocketTimeoutException e) {
|
||||
log.trace e
|
||||
} catch(Exception e) {
|
||||
log.trace e
|
||||
}
|
||||
}
|
||||
return activity
|
||||
def activityListResponse(response, data) {
|
||||
if (response.hasError()) {
|
||||
log.error "Logitech Harmony - response has error: $response.errorMessage"
|
||||
if (response.status == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Logitech Harmony - Access token has expired"
|
||||
}
|
||||
} else {
|
||||
def ResponseValues
|
||||
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) {
|
||||
ResponseValues.hubs.each {
|
||||
def hub = getChildDevice("harmony-${it.key}")
|
||||
if (hub) {
|
||||
def hubname = getHubName("${it.key}")
|
||||
def activities = []
|
||||
def aux = it.value.response?.data.activities.size()
|
||||
if (aux >= 1) {
|
||||
activities = it.value.response.data.activities.collect {
|
||||
[id: it.key, name: it.value['name'], type: it.value['type']]
|
||||
}
|
||||
activities += [id: "off", name: "Activity OFF", type: "0"]
|
||||
log.trace activities
|
||||
}
|
||||
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.debug "Logitech Harmony - did not get json results from response body: $response.data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getActivityName(activity,hubId) {
|
||||
@@ -746,7 +787,7 @@ def addSubscription() {
|
||||
def attribute = data.attributeName
|
||||
def callbackUrl = data.callbackUrl
|
||||
|
||||
log.debug "addSubscription, params: ${params}, request: ${data}"
|
||||
log.debug "Logitech Harmony - addSubscription, params: ${params}, request: ${data}"
|
||||
if (!attribute) {
|
||||
render status: 400, data: '{"msg": "attributeName is required"}'
|
||||
} else {
|
||||
@@ -808,6 +849,7 @@ def deviceHandler(evt) {
|
||||
def deviceInfo = state[evt.deviceId]
|
||||
if (state.harmonyHubs) {
|
||||
state.harmonyHubs.each { harmonyHub ->
|
||||
log.trace "Logitech Harmony - Sending data to $harmonyHub.name"
|
||||
sendToHarmony(evt, harmonyHub.callbackUrl)
|
||||
}
|
||||
} else if (deviceInfo) {
|
||||
|
||||
@@ -26,17 +26,22 @@ definition(
|
||||
)
|
||||
|
||||
preferences {
|
||||
section ("In addition to push notifications, send text alerts to...") {
|
||||
input("recipients", "contact", title: "Send notifications to") {
|
||||
input "phone1", "phone", title: "Phone Number 1", required: false
|
||||
input "phone2", "phone", title: "Phone Number 2", required: false
|
||||
input "phone3", "phone", title: "Phone Number 3", required: false
|
||||
}
|
||||
|
||||
if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') {
|
||||
section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." }
|
||||
}
|
||||
|
||||
section ("Zip code (optional, defaults to location coordinates)...") {
|
||||
input "zipcode", "text", title: "Zip Code", required: false
|
||||
}
|
||||
if (location.channelName != 'samsungtv') {
|
||||
section( "Set your location" ) { input "zipCode", "text", title: "Zip code" }
|
||||
}
|
||||
|
||||
section ("In addition to push notifications, send text alerts to...") {
|
||||
input("recipients", "contact", title: "Send notifications to") {
|
||||
input "phone1", "phone", title: "Phone Number 1", required: false
|
||||
input "phone2", "phone", title: "Phone Number 2", required: false
|
||||
input "phone3", "phone", title: "Phone Number 3", required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
@@ -61,7 +66,7 @@ def checkForSevereWeather() {
|
||||
def alerts
|
||||
if(locationIsDefined()) {
|
||||
if(zipcodeIsValid()) {
|
||||
alerts = getWeatherFeature("alerts", zipcode)?.alerts
|
||||
alerts = getWeatherFeature("alerts", zipCode)?.alerts
|
||||
} else {
|
||||
log.warn "Severe Weather Alert: Invalid zipcode entered, defaulting to location's zipcode"
|
||||
alerts = getWeatherFeature("alerts")?.alerts
|
||||
|
||||
Reference in New Issue
Block a user