Compare commits

..

1 Commits

32 changed files with 2700 additions and 1747 deletions

23
.gitignore vendored
View File

@@ -1,23 +0,0 @@
# Eclipse files
/.settings
/.classpath
/.project
/eclipse/
/target-eclipse
# IntelliJ files
*.iws
*.iml
.idea
/out
*.ipr
# Gradle files
.gradletasknamecache
.gradle/
# Mac OS files
.DS_Store
# Build files
/build

View File

@@ -1,57 +0,0 @@
import java.nio.charset.StandardCharsets
import java.nio.file.Paths
apply plugin: 'groovy'
apply plugin: 'smartthings-executable-deployment'
apply plugin: 'smartthings-hipchat'
buildscript {
dependencies {
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.3"
}
repositories {
jcenter()
maven {
credentials {
username smartThingsArtifactoryUserName
password smartThingsArtifactoryPassword
}
url "http://artifactory.smartthings.com/libs-release-local"
}
}
}
repositories {
mavenLocal()
jcenter()
}
dependencies {
}
hipchatShareFile {
List<String> archives = []
File rootDir = new File("${project.buildDir}/archives")
if (rootDir.exists()) {
// Create a list of archives which were deployed.
java.nio.file.Path rootPath = Paths.get(rootDir.absolutePath)
rootDir.eachFileRecurse { File file ->
if (file.name.endsWith('.tar.gz')) {
java.nio.file.Path archivePath = Paths.get(file.absolutePath)
archives.add(rootPath.relativize(archivePath).toString())
}
}
}
// Set task properties
data = archives.join('\n').getBytes(StandardCharsets.UTF_8)
fileName = 'deployment-notes.txt'
contentType = 'text/html'
}
hipchatSendNotification {
String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown'
message = "Began executable deploy of SmartThingsPublic(${branch})."
color = branch == 'master' ? 'yellow' : 'red'
notify = true
}

View File

@@ -1,27 +0,0 @@
machine:
java:
version:
oraclejdk8
dependencies:
override:
- echo "Nothing to do."
test:
override:
- echo "We don't have any tests :-("
deployment:
develop:
branch: master
commands:
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3BucketName=$S3_BUCKET_NAME_PREPROD_DEV
- ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH
- ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD
stage:
branch: staging
commands:
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3BucketName=$S3_BUCKET_NAME_PREPROD_STAGING
- ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH
- ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD

View File

@@ -120,7 +120,7 @@ def setInstallSmartApp(value){
}
def parse(String description) {
log.debug description
def description_map = parseDescriptionAsMap(description)
def event_name = ""
def measurement_map = [
@@ -129,7 +129,10 @@ def parse(String description) {
zigbeedeviceid: device.zigbeeId,
created: new Date().time /1000 as int
]
if (description_map.cluster == "0001"){
if (description_map.cluster == "0000"){
/* version number, not used */
} else if (description_map.cluster == "0001"){
/* battery voltage in mV (device needs minimium 2.1v to run) */
log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}"
event_name = "battery_status"
@@ -155,10 +158,6 @@ def parse(String description) {
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
if(nameAndValue.length == 2){
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}else{
map += []
}
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}

View File

@@ -1,294 +1,294 @@
/**
* Copyright 2015 SmartThings
*
* 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.
*
*/
metadata {
definition (name: "Arrival Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Tone"
capability "Actuator"
capability "Signal Strength"
capability "Presence Sensor"
capability "Sensor"
capability "Battery"
fingerprint profileId: "FC01", deviceId: "019A"
fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000,0003", outClusters: "0003"
fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000", outClusters: "0006"
}
simulator {
status "present": "presence: 1"
status "not present": "presence: 0"
status "battery": "battery: 27, batteryDivisor: 0A, rssi: 100, lqi: 64"
}
preferences {
section {
image(name: 'educationalcontent', multiple: true, images: [
"http://cdn.device-gse.smartthings.com/Arrival/Arrival1.jpg",
"http://cdn.device-gse.smartthings.com/Arrival/Arrival2.jpg"
])
}
}
tiles {
standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
state "present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ebeef2"
}
standardTile("beep", "device.beep", decoration: "flat") {
state "beep", label:'', action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff"
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
state "battery", label:'${currentValue}% battery', unit:""/*, backgroundColors:[
[value: 5, color: "#BC2323"],
[value: 10, color: "#D04E00"],
[value: 15, color: "#F1D801"],
[value: 16, color: "#FFFFFF"]
]*/
}
/*
valueTile("lqi", "device.lqi", decoration: "flat", inactiveLabel: false) {
state "lqi", label:'${currentValue}% signal', unit:""
}
*/
main "presence"
details(["presence", "beep", "battery"/*, "lqi"*/])
}
}
def beep() {
/*
You can make the speaker turn on for 0.5-second beeps by sending some CLI commands:
Command: send raw, wait 7, send raw, wait 7, send raw
Future: new packet type "st.beep"
raw 0xFC05 {15 0A 11 00 00 15 01}
send 0x2F7F 2 2
where "0xABCD" is the node ID of the Smart Tag, everything else above is a constant. Except
the "15 01" at the end of the first raw command, that sets the speaker's period (reciprocal
of frequency). You can play with this value up or down to experiment with loudness as the
loudness will be strongly dependent upon frequency and the enclosure that it's in. Note that
"15 01" represents the hex number 0x0115 so a lower frequency is "16 01" (longer period) and
a higher frequency is "14 01" (shorter period). Note that since the tag only checks its parent
for messages every 5 seconds (while at rest) or every 3 seconds (while in motion) it will take
up to this long from the time you send the message to the time you hear a sound.
*/
[
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}"
]
}
def parse(String description) {
def results
if (isBatteryMessage(description)) {
results = parseBatteryMessage(description)
}
else {
results = parsePresenceMessage(description)
}
log.debug "Parse returned $results.descriptionText"
results
}
private Map parsePresenceMessage(String description) {
def name = parseName(description)
def value = parseValue(description)
def linkText = getLinkText(device)
def descriptionText = parseDescriptionText(linkText, value, description)
def handlerName = getState(value)
def isStateChange = isStateChange(device, name, value)
def results = [
name: name,
value: value,
unit: null,
linkText: linkText,
descriptionText: descriptionText,
handlerName: handlerName,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
]
results
}
private String parseName(String description) {
if (description?.startsWith("presence: ")) {
return "presence"
}
null
}
private String parseValue(String description) {
if (description?.startsWith("presence: "))
{
if (description?.endsWith("1"))
{
return "present"
}
else if (description?.endsWith("0"))
{
return "not present"
}
}
description
}
private parseDescriptionText(String linkText, String value, String description) {
switch(value) {
case "present": return "$linkText has arrived"
case "not present": return "$linkText has left"
default: return value
}
}
private getState(String value) {
def state = value
if (value == "present") {
state = "arrived"
}
else if (value == "not present") {
state = "left"
}
state
}
private Boolean isBatteryMessage(String description) {
// "raw:36EF1C, dni:36EF, battery:1B, rssi:, lqi:"
description ==~ /.*battery:.*rssi:.*lqi:.*/
}
private List parseBatteryMessage(String description) {
def results = []
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('battery:')) {
def batteryResult = getBatteryResult(part, description)
if (batteryResult) {
results << batteryResult
}
}
else if (part.startsWith('rssi:')) {
def rssiResult = getRssiResult(part, description)
if (rssiResult) {
results << rssiResult
}
}
else if (part.startsWith('lqi:')) {
def lqiResult = getLqiResult(part, description)
if (lqiResult) {
results << lqiResult
}
}
}
results
}
private getBatteryResult(part, description) {
def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null
def name = "battery"
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
def unit = "%"
def linkText = getLinkText(device)
def descriptionText = "$linkText battery was ${value}${unit}"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
unit: unit,
linkText: linkText,
descriptionText: descriptionText,
handlerName: name,
isStateChange: isStateChange,
//displayed: displayed(description, isStateChange)
displayed: false
]
}
private getRssiResult(part, description) {
def name = "rssi"
def parts = part.split(":")
if (parts.size() != 2) return null
def valueString = parts[1].trim()
def valueInt = Integer.parseInt(valueString, 16)
def value = (valueInt - 128).toString()
def linkText = getLinkText(device)
def descriptionText = "$linkText was $value dBm"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
unit: "dBm",
linkText: linkText,
descriptionText: descriptionText,
handlerName: null,
isStateChange: isStateChange,
//displayed: displayed(description, isStateChange)
displayed: false
]
}
/**
* Use LQI (Link Quality Indicator) as a measure of signal strength. The values
* are 0 to 255 (0x00 to 0xFF) and higher values represent higher signal
* strength. Return as a percentage of 255.
*
* Note: To make the signal strength indicator more accurate, we could combine
* LQI with RSSI.
*/
private getLqiResult(part, description) {
def name = "lqi"
def parts = part.split(":")
if (parts.size() != 2) return null
def valueString = parts[1].trim()
def valueInt = Integer.parseInt(valueString, 16)
def percentageOf = 255
def value = Math.round((valueInt / percentageOf * 100)).toString()
def unit = "%"
def linkText = getLinkText(device)
def descriptionText = "$linkText Signal (LQI) was ${value}${unit}"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
unit: unit,
linkText: linkText,
descriptionText: descriptionText,
handlerName: null,
isStateChange: isStateChange,
//displayed: displayed(description, isStateChange)
displayed: false
]
}
/**
* Copyright 2015 SmartThings
*
* 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.
*
*/
metadata {
definition (name: "Arrival Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Tone"
capability "Actuator"
capability "Signal Strength"
capability "Presence Sensor"
capability "Sensor"
capability "Battery"
fingerprint profileId: "FC01", deviceId: "019A"
fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000,0003", outClusters: "0003"
fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000", outClusters: "0006"
}
simulator {
status "present": "presence: 1"
status "not present": "presence: 0"
status "battery": "battery: 27, batteryDivisor: 0A, rssi: 100, lqi: 64"
}
preferences {
section {
image(name: 'educationalcontent', multiple: true, images: [
"http://cdn.device-gse.smartthings.com/Arrival/Arrival1.jpg",
"http://cdn.device-gse.smartthings.com/Arrival/Arrival2.jpg"
])
}
}
tiles {
standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
state "present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ebeef2"
}
standardTile("beep", "device.beep", decoration: "flat") {
state "beep", label:'', action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff"
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
state "battery", label:'${currentValue}% battery', unit:""/*, backgroundColors:[
[value: 5, color: "#BC2323"],
[value: 10, color: "#D04E00"],
[value: 15, color: "#F1D801"],
[value: 16, color: "#FFFFFF"]
]*/
}
/*
valueTile("lqi", "device.lqi", decoration: "flat", inactiveLabel: false) {
state "lqi", label:'${currentValue}% signal', unit:""
}
*/
main "presence"
details(["presence", "beep", "battery"/*, "lqi"*/])
}
}
def beep() {
/*
You can make the speaker turn on for 0.5-second beeps by sending some CLI commands:
Command: send raw, wait 7, send raw, wait 7, send raw
Future: new packet type "st.beep"
raw 0xFC05 {15 0A 11 00 00 15 01}
send 0x2F7F 2 2
where "0xABCD" is the node ID of the Smart Tag, everything else above is a constant. Except
the "15 01" at the end of the first raw command, that sets the speaker's period (reciprocal
of frequency). You can play with this value up or down to experiment with loudness as the
loudness will be strongly dependent upon frequency and the enclosure that it's in. Note that
"15 01" represents the hex number 0x0115 so a lower frequency is "16 01" (longer period) and
a higher frequency is "14 01" (shorter period). Note that since the tag only checks its parent
for messages every 5 seconds (while at rest) or every 3 seconds (while in motion) it will take
up to this long from the time you send the message to the time you hear a sound.
*/
[
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}"
]
}
def parse(String description) {
def results
if (isBatteryMessage(description)) {
results = parseBatteryMessage(description)
}
else {
results = parsePresenceMessage(description)
}
log.debug "Parse returned $results.descriptionText"
results
}
private Map parsePresenceMessage(String description) {
def name = parseName(description)
def value = parseValue(description)
def linkText = getLinkText(device)
def descriptionText = parseDescriptionText(linkText, value, description)
def handlerName = getState(value)
def isStateChange = isStateChange(device, name, value)
def results = [
name: name,
value: value,
unit: null,
linkText: linkText,
descriptionText: descriptionText,
handlerName: handlerName,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
]
results
}
private String parseName(String description) {
if (description?.startsWith("presence: ")) {
return "presence"
}
null
}
private String parseValue(String description) {
if (description?.startsWith("presence: "))
{
if (description?.endsWith("1"))
{
return "present"
}
else if (description?.endsWith("0"))
{
return "not present"
}
}
description
}
private parseDescriptionText(String linkText, String value, String description) {
switch(value) {
case "present": return "$linkText has arrived"
case "not present": return "$linkText has left"
default: return value
}
}
private getState(String value) {
def state = value
if (value == "present") {
state = "arrived"
}
else if (value == "not present") {
state = "left"
}
state
}
private Boolean isBatteryMessage(String description) {
// "raw:36EF1C, dni:36EF, battery:1B, rssi:, lqi:"
description ==~ /.*battery:.*rssi:.*lqi:.*/
}
private List parseBatteryMessage(String description) {
def results = []
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('battery:')) {
def batteryResult = getBatteryResult(part, description)
if (batteryResult) {
results << batteryResult
}
}
else if (part.startsWith('rssi:')) {
def rssiResult = getRssiResult(part, description)
if (rssiResult) {
results << rssiResult
}
}
else if (part.startsWith('lqi:')) {
def lqiResult = getLqiResult(part, description)
if (lqiResult) {
results << lqiResult
}
}
}
results
}
private getBatteryResult(part, description) {
def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null
def name = "battery"
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
def unit = "%"
def linkText = getLinkText(device)
def descriptionText = "$linkText battery was ${value}${unit}"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
unit: unit,
linkText: linkText,
descriptionText: descriptionText,
handlerName: name,
isStateChange: isStateChange,
//displayed: displayed(description, isStateChange)
displayed: false
]
}
private getRssiResult(part, description) {
def name = "rssi"
def parts = part.split(":")
if (parts.size() != 2) return null
def valueString = parts[1].trim()
def valueInt = Integer.parseInt(valueString, 16)
def value = (valueInt - 128).toString()
def linkText = getLinkText(device)
def descriptionText = "$linkText was $value dBm"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
unit: "dBm",
linkText: linkText,
descriptionText: descriptionText,
handlerName: null,
isStateChange: isStateChange,
//displayed: displayed(description, isStateChange)
displayed: false
]
}
/**
* Use LQI (Link Quality Indicator) as a measure of signal strength. The values
* are 0 to 255 (0x00 to 0xFF) and higher values represent higher signal
* strength. Return as a percentage of 255.
*
* Note: To make the signal strength indicator more accurate, we could combine
* LQI with RSSI.
*/
private getLqiResult(part, description) {
def name = "lqi"
def parts = part.split(":")
if (parts.size() != 2) return null
def valueString = parts[1].trim()
def valueInt = Integer.parseInt(valueString, 16)
def percentageOf = 255
def value = Math.round((valueInt / percentageOf * 100)).toString()
def unit = "%"
def linkText = getLinkText(device)
def descriptionText = "$linkText Signal (LQI) was ${value}${unit}"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
unit: unit,
linkText: linkText,
descriptionText: descriptionText,
handlerName: null,
isStateChange: isStateChange,
//displayed: displayed(description, isStateChange)
displayed: false
]
}

View File

@@ -22,6 +22,10 @@ metadata {
capability "Polling"
}
simulator {
// TODO: define status and reply messages here
}
tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F",
@@ -52,12 +56,16 @@ metadata {
}
def refresh() {
log.debug "refresh called"
log.debug "refresh..."
poll()
}
void poll() {
log.debug "Executing 'poll' using parent SmartApp"
parent.pollChild(this)
parent.pollChildren(this)
}
//generate custom mobile activity feeds event
def generateActivityFeedsEvent(notificationMessage) {
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
}

View File

