mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 05:11:52 +00:00
Compare commits
48 Commits
MSA-1296-1
...
MSA-1357-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e3683c5ad | ||
|
|
eac48382e8 | ||
|
|
9f09a4b0b2 | ||
|
|
3c47fe7b60 | ||
|
|
45a0822e9b | ||
|
|
8cc87f3858 | ||
|
|
e818695947 | ||
|
|
bbba20288e | ||
|
|
ff9dd3f6e2 | ||
|
|
a94a62d34c | ||
|
|
e861d3c256 | ||
|
|
7adff88d0f | ||
|
|
ad1f1b2dc9 | ||
|
|
8d8b039dda | ||
|
|
c875547942 | ||
|
|
1676a9c381 | ||
|
|
49d293e749 | ||
|
|
5d1b033486 | ||
|
|
31f77513da | ||
|
|
98d7829d1a | ||
|
|
c6f706e47a | ||
|
|
532afd7336 | ||
|
|
ac7f1a0c66 | ||
|
|
cb26f055d7 | ||
|
|
34107f935e | ||
|
|
cc2d19e951 | ||
|
|
031a15ec86 | ||
|
|
fc2db2575d | ||
|
|
fd549631e6 | ||
|
|
417c246d61 | ||
|
|
038d770691 | ||
|
|
ce28ec2039 | ||
|
|
0f1781c02e | ||
|
|
4ce6ee0890 | ||
|
|
f8050a5cd5 | ||
|
|
d44dac448b | ||
|
|
67c20abcba | ||
|
|
d56e132896 | ||
|
|
009ec2539d | ||
|
|
ff0860cbe1 | ||
|
|
ecfb99974b | ||
|
|
aa3a18f421 | ||
|
|
c2f18a91be | ||
|
|
0c1208928f | ||
|
|
02d9963fab | ||
|
|
32f0385e30 | ||
|
|
ab2ba8104d | ||
|
|
d17cadc4c7 |
600
devicetypes/smartthings-users/nest.src/nest.groovy
Normal file
600
devicetypes/smartthings-users/nest.src/nest.groovy
Normal file
@@ -0,0 +1,600 @@
|
||||
/**
|
||||
* Nest Direct
|
||||
*
|
||||
* Author: dianoga7@3dgo.net
|
||||
* Code: https://github.com/smartthings-users/device-type.nest
|
||||
*
|
||||
* INSTALLATION
|
||||
* =========================================
|
||||
* 1) Create a new device type (https://graph.api.smartthings.com/ide/devices)
|
||||
* Name: Nest
|
||||
* Author: dianoga7@3dgo.net
|
||||
* Capabilities:
|
||||
* Polling
|
||||
* Relative Humidity Measurement
|
||||
* Thermostat
|
||||
* Temperature Measurement
|
||||
* Presence Sensor
|
||||
* Sensor
|
||||
* Custom Attributes:
|
||||
* temperatureUnit
|
||||
* Custom Commands:
|
||||
* away
|
||||
* present
|
||||
* setPresence
|
||||
* heatingSetpointUp
|
||||
* heatingSetpointDown
|
||||
* coolingSetpointUp
|
||||
* coolingSetpointDown
|
||||
* setFahrenheit
|
||||
* setCelsius
|
||||
*
|
||||
* 2) If you want to switch from slider controls to buttons, comment out the slider details line and uncomment the button details line.
|
||||
*
|
||||
* 3) Create a new device (https://graph.api.smartthings.com/device/list)
|
||||
* Name: Your Choice
|
||||
* Device Network Id: Your Choice
|
||||
* Type: Nest (should be the last option)
|
||||
* Location: Choose the correct location
|
||||
* Hub/Group: Leave blank
|
||||
*
|
||||
* 4) Update device preferences
|
||||
* Click on the new device to see the details.
|
||||
* Click the edit button next to Preferences
|
||||
* Fill in your information.
|
||||
* To find your serial number, login to http://home.nest.com. Click on the thermostat
|
||||
* you want to control. Under settings, go to Technical Info. Your serial number is
|
||||
* the second item.
|
||||
*
|
||||
* 5) That's it, you're done.
|
||||
*
|
||||
* Copyright (C) 2013 Brian Steere <dianoga7@3dgo.net>
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
* software and associated documentation files (the "Software"), to deal in the Software
|
||||
* without restriction, including without limitation the rights to use, copy, modify,
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following
|
||||
* conditions: The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
preferences {
|
||||
input("username", "text", title: "Username", description: "Your Nest username (usually an email address)")
|
||||
input("password", "password", title: "Password", description: "Your Nest password")
|
||||
input("serial", "text", title: "Serial #", description: "The serial number of your thermostat")
|
||||
}
|
||||
|
||||
// for the UI
|
||||
metadata {
|
||||
definition (name: "Nest", namespace: "smartthings-users", author: "dianoga7@3dgo.net") {
|
||||
capability "Polling"
|
||||
capability "Relative Humidity Measurement"
|
||||
capability "Thermostat"
|
||||
capability "Temperature Measurement"
|
||||
capability "Presence Sensor"
|
||||
capability "Sensor"
|
||||
|
||||
command "away"
|
||||
command "present"
|
||||
command "setPresence"
|
||||
command "heatingSetpointUp"
|
||||
command "heatingSetpointDown"
|
||||
command "coolingSetpointUp"
|
||||
command "coolingSetpointDown"
|
||||
command "setFahrenheit"
|
||||
command "setCelsius"
|
||||
|
||||
attribute "temperatureUnit", "string"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles {
|
||||
valueTile("temperature", "device.temperature", canChangeIcon: true, canChangeBackground:true) {
|
||||
state("temperature", label: '${currentValue}°', backgroundColors: [
|
||||
// Celsius Color Range
|
||||
[value: 0, color: "#153591"],
|
||||
[value: 7, color: "#1e9cbb"],
|
||||
[value: 15, color: "#90d2a7"],
|
||||
[value: 23, color: "#44b621"],
|
||||
[value: 29, color: "#f1d801"],
|
||||
[value: 33, color: "#d04e00"],
|
||||
[value: 36, color: "#bc2323"],
|
||||
// Fahrenheit Color Range
|
||||
[value: 40, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 92, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: true, decoration: "flat") {
|
||||
state("auto", action:"thermostat.off", icon: "st.thermostat.auto")
|
||||
state("off", action:"thermostat.cool", icon: "st.thermostat.heating-cooling-off")
|
||||
state("cool", action:"thermostat.heat", icon: "st.thermostat.cool")
|
||||
state("heat", action:"thermostat.auto", icon: "st.thermostat.heat")
|
||||
}
|
||||
|
||||
standardTile("thermostatFanMode", "device.thermostatFanMode", inactiveLabel: true, decoration: "flat") {
|
||||
state "auto", action:"thermostat.fanOn", icon: "st.thermostat.fan-auto"
|
||||
state "on", action:"thermostat.fanCirculate", icon: "st.thermostat.fan-on"
|
||||
state "circulate", action:"thermostat.fanAuto", icon: "st.thermostat.fan-circulate"
|
||||
}
|
||||
|
||||
valueTile("heatingSetpoint", "device.heatingSetpoint") {
|
||||
state "default", label:'${currentValue}°', unit:"Heat", backgroundColor:"#bc2323"
|
||||
}
|
||||
|
||||
valueTile("coolingSetpoint", "device.coolingSetpoint") {
|
||||
state "default", label:'${currentValue}°', unit:"Cool", backgroundColor:"#1e9cbb"
|
||||
}
|
||||
|
||||
standardTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false, decoration: "flat") {
|
||||
state "idle", action:"polling.poll", label:'${name}', icon: "st.sonos.pause-icon"
|
||||
state "cooling", action:"polling.poll", label:' ', icon: "st.thermostat.cooling", backgroundColor:"#1e9cbb"
|
||||
state "heating", action:"polling.poll", label:' ', icon: "st.thermostat.heating", backgroundColor:"#bc2323"
|
||||
state "fan only", action:"polling.poll", label:'${name}', icon: "st.Appliances.appliances11"
|
||||
}
|
||||
|
||||
valueTile("humidity", "device.humidity", inactiveLabel: false) {
|
||||
state "default", label:'${currentValue}%', unit:"Humidity"
|
||||
}
|
||||
|
||||
standardTile("presence", "device.presence", inactiveLabel: false, decoration: "flat") {
|
||||
state "present", label:'${name}', action:"away", icon: "st.Home.home2"
|
||||
state "not present", label:'away', action:"present", icon: "st.Transportation.transportation5"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", action:"polling.poll", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
standardTile("temperatureUnit", "device.temperatureUnit", canChangeIcon: false) {
|
||||
state "fahrenheit", label: "°F", icon: "st.alarm.temperature.normal", action:"setCelsius"
|
||||
state "celsius", label: "°C", icon: "st.alarm.temperature.normal", action:"setFahrenheit"
|
||||
}
|
||||
|
||||
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
|
||||
state "setHeatingSetpoint", label:'Set temperature to', action:"thermostat.setHeatingSetpoint"
|
||||
}
|
||||
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
|
||||
state "setCoolingSetpoint", label:'Set temperature to', action:"thermostat.setCoolingSetpoint"
|
||||
}
|
||||
|
||||
standardTile("heatingSetpointUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") {
|
||||
state "heatingSetpointUp", label:' ', action:"heatingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#bc2323"
|
||||
}
|
||||
|
||||
standardTile("heatingSetpointDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") {
|
||||
state "heatingSetpointDown", label:' ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#bc2323"
|
||||
}
|
||||
|
||||
standardTile("coolingSetpointUp", "device.coolingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") {
|
||||
state "coolingSetpointUp", label:' ', action:"coolingSetpointUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#1e9cbb"
|
||||
}
|
||||
|
||||
standardTile("coolingSetpointDown", "device.coolingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") {
|
||||
state "coolingSetpointDown", label:' ', action:"coolingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#1e9cbb"
|
||||
}
|
||||
|
||||
main(["temperature", "thermostatOperatingState", "humidity"])
|
||||
|
||||
// ============================================================
|
||||
// Slider or Buttons...
|
||||
// To expose buttons, comment out the first detials line below and uncomment the second details line below.
|
||||
// To expose sliders, uncomment the first details line below and comment out the second details line below.
|
||||
|
||||
details(["temperature", "thermostatOperatingState", "humidity", "thermostatMode", "thermostatFanMode", "presence", "heatingSetpoint", "heatSliderControl", "coolingSetpoint", "coolSliderControl", "temperatureUnit", "refresh"])
|
||||
// details(["temperature", "thermostatOperatingState", "humidity", "thermostatMode", "thermostatFanMode", "presence", "heatingSetpointDown", "heatingSetpoint", "heatingSetpointUp", "coolingSetpointDown", "coolingSetpoint", "coolingSetpointUp", "temperatureUnit", "refresh"])
|
||||
|
||||
// ============================================================
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
|
||||
}
|
||||
|
||||
// handle commands
|
||||
def setHeatingSetpoint(temp) {
|
||||
def latestThermostatMode = device.latestState('thermostatMode')
|
||||
def temperatureUnit = device.latestValue('temperatureUnit')
|
||||
|
||||
switch (temperatureUnit) {
|
||||
case "celsius":
|
||||
if (temp) {
|
||||
if (temp < 9) {
|
||||
temp = 9
|
||||
}
|
||||
if (temp > 32) {
|
||||
temp = 32
|
||||
}
|
||||
if (latestThermostatMode.stringValue == 'auto') {
|
||||
api('temperature', ['target_change_pending': true, 'target_temperature_low': temp]) {
|
||||
sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: temperatureUnit, state: "heat")
|
||||
}
|
||||
} else if (latestThermostatMode.stringValue == 'heat') {
|
||||
api('temperature', ['target_change_pending': true, 'target_temperature': temp]) {
|
||||
sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: temperatureUnit, state: "heat")
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (temp) {
|
||||
if (temp < 51) {
|
||||
temp = 51
|
||||
}
|
||||
if (temp > 89) {
|
||||
temp = 89
|
||||
}
|
||||
if (latestThermostatMode.stringValue == 'auto') {
|
||||
api('temperature', ['target_change_pending': true, 'target_temperature_low': fToC(temp)]) {
|
||||
sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: temperatureUnit, state: "heat")
|
||||
}
|
||||
} else if (latestThermostatMode.stringValue == 'heat') {
|
||||
api('temperature', ['target_change_pending': true, 'target_temperature': fToC(temp)]) {
|
||||
sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: temperatureUnit, state: "heat")
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
poll()
|
||||
}
|
||||
|
||||
def coolingSetpointUp(){
|
||||
int newSetpoint = device.currentValue("coolingSetpoint") + 1
|
||||
log.debug "Setting cool set point up to: ${newSetpoint}"
|
||||
setCoolingSetpoint(newSetpoint)
|
||||
}
|
||||
|
||||
def coolingSetpointDown(){
|
||||
int newSetpoint = device.currentValue("coolingSetpoint") - 1
|
||||
log.debug "Setting cool set point down to: ${newSetpoint}"
|
||||
setCoolingSetpoint(newSetpoint)
|
||||
}
|
||||
|
||||
def setCoolingSetpoint(temp) {
|
||||
def latestThermostatMode = device.latestState('thermostatMode')
|
||||
def temperatureUnit = device.latestValue('temperatureUnit')
|
||||
|
||||
switch (temperatureUnit) {
|
||||
case "celsius":
|
||||
if (temp) {
|
||||
if (temp < 9) {
|
||||
temp = 9
|
||||
}
|
||||
if (temp > 32) {
|
||||
temp = 32
|
||||
}
|
||||
if (latestThermostatMode.stringValue == 'auto') {
|
||||
api('temperature', ['target_change_pending': true, 'target_temperature_high': temp]) {
|
||||
sendEvent(name: 'coolingSetpoint', value: coolingSetpoint, unit: temperatureUnit, state: "cool")
|
||||
}
|
||||
} else if (latestThermostatMode.stringValue == 'cool') {
|
||||
api('temperature', ['target_change_pending': true, 'target_temperature': temp]) {
|
||||
sendEvent(name: 'coolingSetpoint', value: coolingSetpoint, unit: temperatureUnit, state: "cool")
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (temp) {
|
||||
if (temp < 51) {
|
||||
temp = 51
|
||||
}
|
||||
if (temp > 89) {
|
||||
temp = 89
|
||||
}
|
||||
if (latestThermostatMode.stringValue == 'auto') {
|
||||
api('temperature', ['target_change_pending': true, 'target_temperature_high': fToC(temp)]) {
|
||||
sendEvent(name: 'coolingSetpoint', value: coolingSetpoint, unit: temperatureUnit, state: "cool")
|
||||
}
|
||||
} else if (latestThermostatMode.stringValue == 'cool') {
|
||||
api('temperature', ['target_change_pending': true, 'target_temperature': fToC(temp)]) {
|
||||
sendEvent(name: 'coolingSetpoint', value: coolingSetpoint, unit: temperatureUnit, state: "cool")
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
poll()
|
||||
}
|
||||
|
||||
def heatingSetpointUp(){
|
||||
int newSetpoint = device.currentValue("heatingSetpoint") + 1
|
||||
log.debug "Setting heat set point up to: ${newSetpoint}"
|
||||
setHeatingSetpoint(newSetpoint)
|
||||
}
|
||||
|
||||
def heatingSetpointDown(){
|
||||
int newSetpoint = device.currentValue("heatingSetpoint") - 1
|
||||
log.debug "Setting heat set point down to: ${newSetpoint}"
|
||||
setHeatingSetpoint(newSetpoint)
|
||||
}
|
||||
|
||||
def setFahrenheit() {
|
||||
def temperatureUnit = "fahrenheit"
|
||||
log.debug "Setting temperatureUnit to: ${temperatureUnit}"
|
||||
sendEvent(name: "temperatureUnit", value: temperatureUnit)
|
||||
poll()
|
||||
}
|
||||
|
||||
def setCelsius() {
|
||||
def temperatureUnit = "celsius"
|
||||
log.debug "Setting temperatureUnit to: ${temperatureUnit}"
|
||||
sendEvent(name: "temperatureUnit", value: temperatureUnit)
|
||||
poll()
|
||||
}
|
||||
|
||||
def off() {
|
||||
setThermostatMode('off')
|
||||
}
|
||||
|
||||
def heat() {
|
||||
setThermostatMode('heat')
|
||||
}
|
||||
|
||||
def emergencyHeat() {
|
||||
setThermostatMode('heat')
|
||||
}
|
||||
|
||||
def cool() {
|
||||
setThermostatMode('cool')
|
||||
}
|
||||
|
||||
def auto() {
|
||||
setThermostatMode('range')
|
||||
}
|
||||
|
||||
def setThermostatMode(mode) {
|
||||
mode = mode == 'emergency heat'? 'heat' : mode
|
||||
|
||||
api('thermostat_mode', ['target_change_pending': true, 'target_temperature_type': mode]) {
|
||||
mode = mode == 'range' ? 'auto' : mode
|
||||
sendEvent(name: 'thermostatMode', value: mode)
|
||||
poll()
|
||||
}
|
||||
}
|
||||
|
||||
def fanOn() {
|
||||
setThermostatFanMode('on')
|
||||
}
|
||||
|
||||
def fanAuto() {
|
||||
setThermostatFanMode('auto')
|
||||
}
|
||||
|
||||
def fanCirculate() {
|
||||
setThermostatFanMode('circulate')
|
||||
}
|
||||
|
||||
def setThermostatFanMode(mode) {
|
||||
def modes = [
|
||||
on: ['fan_mode': 'on'],
|
||||
auto: ['fan_mode': 'auto'],
|
||||
circulate: ['fan_mode': 'duty-cycle', 'fan_duty_cycle': 900]
|
||||
]
|
||||
|
||||
api('fan_mode', modes.getAt(mode)) {
|
||||
sendEvent(name: 'thermostatFanMode', value: mode)
|
||||
poll()
|
||||
}
|
||||
}
|
||||
|
||||
def away() {
|
||||
setPresence('away')
|
||||
sendEvent(name: 'presence', value: 'not present')
|
||||
}
|
||||
|
||||
def present() {
|
||||
setPresence('present')
|
||||
sendEvent(name: 'presence', value: 'present')
|
||||
}
|
||||
|
||||
def setPresence(status) {
|
||||
log.debug "Status: $status"
|
||||
api('presence', ['away': status == 'away', 'away_timestamp': new Date().getTime(), 'away_setter': 0]) {
|
||||
poll()
|
||||
}
|
||||
}
|
||||
|
||||
def poll() {
|
||||
log.debug "Executing 'poll'"
|
||||
api('status', []) {
|
||||
data.device = it.data.device.getAt(settings.serial)
|
||||
data.shared = it.data.shared.getAt(settings.serial)
|
||||
data.structureId = it.data.link.getAt(settings.serial).structure.tokenize('.')[1]
|
||||
data.structure = it.data.structure.getAt(data.structureId)
|
||||
|
||||
data.device.fan_mode = data.device.fan_mode == 'duty-cycle'? 'circulate' : data.device.fan_mode
|
||||
data.structure.away = data.structure.away ? 'away' : 'present'
|
||||
|
||||
log.debug(data.shared)
|
||||
|
||||
def humidity = data.device.current_humidity
|
||||
def temperatureType = data.shared.target_temperature_type
|
||||
def fanMode = data.device.fan_mode
|
||||
def heatingSetpoint = '--'
|
||||
def coolingSetpoint = '--'
|
||||
|
||||
temperatureType = temperatureType == 'range' ? 'auto' : temperatureType
|
||||
|
||||
sendEvent(name: 'humidity', value: humidity)
|
||||
sendEvent(name: 'thermostatFanMode', value: fanMode)
|
||||
sendEvent(name: 'thermostatMode', value: temperatureType)
|
||||
|
||||
def temperatureUnit = device.latestValue('temperatureUnit')
|
||||
|
||||
switch (temperatureUnit) {
|
||||
case "celsius":
|
||||
def temperature = Math.round(data.shared.current_temperature)
|
||||
def targetTemperature = Math.round(data.shared.target_temperature)
|
||||
|
||||
if (temperatureType == "cool") {
|
||||
coolingSetpoint = targetTemperature
|
||||
} else if (temperatureType == "heat") {
|
||||
heatingSetpoint = targetTemperature
|
||||
} else if (temperatureType == "auto") {
|
||||
coolingSetpoint = Math.round(data.shared.target_temperature_high)
|
||||
heatingSetpoint = Math.round(data.shared.target_temperature_low)
|
||||
}
|
||||
|
||||
sendEvent(name: 'temperature', value: temperature, unit: temperatureUnit, state: temperatureType)
|
||||
sendEvent(name: 'coolingSetpoint', value: coolingSetpoint, unit: temperatureUnit, state: "cool")
|
||||
sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: temperatureUnit, state: "heat")
|
||||
break;
|
||||
default:
|
||||
def temperature = Math.round(cToF(data.shared.current_temperature))
|
||||
def targetTemperature = Math.round(cToF(data.shared.target_temperature))
|
||||
|
||||
if (temperatureType == "cool") {
|
||||
coolingSetpoint = targetTemperature
|
||||
} else if (temperatureType == "heat") {
|
||||
heatingSetpoint = targetTemperature
|
||||
} else if (temperatureType == "auto") {
|
||||
coolingSetpoint = Math.round(cToF(data.shared.target_temperature_high))
|
||||
heatingSetpoint = Math.round(cToF(data.shared.target_temperature_low))
|
||||
}
|
||||
|
||||
sendEvent(name: 'temperature', value: temperature, unit: temperatureUnit, state: temperatureType)
|
||||
sendEvent(name: 'coolingSetpoint', value: coolingSetpoint, unit: temperatureUnit, state: "cool")
|
||||
sendEvent(name: 'heatingSetpoint', value: heatingSetpoint, unit: temperatureUnit, state: "heat")
|
||||
break;
|
||||
}
|
||||
|
||||
switch (device.latestValue('presence')) {
|
||||
case "present":
|
||||
if (data.structure.away == 'away') {
|
||||
sendEvent(name: 'presence', value: 'not present')
|
||||
}
|
||||
break;
|
||||
case "not present":
|
||||
if (data.structure.away == 'present') {
|
||||
sendEvent(name: 'presence', value: 'present')
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (data.shared.hvac_ac_state) {
|
||||
sendEvent(name: 'thermostatOperatingState', value: "cooling")
|
||||
} else if (data.shared.hvac_heater_state) {
|
||||
sendEvent(name: 'thermostatOperatingState', value: "heating")
|
||||
} else if (data.shared.hvac_fan_state) {
|
||||
sendEvent(name: 'thermostatOperatingState', value: "fan only")
|
||||
} else {
|
||||
sendEvent(name: 'thermostatOperatingState', value: "idle")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def api(method, args = [], success = {}) {
|
||||
if(!isLoggedIn()) {
|
||||
log.debug "Need to login"
|
||||
login(method, args, success)
|
||||
return
|
||||
}
|
||||
|
||||
def methods = [
|
||||
'status': [uri: "/v2/mobile/${data.auth.user}", type: 'get'],
|
||||
'fan_mode': [uri: "/v2/put/device.${settings.serial}", type: 'post'],
|
||||
'thermostat_mode': [uri: "/v2/put/shared.${settings.serial}", type: 'post'],
|
||||
'temperature': [uri: "/v2/put/shared.${settings.serial}", type: 'post'],
|
||||
'presence': [uri: "/v2/put/structure.${data.structureId}", type: 'post']
|
||||
]
|
||||
|
||||
def request = methods.getAt(method)
|
||||
|
||||
log.debug "Logged in"
|
||||
doRequest(request.uri, args, request.type, success)
|
||||
}
|
||||
|
||||
// Need to be logged in before this is called. So don't call this. Call api.
|
||||
def doRequest(uri, args, type, success) {
|
||||
log.debug "Calling $type : $uri : $args"
|
||||
|
||||
if(uri.charAt(0) == '/') {
|
||||
uri = "${data.auth.urls.transport_url}${uri}"
|
||||
}
|
||||
|
||||
def params = [
|
||||
uri: uri,
|
||||
headers: [
|
||||
'X-nl-protocol-version': 1,
|
||||
'X-nl-user-id': data.auth.userid,
|
||||
'Authorization': "Basic ${data.auth.access_token}"
|
||||
],
|
||||
body: args
|
||||
]
|
||||
|
||||
def postRequest = { response ->
|
||||
if (response.getStatus() == 302) {
|
||||
def locations = response.getHeaders("Location")
|
||||
def location = locations[0].getValue()
|
||||
log.debug "redirecting to ${location}"
|
||||
doRequest(location, args, type, success)
|
||||
} else {
|
||||
success.call(response)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (type == 'post') {
|
||||
httpPostJson(params, postRequest)
|
||||
} else if (type == 'get') {
|
||||
httpGet(params, postRequest)
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
login()
|
||||
}
|
||||
}
|
||||
|
||||
def login(method = null, args = [], success = {}) {
|
||||
def params = [
|
||||
uri: 'https://home.nest.com/user/login',
|
||||
body: [username: settings.username, password: settings.password]
|
||||
]
|
||||
|
||||
httpPost(params) {response ->
|
||||
data.auth = response.data
|
||||
data.auth.expires_in = Date.parse('EEE, dd-MMM-yyyy HH:mm:ss z', response.data.expires_in).getTime()
|
||||
log.debug data.auth
|
||||
|
||||
api(method, args, success)
|
||||
}
|
||||
}
|
||||
|
||||
def isLoggedIn() {
|
||||
if(!data.auth) {
|
||||
log.debug "No data.auth"
|
||||
return false
|
||||
}
|
||||
|
||||
def now = new Date().getTime();
|
||||
return data.auth.expires_in > now
|
||||
}
|
||||
|
||||
def cToF(temp) {
|
||||
return (temp * 1.8 + 32).toDouble()
|
||||
}
|
||||
|
||||
def fToC(temp) {
|
||||
return ((temp - 32) / 1.8).toDouble()
|
||||
}
|
||||
@@ -21,6 +21,7 @@ metadata {
|
||||
attribute "tamper", "enum", ["detected", "clear"]
|
||||
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
|
||||
fingerprint mfr:"010F", prod:"0C02", model:"1002"
|
||||
}
|
||||
simulator {
|
||||
//battery
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Hue White Ambiance Bulb
|
||||
*
|
||||
* Philips Hue Type "Color Temperature Light"
|
||||
*
|
||||
* Author: SmartThings
|
||||
*/
|
||||
|
||||
// for the UI
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Hue White Ambiance Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Color Temperature"
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"rich-control", 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"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
}
|
||||
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
|
||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "colorTemperature", label: '${currentValue} K'
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["rich-control"])
|
||||
details(["rich-control", "colorTempSliderControl", "colorTemp", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(description) {
|
||||
log.debug "parse() - $description"
|
||||
def results = []
|
||||
|
||||
def map = description
|
||||
if (description instanceof String) {
|
||||
log.debug "Hue Ambience Bulb stringToMap - ${map}"
|
||||
map = stringToMap(description)
|
||||
}
|
||||
|
||||
if (map?.name && map?.value) {
|
||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
// handle commands
|
||||
void on() {
|
||||
log.trace parent.on(this)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
|
||||
void off() {
|
||||
log.trace parent.off(this)
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
|
||||
void setLevel(percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
if (percent != null && percent >= 0 && percent <= 100) {
|
||||
parent.setLevel(this, percent)
|
||||
sendEvent(name: "level", value: percent)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
} else {
|
||||
log.warn "$percent is not 0-100"
|
||||
}
|
||||
}
|
||||
|
||||
void setColorTemperature(value) {
|
||||
if (value) {
|
||||
log.trace "setColorTemperature: ${value}k"
|
||||
parent.setColorTemperature(this, value)
|
||||
sendEvent(name: "colorTemperature", value: value)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
} else {
|
||||
log.warn "Invalid color temperature"
|
||||
}
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -0,0 +1,30 @@
|
||||
# Smartsense Motion Sensor
|
||||
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
* [Samsung SmartThings Motion Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-motion-sensor)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health]($health)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||
* **Motion Sensor** - can detect motion
|
||||
* **Battery** - defines device uses a battery
|
||||
* **Refresh** - _refresh()_ command for status updates
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
## Device Health
|
||||
|
||||
A Category C2 motion sensor that has 120min check-in interval
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -403,39 +403,21 @@ def refresh() {
|
||||
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
||||
refreshCmds = refreshCmds + [
|
||||
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
|
||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||
as of now.
|
||||
*/
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
/* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276)
|
||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||
as of now.
|
||||
*/
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode])
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
|
||||
} else {
|
||||
refreshCmds = refreshCmds + [
|
||||
/* sensitivity - default value (8) */
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
|
||||
}
|
||||
|
||||
//Common refresh commands
|
||||
refreshCmds = refreshCmds + [
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global read 0xFC02 0x0010",
|
||||
"send 0x${device.deviceNetworkId} 1 1","delay 400"
|
||||
]
|
||||
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
|
||||
zigbee.readAttribute(0x0001, 0x0020) +
|
||||
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
|
||||
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
@@ -443,38 +425,15 @@ def refresh() {
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting"
|
||||
|
||||
def configCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
def configCmds = enrollResponse() +
|
||||
zigbee.batteryConfig() +
|
||||
zigbee.temperatureConfig() +
|
||||
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
|
||||
|
||||
return configCmds + refresh()
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ metadata {
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82"
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x80,0x71,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20" // Philio multi+
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x72,0x5A,0x80,0x73,0x86,0x84,0x85,0x59,0x71,0x70,0x7A,0x98" // Vision door/window
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98,0x86,0x72,0x5A,0x85,0x59,0x73,0x80,0x71,0x31,0x70,0x84,0x7A" // Vision Motion
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -263,5 +264,9 @@ def retypeBasedOnMSR() {
|
||||
log.debug "Changing device type to Door / Window Sensor Plus (SG)"
|
||||
setDeviceType("Door / Window Sensor Plus (SG)")
|
||||
break
|
||||
case "0109-2002-0205": // Vision Motion
|
||||
log.debug "Changing device type to Vision Motion Sensor Plus (SG)"
|
||||
setDeviceType("Vision Motion Sensor Plus (SG)")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,13 @@ metadata {
|
||||
capability "Motion Sensor"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
|
||||
fingerprint mfr: "011F", prod: "0001", model: "0001", deviceJoinName: "Schlage Motion Sensor" // Schlage motion
|
||||
fingerprint mfr: "014A", prod: "0001", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion
|
||||
fingerprint mfr: "014A", prod: "0004", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion +
|
||||
fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814
|
||||
fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02
|
||||
fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -125,9 +132,9 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
||||
}
|
||||
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
|
||||
result << response(zwave.batteryV1.batteryGet())
|
||||
result << response("delay 1200")
|
||||
} else {
|
||||
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
}
|
||||
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* Gidjit Hub
|
||||
*
|
||||
* Copyright 2016 Matthew Page
|
||||
*
|
||||
* 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: "Gidjit Hub",
|
||||
namespace: "com.gidjit.smartthings.hub",
|
||||
author: "Matthew Page",
|
||||
description: "Act as an endpoint so user's of Gidjit can quickly access and control their devices and execute routines. Users can do this quickly as Gidjit filters these actions based on their environment",
|
||||
category: "Convenience",
|
||||
iconUrl: "http://www.gidjit.com/appicon.png",
|
||||
iconX2Url: "http://www.gidjit.com/appicon@2x.png",
|
||||
iconX3Url: "http://www.gidjit.com/appicon@3x.png",
|
||||
oauth: [displayName: "Gidjit", displayLink: "www.gidjit.com"])
|
||||
|
||||
|
||||
|
||||
preferences {
|
||||
section ("Allow Gidjit to have access, there by allowing you to quickly control and monitor the following devices") {
|
||||
input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false
|
||||
input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade
|
||||
//input "bulbs", "capability.colorControl", title: "Control your lights", multiple: true, required: false //windowShade
|
||||
|
||||
}
|
||||
}
|
||||
mappings {
|
||||
path("/structureinfo") {
|
||||
action: [
|
||||
GET: "structureInfo"
|
||||
]
|
||||
}
|
||||
path("/helloactions") {
|
||||
action: [
|
||||
GET: "helloActions"
|
||||
]
|
||||
}
|
||||
path("/helloactions/:label") {
|
||||
action: [
|
||||
PUT: "executeAction"
|
||||
]
|
||||
}
|
||||
|
||||
path("/switch/:id/:command") {
|
||||
action: [
|
||||
PUT: "updateSwitch"
|
||||
]
|
||||
}
|
||||
|
||||
path("/thermostat/:id/:command") {
|
||||
action: [
|
||||
PUT: "updateThermostat"
|
||||
]
|
||||
}
|
||||
|
||||
path("/windowshade/:id/:command") {
|
||||
action: [
|
||||
PUT: "updateWindowShade"
|
||||
]
|
||||
}
|
||||
path("/acquiredata/:id") {
|
||||
action: [
|
||||
GET: "acquiredata"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
// subscribe to attributes, devices, locations, etc.
|
||||
}
|
||||
def helloActions() {
|
||||
def actions = location.helloHome?.getPhrases()*.label
|
||||
if(!actions) {
|
||||
return []
|
||||
}
|
||||
return actions
|
||||
}
|
||||
def executeAction() {
|
||||
def actions = location.helloHome?.getPhrases()*.label
|
||||
def a = actions?.find() { it == params.label }
|
||||
if (!a) {
|
||||
httpError(400, "invalid label $params.label")
|
||||
return
|
||||
}
|
||||
location.helloHome?.execute(params.label)
|
||||
}
|
||||
/* this is the primary function called to query at the structure and its devices */
|
||||
def structureInfo() { //list all devices
|
||||
def list = [:]
|
||||
def currId = location.id
|
||||
list[currId] = [:]
|
||||
list[currId].name = location.name
|
||||
list[currId].id = location.id
|
||||
list[currId].temperatureScale = location.temperatureScale
|
||||
list[currId].devices = [:]
|
||||
|
||||
def setValues = {
|
||||
if (params.brief) {
|
||||
return [id: it.id, name: it.displayName]
|
||||
}
|
||||
def newList = [id: it.id, name: it.displayName, suppCapab: it.capabilities.collect {
|
||||
"$it.name"
|
||||
}, suppAttributes: it.supportedAttributes.collect {
|
||||
"$it.name"
|
||||
}, suppCommands: it.supportedCommands.collect {
|
||||
"$it.name"
|
||||
}]
|
||||
|
||||
return newList
|
||||
}
|
||||
switches?.each {
|
||||
list[currId].devices[it.id] = setValues(it)
|
||||
}
|
||||
thermostats?.each {
|
||||
list[currId].devices[it.id] = setValues(it)
|
||||
}
|
||||
windowShades?.each {
|
||||
list[currId].devices[it.id] = setValues(it)
|
||||
}
|
||||
|
||||
return list
|
||||
|
||||
}
|
||||
/* This function returns all of the current values of the specified Devices attributes */
|
||||
def acquiredata() {
|
||||
def resp = [:]
|
||||
if (!params.id) {
|
||||
httpError(400, "invalid id $params.id")
|
||||
return
|
||||
}
|
||||
def dev = switches.find() { it.id == params.id } ?: windowShades.find() { it.id == params.id } ?:
|
||||
thermostats.find() { it.id == params.id }
|
||||
|
||||
if (!dev) {
|
||||
httpError(400, "invalid id $params.id")
|
||||
return
|
||||
}
|
||||
def att = dev.supportedAttributes
|
||||
att.each {
|
||||
resp[it.name] = dev.currentValue("$it.name")
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
void updateSwitch() {
|
||||
// use the built-in request object to get the command parameter
|
||||
def command = params.command
|
||||
def sw = switches.find() { it.id == params.id }
|
||||
if (!sw) {
|
||||
httpError(400, "invalid id $params.id")
|
||||
return
|
||||
}
|
||||
switch(command) {
|
||||
case "on":
|
||||
if ( sw.currentSwitch != "on" ) {
|
||||
sw.on()
|
||||
}
|
||||
break
|
||||
case "off":
|
||||
if ( sw.currentSwitch != "off" ) {
|
||||
sw.off()
|
||||
}
|
||||
break
|
||||
default:
|
||||
httpError(400, "$command is not a valid")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void updateThermostat() {
|
||||
// use the built-in request object to get the command parameter
|
||||
def command = params.command
|
||||
def therm = thermostats.find() { it.id == params.id }
|
||||
if (!therm || !command) {
|
||||
httpError(400, "invalid id $params.id")
|
||||
return
|
||||
}
|
||||
def passComm = [
|
||||
"off",
|
||||
"heat",
|
||||
"emergencyHeat",
|
||||
"cool",
|
||||
"fanOn",
|
||||
"fanAuto",
|
||||
"fanCirculate",
|
||||
"auto"
|
||||
|
||||
]
|
||||
def passNumParamComm = [
|
||||
"setHeatingSetpoint",
|
||||
"setCoolingSetpoint",
|
||||
]
|
||||
def passStringParamComm = [
|
||||
"setThermostatMode",
|
||||
"setThermostatFanMode",
|
||||
]
|
||||
if (command in passComm) {
|
||||
therm."$command"()
|
||||
} else if (command in passNumParamComm && params.p1 && params.p1.isFloat()) {
|
||||
therm."$command"(Float.parseFloat(params.p1))
|
||||
} else if (command in passStringParamComm && params.p1) {
|
||||
therm."$command"(params.p1)
|
||||
} else {
|
||||
httpError(400, "$command is not a valid command")
|
||||
}
|
||||
}
|
||||
|
||||
void updateWindowShade() {
|
||||
// use the built-in request object to get the command parameter
|
||||
def command = params.command
|
||||
def ws = windowShades.find() { it.id == params.id }
|
||||
if (!ws || !command) {
|
||||
httpError(400, "invalid id $params.id")
|
||||
return
|
||||
}
|
||||
def passComm = [
|
||||
"open",
|
||||
"close",
|
||||
"presetPosition",
|
||||
]
|
||||
if (command in passComm) {
|
||||
ws."$command"()
|
||||
} else {
|
||||
httpError(400, "$command is not a valid command")
|
||||
}
|
||||
}
|
||||
// TODO: implement event handlers
|
||||
@@ -337,10 +337,10 @@ def initialize() {
|
||||
|
||||
settings.devices.each {
|
||||
def deviceId = it
|
||||
def detail = state.deviceDetail[deviceId]
|
||||
def detail = state?.deviceDetail[deviceId]
|
||||
|
||||
try {
|
||||
switch(detail.type) {
|
||||
switch(detail?.type) {
|
||||
case 'NAMain':
|
||||
log.debug "Base station"
|
||||
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
|
||||
@@ -487,12 +487,12 @@ def poll() {
|
||||
log.debug "State: ${state.deviceState}"
|
||||
|
||||
settings.devices.each { deviceId ->
|
||||
def detail = state.deviceDetail[deviceId]
|
||||
def data = state.deviceState[deviceId]
|
||||
def child = children.find { it.deviceNetworkId == deviceId }
|
||||
def detail = state?.deviceDetail[deviceId]
|
||||
def data = state?.deviceState[deviceId]
|
||||
def child = children?.find { it.deviceNetworkId == deviceId }
|
||||
|
||||
log.debug "Update: $child";
|
||||
switch(detail.type) {
|
||||
switch(detail?.type) {
|
||||
case 'NAMain':
|
||||
log.debug "Updating NAMain $data"
|
||||
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())
|
||||
|
||||
@@ -1,345 +0,0 @@
|
||||
/**
|
||||
* SmartThings service for Prempoint
|
||||
*
|
||||
* Author: Prempoint Inc. (c) 2016
|
||||
*
|
||||
*/
|
||||
definition(
|
||||
name: "Prempoint",
|
||||
namespace: "prempoint.com",
|
||||
author: "Prempoint Inc.",
|
||||
description: "SmartThings service for Prempoint",
|
||||
category: "Connections",
|
||||
iconUrl: "http://www.prempoint.com/images/social_app_emblem_50x50.png",
|
||||
iconX2Url: "http://www.prempoint.com/images/social_app_emblem_100x100.png",
|
||||
iconX3Url: "http://www.prempoint.com/images/social_app_emblem_150x150.png",
|
||||
oauth: [displayName: "Prempoint", displayLink: "http://www.prempoint.com/"])
|
||||
|
||||
preferences {
|
||||
section("Allow Prempoint to Control & Access These Things...") {
|
||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
||||
input "garagedoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false
|
||||
//input "doors", "capability.doorControl", title: "Which Doors?", multiple: true, required: false
|
||||
input "cameras", "capability.imageCapture", title: "Which Cameras?", multiple: true, required: false
|
||||
}
|
||||
}
|
||||
|
||||
mappings {
|
||||
path("/list") {
|
||||
action: [
|
||||
GET: "listDevices"
|
||||
]
|
||||
}
|
||||
path("/switches") {
|
||||
action: [
|
||||
GET: "listSwitches"
|
||||
]
|
||||
}
|
||||
path("/switches/:id") {
|
||||
action: [
|
||||
GET: "showSwitch"
|
||||
]
|
||||
}
|
||||
path("/switches/:id/:command") {
|
||||
action: [
|
||||
GET: "updateSwitch"
|
||||
]
|
||||
}
|
||||
path("/switches/:id/:command/:level") {
|
||||
action: [
|
||||
GET: "updateSwitch"
|
||||
]
|
||||
}
|
||||
path("/locks") {
|
||||
action: [
|
||||
GET: "listLocks"
|
||||
]
|
||||
}
|
||||
path("/locks/:id") {
|
||||
action: [
|
||||
GET: "showLock"
|
||||
]
|
||||
}
|
||||
path("/locks/:id/:command") {
|
||||
action: [
|
||||
GET: "updateLock"
|
||||
]
|
||||
}
|
||||
path("/doors/:id") {
|
||||
action: [
|
||||
GET: "showDoor"
|
||||
]
|
||||
}
|
||||
path("/doors/:id/:command") {
|
||||
action: [
|
||||
GET: "updateDoor"
|
||||
]
|
||||
}
|
||||
path("/garagedoors/:id") {
|
||||
action: [
|
||||
GET: "showGarageDoor"
|
||||
]
|
||||
}
|
||||
path("/garagedoors/:id/:command") {
|
||||
action: [
|
||||
GET: "updateGarageDoor"
|
||||
]
|
||||
}
|
||||
path("/cameras/:id") {
|
||||
action: [
|
||||
GET: "showCamera"
|
||||
]
|
||||
}
|
||||
path("/cameras/:id/:command") {
|
||||
action: [
|
||||
GET: "updateCamera"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {}
|
||||
|
||||
def updated() {}
|
||||
|
||||
def listDevices() {
|
||||
log.debug "entering listDevices"
|
||||
//return listSwitches() + listLocks() + listGarageDoors() + listDoors() + listCameras()
|
||||
return listSwitches() + listLocks() + listGarageDoors() + listCameras()
|
||||
}
|
||||
|
||||
//switches
|
||||
def listSwitches() {
|
||||
log.debug "entering listSwitches"
|
||||
switches.collect{showDevice(it,"switch")}
|
||||
}
|
||||
|
||||
def showSwitch() {
|
||||
log.debug "entering showSwitches"
|
||||
show(switches, "switch")
|
||||
}
|
||||
|
||||
def updateSwitch() {
|
||||
log.debug "entering updateSwitches"
|
||||
update(switches, "switch")
|
||||
}
|
||||
|
||||
//locks
|
||||
def listLocks() {
|
||||
log.debug "entering listLocks"
|
||||
locks.collect{showDevice(it,"lock")}
|
||||
}
|
||||
|
||||
def showLock() {
|
||||
log.debug "entering showLock"
|
||||
show(locks, "lock")
|
||||
}
|
||||
|
||||
def updateLock() {
|
||||
log.debug "entering updateLock"
|
||||
update(locks, "lock")
|
||||
}
|
||||
|
||||
//doors
|
||||
def listDoors() {
|
||||
log.debug "entering listDoors"
|
||||
locks.collect{showDevice(it,"door")}
|
||||
}
|
||||
|
||||
def showDoor() {
|
||||
log.debug "entering showDoors"
|
||||
show(doors, "door")
|
||||
}
|
||||
|
||||
def updateDoor() {
|
||||
log.debug "entering updateDoor"
|
||||
update(doors, "door")
|
||||
}
|
||||
|
||||
//garagedoors
|
||||
def listGarageDoors() {
|
||||
log.debug "entering listGarageDoors"
|
||||
locks.collect{showDevice(it,"garagedoor")}
|
||||
}
|
||||
|
||||
def showGarageDoor() {
|
||||
log.debug "entering showGarageDoors"
|
||||
show(garagedoors, "garagedoor")
|
||||
}
|
||||
|
||||
def updateGarageDoor() {
|
||||
log.debug "entering updateGarageDoor"
|
||||
update(gargedoors, "garagedoor")
|
||||
}
|
||||
|
||||
//cameras
|
||||
def listCameras() {
|
||||
log.debug "entering listCameras"
|
||||
cameras.collect{showDevice(it,"image")}
|
||||
}
|
||||
|
||||
def showCamera() {
|
||||
log.debug "entering showCameras"
|
||||
show(cameras, "camera")
|
||||
}
|
||||
|
||||
def updateCamera() {
|
||||
log.debug "entering updateCamera"
|
||||
update(cameras, "camera")
|
||||
}
|
||||
|
||||
def deviceHandler(evt) {}
|
||||
|
||||
private update(devices, type) {
|
||||
def rc = null
|
||||
|
||||
//def command = request.JSON?.command
|
||||
def command = params.command
|
||||
|
||||
log.debug "update, request: params: ${params}, devices: $devices.id type=$type command=$command"
|
||||
|
||||
// Process the command.
|
||||
if (command)
|
||||
{
|
||||
def dev = devices.find { it.id == params.id }
|
||||
if (!dev) {
|
||||
httpError(404, "Device not found: $params.id")
|
||||
} else if (type == "switch") {
|
||||
switch(command) {
|
||||
case "on":
|
||||
rc = dev.on()
|
||||
break
|
||||
case "off":
|
||||
rc = dev.off()
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
|
||||
}
|
||||
} else if (type == "lock") {
|
||||
switch(command) {
|
||||
case "lock":
|
||||
rc = dev.lock()
|
||||
break
|
||||
case "unlock":
|
||||
rc = dev.unlock()
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device:=$it.id $dev")
|
||||
}
|
||||
} else if (type == "door") {
|
||||
switch(command) {
|
||||
case "open":
|
||||
rc = dev.open()
|
||||
break
|
||||
case "close":
|
||||
rc = dev.close()
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
|
||||
}
|
||||
} else if (type == "garagedoor") {
|
||||
switch(command) {
|
||||
case "open":
|
||||
rc = dev.open()
|
||||
break
|
||||
case "close":
|
||||
rc = dev.close()
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
|
||||
}
|
||||
} else if (type == "camera") {
|
||||
switch(command) {
|
||||
case "take":
|
||||
rc = dev.take()
|
||||
log.debug "Device command=$command device=$it.id $dev current image=$it.currentImage"
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "executed device=$it.id $dev command=$command rc=$rc"
|
||||
|
||||
// Check that the device is a switch that is currently on, supports 'setLevel"
|
||||
// and that a level was specified.
|
||||
int level = params.level ? params.level as int : -1;
|
||||
if ((type == "switch") && (dev.currentValue('switch') == "on") && hasLevel(dev) && (level != -1)) {
|
||||
log.debug "device about to setLevel=$level"
|
||||
dev.setLevel(level);
|
||||
}
|
||||
|
||||
// Show the device info if necessary.
|
||||
if (rc == null) {
|
||||
rc = showDevice(dev, type)
|
||||
}
|
||||
}
|
||||
|
||||
return rc
|
||||
}
|
||||
|
||||
private show(devices, type) {
|
||||
def dev = devices.find { it.id == params.id }
|
||||
if (!dev) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
// Show the device info.
|
||||
showDevice(dev, type)
|
||||
}
|
||||
}
|
||||
|
||||
private showDevice(it, type) {
|
||||
def props = null
|
||||
|
||||
// Get the current state for the device type.
|
||||
def state = [it.currentState(type)]
|
||||
|
||||
// Check that whether the a switch device with level support is located and update the returned device type.
|
||||
def devType = type
|
||||
|
||||
if (type == "switch" && hasLevel(it)) {
|
||||
// Assign "switchWithLevel" to device type.
|
||||
devType = "switchWithLevel"
|
||||
// Add the level state.
|
||||
def levelState = it.currentState("level")
|
||||
if (levelState) {
|
||||
state.add(levelState)
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "device label=$it.label type=$devType"
|
||||
|
||||
// Assign the device item properties if appropriate.
|
||||
if (it) {
|
||||
props = [id: it.id, label: it.label, type: devType, state: state]
|
||||
// Add the hub information to the device properties
|
||||
// if appropriate.
|
||||
if (it.hub) {
|
||||
props.put("location", it.hub.hub.location)
|
||||
}
|
||||
if (it.currentImage) {
|
||||
props.put("currentImage", it.currentImage)
|
||||
}
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
private hasLevel(device) {
|
||||
// Default return value.
|
||||
def rc = false;
|
||||
|
||||
// Get the device supported commands.
|
||||
def supportedCommands = device.supportedCommands
|
||||
|
||||
// Check to see if the "setLevel" was found and assign
|
||||
// the appropriate return value.
|
||||
if (supportedCommands) {
|
||||
// Find the "setLevel" command.
|
||||
rc = supportedCommands.toString().indexOf("setLevel") != -1
|
||||
}
|
||||
|
||||
log.debug "hasLevel device label=$device.label supportedCommands=$supportedCommands rc=$rc"
|
||||
|
||||
return rc
|
||||
}
|
||||
@@ -30,6 +30,7 @@ definition(
|
||||
preferences {
|
||||
page(name:"mainPage", title:"Hue Device Setup", content:"mainPage", refreshTimeout:5)
|
||||
page(name:"bridgeDiscovery", title:"Hue Bridge Discovery", content:"bridgeDiscovery", refreshTimeout:5)
|
||||
page(name:"bridgeDiscoveryFailed", title:"Bridge Discovery Failed", content:"bridgeDiscoveryFailed", refreshTimeout:0)
|
||||
page(name:"bridgeBtnPush", title:"Linking with your Hue", content:"bridgeLinking", refreshTimeout:5)
|
||||
page(name:"bulbDiscovery", title:"Hue Device Setup", content:"bulbDiscovery", refreshTimeout:5)
|
||||
}
|
||||
@@ -53,12 +54,21 @@ def bridgeDiscovery(params=[:])
|
||||
def options = bridges ?: []
|
||||
def numFound = options.size() ?: 0
|
||||
|
||||
if (numFound == 0 && state.bridgeRefreshCount > 25) {
|
||||
log.trace "Cleaning old bridges memory"
|
||||
state.bridges = [:]
|
||||
state.bridgeRefreshCount = 0
|
||||
app.updateSetting("selectedHue", "")
|
||||
}
|
||||
if (numFound == 0) {
|
||||
if (state.bridgeRefreshCount == 25) {
|
||||
log.trace "Cleaning old bridges memory"
|
||||
state.bridges = [:]
|
||||
app.updateSetting("selectedHue", "")
|
||||
} else if (state.bridgeRefreshCount > 100) {
|
||||
// five minutes have passed, give up
|
||||
// there seems to be a problem going back from discovey failed page in some instances (compared to pressing next)
|
||||
// however it is probably a SmartThings settings issue
|
||||
state.bridges = [:]
|
||||
app.updateSetting("selectedHue", "")
|
||||
state.bridgeRefreshCount = 0
|
||||
return bridgeDiscoveryFailed()
|
||||
}
|
||||
}
|
||||
|
||||
ssdpSubscribe()
|
||||
|
||||
@@ -79,6 +89,13 @@ def bridgeDiscovery(params=[:])
|
||||
}
|
||||
}
|
||||
|
||||
def bridgeDiscoveryFailed() {
|
||||
return dynamicPage(name:"bridgeDiscoveryFailed", title: "Bridge Discovery Failed", nextPage: "bridgeDiscovery") {
|
||||
section("Failed to discover any Hue Bridges. Please confirm that the Hue Bridge is connected to the same network as your SmartThings Hub, and that it has power.") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def bridgeLinking()
|
||||
{
|
||||
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
|
||||
@@ -88,19 +105,15 @@ def bridgeLinking()
|
||||
def nextPage = ""
|
||||
def title = "Linking with your Hue"
|
||||
def paragraphText
|
||||
def hueimage = null
|
||||
if (selectedHue) {
|
||||
paragraphText = "Press the button on your Hue Bridge to setup a link. "
|
||||
hueimage = "http://huedisco.mediavibe.nl/wp-content/uploads/2013/09/pair-bridge.png"
|
||||
} else {
|
||||
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
|
||||
hueimage = null
|
||||
}
|
||||
if (state.username) { //if discovery worked
|
||||
nextPage = "bulbDiscovery"
|
||||
title = "Success!"
|
||||
paragraphText = "Linking to your hub was a success! Please click 'Next'!"
|
||||
hueimage = null
|
||||
}
|
||||
|
||||
if((linkRefreshcount % 2) == 0 && !state.username) {
|
||||
@@ -110,8 +123,6 @@ def bridgeLinking()
|
||||
return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) {
|
||||
section("") {
|
||||
paragraph """${paragraphText}"""
|
||||
if (hueimage != null)
|
||||
image "${hueimage}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,13 +146,14 @@ def bulbDiscovery() {
|
||||
if((bulbRefreshCount % 5) == 0) {
|
||||
discoverHueBulbs()
|
||||
}
|
||||
def selectedBridge = state.bridges.find { key, value -> value?.serialNumber?.equalsIgnoreCase(selectedHue) }
|
||||
def title = selectedBridge?.value?.name ?: "Find bridges"
|
||||
|
||||
return dynamicPage(name:"bulbDiscovery", title:"Bulb Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||
section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||
input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions
|
||||
}
|
||||
section {
|
||||
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
|
||||
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||
|
||||
}
|
||||
@@ -323,6 +335,8 @@ private getDeviceType(hueType) {
|
||||
return "Hue Bulb"
|
||||
else if (hueType?.equalsIgnoreCase("Color Light"))
|
||||
return "Hue Bloom"
|
||||
else if (hueType?.equalsIgnoreCase("Color Temperature Light"))
|
||||
return "Hue White Ambiance Bulb"
|
||||
else
|
||||
return null
|
||||
}
|
||||
@@ -346,26 +360,29 @@ def addBulbs() {
|
||||
def newHueBulb
|
||||
if (bulbs instanceof java.util.Map) {
|
||||
newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
if (newHueBulb != null) {
|
||||
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
||||
|
||||
if (newHueBulb != null) {
|
||||
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
||||
if (d) {
|
||||
log.debug "created ${d.displayName} with id $dni"
|
||||
d.completedSetup = true
|
||||
d.refresh()
|
||||
}
|
||||
} else {
|
||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||
}
|
||||
} else {
|
||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||
}
|
||||
} else {
|
||||
//backwards compatable
|
||||
//backwards compatable
|
||||
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
|
||||
d = addChildBulb(dni, "Extended Color Light", newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
||||
d?.completedSetup = true
|
||||
d?.refresh()
|
||||
}
|
||||
} else {
|
||||
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
|
||||
if (bulbs instanceof java.util.Map) {
|
||||
// Update device type if incorrect
|
||||
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
upgradeDeviceType(d, newHueBulb?.value?.type)
|
||||
}
|
||||
}
|
||||
@@ -397,6 +414,7 @@ def addBridge() {
|
||||
}
|
||||
if (newbridge) {
|
||||
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
|
||||
d?.completedSetup = true
|
||||
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
||||
def childDevice = getChildDevice(d.deviceNetworkId)
|
||||
childDevice.sendEvent(name: "serialNumber", value: vbridge.value.serialNumber)
|
||||
@@ -484,7 +502,21 @@ void bridgeDescriptionHandler(physicalgraph.device.HubResponse hubResponse) {
|
||||
def bridges = getHueBridges()
|
||||
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
|
||||
if (bridge) {
|
||||
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
||||
// serialNumber from API is in format of 0017882413ad (mac address), however on the actual bridge only last six
|
||||
// characters are printed on the back so using that to identify bridge
|
||||
def idNumber = body?.device?.serialNumber?.text()
|
||||
if (idNumber?.size() >= 6)
|
||||
idNumber = idNumber[-6..-1].toUpperCase()
|
||||
|
||||
// usually in form of bridge name followed by (ip), i.e. defaults to Philips Hue (192.168.1.2)
|
||||
// replace IP with serial number to make it easier for user to identify
|
||||
def name = body?.device?.friendlyName?.text()
|
||||
def index = name?.indexOf('(')
|
||||
if (index != -1) {
|
||||
name = name.substring(0,index)
|
||||
name += " ($idNumber)"
|
||||
}
|
||||
bridge.value << [name:name, serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
||||
} else {
|
||||
log.error "/description.xml returned a bridge that didn't exist"
|
||||
}
|
||||
|
||||
@@ -107,8 +107,8 @@ mappings {
|
||||
path("/locks") {
|
||||
action: [
|
||||
GET: "listLocks",
|
||||
PUT: "updateLock",
|
||||
POST: "updateLock"
|
||||
PUT: "updateLocks",
|
||||
POST: "updateLocks"
|
||||
]
|
||||
}
|
||||
path("/locks/:id") {
|
||||
@@ -442,31 +442,87 @@ def executePhrase() {
|
||||
}
|
||||
|
||||
private void updateAll(devices) {
|
||||
def type = params.param1
|
||||
def command = request.JSON?.command
|
||||
if (command)
|
||||
{
|
||||
command = command.toLowerCase()
|
||||
devices."$command"()
|
||||
if (!devices) {
|
||||
httpError(404, "Devices not found")
|
||||
}
|
||||
if (command){
|
||||
devices.each { device ->
|
||||
executeCommand(device, type, command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void update(devices) {
|
||||
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
|
||||
//def command = request.JSON?.command
|
||||
def command = params.command
|
||||
if (command)
|
||||
{
|
||||
command = command.toLowerCase()
|
||||
def device = devices.find { it.id == params.id }
|
||||
if (!device)
|
||||
{
|
||||
def type = params.param1
|
||||
def command = request.JSON?.command
|
||||
def device = devices?.find { it.id == params.id }
|
||||
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
}
|
||||
else
|
||||
{
|
||||
device."$command"()
|
||||
}
|
||||
}
|
||||
|
||||
if (command) {
|
||||
executeCommand(device, type, command)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, deviceType, command) {
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
def currentDeviceCapability = getCapabilityName(deviceType)
|
||||
if (capabilityCommands[currentDeviceCapability]) {
|
||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(type) {
|
||||
switch(type) {
|
||||
case "switches":
|
||||
return "Switch"
|
||||
case "locks":
|
||||
return "Lock"
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and executes the command
|
||||
* on the device or devices
|
||||
*/
|
||||
def executeCommand(device, type, command) {
|
||||
if (validateCommand(device, type, command)) {
|
||||
device."$command"()
|
||||
} else {
|
||||
httpError(403, "Access denied. This command is not supported by current capability.")
|
||||
}
|
||||
}
|
||||
|
||||
private show(devices, type) {
|
||||
|
||||
@@ -92,22 +92,87 @@ void updateLock() {
|
||||
|
||||
private void updateAll(devices) {
|
||||
def command = request.JSON?.command
|
||||
if (command) {
|
||||
devices."$command"()
|
||||
def type = params.param1
|
||||
if (!devices) {
|
||||
httpError(404, "Devices not found")
|
||||
}
|
||||
|
||||
if (command){
|
||||
devices.each { device ->
|
||||
executeCommand(device, type, command)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void update(devices) {
|
||||
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
|
||||
def command = request.JSON?.command
|
||||
if (command) {
|
||||
def device = devices.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
def type = params.param1
|
||||
def device = devices?.find { it.id == params.id }
|
||||
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
}
|
||||
|
||||
if (command) {
|
||||
executeCommand(device, type, command)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, deviceType, command) {
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
def currentDeviceCapability = getCapabilityName(deviceType)
|
||||
if (capabilityCommands[currentDeviceCapability]) {
|
||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(type) {
|
||||
switch(type) {
|
||||
case "switches":
|
||||
return "Switch"
|
||||
case "locks":
|
||||
return "Lock"
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and executes the command
|
||||
* on the device or devices
|
||||
*/
|
||||
def executeCommand(device, type, command) {
|
||||
if (validateCommand(device, type, command)) {
|
||||
device."$command"()
|
||||
} else {
|
||||
httpError(403, "Access denied. This command is not supported by current capability.")
|
||||
}
|
||||
}
|
||||
|
||||
private show(devices, name) {
|
||||
|
||||
Reference in New Issue
Block a user