@@ -19,49 +19,34 @@ metadata {
definition (name: "Ecobee Thermostat", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Thermostat"
capability "Temperature Measurement"
capability "Polling"
capability "Sensor"
capability "Refresh"
capability "Relative Humidity Measurement"
capability "Refresh"
command "generateEvent"
command "raiseSetpoint"
command "lowerSetpoint"
command "resumeProgram"
command "switchMode"
command "switchFanMode"
command "generateEvent"
command "raiseSetpoint"
command "lowerSetpoint"
command "resumeProgram"
command "switchMode"
attribute "thermostatSetpoint","number"
attribute "thermostatStatus","string"
attribute "maxHeatingSetpoint", "number"
attribute "minHeatingSetpoint", "number"
attribute "maxCoolingSetpoint", "number"
attribute "minCoolingSetpoint", "number"
attribute "deviceTemperatureUnit", "number"
attribute "thermostatSetpoint","number"
attribute "thermostatStatus","string"
}
tiles {
simulator { }
tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[
// Celsius
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 28, color: "#f1d801"],
[value: 35, color: "#d04e00"],
[value: 37, color: "#bc2323"],
// Fahrenheit
[value: 40, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
@@ -73,9 +58,10 @@ metadata {
state "updating", label:"Working", icon: "st.secondary.secondary"
}
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
state "auto", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-auto"
state "on", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-on"
state "updating", label:"Working", icon: "st.secondary.secondary"
state "auto", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "on"
state "on", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "off"
state "off", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "circulate"
state "circulate", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "auto"
}
standardTile("upButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") {
state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up"
@@ -105,14 +91,11 @@ metadata {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("resumeProgram", "device.resumeProgram", inactiveLabel: false, decoration: "flat") {
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume', icon:"st.samsung.da.oven_ic_send"
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume Schedule', icon:"st.samsung.da.oven_ic_send"
state "updating", label:"Working", icon: "st.secondary.secondary"
}
valueTile("humidity", "device.humidity", decoration: "flat") {
state "humidity", label:'${currentValue}%'
}
main "temperature"
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "fanMode","humidity", "resumeProgram", "refresh"])
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh"])
}
preferences {
@@ -124,6 +107,8 @@ metadata {
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
// TODO: handle '' attribute
}
def refresh() {
@@ -148,27 +133,16 @@ def generateEvent(Map results) {
def isChange = false
def isDisplayed = true
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name]
handlerName: name]
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") {
def sendValue = value? convertTemperatureIfNeeded(value.toDouble(), "F", 1): value //API return temperature value in F
isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
event << [value: sendValue, displayed: false]
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false]
} else if (name=="thermostatFanMode"){
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false]
} else if (name=="humidity") {
isChange = isStateChange(device, name, value.toString())
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
} else {
isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange
@@ -184,19 +158,13 @@ def generateEvent(Map results) {
//return descriptionText to be shown on mobile activity feed
private getThermostatDescriptionText(name, value, linkText) {
if(name == "temperature") {
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
return "$linkText temperature is $sendValue ${location.temperatureScale}"
return "$linkText temperature is $value°F"
} else if(name == "heatingSetpoint") {
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
return "heating setpoint is $sendValue ${location.temperatureScale}"
return "heating setpoint is $value°F"
} else if(name == "coolingSetpoint"){
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
return "cooling setpoint is $sendValue ${location.temperatureScale}"
return "cooling setpoint is $value°F"
} else if (name == "thermostatMode") {
return "thermostat mode is ${value}"
@@ -204,26 +172,26 @@ private getThermostatDescriptionText(name, value, linkText) {
} else if (name == "thermostatFanMode") {
return "thermostat fan mode is ${value}"
} else if (name == "humidity") {
return "humidity is ${value} %"
} else {
return "${name} = ${value}"
}
}
void setHeatingSetpoint(setpoint) {
log.debug "***heating setpoint $setpoint"
setHeatingSetpoint(setpoint.toDouble())
}
void setHeatingSetpoint(Double setpoint) {
// def mode = device.currentValue("thermostatMode")
def heatingSetpoint = setpoint
def coolingSetpoint = device.currentValue("coolingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble()
def deviceId = device.deviceNetworkId.split(/\./).last()
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
//enforce limits of heatingSetpoint
if (heatingSetpoint > maxHeatingSetpoint) {
heatingSetpoint = maxHeatingSetpoint
} else if (heatingSetpoint < minHeatingSetpoint) {
heatingSetpoint = minHeatingSetpoint
if (heatingSetpoint > 79) {
heatingSetpoint = 79
} else if (heatingSetpoint < 45) {
heatingSetpoint = 45
}
//enforce limits of heatingSetpoint vs coolingSetpoint
@@ -233,34 +201,32 @@ void setHeatingSetpoint(setpoint) {
log.debug "Sending setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
generateSetpointEvent()
generateStatusEvent()
} else {
log.error "Error setHeatingSetpoint(setpoint)"
log.error "Error setHeatingSetpoint(setpoint)" //This error is handled by the connect app
}
}
void setCoolingSetpoint(setpoint) {
log.debug "***cooling setpoint $setpoint"
def heatingSetpoint = device.currentValue("heatingSetpoint")
setCoolingSetpoint(setpoint.toDouble())
}
void setCoolingSetpoint(Double setpoint) {
// def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble()
def coolingSetpoint = setpoint
def deviceId = device.deviceNetworkId.split(/\./).last()
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if (coolingSetpoint > maxCoolingSetpoint) {
coolingSetpoint = maxCoolingSetpoint
} else if (coolingSetpoint < minCoolingSetpoint) {
coolingSetpoint = minCoolingSetpoint
if (coolingSetpoint > 92) {
coolingSetpoint = 92
} else if (coolingSetpoint < 65) {
coolingSetpoint = 65
}
//enforce limits of heatingSetpoint vs coolingSetpoint
@@ -270,18 +236,15 @@ void setCoolingSetpoint(setpoint) {
log.debug "Sending setCoolingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) {
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
generateSetpointEvent()
generateStatusEvent()
} else {
log.error "Error setCoolingSetpoint(setpoint)"
log.error "Error setCoolingSetpoint(setpoint)" //This error is handled by the connect app
}
}
@@ -315,7 +278,7 @@ def modes() {
}
def fanModes() {
["on", "auto"]
["off", "on", "auto", "circulate"]
}
def switchMode() {
@@ -344,15 +307,17 @@ def switchFanMode() {
def returnCommand
switch (currentFanMode) {
case "on":
returnCommand = switchToFanMode("auto")
case "fanAuto":
returnCommand = switchToFanMode("fanOn")
break
case "auto":
returnCommand = switchToFanMode("on")
case "fanOn":
returnCommand = switchToFanMode("fanCirculate")
break
case "fanCirculate":
returnCommand = switchToFanMode("fanAuto")
break
}
if(!currentFanMode) { returnCommand = switchToFanMode("auto") }
if(!currentFanMode) { returnCommand = switchToFanMode("fanOn") }
returnCommand
}
@@ -361,20 +326,25 @@ def switchToFanMode(nextMode) {
log.debug "switching to fan mode: $nextMode"
def returnCommand
if(nextMode == "auto") {
if(!fanModes.contains("auto")) {
if(nextMode == "fanAuto") {
if(!fanModes.contains("fanAuto")) {
returnCommand = fanAuto()
} else {
returnCommand = switchToFanMode("on")
returnCommand = switchToFanMode("fanOn")
}
} else if(nextMode == "on") {
if(!fanModes.contains("on")) {
} else if(nextMode == "fanOn") {
if(!fanModes.contains("fanOn")) {
returnCommand = fanOn()
} else {
returnCommand = switchToFanMode("auto")
returnCommand = switchToFanMode("fanCirculate")
}
} else if(nextMode == "fanCirculate") {
if(!fanModes.contains("fanCirculate")) {
returnCommand = fanCirculate()
} else {
returnCommand = switchToFanMode("fanAuto")
}
}
returnCommand
}
@@ -384,10 +354,13 @@ def getDataByName(String name) {
def setThermostatMode(String value) {
log.debug "setThermostatMode({$value})"
}
def setThermostatFanMode(String value) {
log.debug "setThermostatFanMode({$value})"
}
def generateModeEvent(mode) {
@@ -430,10 +403,6 @@ def heat() {
generateStatusEvent()
}
def emergencyHeat() {
auxHeatOnly()
}
def auxHeatOnly() {
log.debug "auxHeatOnly"
def deviceId = device.deviceNetworkId.split(/\./).last()
@@ -478,44 +447,26 @@ def auto() {
def fanOn() {
log.debug "fanOn"
String fanMode = "on"
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last()
// parent.setFanMode (this,"on")
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
generateFanModeEvent(fanMode)
} else {
log.debug "Error setting new mode."
def currentFanMode = device.currentState("thermostatFanMode")?.value
generateModeEvent(currentFanMode) // reset the tile back
}
}
def fanAuto() {
log.debug "fanAuto"
String fanMode = "auto"
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last()
// parent.setFanMode (this,"auto")
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
}
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
def fanCirculate() {
log.debug "fanCirculate"
// parent.setFanMode (this,"circulate")
}
def fanOff() {
log.debug "fanOff"
// parent.setFanMode (this,"off")
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
generateFanModeEvent(fanMode)
} else {
log.debug "Error setting new mode."
def currentFanMode = device.currentState("thermostatFanMode")?.value
generateModeEvent(currentFanMode) // reset the tile back
}
}
def generateSetpointEvent() {
@@ -525,42 +476,20 @@ def generateSetpointEvent() {
def mode = device.currentValue("thermostatMode")
log.debug "Current Mode = ${mode}"
def heatingSetpoint = device.currentValue("heatingSetpoint")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
log.debug "Heating Setpoint = ${heatingSetpoint}"
def coolingSetpoint = device.currentValue("coolingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
log.debug "Cooling Setpoint = ${coolingSetpoint}"
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if(location.temperatureScale == "C")
{
maxHeatingSetpoint = roundC(maxHeatingSetpoint)
maxCoolingSetpoint = roundC(maxCoolingSetpoint)
minHeatingSetpoint = roundC(minHeatingSetpoint)
minCoolingSetpoint = roundC(minCoolingSetpoint)
heatingSetpoint = roundC(heatingSetpoint)
coolingSetpoint = roundC(coolingSetpoint)
}
sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale)
sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale)
if (mode == "heat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint )
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
}
else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint)
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString())
} else if (mode == "auto") {
@@ -570,9 +499,9 @@ def generateSetpointEvent() {
sendEvent("name":"thermostatSetpoint", "value":"Off")
} else if (mode == "auxHeatOnly") {
} else if (mode == "emergencyHeat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint)
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
}
@@ -581,30 +510,26 @@ def generateSetpointEvent() {
void raiseSetpoint() {
def mode = device.currentValue("thermostatMode")
def targetvalue
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow raiseSetpoint"
} else {
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger()
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
if (device.latestState('thermostatSetpoint')) {
targetvalue = device.latestState('thermostatSetpoint').value
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
targetvalue = device.latestState('thermostatSetpoint').value as Integer
} else {
targetvalue = 0
}
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
targetvalue = targetvalue + 1
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) {
targetvalue = maxHeatingSetpoint
} else if (mode == "cool" && targetvalue > maxCoolingSetpoint) {
targetvalue = maxCoolingSetpoint
if (mode == "heat" && targetvalue > 79) {
targetvalue = 79
} else if (mode == "cool" && targetvalue > 92) {
targetvalue = 92
}
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
@@ -618,29 +543,25 @@ void raiseSetpoint() {
void lowerSetpoint() {
def mode = device.currentValue("thermostatMode")
def targetvalue
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow lowerSetpoint"
} else {
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger()
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
if (device.latestState('thermostatSetpoint')) {
targetvalue = device.latestState('thermostatSetpoint').value
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
targetvalue = device.latestState('thermostatSetpoint').value as Integer
} else {
targetvalue = 0
}
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
targetvalue = targetvalue - 1
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) {
targetvalue = minHeatingSetpoint
} else if (mode == "cool" && targetvalue < minCoolingSetpoint) {
targetvalue = minCoolingSetpoint
if (mode == "heat" && targetvalue.toInteger() < 45) {
targetvalue = 45
} else if (mode == "cool" && targetvalue.toInteger() < 65) {
targetvalue = 65
}
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
@@ -654,15 +575,15 @@ void lowerSetpoint() {
void alterSetpoint(temp) {
def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def deviceId = device.deviceNetworkId.split(/\./).last()
def targetHeatingSetpoint
def targetCoolingSetpoint
//step1: check thermostatMode, enforce limits before sending request to cloud
if (mode == "heat" || mode == "auxHeatOnly"){
if (mode == "heat"){
if (temp.value > coolingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
@@ -681,22 +602,19 @@ void alterSetpoint(temp) {
}
}
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
"coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to ${targetHeatingSetpoint} " +
"coolingSetpoint to ${targetCoolingSetpoint} with holdType : ${holdType}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
//step2: call parent.setHold to send http request to 3rd party cloud
if (parent.setHold(this, targetHeatingSetpoint, targetCoolingSetpoint, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value.toString(), displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint)
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
} else {
log.error "Error alterSetpoint()"
if (mode == "heat" || mode == "auxHeatOnly"){
if (mode == "heat"){
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
} else if (mode == "cool") {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
@@ -708,9 +626,9 @@ void alterSetpoint(temp) {
def generateStatusEvent() {
def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def temperature = device.currentValue("temperature")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def temperature = device.currentValue("temperature").toInteger()
def statusText
@@ -725,14 +643,14 @@ def generateStatusEvent() {
if (temperature >= heatingSetpoint)
statusText = "Right Now: Idle"
else
statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}"
statusText = "Heating to ${heatingSetpoint}° F"
} else if (mode == "cool") {
if (temperature <= coolingSetpoint)
statusText = "Right Now: Idle"
else
statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}"
statusText = "Cooling to ${coolingSetpoint}° F"
} else if (mode == "auto") {
@@ -742,7 +660,7 @@ def generateStatusEvent() {
statusText = "Right Now: Off"
} else if (mode == "auxHeatOnly") {
} else if (mode == "emergencyHeat") {
statusText = "Emergency Heat"
@@ -755,18 +673,7 @@ def generateStatusEvent() {
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
}
//generate custom mobile activity feeds event
def generateActivityFeedsEvent(notificationMessage) {
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
}
def roundC (tempC) {
return (Math.round(tempC.toDouble() * 2))/2
}
def convertFtoC (tempF) {
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
}
def convertCtoF (tempC) {
return (Math.round(tempC * (9/5)) + 32).toInteger()
}

View File

@@ -84,7 +84,6 @@ def setHue(percentage) {
log.error("Bad setHue result: [${resp.status}] ${resp.data}")
}
}
return []
}
def setSaturation(percentage) {
@@ -98,7 +97,6 @@ def setSaturation(percentage) {
log.error("Bad setSaturation result: [${resp.status}] ${resp.data}")
}
}
return []
}
def setColor(Map color) {
@@ -124,15 +122,13 @@ def setColor(Map color) {
parent.logErrors(logObject:log) {
def resp = parent.apiPUT("/lights/${selector()}/state", [color: attrs.join(" "), power: "on"])
if (resp.status < 300) {
if (color.hex)
sendEvent(name: "color", value: color.hex)
sendEvent(name: "color", value: color.hex)
sendEvent(name: "switch", value: "on")
events.each { sendEvent(it) }
} else {
log.error("Bad setColor result: [${resp.status}] ${resp.data}")
}
}
return []
}
def setLevel(percentage) {
@@ -154,7 +150,6 @@ def setLevel(percentage) {
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
}
}
return []
}
def setColorTemperature(kelvin) {
@@ -170,7 +165,6 @@ def setColorTemperature(kelvin) {
}
}
return []
}
def on() {
@@ -180,7 +174,6 @@ def on() {
sendEvent(name: "switch", value: "on")
}
}
return []
}
def off() {
@@ -190,7 +183,6 @@ def off() {
sendEvent(name: "switch", value: "off")
}
}
return []
}
def poll() {

View File

@@ -84,7 +84,6 @@ def setLevel(percentage) {
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
}
}
return []
}
def setColorTemperature(kelvin) {
@@ -100,7 +99,6 @@ def setColorTemperature(kelvin) {
log.error("Bad setColorTemperature result: [${resp.status}] ${resp.data}")
}
}
return []
}
def on() {
@@ -110,7 +108,6 @@ def on() {
sendEvent(name: "switch", value: "on")
}
}
return []
}
def off() {
@@ -120,7 +117,6 @@ def off() {
sendEvent(name: "switch", value: "off")
}
}
return []
}
def poll() {

View File

@@ -0,0 +1,235 @@
/**
* Copyright 2015 SmartThings
*
* 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.
*
* Samsung TV
*
* Author: SmartThings (juano23@gmail.com)
* Date: 2015-01-08
*/
metadata {
definition (name: "Samsung Smart TV", namespace: "smartthings", author: "SmartThings") {
capability "switch"
command "mute"
command "source"
command "menu"
command "tools"
command "HDMI"
command "Sleep"
command "Up"
command "Down"
command "Left"
command "Right"
command "chup"
command "chdown"
command "prech"
command "volup"
command "voldown"
command "Enter"
command "Return"
command "Exit"
command "Info"
command "Size"
}
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
state "default", label:'TV', action:"switch.off", icon:"st.Electronics.electronics15", backgroundColor:"#ffffff"
}
standardTile("power", "device.switch", width: 1, height: 1, canChangeIcon: false) {
state "default", label:'', action:"switch.off", decoration: "flat", icon:"st.thermostat.heating-cooling-off", backgroundColor:"#ffffff"
}
standardTile("mute", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Mute', action:"mute", icon:"st.custom.sonos.muted", backgroundColor:"#ffffff"
}
standardTile("source", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Source', action:"source", icon:"st.Electronics.electronics15"
}
standardTile("tools", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Tools', action:"tools", icon:"st.secondary.tools"
}
standardTile("menu", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Menu', action:"menu", icon:"st.vents.vent"
}
standardTile("HDMI", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Source', action:"HDMI", icon:"st.Electronics.electronics15"
}
standardTile("Sleep", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Sleep', action:"Sleep", icon:"st.Bedroom.bedroom10"
}
standardTile("Up", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Up', action:"Up", icon:"st.thermostat.thermostat-up"
}
standardTile("Down", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Down', action:"Down", icon:"st.thermostat.thermostat-down"
}
standardTile("Left", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Left', action:"Left", icon:"st.thermostat.thermostat-left"
}
standardTile("Right", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Right', action:"Right", icon:"st.thermostat.thermostat-right"
}
standardTile("chup", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'CH Up', action:"chup", icon:"st.thermostat.thermostat-up"
}
standardTile("chdown", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'CH Down', action:"chdown", icon:"st.thermostat.thermostat-down"
}
standardTile("prech", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Pre CH', action:"prech", icon:"st.secondary.refresh-icon"
}
standardTile("volup", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Vol Up', action:"volup", icon:"st.thermostat.thermostat-up"
}
standardTile("voldown", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Vol Down', action:"voldown", icon:"st.thermostat.thermostat-down"
}
standardTile("Enter", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Enter', action:"Enter", icon:"st.illuminance.illuminance.dark"
}
standardTile("Return", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Return', action:"Return", icon:"st.secondary.refresh-icon"
}
standardTile("Exit", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Exit', action:"Exit", icon:"st.locks.lock.unlocked"
}
standardTile("Info", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Info', action:"Info", icon:"st.motion.acceleration.active"
}
standardTile("Size", "device.switch", decoration: "flat", canChangeIcon: false) {
state "default", label:'Picture Size', action:"Size", icon:"st.contact.contact.open"
}
main "switch"
details (["power","HDMI","Sleep","chup","prech","volup","chdown","mute","voldown", "menu", "Up", "tools", "Left", "Enter", "Right", "Return", "Down", "Exit", "Info","Size"])
}
def parse(String description) {
return null
}
def off() {
log.debug "Turning TV OFF"
parent.tvAction("POWEROFF",device.deviceNetworkId)
sendEvent(name:"Command", value: "Power Off", displayed: true)
}
def mute() {
log.trace "MUTE pressed"
parent.tvAction("MUTE",device.deviceNetworkId)
sendEvent(name:"Command", value: "Mute", displayed: true)
}
def source() {
log.debug "SOURCE pressed"
parent.tvAction("SOURCE",device.deviceNetworkId)
sendEvent(name:"Command", value: "Source", displayed: true)
}
def menu() {
log.debug "MENU pressed"
parent.tvAction("MENU",device.deviceNetworkId)
}
def tools() {
log.debug "TOOLS pressed"
parent.tvAction("TOOLS",device.deviceNetworkId)
sendEvent(name:"Command", value: "Tools", displayed: true)
}
def HDMI() {
log.debug "HDMI pressed"
parent.tvAction("HDMI",device.deviceNetworkId)
sendEvent(name:"Command sent", value: "Source", displayed: true)
}
def Sleep() {
log.debug "SLEEP pressed"
parent.tvAction("SLEEP",device.deviceNetworkId)
sendEvent(name:"Command", value: "Sleep", displayed: true)
}
def Up() {
log.debug "UP pressed"
parent.tvAction("UP",device.deviceNetworkId)
}
def Down() {
log.debug "DOWN pressed"
parent.tvAction("DOWN",device.deviceNetworkId)
}
def Left() {
log.debug "LEFT pressed"
parent.tvAction("LEFT",device.deviceNetworkId)
}
def Right() {
log.debug "RIGHT pressed"
parent.tvAction("RIGHT",device.deviceNetworkId)
}
def chup() {
log.debug "CHUP pressed"
parent.tvAction("CHUP",device.deviceNetworkId)
sendEvent(name:"Command", value: "Channel Up", displayed: true)
}
def chdown() {
log.debug "CHDOWN pressed"
parent.tvAction("CHDOWN",device.deviceNetworkId)
sendEvent(name:"Command", value: "Channel Down", displayed: true)
}
def prech() {
log.debug "PRECH pressed"
parent.tvAction("PRECH",device.deviceNetworkId)
sendEvent(name:"Command", value: "Prev Channel", displayed: true)
}
def Exit() {
log.debug "EXIT pressed"
parent.tvAction("EXIT",device.deviceNetworkId)
}
def volup() {
log.debug "VOLUP pressed"
parent.tvAction("VOLUP",device.deviceNetworkId)
sendEvent(name:"Command", value: "Volume Up", displayed: true)
}
def voldown() {
log.debug "VOLDOWN pressed"
parent.tvAction("VOLDOWN",device.deviceNetworkId)
sendEvent(name:"Command", value: "Volume Down", displayed: true)
}
def Enter() {
log.debug "ENTER pressed"
parent.tvAction("ENTER",device.deviceNetworkId)
}
def Return() {
log.debug "RETURN pressed"
parent.tvAction("RETURN",device.deviceNetworkId)
}
def Info() {
log.debug "INFO pressed"
parent.tvAction("INFO",device.deviceNetworkId)
sendEvent(name:"Command", value: "Info", displayed: true)
}
def Size() {
log.debug "PICTURE_SIZE pressed"
parent.tvAction("PICTURE_SIZE",device.deviceNetworkId)
sendEvent(name:"Command", value: "Picture Size", displayed: true)
}

View File

@@ -16,7 +16,6 @@ metadata {
capability "Water Sensor"
capability "Sensor"
capability "Battery"
capability "Temperature Measurement"
fingerprint deviceId: "0x2001", inClusters: "0x30,0x9C,0x9D,0x85,0x80,0x72,0x31,0x84,0x86"
fingerprint deviceId: "0x2101", inClusters: "0x71,0x70,0x85,0x80,0x72,0x31,0x84,0x86"
@@ -40,29 +39,17 @@ metadata {
attributeState "wet", label: "Wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
}
}
standardTile("temperatureState", "device.temperature", width: 2, height: 2) {
standardTile("temperature", "device.temperature", width: 2, height: 2) {
state "normal", icon:"st.alarm.temperature.normal", backgroundColor:"#ffffff"
state "freezing", icon:"st.alarm.temperature.freeze", backgroundColor:"#53a7c0"
state "overheated", icon:"st.alarm.temperature.overheat", backgroundColor:"#F80000"
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
main (["water", "temperatureState"])
details(["water", "temperatureState", "temperature", "battery"])
main (["water", "temperature"])
details(["water", "temperature", "battery"])
}
}
@@ -128,7 +115,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
map.descriptionText = "${device.displayName} is ${map.value}"
}
if(cmd.zwaveAlarmType == physicalgraph.zwave.commands.alarmv2.AlarmReport.ZWAVE_ALARM_TYPE_HEAT) {
map.name = "temperatureState"
map.name = "temperature"
if(cmd.zwaveAlarmEvent == 1) { map.value = "overheated"}
if(cmd.zwaveAlarmEvent == 2) { map.value = "overheated"}
if(cmd.zwaveAlarmEvent == 3) { map.value = "changing temperature rapidly"}
@@ -142,30 +129,17 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
map
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd)
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd)
{
def map = [:]
if(cmd.sensorType == 1) {
map.name = "temperature"
if(cmd.scale == 0) {
map.value = getTemperature(cmd.scaledSensorValue)
} else {
map.value = cmd.scaledSensorValue
}
map.unit = location.temperatureScale
}
map.name = "water"
map.value = cmd.value ? "wet" : "dry"
map.descriptionText = "${device.displayName} is ${map.value}"
map
}
def getTemperature(value) {
if(location.temperatureScale == "C"){
return value
} else {
return Math.round(celsiusToFahrenheit(value))
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd)
{
log.debug "COMMAND CLASS: $cmd"
}
}

View File

@@ -29,7 +29,6 @@ metadata {
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326-L", deviceJoinName: "Iris Motion Sensor"
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor"
}

View File

@@ -100,6 +100,9 @@ metadata {
]
)
}
valueTile("3axis", "device.threeAxis", decoration: "flat", wordWrap: false, width: 2, height: 2) {
state("threeAxis", label:'${currentValue}', unit:"", backgroundColor:"#ffffff")
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
@@ -109,7 +112,7 @@ metadata {
main(["status", "acceleration", "temperature"])
details(["status", "acceleration", "temperature", "battery", "refresh"])
details(["status", "acceleration", "temperature", "3axis", "battery", "refresh"])
}
}

View File

@@ -16,18 +16,17 @@
metadata {
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Battery"
capability "Battery"
capability "Configuration"
capability "Contact Sensor"
capability "Contact Sensor"
capability "Refresh"
capability "Temperature Measurement"
command "enrollResponse"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
}
simulator {

View File

@@ -25,7 +25,6 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702, 0B05", outClusters: "0003, 000A, 0019", manufacturer: "Jasco Products", model: "45853", deviceJoinName: "GE ZigBee Plug-In Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702, 0B05", outClusters: "000A, 0019", manufacturer: "Jasco Products", model: "45856", deviceJoinName: "GE ZigBee In-Wall Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 000F, 0B04", outClusters: "0019", manufacturer: "SmartThings", model: "outletv4", deviceJoinName: "Outlet"
}
tiles(scale: 2) {

View File

@@ -119,10 +119,6 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
{
def result = [createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)]
if (state.MSR == "011A-0601-0901" && device.currentState('motion') == null) { // Enerwave motion doesn't always get the associationSet that the hub sends on join
result << response(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))
}
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
result << response(zwave.batteryV1.batteryGet())
result << response("delay 1200")
@@ -183,4 +179,4 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false)
result
}
}

View File

@@ -0,0 +1,970 @@
/**
* Total Comfort API
*
* Based on Code by Eric Thomas
*
* 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
* lgk v 3 added optional outdoor temp sensors and preferences for it, also made api login required.
* 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.
* lgk version 4 supports celsius and fahrenheit with option, and now colors.
* lgk version 5, due to intermittant update failures added last update date/time tile so that you can see when it happended
* not there is a new input tzoffset which defaults to my time ie -5 which you must set .
* lgk version 6 add support for actually knowing the fan is on or not (added tile),
* and also the actual operating state ie heating,cooling or idle via new response variables.
* lgk version 7, change the new operating state to be a value vs standard tile
* to work around a bug smartthings caused in the latest 2.08 release with text wrapping.
* related also added icons to the operating state, and increase the width of the last update
* to avoid wrapping.
* 2-14-16 llb added full path to icons so the show on android Fire Tablet
* 2-14-16 llb added check for indoor humidity sensor to avoid 128 value if not supported
* 2-14-16 llb modified operating state tile to show control by Hold or Schedule
*
*/
preferences {
input("username", "text", title: "Username", description: "Your Total Comfort User Name", required: true)
input("password", "password", title: "Password", description: "Your Total Comfort password",required: true)
input("honeywelldevice", "text", title: "Device ID", description: "Your Device ID", required: true)
input ("enableOutdoorTemps", "enum", title: "Do you have the optional outdoor temperature sensor and want to enable it?", options: ["Yes", "No"], required: false, defaultValue: "No")
input ("tempScale", "enum", title: "Fahrenheit or Celsius?", options: ["F", "C"], required: false, defaultValue: "F")
input("tzOffset", "number", title: "Time zone offset +/-xx?", required: false, defaultValue: -5, description: "Time Zone Offset ie -5.")
}
metadata {
definition (name: "Total Comfort API", namespace:
"Total Comfort API", author: "Eric Thomas, modified lg kahn") {
capability "Polling"
capability "Thermostat"
capability "Refresh"
capability "Temperature Measurement"
capability "Sensor"
capability "Relative Humidity Measurement"
command "heatLevelUp"
command "heatLevelDown"
command "coolLevelUp"
command "coolLevelDown"
attribute "outdoorHumidity", "number"
attribute "outdoorTemperature", "number"
attribute "lastUpdate", "string"
}
simulator {
// TODO: define status and reply messages here
}
tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2, canChangeIcon: true) {
state("temperature", label: '${currentValue}°',
icon: "http://cdn.device-icons.smartthings.com/Weather/weather2-icn@3x.png",
unit:"F", backgroundColors: [
[value: -14, color: "#1e9cbb"],
[value: -10, color: "#90d2a7"],
[value: -5, color: "#44b621"],
[value: -2, color: "#f1d801"],
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 29, color: "#f1d801"],
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: false, canChangeIcon: true) {
state "off", label:'${name}', action:"thermostat.cool", icon: "http://cdn.device-icons.smartthings.com/Outdoor/outdoor19.png"
state "cool", label:'${name}', action:"thermostat.heat", icon: "http://cdn.device-icons.smartthings.com/Weather/weather7.png", backgroundColor: '#1e9cbb'
state "heat", label:'${name}', action:"thermostat.auto", icon: "http://cdn.device-icons.smartthings.com/Weather/weather14.png", backgroundColor: '#E14902'
state "auto", label:'${name}', action:"thermostat.off", icon: "http://cdn.device-icons.smartthings.com/Weather/weather3.png", backgroundColor: '#44b621'
}
standardTile("thermostatFanMode", "device.thermostatFanMode", inactiveLabel: false, canChangeIcon: true) {
state "auto", label:'${name}', action:"thermostat.fanAuto", icon: "http://cdn.device-icons.smartthings.com/Appliances/appliances11.png", backgroundColor: '#44b621'
state "circulate", label:'${name}', action:"thermostat.fanCirculate", icon: "http://cdn.device-icons.smartthings.com/Appliances/appliances11.png", backgroundColor: '#44b621'
state "on", label:'${name}', action:"thermostat.fanOn", icon: "http://cdn.device-icons.smartthings.com/Appliances/appliances11.png", backgroundColor: '#44b621'
}
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 3, width: 1, inactiveLabel: false) {
state "setCoolingSetpoint", label:'Set temperarure to', action:"thermostat.setCoolingSetpoint",
backgroundColors:[
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 29, color: "#f1d801"],
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
}
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false)
{
state "default", label:'Cool\n${currentValue}°', unit:"F",
backgroundColors: [
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 29, color: "#f1d801"],
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
}
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false)
{
state "default", label:'Heat\n${currentValue}°', unit: "F",
backgroundColors:[
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 29, color: "#f1d801"],
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
}
//tile added for operating state - Create the tiles for each possible state, look at other examples if you wish to change the icons here.
valueTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false) {
state 'default', label:'${currentValue}',
backgroundColors:[
[value: 0, color: "#911535"]]
state "Unknown", label:'${name}', backgroundColor : '#cc0000', icon: ""
}
standardTile("fanOperatingState", "device.fanOperatingState", inactiveLabel: false) {
state "On", label:'${name}',icon: "http://cdn.device-icons.smartthings.com/Appliances/appliances11.png", backgroundColor : '#53a7c0'
state "Idle", label:'${name}',icon: "http://cdn.device-icons.smartthings.com/Appliances/appliances11.png"
state "Unknown", label:'${name}',icon: "http://cdn.device-icons.smartthings.com/Appliances/appliances11.png", backgroundColor : '#cc0000'
}
standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
state "default", action:"polling.poll", icon:"http://cdn.device-icons.smartthings.com/secondary/refresh@2x.png"
}
standardTile("heatLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
state "heatLevelUp", label:' ', action:"heatLevelUp", icon:"http://cdn.device-icons.smartthings.com/thermostat/thermostat-up.png"
}
standardTile("heatLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
state "heatLevelDown", label:' ', action:"heatLevelDown", icon:"http://cdn.device-icons.smartthings.com/thermostat/thermostat-down.png"
}
standardTile("coolLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
state "coolLevelUp", label:' ', action:"coolLevelUp", icon:"http://cdn.device-icons.smartthings.com/thermostat/thermostat-up.png"
}
standardTile("coolLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
state "coolLevelDown", label:' ', action:"coolLevelDown", icon:"http://cdn.device-icons.smartthings.com/thermostat/thermostat-down.png"
}
valueTile("relativeHumidity", "device.relativeHumidity", inactiveLabel: false)
{
state "default", label:'Humidity\n${currentValue}%',
icon: "http://cdn.device-icons.smartthings.com/Weather/weather12-icn@3x.png",
unit:"%", backgroundColors : [
[value: 01, color: "#724529"],
[value: 11, color: "#724529"],
[value: 21, color: "#724529"],
[value: 35, color: "#44b621"],
[value: 49, color: "#44b621"],
[value: 50, color: "#1e9cbb"]
]
}
standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: false, canChangeIcon: true)
{
}
/* lgk new tiles for outside temp and hummidity */
valueTile("outdoorTemperature", "device.outdoorTemperature", width: 1, height: 1, canChangeIcon: true) {
state("temperature", label: 'Outdoor\n ${currentValue}°',
icon: "http://cdn.device-icons.smartthings.com/Weather/weather2-icn@3x.png",
unit:"F", backgroundColors: [
[value: -31, color: "#003591"],
[value: -10, color: "#90d2a7"],
[value: -5, color: "#44b621"],
[value: -2, color: "#f1d801"],
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 00, color: "#cccccc"],
[value: 31, color: "#153500"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
valueTile("outdoorHumidity", "device.outdoorHumidity", inactiveLabel: false){
state "default", label:'Outdoor\n ${currentValue}%',
icon: "http://cdn.device-icons.smartthings.com/Weather/weather12-icn@3x.png",
unit:"%", backgroundColors : [
[value: 01, color: "#724529"],
[value: 11, color: "#724529"],
[value: 21, color: "#724529"],
[value: 35, color: "#44b621"],
[value: 49, color: "#44b621"],
[value: 70, color: "#449c00"],
[value: 90, color: "#009cbb"]
]
}
valueTile("status", "device.lastUpdate", width: 3, height: 1, decoration: "flat") {
state "default", label: 'Last Update: ${currentValue}'
}
main "temperature"
details(["temperature", "thermostatMode", "thermostatFanMode",
"heatLevelUp", "heatingSetpoint" , "heatLevelDown", "coolLevelUp",
"coolingSetpoint", "coolLevelDown" ,"thermostatOperatingState","fanOperatingState",
"refresh","relativeHumidity","outdoorTemperature","outdoorHumidity", "status"])
}
}
def coolLevelUp()
{
state.DisplayUnits = settings.tempScale
if (state.DisplayUnits == "F")
{
int nextLevel = device.currentValue("coolingSetpoint") + 1
if( nextLevel > 99){
nextLevel = 99
}
log.debug "Setting cool set point up to: ${nextLevel}"
setCoolingSetpoint(nextLevel)
}
else
{
int nextLevel = device.currentValue("coolingSetpoint") + 0.5
if( nextLevel > 37){
nextLevel = 37
}
log.debug "Setting cool set point up to: ${nextLevel}"
setCoolingSetpoint(nextLevel)
}
}
def coolLevelDown()
{
state.DisplayUnits = settings.tempScale
if (state.DisplayUnits == "F")
{
int nextLevel = device.currentValue("coolingSetpoint") - 1
if( nextLevel < 50){
nextLevel = 50
}
log.debug "Setting cool set point down to: ${nextLevel}"
setCoolingSetpoint(nextLevel)
}
else
{
double nextLevel = device.currentValue("coolingSetpoint") - 0.5
if( nextLevel < 10){
nextLevel = 10
}
log.debug "Setting cool set point down to: ${nextLevel}"
setCoolingSetpoint(nextLevel)
}
}
def heatLevelUp()
{
state.DisplayUnits = settings.tempScale
if (state.DisplayUnits == "F")
{
log.debug "in fahrenheit level up"
int nextLevel = device.currentValue("heatingSetpoint") + 1
if( nextLevel > 90){
nextLevel = 90
}
log.debug "Setting heat set point up to: ${nextLevel}"
setHeatingSetpoint(nextLevel)
}
else
{
log.debug "in celsius level uo"
double nextLevel = device.currentValue("heatingSetpoint") + 0.5
if( nextLevel > 33){
nextLevel = 33
}
log.debug "Setting heat set point up to: ${nextLevel}"
setHeatingSetpoint(nextLevel)
}
}
def heatLevelDown()
{
state.DisplayUnits = settings.tempScale
if (state.DisplayUnits == "F")
{
log.debug "in fahrenheit level down"
int nextLevel = device.currentValue("heatingSetpoint") - 1
if( nextLevel < 40){
nextLevel = 40
}
log.debug "Setting heat set point down to: ${nextLevel}"
setHeatingSetpoint(nextLevel)
}
else
{
log.debug "in celsius level down"
double nextLevel = device.currentValue("heatingSetpoint") - 0.5
if( nextLevel < 4){
nextLevel = 4
}
log.debug "Setting heat set point down to: ${nextLevel}"
setHeatingSetpoint(nextLevel)
}
}
// parse events into attributes
def parse(String description) {
}
// handle commands
def setHeatingSetpoint(Double temp)
{
data.SystemSwitch = 'null'
data.HeatSetpoint = temp
data.CoolSetpoint = 'null'
data.HeatNextPeriod = 'null'
data.CoolNextPeriod = 'null'
data.StatusHeat='1'
data.StatusCool='1'
data.FanMode = 'null'
setStatus()
if(data.SetStatus==1)
{
sendEvent(name: 'heatingSetpoint', value: temp as double)
}
}
def setHeatingSetpoint(temp) {
data.SystemSwitch = 'null'
data.HeatSetpoint = temp
data.CoolSetpoint = 'null'
data.HeatNextPeriod = 'null'
data.CoolNextPeriod = 'null'
data.StatusHeat='1'
data.StatusCool='1'
data.FanMode = 'null'
setStatus()
if(data.SetStatus==1)
{
sendEvent(name: 'heatingSetpoint', value: temp as Integer)
}
}
def setCoolingSetpoint(double temp) {
data.SystemSwitch = 'null'
data.HeatSetpoint = 'null'
data.CoolSetpoint = temp
data.HeatNextPeriod = 'null'
data.CoolNextPeriod = 'null'
data.StatusHeat='1'
data.StatusCool='1'
data.FanMode = 'null'
setStatus()
if(data.SetStatus==1)
{
sendEvent(name: 'coolingSetpoint', value: temp as double)
}
}
def setCoolingSetpoint(temp) {
data.SystemSwitch = 'null'
data.HeatSetpoint = 'null'
data.CoolSetpoint = temp
data.HeatNextPeriod = 'null'
data.CoolNextPeriod = 'null'
data.StatusHeat='1'
data.StatusCool='1'
data.FanMode = 'null'
setStatus()
if(data.SetStatus==1)
{
sendEvent(name: 'coolingSetpoint', value: temp as Integer)
}
}
def setTargetTemp(temp) {
data.SystemSwitch = 'null'
data.HeatSetpoint = temp
data.CoolSetpoint = temp
data.HeatNextPeriod = 'null'
data.CoolNextPeriod = 'null'
data.StatusHeat='1'
data.StatusCool='1'
data.FanMode = 'null'
setStatus()
}
def setTargetTemp(double temp) {
data.SystemSwitch = 'null'
data.HeatSetpoint = temp
data.CoolSetpoint = temp
data.HeatNextPeriod = 'null'
data.CoolNextPeriod = 'null'
data.StatusHeat='1'
data.StatusCool='1'
data.FanMode = 'null'
setStatus()
}
def off() {
setThermostatMode(2)
}
def auto() {
setThermostatMode(4)
}
def heat() {
setThermostatMode(1)
}
def emergencyHeat() {
}
def cool() {
setThermostatMode(3)
}
def setThermostatMode(mode) {
data.SystemSwitch = mode
data.HeatSetpoint = 'null'
data.CoolSetpoint = 'null'
data.HeatNextPeriod = 'null'
data.CoolNextPeriod = 'null'
data.StatusHeat=1
data.StatusCool=1
data.FanMode = 'null'
setStatus()
def switchPos
if(mode==1)
switchPos = 'heat'
if(mode==2)
switchPos = 'off'
if(mode==3)
switchPos = 'cool'
/* lgk modified my therm has pos 5 for auto vision pro */
if(mode==4 || swithPos == 5)
switchPos = 'auto'
if(data.SetStatus==1)
{
sendEvent(name: 'thermostatMode', value: switchPos)
}
}
def fanOn() {
setThermostatFanMode(1)
}
def fanAuto() {
setThermostatFanMode(0)
}
def fanCirculate() {
setThermostatFanMode(2)
}
def setThermostatFanMode(mode) {
data.SystemSwitch = 'null'
data.HeatSetpoint = 'null'
data.CoolSetpoint = 'null'
data.HeatNextPeriod = 'null'
data.CoolNextPeriod = 'null'
data.StatusHeat='null'
data.StatusCool='null'
data.FanMode = mode
setStatus()
def fanMode
if(mode==0)
fanMode = 'auto'
if(mode==1)
fanMode = 'on'
if(mode==2)
fanMode = 'circulate'
if(data.SetStatus==1)
{
sendEvent(name: 'thermostatFanMode', value: fanMode)
}
}
def poll() {
refresh()
}
def setStatus() {
data.SetStatus = 0
login()
log.debug "Executing 'setStatus'"
def today= new Date()
log.debug "https://www.mytotalconnectcomfort.com/portal/Device/SubmitControlScreenChanges"
log.debug "setting heat setpoint to $data.HeatSetpoint"
log.debug "setting cool setpoint to $data.CoolSetpoint"
def params = [
uri: "https://www.mytotalconnectcomfort.com/portal/Device/SubmitControlScreenChanges",
headers: [
'Accept': 'application/json, text/javascript, */*; q=0.01',
'DNT': '1',
'Accept-Encoding': 'gzip,deflate,sdch',
'Cache-Control': 'max-age=0',
'Accept-Language': 'en-US,en,q=0.8',
'Connection': 'keep-alive',
'Host': 'rs.alarmnet.com',
'Referer': "https://www.mytotalconnectcomfort.com/portal/Device/Control/${settings.honeywelldevice}",
'X-Requested-With': 'XMLHttpRequest',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36',
'Cookie': data.cookiess ],
body: [ DeviceID: "${settings.honeywelldevice}", SystemSwitch : data.SystemSwitch ,HeatSetpoint :
data.HeatSetpoint, CoolSetpoint: data.CoolSetpoint, HeatNextPeriod:
data.HeatNextPeriod,CoolNextPeriod:data.CoolNextPeriod,StatusHeat:data.StatusHeat,
StatusCool:data.StatusCool,FanMode:data.FanMode,ThermostatUnits: settings.tempScale]
]
log.debug "params = $params"
httpPost(params) { response ->
log.debug "Request was successful, $response.status"
}
log.debug "SetStatus is 1 now"
data.SetStatus = 1
}
def getStatus() {
log.debug "Executing getStatus"
log.debug "enable outside temps = $enableOutdoorTemps"
def today= new Date()
log.debug "https://www.mytotalconnectcomfort.com/portal/Device/CheckDataSession/${settings.honeywelldevice}?_=$today.time"
def params = [
uri: "https://www.mytotalconnectcomfort.com/portal/Device/CheckDataSession/${settings.honeywelldevice}",
headers: [
'Accept': '*/*',
'DNT': '1',
'Cache' : 'false',
'dataType': 'json',
'Accept-Encoding': 'plain',
'Cache-Control': 'max-age=0',
'Accept-Language': 'en-US,en,q=0.8',
'Connection': 'keep-alive',
'Referer': 'https://www.mytotalconnectcomfort.com/portal',
'X-Requested-With': 'XMLHttpRequest',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36',
'Cookie': data.cookiess ],
]
log.debug "doing request"
httpGet(params) { response ->
log.debug "Request was successful, $response.status"
//log.debug "data = $response.data"
log.debug "ld = $response.data.latestData"
def curTemp = response.data.latestData.uiData.DispTemperature
def fanMode = response.data.latestData.fanData.fanMode
def switchPos = response.data.latestData.uiData.SystemSwitchPosition
def coolSetPoint = response.data.latestData.uiData.CoolSetpoint
def heatSetPoint = response.data.latestData.uiData.HeatSetpoint
def statusCool = response.data.latestData.uiData.StatusCool
def statusHeat = response.data.latestData.uiData.StatusHeat
def curHumidity = response.data.latestData.uiData.IndoorHumidity
def Boolean hasOutdoorHumid = response.data.latestData.uiData.OutdoorHumidityAvailable
def Boolean hasOutdoorTemp = response.data.latestData.uiData.OutdoorTemperatureAvailable
def curOutdoorHumidity = response.data.latestData.uiData.OutdoorHumidity
def curOutdoorTemp = response.data.latestData.uiData.OutdoorTemperature
def displayUnits = response.data.latestData.uiData.DisplayUnits
def fanIsRunning = response.data.latestData.fanData.fanIsRunning
def equipmentStatus = response.data.latestData.uiData.EquipmentOutputStatus
def Boolean hasIndoorHumid = response.data.latestData.uiData.IndoorHumiditySensorAvailable
def curSetpointStatus = response.data.latestData.uiData.CurrentSetpointStatus
def TempHoldTime = response.data.latestData.uiData.TemporaryHoldUntilTime
def int TempHoldTimeHrs = TempHoldTime / 60
def TempHoldTimeMins = TempHoldTime % 60
/*ld = [fanData:[fanModeCirculateAllowed:true, fanModeAutoAllowed:true, fanModeFollowScheduleAllowed:false,
fanIsRunning:false, fanModeOnAllowed:true, fanMode:0],
drData:[Load:127.5, HeatSetpLimit:0,
OptOutable:false, DeltaHeatSP:-0.01, CoolSetpLimit:0, Phase:-1, DeltaCoolSP:-0.01],
uiData:[OutdoorTemperature:128.0000, TemporaryHoldUntilTime:0, ScheduleHeatSp:67.0000,
DeviceID:453824, DispTemperatureAvailable:true, VacationHold:0, VacationHoldUntilTime:0,
CoolSetpoint:76.0000, ScheduleCoolSp:76.0000, SwitchHeatAllowed:true, CoolNextPeriod:67,
IndoorHumidity:31.0000, SwitchAutoAllowed:true, SetpointChangeAllowed:true, HeatLowerSetptLimit:40.0000,
OutdoorHumidStatus:128, SwitchOffAllowed:true, OutdoorHumidityAvailable:false,
StatusCool:0, OutdoorTemperatureAvailable:false, EquipmentOutputStatus:0, StatusHeat:0,
CurrentSetpointStatus:0, HoldUntilCapable:true, CoolUpperSetptLimit:99.0000, SwitchCoolAllowed:true,
OutdoorHumidity:128.0000, DualSetpointStatus:false, SwitchEmergencyHeatAllowed:false, Commercial:false,
CoolLowerSetptLimit:50.0000, OutdoorHumiditySensorNotFault:true, IndoorHumiditySensorAvailable:true,
ScheduleCapable:true, DisplayUnits:F, DispTemperature:70.0000, Deadband:3.0000, HeatUpperSetptLimit:90.0000,
IsInVacationHoldMode:false, OutdoorTemperatureSensorNotFault:true, HeatSetpoint:67.0000, DispTemperatureStatus:0,
HeatNextPeriod:67, IndoorHumiditySensorNotFault:true, OutdoorTempStatus:128, IndoorHumidStatus:0,
SystemSwitchPosition:4], canControlHumidification:false, hasFan:true]
log.trace("Fan operating state: ${response.data.latestData.fanData.fanIsRunning}")
log.trace("EquipmentOutputStatus: ${response.data.latestData.uiData.EquipmentOutputStatus}")
log.trace("IndoorHumidity: ${response.data.latestData.uiData.IndoorHumidity}")
log.trace("OutdoorTemp = ${response.data.latestData.uiData.OutdoorTemperature}")
log.trace("fanMode: ${response.data.latestData.fanData.fanMode}")
log.trace("SystenSwitchPosition: ${response.data.latestData.uiData.SystemSwitchPosition}")
log.trace("StatusCool: ${response.data.latestData.uiData.StatusCool}")
log.trace("StatusHeat: ${response.data.latestData.uiData.StatusHeat}")
log.trace("IndoorHumiditySensorAvailable: ${response.data.latestData.uiData.IndoorHumiditySensorAvailable}")
log.trace("IndoorHumidityAvailable: ${response.data.latestData.uiData.IndoorHumidityAvailable}")
log.debug "OutdoorHumidityAvailable: response.data.latestData.uiData.OutdoorHumidityAvailable"
log.debug "OutdoorTemperatureAvailable: $response.data.latestData.uiData.OutdoorTemperatureAvailable"
log.debug "OutdoorHumiditySensorNotFault = $response.data.latestData.uiData.OutdoorHumiditySensorNotFault"
log.debug "OutdoorTemperatureSensorNotFault = $response.data.latestData.uiData.OutdoorTemperatureSensorNotFault"
log.debug "IndoorHumiditySensorNotFault: $response.data.latestData.uiData.IndoorHumiditySensorNotFault"
log.debug "IndoorHumidStatus: $response.data.latestData.uiData.IndoorHumidStatus"
log.debug "OutdoorHumidStatus: $response.data.latestData.uiData.OutdoorHumidStatus"
log.debug "OutdoorHumidity: = $response.data.latestData.uiData.OutdoorHumidity"
log.debug "OutdoorTemperature = $response.data.latestData.uiData.OutdoorTemperature"
log.debug "got curOutdoorTemp = $curOutdoorTemp"
log.debug "got curOutdoorHumidity = $curOutdoorHumidity"
log.debug "hasOutdoorHumid = $hasOutdoorHumid"
log.debug "hasOutdoorTemp = $hasOutdoorTemp"
*/
// log.debug "displayUnits = $displayUnits"
state.DisplayUnits = $displayUnits
//Operating State Section
//Set the operating state to off
def operatingState = "Unknown"
// lgk operating state not working here.. shows both on ie 1 when heat doesnt go on to 67 and heat till 76 and current is 73
//Check the status of heat and cool
// lgk old method now use equipment status
if (equipmentStatus == 1) {
operatingState = "HEAT ON"
} else if (equipmentStatus == 2) {
operatingState = "COOL ON"
} else if (equipmentStatus == 0) {
operatingState = "IDLE"
} else {
operatingState = "Unknown"
}
if(curSetpointStatus == 0) {
operatingState = "$operatingState\nControl By\nFollowing\nSchedule"
} else if(curSetpointStatus == 1) {
operatingState = "$operatingState\nControl By\nHold Until\n" + String.format("%02d:%02d", TempHoldTimeHrs, TempHoldTimeMins)
} else if(curSetpointStatus == 2) {
operatingState = "$operatingState\nControl By\nPermanent\nHold"
} else {
operatingState = "unknown"
}
/*
if(statusCool == 1 && (switchPos == 3 || switchPos == 5 || swithPos == 4)) {
operatingState = "cooling"
} else if (statusHeat == 1 && (switchPos == 1 || switchPos == 5 || switchPos == 4)) {
operatingState = "heating"
} else if (statusCool == 0 && statusHeat == 0) {
operatingState = "idle"
} else {
operatingState = "unknown"
}
*/
log.trace("Set operating State to: ${operatingState}")
// set fast state
def fanState = "Unknown"
if (fanIsRunning == true)
fanState = "On"
else fanState = "Idle"
log.trace("Set Fan operating State to: ${fanState}")
//End Operating State
// log.debug curTemp
// log.debug fanMode
// log.debug switchPos
//fan mode 0=auto, 2=circ, 1=on
if(fanMode==0)
fanMode = 'auto'
if(fanMode==1)
fanMode = 'on'
if(fanMode==2)
fanMode = 'circulate'
if(switchPos==1)
switchPos = 'heat'
if(switchPos==2)
switchPos = 'off'
if(switchPos==3)
switchPos = 'cool'
if(switchPos==4 || switchPos==5)
switchPos = 'auto'
def formattedCoolSetPoint = String.format("%5.1f", coolSetPoint)
def formattedHeatSetPoint = String.format("%5.1f", heatSetPoint)
def formattedTemp = String.format("%5.1f", curTemp)
def finalCoolSetPoint = formattedCoolSetPoint as BigDecimal
def finalHeatSetPoint = formattedHeatSetPoint as BigDecimal
def finalTemp = formattedTemp as BigDecimal
//Send events
sendEvent(name: 'thermostatOperatingState', value: operatingState)
sendEvent(name: 'fanOperatingState', value: fanState)
sendEvent(name: 'thermostatFanMode', value: fanMode)
sendEvent(name: 'thermostatMode', value: switchPos)
sendEvent(name: 'coolingSetpoint', value: finalCoolSetPoint )
sendEvent(name: 'heatingSetpoint', value: finalHeatSetPoint )
sendEvent(name: 'temperature', value: finalTemp, state: switchPos)
if (hasIndoorHumid){sendEvent(name: 'relativeHumidity', value: curHumidity as Integer)}
if (settings.tzOffset == null)
settings.tzOffset = -5
def now = new Date()
def tf = new java.text.SimpleDateFormat("MM/dd/yyyy h:mm a")
tf.setTimeZone(TimeZone.getTimeZone("GMT${settings.tzOffset}"))
def newtime = "${tf.format(now)}" as String
sendEvent(name: "lastUpdate", value: newtime, descriptionText: "Last Update: $newtime")
if (enableOutdoorTemps == "Yes")
{
if (hasOutdoorHumid)
{
sendEvent(name: 'outdoorHumidity', value: curOutdoorHumidity as Integer)
}
if (hasOutdoorTemp)
{
sendEvent(name: 'outdoorTemperature', value: curOutdoorTemp as Integer)
}
}
}
}
def getHumidifierStatus()
{
/*
$.ajax({
url: humUrl,
type: 'POST',
cache: false,
dataType: "json",
success: function(data) {
/portal/Device/Menu/GetHumData/454832';
*/
def params = [
uri: "https://www.mytotalconnectcomfort.com/portal/Device/Menu/GetHumData/${settings.honeywelldevice}",
headers: [
'Accept': '*/*',
'DNT': '1',
'dataType': 'json',
'cache': 'false',
'Accept-Encoding': 'plain',
'Cache-Control': 'max-age=0',
'Accept-Language': 'en-US,en,q=0.8',
'Connection': 'keep-alive',
'Host': 'rs.alarmnet.com',
'Referer': 'https://www.mytotalconnectcomfort.com/portal/Menu/${settings.honeywelldevice}',
'X-Requested-With': 'XMLHttpRequest',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36',
'Cookie': data.cookiess ],
]
httpGet(params) { response ->
log.debug "GetHumidity Request was successful, $response.status"
log.debug "response = $response.data"
// log.debug "ld = $response.data.latestData"
// log.debug "humdata = $response.data.latestData.humData"
log.trace("lowerLimit: ${response.data.latestData.humData.lowerLimit}")
log.trace("upperLimit: ${response.data.humData.upperLimit}")
log.trace("SetPoint: ${response.data.humData.Setpoint}")
log.trace("DeviceId: ${response.data.humData.DeviceId}")
log.trace("IndoorHumidity: ${response.data.humData.IndoorHumidity}")
}
}
def api(method, args = [], success = {}) {
}
// Need to be logged in before this is called. So don't call this. Call api.
def doRequest(uri, args, type, success) {
}
def refresh() {
log.debug "Executing 'refresh'"
def unit = getTemperatureScale()
log.debug "units = $unit"
login()
//getHumidifierStatus()
getStatus()
}
def login() {
log.debug "Executing 'login'"
def params = [
uri: 'https://www.mytotalconnectcomfort.com/portal',
headers: [
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding': 'sdch',
'Host': 'www.mytotalconnectcomfort.com',
'DNT': '1',
'Origin': 'www.mytotalconnectcomfort.com/portal/',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36'
],
body: [timeOffset: '240', UserName: "${settings.username}", Password: "${settings.password}", RememberMe: 'false']
]
data.cookiess = ''
httpPost(params) { response ->
log.debug "Request was successful, $response.status"
log.debug response.headers
response.getHeaders('Set-Cookie').each {
String cookie = it.value.split(';|,')[0]
log.debug "Adding cookie to collection: $cookie"
if(cookie != ".ASPXAUTH_TH_A=") {
data.cookiess = data.cookiess+cookie+';'
}
}
log.debug "cookies: $data.cookiess"
}
}
def isLoggedIn() {
if(!data.auth) {
log.debug "No data.auth"
return false
}
def now = new Date().getTime();
return data.auth.expires_in > now
}
def updated()
{
log.debug "in updated"
state.DisplayUnits = settings.tempScale
log.debug "display units now = $state.DisplayUnits"
}
def installed() {
state.DisplayUnits = settings.tempScale
log.debug "display units now = $state.DisplayUnits"
}

Binary file not shown.

View File

@@ -1,6 +0,0 @@
#Thu Feb 25 08:56:06 CST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip

160
gradlew vendored
View File

@@ -1,160 +0,0 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
gradlew.bat vendored
View File

@@ -1,90 +0,0 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1,19 +0,0 @@
/*
* This settings file was auto generated by the Gradle buildInit task
* by 'jblaisdell' at '2/25/16 8:56 AM' with Gradle 2.10
*
* The settings file is used to specify which projects to include in your build.
* In a single project build this file can be empty or even removed.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user guide at https://docs.gradle.org/2.10/userguide/multi_project_builds.html
*/
/*
// To declare projects as part of a multi-project build use the 'include' method
include 'shared'
include 'api'
include 'services:webservice'
*/
rootProject.name = 'SmartThingsPublic'

View File

@@ -353,7 +353,7 @@ def onLocation(evt) {
}
else if (
lanEvent.headers && lanEvent.body &&
lanEvent.headers."content-type"?.contains("xml")
lanEvent.headers."content-type".contains("xml")
)
{
def parsers = getParsers()

View File

@@ -1,322 +1,322 @@
/**
* Copyright 2015 SmartThings
*
* 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.
*
* Button Controller
*
* Author: SmartThings
* Date: 2014-5-21
*/
definition(
name: "Button Controller",
namespace: "smartthings",
author: "SmartThings",
description: "Control devices with buttons like the Aeon Labs Minimote",
category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps@2x.png"
)
preferences {
page(name: "selectButton")
page(name: "configureButton1")
page(name: "configureButton2")
page(name: "configureButton3")
page(name: "configureButton4")
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
input "starting", "time", title: "Starting", required: false
input "ending", "time", title: "Ending", required: false
}
}
}
def selectButton() {
dynamicPage(name: "selectButton", title: "First, select your button device", nextPage: "configureButton1", uninstall: configured()) {
section {
input "buttonDevice", "capability.button", title: "Button", multiple: false, required: true
}
section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
def timeLabel = timeIntervalLabel()
href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
input "modes", "mode", title: "Only when mode is", multiple: true, required: false
}
}
}
def configureButton1() {
dynamicPage(name: "configureButton1", title: "Now let's decide how to use the first button",
nextPage: "configureButton2", uninstall: configured(), getButtonSections(1))
}
def configureButton2() {
dynamicPage(name: "configureButton2", title: "If you have a second button, set it up here",
nextPage: "configureButton3", uninstall: configured(), getButtonSections(2))
}
def configureButton3() {
dynamicPage(name: "configureButton3", title: "If you have a third button, you can do even more here",
nextPage: "configureButton4", uninstall: configured(), getButtonSections(3))
}
def configureButton4() {
dynamicPage(name: "configureButton4", title: "If you have a fourth button, you rule, and can set it up here",
install: true, uninstall: true, getButtonSections(4))
}
def getButtonSections(buttonNumber) {
return {
section("Lights") {
input "lights_${buttonNumber}_pushed", "capability.switch", title: "Pushed", multiple: true, required: false
input "lights_${buttonNumber}_held", "capability.switch", title: "Held", multiple: true, required: false
}
section("Locks") {
input "locks_${buttonNumber}_pushed", "capability.lock", title: "Pushed", multiple: true, required: false
input "locks_${buttonNumber}_held", "capability.lock", title: "Held", multiple: true, required: false
}
section("Sonos") {
input "sonos_${buttonNumber}_pushed", "capability.musicPlayer", title: "Pushed", multiple: true, required: false
input "sonos_${buttonNumber}_held", "capability.musicPlayer", title: "Held", multiple: true, required: false
}
section("Modes") {
input "mode_${buttonNumber}_pushed", "mode", title: "Pushed", required: false
input "mode_${buttonNumber}_held", "mode", title: "Held", required: false
}
def phrases = location.helloHome?.getPhrases()*.label
if (phrases) {
section("Hello Home Actions") {
log.trace phrases
input "phrase_${buttonNumber}_pushed", "enum", title: "Pushed", required: false, options: phrases
input "phrase_${buttonNumber}_held", "enum", title: "Held", required: false, options: phrases
}
}
section("Sirens") {
input "sirens_${buttonNumber}_pushed","capability.alarm" ,title: "Pushed", multiple: true, required: false
input "sirens_${buttonNumber}_held", "capability.alarm", title: "Held", multiple: true, required: false
}
section("Custom Message") {
input "textMessage_${buttonNumber}", "text", title: "Message", required: false
}
section("Push Notifications") {
input "notifications_${buttonNumber}_pushed","bool" ,title: "Pushed", required: false, defaultValue: false
input "notifications_${buttonNumber}_held", "bool", title: "Held", required: false, defaultValue: false
}
section("Sms Notifications") {
input "phone_${buttonNumber}_pushed","phone" ,title: "Pushed", required: false
input "phone_${buttonNumber}_held", "phone", title: "Held", required: false
}
}
}
def installed() {
initialize()
}
def updated() {
unsubscribe()
initialize()
}
def initialize() {
subscribe(buttonDevice, "button", buttonEvent)
}
def configured() {
return buttonDevice || buttonConfigured(1) || buttonConfigured(2) || buttonConfigured(3) || buttonConfigured(4)
}
def buttonConfigured(idx) {
return settings["lights_$idx_pushed"] ||
settings["locks_$idx_pushed"] ||
settings["sonos_$idx_pushed"] ||
settings["mode_$idx_pushed"] ||
settings["notifications_$idx_pushed"] ||
settings["sirens_$idx_pushed"] ||
settings["notifications_$idx_pushed"] ||
settings["phone_$idx_pushed"]
}
def buttonEvent(evt){
if(allOk) {
def buttonNumber = evt.data // why doesn't jsonData work? always returning [:]
def value = evt.value
log.debug "buttonEvent: $evt.name = $evt.value ($evt.data)"
log.debug "button: $buttonNumber, value: $value"
def recentEvents = buttonDevice.eventsSince(new Date(now() - 3000)).findAll{it.value == evt.value && it.data == evt.data}
log.debug "Found ${recentEvents.size()?:0} events in past 3 seconds"
if(recentEvents.size <= 1){
switch(buttonNumber) {
case ~/.*1.*/:
executeHandlers(1, value)
break
case ~/.*2.*/:
executeHandlers(2, value)
break
case ~/.*3.*/:
executeHandlers(3, value)
break
case ~/.*4.*/:
executeHandlers(4, value)
break
}
} else {
log.debug "Found recent button press events for $buttonNumber with value $value"
}
}
}
def executeHandlers(buttonNumber, value) {
log.debug "executeHandlers: $buttonNumber - $value"
def lights = find('lights', buttonNumber, value)
if (lights != null) toggle(lights)
def locks = find('locks', buttonNumber, value)
if (locks != null) toggle(locks)
def sonos = find('sonos', buttonNumber, value)
if (sonos != null) toggle(sonos)
def mode = find('mode', buttonNumber, value)
if (mode != null) changeMode(mode)
def phrase = find('phrase', buttonNumber, value)
if (phrase != null) location.helloHome.execute(phrase)
def textMessage = findMsg('textMessage', buttonNumber)
def notifications = find('notifications', buttonNumber, value)
if (notifications?.toBoolean()) sendPush(textMessage ?: "Button $buttonNumber was pressed" )
def phone = find('phone', buttonNumber, value)
if (phone != null) sendSms(phone, textMessage ?:"Button $buttonNumber was pressed")
def sirens = find('sirens', buttonNumber, value)
if (sirens != null) toggle(sirens)
}
def find(type, buttonNumber, value) {
def preferenceName = type + "_" + buttonNumber + "_" + value
def pref = settings[preferenceName]
if(pref != null) {
log.debug "Found: $pref for $preferenceName"
}
return pref
}
def findMsg(type, buttonNumber) {
def preferenceName = type + "_" + buttonNumber
def pref = settings[preferenceName]
if(pref != null) {
log.debug "Found: $pref for $preferenceName"
}
return pref
}
def toggle(devices) {
log.debug "toggle: $devices = ${devices*.currentValue('switch')}"
if (devices*.currentValue('switch').contains('on')) {
devices.off()
}
else if (devices*.currentValue('switch').contains('off')) {
devices.on()
}
else if (devices*.currentValue('lock').contains('locked')) {
devices.unlock()
}
else if (devices*.currentValue('lock').contains('unlocked')) {
devices.lock()
}
else if (devices*.currentValue('alarm').contains('off')) {
devices.siren()
}
else {
devices.on()
}
}
def changeMode(mode) {
log.debug "changeMode: $mode, location.mode = $location.mode, location.modes = $location.modes"
if (location.mode != mode && location.modes?.find { it.name == mode }) {
setLocationMode(mode)
}
}
// execution filter methods
private getAllOk() {
modeOk && daysOk && timeOk
}
private getModeOk() {
def result = !modes || modes.contains(location.mode)
log.trace "modeOk = $result"
result
}
private getDaysOk() {
def result = true
if (days) {
def df = new java.text.SimpleDateFormat("EEEE")
if (location.timeZone) {
df.setTimeZone(location.timeZone)
}
else {
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
}
def day = df.format(new Date())
result = days.contains(day)
}
log.trace "daysOk = $result"
result
}
private getTimeOk() {
def result = true
if (starting && ending) {
def currTime = now()
def start = timeToday(starting).time
def stop = timeToday(ending).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
}
log.trace "timeOk = $result"
result
}
private hhmm(time, fmt = "h:mm a")
{
def t = timeToday(time, location.timeZone)
def f = new java.text.SimpleDateFormat(fmt)
f.setTimeZone(location.timeZone ?: timeZone(time))
f.format(t)
}
private hideOptionsSection() {
(starting || ending || days || modes) ? false : true
}
private timeIntervalLabel() {
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
}
/**
* Copyright 2015 SmartThings
*
* 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.
*
* Button Controller
*
* Author: SmartThings
* Date: 2014-5-21
*/
definition(
name: "Button Controller",
namespace: "smartthings",
author: "SmartThings",
description: "Control devices with buttons like the Aeon Labs Minimote",
category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps@2x.png"
)
preferences {
page(name: "selectButton")
page(name: "configureButton1")
page(name: "configureButton2")
page(name: "configureButton3")
page(name: "configureButton4")
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
input "starting", "time", title: "Starting", required: false
input "ending", "time", title: "Ending", required: false
}
}
}
def selectButton() {
dynamicPage(name: "selectButton", title: "First, select your button device", nextPage: "configureButton1", uninstall: configured()) {
section {
input "buttonDevice", "capability.button", title: "Button", multiple: false, required: true
}
section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
def timeLabel = timeIntervalLabel()
href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
input "modes", "mode", title: "Only when mode is", multiple: true, required: false
}
}
}
def configureButton1() {
dynamicPage(name: "configureButton1", title: "Now let's decide how to use the first button",
nextPage: "configureButton2", uninstall: configured(), getButtonSections(1))
}
def configureButton2() {
dynamicPage(name: "configureButton2", title: "If you have a second button, set it up here",
nextPage: "configureButton3", uninstall: configured(), getButtonSections(2))
}
def configureButton3() {
dynamicPage(name: "configureButton3", title: "If you have a third button, you can do even more here",
nextPage: "configureButton4", uninstall: configured(), getButtonSections(3))
}
def configureButton4() {
dynamicPage(name: "configureButton4", title: "If you have a fourth button, you rule, and can set it up here",
install: true, uninstall: true, getButtonSections(4))
}
def getButtonSections(buttonNumber) {
return {
section("Lights") {
input "lights_${buttonNumber}_pushed", "capability.switch", title: "Pushed", multiple: true, required: false
input "lights_${buttonNumber}_held", "capability.switch", title: "Held", multiple: true, required: false
}
section("Locks") {
input "locks_${buttonNumber}_pushed", "capability.lock", title: "Pushed", multiple: true, required: false
input "locks_${buttonNumber}_held", "capability.lock", title: "Held", multiple: true, required: false
}
section("Sonos") {
input "sonos_${buttonNumber}_pushed", "capability.musicPlayer", title: "Pushed", multiple: true, required: false
input "sonos_${buttonNumber}_held", "capability.musicPlayer", title: "Held", multiple: true, required: false
}
section("Modes") {
input "mode_${buttonNumber}_pushed", "mode", title: "Pushed", required: false
input "mode_${buttonNumber}_held", "mode", title: "Held", required: false
}
def phrases = location.helloHome?.getPhrases()*.label
if (phrases) {
section("Hello Home Actions") {
log.trace phrases
input "phrase_${buttonNumber}_pushed", "enum", title: "Pushed", required: false, options: phrases
input "phrase_${buttonNumber}_held", "enum", title: "Held", required: false, options: phrases
}
}
section("Sirens") {
input "sirens_${buttonNumber}_pushed","capability.alarm" ,title: "Pushed", multiple: true, required: false
input "sirens_${buttonNumber}_held", "capability.alarm", title: "Held", multiple: true, required: false
}
section("Custom Message") {
input "textMessage_${buttonNumber}", "text", title: "Message", required: false
}
section("Push Notifications") {
input "notifications_${buttonNumber}_pushed","bool" ,title: "Pushed", required: false, defaultValue: false
input "notifications_${buttonNumber}_held", "bool", title: "Held", required: false, defaultValue: false
}
section("Sms Notifications") {
input "phone_${buttonNumber}_pushed","phone" ,title: "Pushed", required: false
input "phone_${buttonNumber}_held", "phone", title: "Held", required: false
}
}
}
def installed() {
initialize()
}
def updated() {
unsubscribe()
initialize()
}
def initialize() {
subscribe(buttonDevice, "button", buttonEvent)
}
def configured() {
return buttonDevice || buttonConfigured(1) || buttonConfigured(2) || buttonConfigured(3) || buttonConfigured(4)
}
def buttonConfigured(idx) {
return settings["lights_$idx_pushed"] ||
settings["locks_$idx_pushed"] ||
settings["sonos_$idx_pushed"] ||
settings["mode_$idx_pushed"] ||
settings["notifications_$idx_pushed"] ||
settings["sirens_$idx_pushed"] ||
settings["notifications_$idx_pushed"] ||
settings["phone_$idx_pushed"]
}
def buttonEvent(evt){
if(allOk) {
def buttonNumber = evt.data // why doesn't jsonData work? always returning [:]
def value = evt.value
log.debug "buttonEvent: $evt.name = $evt.value ($evt.data)"
log.debug "button: $buttonNumber, value: $value"
def recentEvents = buttonDevice.eventsSince(new Date(now() - 3000)).findAll{it.value == evt.value && it.data == evt.data}
log.debug "Found ${recentEvents.size()?:0} events in past 3 seconds"
if(recentEvents.size <= 1){
switch(buttonNumber) {
case ~/.*1.*/:
executeHandlers(1, value)
break
case ~/.*2.*/:
executeHandlers(2, value)
break
case ~/.*3.*/:
executeHandlers(3, value)
break
case ~/.*4.*/:
executeHandlers(4, value)
break
}
} else {
log.debug "Found recent button press events for $buttonNumber with value $value"
}
}
}
def executeHandlers(buttonNumber, value) {
log.debug "executeHandlers: $buttonNumber - $value"
def lights = find('lights', buttonNumber, value)
if (lights != null) toggle(lights)
def locks = find('locks', buttonNumber, value)
if (locks != null) toggle(locks)
def sonos = find('sonos', buttonNumber, value)
if (sonos != null) toggle(sonos)
def mode = find('mode', buttonNumber, value)
if (mode != null) changeMode(mode)
def phrase = find('phrase', buttonNumber, value)
if (phrase != null) location.helloHome.execute(phrase)
def textMessage = findMsg('textMessage', buttonNumber)
def notifications = find('notifications', buttonNumber, value)
if (notifications?.toBoolean()) sendPush(textMessage ?: "Button $buttonNumber was pressed" )
def phone = find('phone', buttonNumber, value)
if (phone != null) sendSms(phone, textMessage ?:"Button $buttonNumber was pressed")
def sirens = find('sirens', buttonNumber, value)
if (sirens != null) toggle(sirens)
}
def find(type, buttonNumber, value) {
def preferenceName = type + "_" + buttonNumber + "_" + value
def pref = settings[preferenceName]
if(pref != null) {
log.debug "Found: $pref for $preferenceName"
}
return pref
}
def findMsg(type, buttonNumber) {
def preferenceName = type + "_" + buttonNumber
def pref = settings[preferenceName]
if(pref != null) {
log.debug "Found: $pref for $preferenceName"
}
return pref
}
def toggle(devices) {
log.debug "toggle: $devices = ${devices*.currentValue('switch')}"
if (devices*.currentValue('switch').contains('on')) {
devices.off()
}
else if (devices*.currentValue('switch').contains('off')) {
devices.on()
}
else if (devices*.currentValue('lock').contains('locked')) {
devices.unlock()
}
else if (devices*.currentValue('lock').contains('unlocked')) {
devices.lock()
}
else if (devices*.currentValue('alarm').contains('off')) {
devices.siren()
}
else {
devices.on()
}
}
def changeMode(mode) {
log.debug "changeMode: $mode, location.mode = $location.mode, location.modes = $location.modes"
if (location.mode != mode && location.modes?.find { it.name == mode }) {
setLocationMode(mode)
}
}
// execution filter methods
private getAllOk() {
modeOk && daysOk && timeOk
}
private getModeOk() {
def result = !modes || modes.contains(location.mode)
log.trace "modeOk = $result"
result
}
private getDaysOk() {
def result = true
if (days) {
def df = new java.text.SimpleDateFormat("EEEE")
if (location.timeZone) {
df.setTimeZone(location.timeZone)
}
else {
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
}
def day = df.format(new Date())
result = days.contains(day)
}
log.trace "daysOk = $result"
result
}
private getTimeOk() {
def result = true
if (starting && ending) {
def currTime = now()
def start = timeToday(starting).time
def stop = timeToday(ending).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
}
log.trace "timeOk = $result"
result
}
private hhmm(time, fmt = "h:mm a")
{
def t = timeToday(time, location.timeZone)
def f = new java.text.SimpleDateFormat(fmt)
f.setTimeZone(location.timeZone ?: timeZone(time))
f.format(t)
}
private hideOptionsSection() {
(starting || ending || days || modes) ? false : true
}
private timeIntervalLabel() {
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
}

View File

@@ -28,7 +28,7 @@ definition(
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png",
singleInstance: true
singleInstance: true
) {
appSetting "clientId"
}
@@ -61,7 +61,7 @@ def authPage() {
description = "Click to enter Ecobee Credentials"
}
def redirectUrl = buildRedirectUrl
def redirectUrl = buildRedirectUrl //"${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}"
log.debug "RedirectUrl = ${redirectUrl}"
// get rid of next button until the user is actually auth'd
if (!oauthTokenProvided) {
@@ -103,7 +103,7 @@ def oauthInitUrl() {
scope: "smartRead,smartWrite",
client_id: smartThingsClientId,
state: atomicState.oauthInitState,
redirect_uri: callbackUrl
redirect_uri: callbackUrl //"https://graph.api.smartthings.com/oauth/callback"
]
redirect(location: "${apiEndpoint}/authorize?${toQueryString(oauthParams)}")
@@ -115,13 +115,14 @@ def callback() {
def code = params.code
def oauthState = params.state
//verify oauthState == atomicState.oauthInitState, so the callback corresponds to the authentication request
if (oauthState == atomicState.oauthInitState){
def tokenParams = [
grant_type: "authorization_code",
code : code,
client_id : smartThingsClientId,
redirect_uri: callbackUrl
redirect_uri: callbackUrl //"https://graph.api.smartthings.com/oauth/callback"
]
def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}"
@@ -246,26 +247,31 @@ def getEcobeeThermostats() {
]
def stats = [:]
try {
httpGet(deviceListParams) { resp ->
try {
httpGet(deviceListParams) { resp ->
if (resp.status == 200) {
resp.data.thermostatList.each { stat ->
atomicState.remoteSensors = stat.remoteSensors
def dni = [app.id, stat.identifier].join('.')
stats[dni] = getThermostatDisplayName(stat)
}
} else {
log.debug "http status: ${resp.status}"
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) {
atomicState.action = "getEcobeeThermostats"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
if (resp.status == 200) {
resp.data.thermostatList.each { stat ->
atomicState.remoteSensors = stat.remoteSensors
def dni = [app.id, stat.identifier].join('.')
stats[dni] = getThermostatDisplayName(stat)
}
} else {
log.debug "http status: ${resp.status}"
//refresh the auth token
if (resp.status == 500 && resp.data.status.code == 14) {
log.debug "Storing the failed action to try later"
atomicState.action = "getEcobeeThermostats"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
} else {
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
}
}
}
} catch(Exception e) {
log.debug "___exception getEcobeeThermostats(): " + e
refreshAuthToken()
}
atomicState.thermostats = stats
return stats
@@ -311,7 +317,7 @@ def initialize() {
def devices = thermostats.collect { dni ->
def d = getChildDevice(dni)
if(!d) {
d = addChildDevice(app.namespace, getChildName(), dni, null, ["label":"${atomicState.thermostats[dni]}" ?: "Ecobee Thermostat"])
d = addChildDevice(app.namespace, getChildName(), dni, null, ["label":"Ecobee Thermostat:${atomicState.thermostats[dni]}"])
log.debug "created ${d.displayName} with id $dni"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
@@ -322,7 +328,7 @@ def initialize() {
def sensors = ecobeesensors.collect { dni ->
def d = getChildDevice(dni)
if(!d) {
d = addChildDevice(app.namespace, getSensorChildName(), dni, null, ["label":"${atomicState.sensors[dni]}" ?:"Ecobee Sensor"])
d = addChildDevice(app.namespace, getSensorChildName(), dni, null, ["label":"Ecobee Sensor:${atomicState.sensors[dni]}"])
log.debug "created ${d.displayName} with id $dni"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
@@ -348,17 +354,21 @@ def initialize() {
atomicState.thermostatData = [:] //reset Map to store thermostat data
//send activity feeds to tell that device is connected
def notificationMessage = "is connected to SmartThings"
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = null
atomicState.reAttempt = 0
//send activity feeds to tell that device is connected
def notificationMessage = "is connected to SmartThings"
sendActivityFeeds(notificationMessage)
state.timeSendPush = null
pollHandler() //first time polling data data from thermostat
//automatically update devices status every 5 mins
runEvery5Minutes("poll")
//since access_token expires every 2 hours
runEvery1Hour("refreshAuthToken")
atomicState.reAttempt = 0
}
def pollHandler() {
@@ -379,10 +389,18 @@ def pollHandler() {
def pollChildren(child = null) {
def thermostatIdsString = getChildDeviceIdsString()
log.debug "polling children: $thermostatIdsString"
def data = ""
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeExtendedRuntime":"true","includeSettings":"true","includeRuntime":"true","includeSensors":true}}'
def result = false
// // TODO: test this:
//
// def jsonRequestBody = toJson([
// selection:[
// selectionType: "thermostats",
// selectionMatch: getChildDeviceIdsString(),
// includeRuntime: true
// ]
// ])
def pollParams = [
uri: apiEndpoint,
@@ -393,6 +411,11 @@ def pollChildren(child = null) {
try{
httpGet(pollParams) { resp ->
// if (resp.data) {
// debugEventFromParent(child, "pollChildren(child) >> resp.status = ${resp.status}, resp.data = ${resp.data}")
// }
if(resp.status == 200) {
log.debug "poll results returned resp.data ${resp.data}"
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
@@ -403,57 +426,45 @@ def pollChildren(child = null) {
log.debug "updating dni $dni"
data = [
def data = [
coolMode: (stat.settings.coolStages > 0),
heatMode: (stat.settings.heatStages > 0),
deviceTemperatureUnit: stat.settings.useCelsius,
minHeatingSetpoint: (stat.settings.heatRangeLow / 10),
maxHeatingSetpoint: (stat.settings.heatRangeHigh / 10),
minCoolingSetpoint: (stat.settings.coolRangeLow / 10),
maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10),
autoMode: stat.settings.autoHeatCoolFeatureEnabled,
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
temperature: (stat.runtime.actualTemperature / 10),
temperature: stat.runtime.actualTemperature / 10,
heatingSetpoint: stat.runtime.desiredHeat / 10,
coolingSetpoint: stat.runtime.desiredCool / 10,
thermostatMode: stat.settings.hvacMode,
humidity: stat.runtime.actualHumidity,
thermostatFanMode: stat.runtime.desiredFanMode
thermostatMode: stat.settings.hvacMode
]
if (location.temperatureScale == "F")
{
data["temperature"] = data["temperature"] ? Math.round(data["temperature"].toDouble()) : data["temperature"]
data["heatingSetpoint"] = data["heatingSetpoint"] ? Math.round(data["heatingSetpoint"].toDouble()) : data["heatingSetpoint"]
data["coolingSetpoint"] = data["coolingSetpoint"] ? Math.round(data["coolingSetpoint"].toDouble()) : data["coolingSetpoint"]
data["minHeatingSetpoint"] = data["minHeatingSetpoint"] ? Math.round(data["minHeatingSetpoint"].toDouble()) : data["minHeatingSetpoint"]
data["maxHeatingSetpoint"] = data["maxHeatingSetpoint"] ? Math.round(data["maxHeatingSetpoint"].toDouble()) : data["maxHeatingSetpoint"]
data["minCoolingSetpoint"] = data["minCoolingSetpoint"] ? Math.round(data["minCoolingSetpoint"].toDouble()) : data["minCoolingSetpoint"]
data["maxCoolingSetpoint"] = data["maxCoolingSetpoint"] ? Math.round(data["maxCoolingSetpoint"].toDouble()) : data["maxCoolingSetpoint"]
}
if (data?.deviceTemperatureUnit == false && location.temperatureScale == "F") {
data["deviceTemperatureUnit"] = "F"
} else {
data["deviceTemperatureUnit"] = "C"
}
data["temperature"] = data["temperature"] ? data["temperature"].toDouble().toInteger() : data["temperature"]
data["heatingSetpoint"] = data["heatingSetpoint"] ? data["heatingSetpoint"].toDouble().toInteger() : data["heatingSetpoint"]
data["coolingSetpoint"] = data["coolingSetpoint"] ? data["coolingSetpoint"].toDouble().toInteger() : data["coolingSetpoint"]
// debugEventFromParent(child, "Event Data = ${data}")
collector[dni] = [data:data]
return collector
}
result = true
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
} else {
log.error "polling children & got http status ${resp.status}"
//refresh the auth token
if (resp.status == 500 && resp.data.status.code == 14) {
log.debug "Storing the failed action to try later"
atomicState.action = "pollChildren";
log.debug "Refreshing your auth_token!"
refreshAuthToken()
}
else {
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
}
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception polling children: " + e.response.data.status
if (e.response.data.status.code == 14) {
atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
}
} catch(Exception e) {
log.debug "___exception polling children: " + e
// debugEventFromParent(child, "___exception polling children: " + e)
refreshAuthToken()
}
return result
}
@@ -465,14 +476,18 @@ def pollChild(child){
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
def tData = atomicState.thermostats[child.device.deviceNetworkId]
// debugEventFromParent(child, "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}") //TODO comment
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
child.generateEvent(tData.data) //parse received message from parent
// return tData.data
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
// debugEventFromParent(child, "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling") //TODO comment
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
return null
}
}
} else {
// debugEventFromParent(child, "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling") //TODO comment
log.info "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling"
return null
}
@@ -498,6 +513,9 @@ def availableModes(child) {
{
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
// TODO: flag device as in error state
// child.errorState = true
return null
}
@@ -524,6 +542,8 @@ def currentMode(child) {
if(!tData) {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
// TODO: flag device as in error state
// child.errorState = true
return null
}
@@ -541,12 +561,8 @@ def updateSensorData() {
def occupancy = ""
it.capability.each {
if (it.type == "temperature") {
if (location.temperatureScale == "F") {
temperature = Math.round(it.value.toDouble() / 10)
} else {
temperature = convertFtoC(it.value.toDouble() / 10)
}
temperature = it.value as Double
temperature = (temperature / 10).toInteger()
} else if (it.type == "occupancy") {
if(it.value == "true")
occupancy = "active"
@@ -559,6 +575,7 @@ def updateSensorData() {
if(d) {
d.sendEvent(name:"temperature", value: temperature)
d.sendEvent(name:"motion", value: occupancy)
// debugEventFromParent(d, "temperature : ${temperature}, motion:${occupancy}")
}
}
}
@@ -578,65 +595,68 @@ def toQueryString(Map m) {
}
private refreshAuthToken() {
log.debug "refreshing auth token"
log.debug "refreshing auth token"
if(!atomicState.refreshToken) {
log.warn "Can not refresh OAuth token since there is no refreshToken stored"
} else {
if(!atomicState.refreshToken) {
log.warn "Can not refresh OAuth token since there is no refreshToken stored"
} else {
def refreshParams = [
method: 'POST',
uri : apiEndpoint,
path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
]
def refreshParams = [
method: 'POST',
uri : apiEndpoint,
path : "/token",
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
]
log.debug refreshParams
log.debug refreshParams
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
//changed to httpPost
try {
def jsonMap
httpPost(refreshParams) { resp ->
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
//changed to httpPost
try {
def jsonMap
httpPost(refreshParams) { resp ->
if(resp.status == 200) {
log.debug "Token refreshed...calling saved RestAction now!"
if(resp.status == 200) {
log.debug "Token refreshed...calling saved RestAction now!"
debugEvent("Token refreshed ... calling saved RestAction now!")
debugEvent("Token refreshed ... calling saved RestAction now!")
log.debug resp
log.debug resp
jsonMap = resp.data
jsonMap = resp.data
if(resp.data) {
if(resp.data) {
log.debug resp.data
debugEvent("Response = ${resp.data}")
log.debug resp.data
debugEvent("Response = ${resp.data}")
atomicState.refreshToken = resp?.data?.refresh_token
atomicState.authToken = resp?.data?.access_token
atomicState.refreshToken = resp?.data?.refresh_token
atomicState.authToken = resp?.data?.access_token
debugEvent("Refresh Token = ${atomicState.refreshToken}")
debugEvent("OAUTH Token = ${atomicState.authToken}")
debugEvent("Refresh Token = ${atomicState.refreshToken}")
debugEvent("OAUTH Token = ${atomicState.authToken}")
if(atomicState.action && atomicState.action != "") {
log.debug "Executing next action: ${atomicState.action}"
if(atomicState.action && atomicState.action != "") {
log.debug "Executing next action: ${atomicState.action}"
"${atomicState.action}"()
"${atomicState.action}"()
atomicState.action = ""
}
//remove saved action
atomicState.action = ""
}
}
atomicState.action = ""
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
}
atomicState.action = ""
} else {
log.debug "refresh failed ${resp.status} : ${resp.status.code}"
}
}
} catch(Exception e) {
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
def reAttemptPeriod = 300 // in sec
if (e.statusCode != 401) { // this issue might comes from exceed 20sec app execution, connectivity issue etc.
if (e.statusCode != 401) { //this issue might comes from exceed 20sec app execution, connectivity issue etc.
runIn(reAttemptPeriod, "refreshAuthToken")
} else if (e.statusCode == 401) { // unauthorized
} else if (e.statusCode == 401) { //refresh token is expired
atomicState.reAttempt = atomicState.reAttempt + 1
log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
if (atomicState.reAttempt <= 3) {
@@ -645,16 +665,20 @@ private refreshAuthToken() {
sendPushAndFeeds(notificationMessage)
atomicState.reAttempt = 0
}
}
}
}
}
}
}
}
def resumeProgram(child, deviceId) {
// def thermostatIdsString = getChildDeviceIdsString()
// log.debug "resumeProgram children: $thermostatIdsString"
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}'
//, { "type": "sendMessage", "params": { "text": "Setpoint Updated" } }
def result = sendJson(jsonRequestBody)
// debugEventFromParent(child, "resumeProgram(child) with result ${result}")
return result
}
@@ -662,27 +686,28 @@ def setHold(child, heating, cooling, deviceId, sendHoldType) {
int h = heating * 10
int c = cooling * 10
// log.debug "setpoints____________ - h: $heating - $h, c: $cooling - $c"
// def thermostatIdsString = getChildDeviceIdsString()
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+' } } ]}'
def result = sendJson(child, jsonRequestBody)
return result
}
def setFanMode(child, heating, cooling, deviceId, sendHoldType, fanMode) {
int h = heating * 10
int c = cooling * 10
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+', "fan": '+fanMode+' } } ]}'
// def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}, { "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": "indefinite" } } ]}'
def result = sendJson(child, jsonRequestBody)
// debugEventFromParent(child, "setHold: heating: ${h}, cooling: ${c} with result ${result}")
return result
}
def setMode(child, mode, deviceId) {
// def thermostatIdsString = getChildDeviceIdsString()
// log.debug "setCoolingSetpoint children: $thermostatIdsString"
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"thermostat": {"settings":{"hvacMode":"'+"${mode}"+'"}}}'
// log.debug "Mode Request Body = ${jsonRequestBody}"
// debugEvent ("Mode Request Body = ${jsonRequestBody}")
def result = sendJson(jsonRequestBody)
// debugEventFromParent(child, "setMode to ${mode} with result ${result}")
return result
}
@@ -699,6 +724,8 @@ def sendJson(child = null, String jsonBody) {
try{
httpPost(cmdParams) { resp ->
// debugEventFromParent(child, "sendJson >> resp.status ${resp.status}, resp.data: ${resp.data}")
if(resp.status == 200) {
log.debug "updated ${resp.data}"
@@ -709,21 +736,30 @@ def sendJson(child = null, String jsonBody) {
log.debug "Error return code = ${resp.data.status.code}"
debugEvent("Error return code = ${resp.data.status.code}")
}
} else {
log.error "sent Json & got http status ${resp.status} - ${resp.status.code}"
debugEvent ("sent Json & got http status ${resp.status} - ${resp.status.code}")
//refresh the auth token
if (resp.status == 500 && resp.status.code == 14) {
//log.debug "Storing the failed action to try later"
log.debug "Refreshing your auth_token!"
debugEvent ("Refreshing OAUTH Token")
refreshAuthToken()
return false
} else {
debugEvent ("Authentication error, invalid authentication method, lack of credentials, etc.")
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
return false
}
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace "Exception Sending Json: " + e.response.data.status
debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}")
if (e.response.data.status.code == 14) {
atomicState.action = "pollChildren"
log.debug "Refreshing your auth_token!"
refreshAuthToken()
}
else {
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.")
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
}
}
} catch(Exception e) {
log.debug "Exception Sending Json: " + e
debugEvent ("Exception Sending JSON: " + e)
refreshAuthToken()
return false
}
if (returnStatus == 0)
return true
@@ -758,37 +794,25 @@ def debugEventFromParent(child, message) {
//send both push notification and mobile activity feeds
def sendPushAndFeeds(notificationMessage){
log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}"
log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}"
if (atomicState.timeSendPush){
if (now() - atomicState.timeSendPush > 86400000){ // notification is sent to remind user once a day
sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now()
}
} else {
sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now()
}
atomicState.authToken = null
log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}"
log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}"
if (atomicState.timeSendPush){
if (now() - atomicState.timeSendPush > 86400000){ // notification is sent to remind user once a day
sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now()
}
} else {
sendPush("Your Ecobee thermostat " + notificationMessage)
sendActivityFeeds(notificationMessage)
atomicState.timeSendPush = now()
}
atomicState.authToken = null
}
def sendActivityFeeds(notificationMessage) {
def devices = getChildDevices()
devices.each { child ->
child.generateActivityFeedsEvent(notificationMessage) //parse received message from parent
}
}
def roundC (tempC) {
return String.format("%.1f", (Math.round(tempC * 2))/2)
}
def convertFtoC (tempF) {
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
}
def convertCtoF (tempC) {
return (Math.round(tempC * (9/5)) + 32).toInteger()
def devices = getChildDevices()
devices.each { child ->
child.generateActivityFeedsEvent(notificationMessage) //parse received message from parent
}
}

View File

@@ -201,8 +201,8 @@ def completionPage() {
section("Switches") {
input(name: "completionSwitches", type: "capability.switch", title: "Set these switches", description: null, required: false, multiple: true, submitOnChange: true)
if (completionSwitches) {
input(name: "completionSwitchesState", type: "enum", title: "To", description: null, required: false, multiple: false, options: ["on", "off"], defaultValue: "on")
if (completionSwitches || androidClient()) {
input(name: "completionSwitchesState", type: "enum", title: "To", description: null, required: false, multiple: false, options: ["on", "off"], style: "segmented", defaultValue: "on")
input(name: "completionSwitchesLevel", type: "number", title: "Optionally, Set Dimmer Levels To", description: null, required: false, multiple: false, range: "(0..99)")
}
}

View File

@@ -35,11 +35,23 @@ preferences {
}
def mainPage() {
def bridges = bridgesDiscovered()
if (state.username && bridges) {
return bulbDiscovery()
if(canInstallLabs()) {
def bridges = bridgesDiscovered()
if (state.username && bridges) {
return bulbDiscovery()
} else {
return bridgeDiscovery()
}
} else {
return bridgeDiscovery()
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
return dynamicPage(name:"bridgeDiscovery", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
section("Upgrade") {
paragraph "$upgradeNeeded"
}
}
}
}
@@ -753,6 +765,10 @@ private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private Boolean canInstallLabs() {
return hasAllHubsOver("000.011.00603")
}
private Boolean hasAllHubsOver(String desiredFirmware) {
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}

View File

@@ -89,7 +89,7 @@ mappings {
}
def getServerUrl() { return "https://graph.api.smartthings.com" }
def getServercallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" }
def getCallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" }
def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" }
def authPage() {
@@ -166,7 +166,7 @@ def callback() {
def init() {
log.debug "Requesting Code"
def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${servercallbackUrl}" ]
def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${callbackUrl}" ]
redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}")
}
@@ -419,11 +419,9 @@ def addDevice() {
def d = getChildDevice(dni)
if(!d) {
def newAction = state.HarmonyActivities.find { it.key == dni }
if (newAction) {
d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"])
log.trace "created ${d.displayName} with id $dni"
poll()
}
d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"])
log.trace "created ${d.displayName} with id $dni"
poll()
} else {
log.trace "found ${d.displayName} with id $dni already exists"
}

View File

@@ -1,31 +0,0 @@
'''Acceleration Detected'''.ko=가속화 감지됨
'''Arrival Of'''.ko=도착
'''Both Push and SMS?'''.ko=푸시 메시지와 SMS를 모두 사용하시겠습니까?
'''Button Pushed'''.ko=버튼이 눌렸습니다
'''Contact Closes'''.ko=접점 닫힘
'''Contact Opens'''.ko=접점 열림
'''Departure Of'''.ko=출발
'''Message Text'''.ko=문자 메시지
'''Minutes'''.ko=
'''Motion Here'''.ko=동작
'''Phone Number (for SMS, optional)'''.ko=휴대전화 번호(문자 메시지 - 옵션)
'''Receive notifications when anything happens in your home.'''.ko=집 안에 무슨 일이 일어나면 알림이 전송됩니다.
'''Smoke Detected'''.ko=연기가 감지되었습니다
'''Switch Turned Off'''.ko=스위치 꺼짐
'''Switch Turned On'''.ko=스위치 꺼짐
'''Choose one or more, when...'''.ko=다음의 경우 하나 이상 선택
'''Yes'''.ko=
'''No'''.ko=아니요
'''Send this message (optional, sends standard status message if not specified)'''.ko=이 메시지 전송(선택적, 지정되지 않은 경우 표준 상태 메시지를 보냅니다)
'''Via a push notification and/or an SMS message'''.ko=푸시 알림 및/또는 문자 메시지를 통해
'''Set for specific mode(s)'''.ko=특정 모드 설정
'''Tap to set'''.ko=눌러서 설정
'''Minimum time between messages (optional, defaults to every message)'''.ko=메시지작 간 최소 시간(선택 사항, 모든 메시지의 기본 설정)
'''If outside the US please make sure to enter the proper country code'''.ko=미국 이외 거주자는 적절한 국가 코드를 입력했는지 확인하십시오
'''Water Sensor Wet'''.ko=Water Sensor에서 물이 감지되었습니다
'''{{ triggerEvent.linkText }} has arrived at the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다
'''{{ triggerEvent.linkText }} has arrived at {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다
'''{{ triggerEvent.linkText }} has left the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
'''{{ triggerEvent.linkText }} has left {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
'''Assign a name'''.ko=이름 배정
'''Choose Modes'''.ko=모드 선택

View File

@@ -20,19 +20,19 @@
* 2014-10-03: Added capability.button device picker and button.pushed event subscription. For Doorbell.
*/
definition(
name: "Notify Me When",
namespace: "smartthings",
author: "SmartThings",
description: "Receive notifications when anything happens in your home.",
category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact@2x.png"
name: "Notify Me When",
namespace: "smartthings",
author: "SmartThings",
description: "Receive notifications when anything happens in your home.",
category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact@2x.png"
)
preferences {
section("Choose one or more, when..."){
input "button", "capability.button", title: "Button Pushed", required: false, multiple: true //tw
input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
input "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
input "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
input "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
@@ -47,11 +47,11 @@ preferences {
input "messageText", "text", title: "Message Text", required: false
}
section("Via a push notification and/or an SMS message"){
input("recipients", "contact", title: "Send notifications to") {
input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false
paragraph "If outside the US please make sure to enter the proper country code"
input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"]
}
input("recipients", "contact", title: "Send notifications to") {
input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false
paragraph "If outside the US please make sure to enter the proper country code"
input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"]
}
}
section("Minimum time between messages (optional, defaults to every message)") {
input "frequency", "decimal", title: "Minutes", required: false
@@ -71,7 +71,7 @@ def updated() {
def subscribeToEvents() {
subscribe(button, "button.pushed", eventHandler) //tw
subscribe(contact, "contact.open", eventHandler)
subscribe(contact, "contact.open", eventHandler)
subscribe(contactClosed, "contact.closed", eventHandler)
subscribe(acceleration, "acceleration.active", eventHandler)
subscribe(motion, "motion.active", eventHandler)
@@ -99,55 +99,49 @@ def eventHandler(evt) {
}
private sendMessage(evt) {
String msg = messageText
Map options = [:]
if (!messageText) {
msg = defaultText(evt)
options = [translatable: true, triggerEvent: evt]
}
def msg = messageText ?: defaultText(evt)
log.debug "$evt.name:$evt.value, pushAndPhone:$pushAndPhone, '$msg'"
if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients, options)
} else {
if (!phone || pushAndPhone != 'No') {
log.debug 'sending push'
options.method = 'push'
//sendPush(msg)
}
if (phone) {
options.phone = phone
log.debug 'sending SMS'
//sendSms(phone, msg)
}
sendNotification(msg, options)
}
if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients)
}
else {
if (!phone || pushAndPhone != "No") {
log.debug "sending push"
sendPush(msg)
}
if (phone) {
log.debug "sending SMS"
sendSms(phone, msg)
}
}
if (frequency) {
state[evt.deviceId] = now()
}
}
private defaultText(evt) {
if (evt.name == 'presence') {
if (evt.value == 'present') {
if (evt.name == "presence") {
if (evt.value == "present") {
if (includeArticle) {
'{{ triggerEvent.linkText }} has arrived at the {{ location.name }}'
"$evt.linkText has arrived at the $location.name"
}
else {
'{{ triggerEvent.linkText }} has arrived at {{ location.name }}'
}
} else {
if (includeArticle) {
'{{ triggerEvent.linkText }} has left the {{ location.name }}'
}
else {
'{{ triggerEvent.linkText }} has left {{ location.name }}'
"$evt.linkText has arrived at $location.name"
}
}
} else {
'{{ triggerEvent.descriptionText }}'
else {
if (includeArticle) {
"$evt.linkText has left the $location.name"
}
else {
"$evt.linkText has left $location.name"
}
}
}
else {
evt.descriptionText
}
}

View File

@@ -0,0 +1,401 @@
/**
* Copyright 2015 SmartThings
*
* 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.
*
* Samsung TV Service Manager
*
* Author: SmartThings (Juan Risso)
*/
definition(
name: "Samsung TV (Connect)",
namespace: "smartthings",
author: "SmartThings",
description: "Allows you to control your Samsung TV from the SmartThings app. Perform basic functions like power Off, source, volume, channels and other remote control functions.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%402x.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%403x.png",
singleInstance: true
)
preferences {
page(name:"samsungDiscovery", title:"Samsung TV Setup", content:"samsungDiscovery", refreshTimeout:5)
}
def getDeviceType() {
return "urn:samsung.com:device:RemoteControlReceiver:1"
}
//PAGES
def samsungDiscovery()
{
if(canInstallLabs())
{
int samsungRefreshCount = !state.samsungRefreshCount ? 0 : state.samsungRefreshCount as int
state.samsungRefreshCount = samsungRefreshCount + 1
def refreshInterval = 3
def options = samsungesDiscovered() ?: []
def numFound = options.size() ?: 0
if(!state.subscribe) {
log.trace "subscribe to location"
subscribe(location, null, locationHandler, [filterEvents:false])
state.subscribe = true
}
//samsung discovery request every 5 //25 seconds
if((samsungRefreshCount % 5) == 0) {
log.trace "Discovering..."
discoversamsunges()
}
//setup.xml request every 3 seconds except on discoveries
if(((samsungRefreshCount % 1) == 0) && ((samsungRefreshCount % 8) != 0)) {
log.trace "Verifing..."
verifysamsungPlayer()
}
return dynamicPage(name:"samsungDiscovery", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your Samsung TV. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedsamsung", "enum", required:false, title:"Select Samsung TV (${numFound} found)", multiple:true, options:options
}
}
}
else
{
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
return dynamicPage(name:"samsungDiscovery", title:"Upgrade needed!", nextPage:"", install:true, uninstall: true) {
section("Upgrade") {
paragraph "$upgradeNeeded"
}
}
}
}
def installed() {
log.trace "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.trace "Updated with settings: ${settings}"
unschedule()
initialize()
}
def uninstalled() {
def devices = getChildDevices()
log.trace "deleting ${devices.size()} samsung"
devices.each {
deleteChildDevice(it.deviceNetworkId)
}
}
def initialize() {
// remove location subscription afterwards
if (selectedsamsung) {
addsamsung()
}
//Check every 5 minutes for IP change
runEvery5Minutes("discoversamsunges")
}
//CHILD DEVICE METHODS
def addsamsung() {
def players = getVerifiedsamsungPlayer()
log.trace "Adding childs"
selectedsamsung.each { dni ->
def d = getChildDevice(dni)
if(!d) {
def newPlayer = players.find { (it.value.ip + ":" + it.value.port) == dni }
log.trace "newPlayer = $newPlayer"
log.trace "dni = $dni"
d = addChildDevice("smartthings", "Samsung Smart TV", dni, newPlayer?.value.hub, [label:"${newPlayer?.value.name}"])
log.trace "created ${d.displayName} with id $dni"
d.setModel(newPlayer?.value.model)
log.trace "setModel to ${newPlayer?.value.model}"
} else {
log.trace "found ${d.displayName} with id $dni already exists"
}
}
}
private tvAction(key,deviceNetworkId) {
log.debug "Executing ${tvCommand}"
def tvs = getVerifiedsamsungPlayer()
def thetv = tvs.find { (it.value.ip + ":" + it.value.port) == deviceNetworkId }
// Standard Connection Data
def appString = "iphone..iapp.samsung"
def appStringLength = appString.getBytes().size()
def tvAppString = "iphone.UN60ES8000.iapp.samsung"
def tvAppStringLength = tvAppString.getBytes().size()
def remoteName = "SmartThings".encodeAsBase64().toString()
def remoteNameLength = remoteName.getBytes().size()
// Device Connection Data
def ipAddress = convertHexToIP(thetv?.value.ip).encodeAsBase64().toString()
def ipAddressHex = deviceNetworkId.substring(0,8)
def ipAddressLength = ipAddress.getBytes().size()
def macAddress = thetv?.value.mac.encodeAsBase64().toString()
def macAddressLength = macAddress.getBytes().size()
// The Authentication Message
def authenticationMessage = "${(char)0x64}${(char)0x00}${(char)ipAddressLength}${(char)0x00}${ipAddress}${(char)macAddressLength}${(char)0x00}${macAddress}${(char)remoteNameLength}${(char)0x00}${remoteName}"
def authenticationMessageLength = authenticationMessage.getBytes().size()
def authenticationPacket = "${(char)0x00}${(char)appStringLength}${(char)0x00}${appString}${(char)authenticationMessageLength}${(char)0x00}${authenticationMessage}"
// If our initial run, just send the authentication packet so the prompt appears on screen
if (key == "AUTHENTICATE") {
sendHubCommand(new physicalgraph.device.HubAction(authenticationPacket, physicalgraph.device.Protocol.LAN, "${ipAddressHex}:D6D8"))
} else {
// Build the command we will send to the Samsung TV
def command = "KEY_${key}".encodeAsBase64().toString()
def commandLength = command.getBytes().size()
def actionMessage = "${(char)0x00}${(char)0x00}${(char)0x00}${(char)commandLength}${(char)0x00}${command}"
def actionMessageLength = actionMessage.getBytes().size()
def actionPacket = "${(char)0x00}${(char)tvAppStringLength}${(char)0x00}${tvAppString}${(char)actionMessageLength}${(char)0x00}${actionMessage}"
// Send both the authentication and action at the same time
sendHubCommand(new physicalgraph.device.HubAction(authenticationPacket + actionPacket, physicalgraph.device.Protocol.LAN, "${ipAddressHex}:D6D8"))
}
}
private discoversamsunges()
{
sendHubCommand(new physicalgraph.device.HubAction("lan discovery ${getDeviceType()}", physicalgraph.device.Protocol.LAN))
}
private verifysamsungPlayer() {
def devices = getsamsungPlayer().findAll { it?.value?.verified != true }
if(devices) {
log.warn "UNVERIFIED PLAYERS!: $devices"
}
devices.each {
verifysamsung((it?.value?.ip + ":" + it?.value?.port), it?.value?.ssdpPath)
}
}
private verifysamsung(String deviceNetworkId, String devicessdpPath) {
log.trace "dni: $deviceNetworkId, ssdpPath: $devicessdpPath"
String ip = getHostAddress(deviceNetworkId)
log.trace "ip:" + ip
sendHubCommand(new physicalgraph.device.HubAction("""GET ${devicessdpPath} HTTP/1.1\r\nHOST: $ip\r\n\r\n""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}"))
}
Map samsungesDiscovered() {
def vsamsunges = getVerifiedsamsungPlayer()
def map = [:]
vsamsunges.each {
def value = "${it.value.name}"
def key = it.value.ip + ":" + it.value.port
map["${key}"] = value
}
log.trace "Devices discovered $map"
map
}
def getsamsungPlayer()
{
state.samsunges = state.samsunges ?: [:]
}
def getVerifiedsamsungPlayer()
{
getsamsungPlayer().findAll{ it?.value?.verified == true }
}
def locationHandler(evt) {
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseEventMessage(description)
parsedEvent << ["hub":hub]
log.trace "${parsedEvent}"
log.trace "${getDeviceType()} - ${parsedEvent.ssdpTerm}"
if (parsedEvent?.ssdpTerm?.contains(getDeviceType()))
{ //SSDP DISCOVERY EVENTS
log.trace "TV found"
def samsunges = getsamsungPlayer()
if (!(samsunges."${parsedEvent.ssdpUSN.toString()}"))
{ //samsung does not exist
log.trace "Adding Device to state..."
samsunges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
}
else
{ // update the values
log.trace "Device was already found in state..."
def d = samsunges."${parsedEvent.ssdpUSN.toString()}"
boolean deviceChangedValues = false
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
d.ip = parsedEvent.ip
d.port = parsedEvent.port
deviceChangedValues = true
log.trace "Device's port or ip changed..."
}
if (deviceChangedValues) {
def children = getChildDevices()
children.each {
if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
log.trace "updating dni for device ${it} with mac ${parsedEvent.mac}"
it.setDeviceNetworkId((parsedEvent.ip + ":" + parsedEvent.port)) //could error if device with same dni already exists
}
}
}
}
}
else if (parsedEvent.headers && parsedEvent.body)
{ // samsung RESPONSES
def deviceHeaders = parseLanMessage(description, false)
def type = deviceHeaders.headers."content-type"
def body
log.trace "REPONSE TYPE: $type"
if (type?.contains("xml"))
{ // description.xml response (application/xml)
body = new XmlSlurper().parseText(deviceHeaders.body)
log.debug body.device.deviceType.text()
if (body?.device?.deviceType?.text().contains(getDeviceType()))
{
def samsunges = getsamsungPlayer()
def player = samsunges.find {it?.key?.contains(body?.device?.UDN?.text())}
if (player)
{
player.value << [name:body?.device?.friendlyName?.text(),model:body?.device?.modelName?.text(), serialNumber:body?.device?.serialNum?.text(), verified: true]
}
else
{
log.error "The xml file returned a device that didn't exist"
}
}
}
else if(type?.contains("json"))
{ //(application/json)
body = new groovy.json.JsonSlurper().parseText(bodyString)
log.trace "GOT JSON $body"
}
}
else {
log.trace "TV not found..."
//log.trace description
}
}
private def parseEventMessage(String description) {
def event = [:]
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('devicetype:')) {
part -= "devicetype:"
event.devicetype = part.trim()
}
else if (part.startsWith('mac:')) {
part -= "mac:"
event.mac = part.trim()
}
else if (part.startsWith('networkAddress:')) {
part -= "networkAddress:"
event.ip = part.trim()
}
else if (part.startsWith('deviceAddress:')) {
part -= "deviceAddress:"
event.port = part.trim()
}
else if (part.startsWith('ssdpPath:')) {
part -= "ssdpPath:"
event.ssdpPath = part.trim()
}
else if (part.startsWith('ssdpUSN:')) {
part -= "ssdpUSN:"
event.ssdpUSN = part.trim()
}
else if (part.startsWith('ssdpTerm:')) {
part -= "ssdpTerm:"
event.ssdpTerm = part.trim()
}
else if (part.startsWith('headers')) {
part -= "headers:"
event.headers = part.trim()
}
else if (part.startsWith('body')) {
part -= "body:"
event.body = part.trim()
}
}
event
}
def parse(childDevice, description) {
def parsedEvent = parseEventMessage(description)
if (parsedEvent.headers && parsedEvent.body) {
def headerString = new String(parsedEvent.headers.decodeBase64())
def bodyString = new String(parsedEvent.body.decodeBase64())
log.trace "parse() - ${bodyString}"
def body = new groovy.json.JsonSlurper().parseText(bodyString)
} else {
log.trace "parse - got something other than headers,body..."
return []
}
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private getHostAddress(d) {
def parts = d.split(":")
def ip = convertHexToIP(parts[0])
def port = convertHexToInt(parts[1])
return ip + ":" + port
}
private Boolean canInstallLabs()
{
return hasAllHubsOver("000.011.00603")
}
private Boolean hasAllHubsOver(String desiredFirmware)
{
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}
private List getRealHubFirmwareVersions()
{
return location.hubs*.firmwareVersionString.findAll { it }
}

View File

@@ -16,14 +16,14 @@
* Date: 2013-09-06
*/
definition(
name: "Wemo (Connect)",
namespace: "smartthings",
author: "SmartThings",
description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
singleInstance: true
name: "Wemo (Connect)",
namespace: "smartthings",
author: "SmartThings",
description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
singleInstance: true
)
preferences {
@@ -39,7 +39,7 @@ private getFriendlyName(String deviceNetworkId) {
sendHubCommand(new physicalgraph.device.HubAction("""GET /setup.xml HTTP/1.1
HOST: ${deviceNetworkId}
""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}", [callback: "setupHandler"]))
""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}"))
}
private verifyDevices() {
@@ -52,13 +52,6 @@ private verifyDevices() {
}
}
void ssdpSubscribe() {
subscribe(location, "ssdpTerm.urn:Belkin:device:insight:1", ssdpSwitchHandler)
subscribe(location, "ssdpTerm.urn:Belkin:device:controllee:1", ssdpSwitchHandler)
subscribe(location, "ssdpTerm.urn:Belkin:device:sensor:1", ssdpMotionHandler)
subscribe(location, "ssdpTerm.urn:Belkin:device:lightswitch:1", ssdpLightSwitchHandler)
}
def firstPage()
{
if(canInstallLabs())
@@ -69,7 +62,7 @@ def firstPage()
log.debug "REFRESH COUNT :: ${refreshCount}"
ssdpSubscribe()
subscribe(location, null, locationHandler, [filterEvents:false])
//ssdp request every 25 seconds
if((refreshCount % 5) == 0) {
@@ -112,7 +105,9 @@ def devicesDiscovered() {
def motions = getWemoMotions()
def lightSwitches = getWemoLightSwitches()
def devices = switches + motions + lightSwitches
devices?.collect{ [app.id, it.ssdpUSN].join('.') }
def list = []
list = devices?.collect{ [app.id, it.ssdpUSN].join('.') }
}
def switchesDiscovered() {
@@ -180,9 +175,8 @@ def updated() {
def initialize() {
unsubscribe()
unschedule()
ssdpSubscribe()
unschedule()
subscribe(location, null, locationHandler, [filterEvents:false])
if (selectedSwitches)
addSwitches()
@@ -195,7 +189,7 @@ def initialize() {
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
runEvery5Minutes("refresh")
runEvery5Minutes("refresh")
}
def resubscribe() {
@@ -205,7 +199,7 @@ def resubscribe() {
def refresh() {
log.debug "refresh() called"
doDeviceSync()
doDeviceSync()
refreshDevices()
}
@@ -241,14 +235,14 @@ def addSwitches() {
if (!d) {
log.debug "Creating WeMo Switch with dni: ${selectedSwitch.value.mac}"
d = addChildDevice("smartthings", "Wemo Switch", selectedSwitch.value.mac, selectedSwitch?.value.hub, [
"label": selectedSwitch?.value?.name ?: "Wemo Switch",
"data": [
"mac": selectedSwitch.value.mac,
"ip": selectedSwitch.value.ip,
"port": selectedSwitch.value.port
]
"label": selectedSwitch?.value?.name ?: "Wemo Switch",
"data": [
"mac": selectedSwitch.value.mac,
"ip": selectedSwitch.value.ip,
"port": selectedSwitch.value.port
]
])
def ipvalue = convertHexToIP(selectedSwitch.value.ip)
def ipvalue = convertHexToIP(selectedSwitch.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
} else {
@@ -279,9 +273,9 @@ def addMotions() {
"port": selectedMotion.value.port
]
])
def ipvalue = convertHexToIP(selectedMotion.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
def ipvalue = convertHexToIP(selectedMotion.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
}
@@ -310,155 +304,26 @@ def addLightSwitches() {
"port": selectedLightSwitch.value.port
]
])
def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "created ${d.displayName} with id $dni"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
log.debug "found ${d.displayName} with id $dni already exists"
}
}
}
def ssdpSwitchHandler(evt) {
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseDiscoveryMessage(description)
parsedEvent << ["hub":hub]
log.debug parsedEvent
def switches = getWemoSwitches()
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else {
log.debug "Device was already found in state..."
def d = switches."${parsedEvent.ssdpUSN.toString()}"
boolean deviceChangedValues = false
log.debug "$d.ip <==> $parsedEvent.ip"
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
d.ip = parsedEvent.ip
d.port = parsedEvent.port
deviceChangedValues = true
log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
if (child) {
child.subscribe(parsedEvent.ip, parsedEvent.port)
child.poll()
} else {
log.debug "Device with mac $parsedEvent.mac not found"
}
}
}
}
def ssdpMotionHandler(evt) {
log.info("ssdpMotionHandler")
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseDiscoveryMessage(description)
parsedEvent << ["hub":hub]
log.debug parsedEvent
def motions = getWemoMotions()
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else { // just update the values
log.debug "Device was already found in state..."
def d = motions."${parsedEvent.ssdpUSN.toString()}"
boolean deviceChangedValues = false
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
d.ip = parsedEvent.ip
d.port = parsedEvent.port
deviceChangedValues = true
log.debug "Device's port or ip changed..."
}
if (deviceChangedValues) {
def children = getChildDevices()
log.debug "Found children ${children}"
children.each {
if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}"
it.subscribe(parsedEvent.ip, parsedEvent.port)
}
}
}
}
}
def ssdpLightSwitchHandler(evt) {
log.info("ssdpLightSwitchHandler")
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseDiscoveryMessage(description)
parsedEvent << ["hub":hub]
log.debug parsedEvent
def lightSwitches = getWemoLightSwitches()
if (!(lightSwitches."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
lightSwitches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else {
log.debug "Device was already found in state..."
def d = lightSwitches."${parsedEvent.ssdpUSN.toString()}"
boolean deviceChangedValues = false
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
d.ip = parsedEvent.ip
d.port = parsedEvent.port
deviceChangedValues = true
log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
if (child) {
log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
child.subscribe(parsedEvent.ip, parsedEvent.port)
} else {
log.debug "Device with mac $parsedEvent.mac not found"
}
}
}
}
void setupHandler(hubResponse) {
String contentType = hubResponse?.headers['Content-Type']
if (contentType != null && contentType == 'text/xml') {
def body = hubResponse.xml
def wemoDevices = []
String deviceType = body?.device?.deviceType?.text() ?: ""
if (deviceType.startsWith("urn:Belkin:device:controllee:1") || deviceType.startsWith("urn:Belkin:device:insight:1")) {
wemoDevices = getWemoSwitches()
} else if (deviceType.startsWith("urn:Belkin:device:sensor")) {
wemoDevices = getWemoMotions()
} else if (deviceType.startsWith("urn:Belkin:device:lightswitch")) {
wemoDevices = getWemoLightSwitches()
}
def wemoDevice = wemoDevices.find {it?.key?.contains(body?.device?.UDN?.text())}
if (wemoDevice) {
wemoDevice.value << [name:body?.device?.friendlyName?.text(), verified: true]
} else {
log.error "/setup.xml returned a wemo device that didn't exist"
}
}
}
@Deprecated
def locationHandler(evt) {
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseDiscoveryMessage(description)
parsedEvent << ["hub":hub]
log.debug parsedEvent
log.debug parsedEvent
if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) {
def switches = getWemoSwitches()
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
//if it doesn't already exist
switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else {
log.debug "Device was already found in state..."
@@ -470,20 +335,16 @@ def locationHandler(evt) {
d.port = parsedEvent.port
deviceChangedValues = true
log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
if (child) {
child.subscribe(parsedEvent.ip, parsedEvent.port)
child.poll()
} else {
log.debug "Device with mac $parsedEvent.mac not found"
}
def child = getChildDevice(parsedEvent.mac)
child.subscribe(parsedEvent.ip, parsedEvent.port)
child.poll()
}
}
}
else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) {
def motions = getWemoMotions()
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
//if it doesn't already exist
motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else { // just update the values
log.debug "Device was already found in state..."
@@ -530,12 +391,8 @@ def locationHandler(evt) {
deviceChangedValues = true
log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
if (child) {
log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
child.subscribe(parsedEvent.ip, parsedEvent.port)
} else {
log.debug "Device with mac $parsedEvent.mac not found"
}
log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
child.subscribe(parsedEvent.ip, parsedEvent.port)
}
}
}
@@ -602,7 +459,6 @@ def locationHandler(evt) {
}
}
@Deprecated
private def parseXmlBody(def body) {
def decodedBytes = body.decodeBase64()
def bodyString
@@ -684,4 +540,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it }
}
}