Compare commits

..

1 Commits

Author SHA1 Message Date
Anders Heie
625940423b MSA-689: Fixed a presence problem. 2015-11-16 17:32:48 -06:00
70 changed files with 2631 additions and 9887 deletions

View File

@@ -1,104 +0,0 @@
/**
* EnerTalk Energy Meter
*
* Copyright 2015 hyeon seok yang
*
* 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: "EnerTalk Energy Meter", namespace: "Encored Technologies", author: "hyeon seok yang") {
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale:2) {
valueTile("view", "device.view", decoration: "flat") {
state "view", label:' ${currentValue} kWh'
}
valueTile("month", "device.month", width: 6, height : 3, decoration: "flat") {
state "month", label:' ${currentValue}'
}
valueTile("real", "device.real", width: 2, height : 2, decoration: "flat") {
state "real", label:' ${currentValue}'
}
valueTile("tier", "device.tier", width: 2, height : 2, decoration: "flat") {
state "tier", label:' ${currentValue}'
}
valueTile("plan", "device.plan", width: 2, height : 2, decoration: "flat") {
state "plan", label:' ${currentValue}'
}
htmlTile(name:"deepLink", action:"linkApp", whitelist:["code.jquery.com",
"ajax.googleapis.com",
"fonts.googleapis.com",
"code.highcharts.com",
"enertalk-card.encoredtech.com",
"s3-ap-northeast-1.amazonaws.com",
"s3.amazonaws.com",
"ui-hub.encoredtech.com",
"enertalk-auth.encoredtech.com",
"api.encoredtech.com",
"cdnjs.cloudflare.com",
"encoredtech.com",
"itunes.apple.com"], width:2, height:2){}
main (["view"])
details (["month", "real", "tier", "plan", "deepLink"])
}
}
mappings {
path("/linkApp") {action: [ GET: "getLinkedApp" ]}
}
def getLinkedApp() {
def lang = clientLocale?.language
if ("${lang}" == "ko") {
lang = "<p style=\'margin-left:15vw; color: #aeaeb0;\'>기기 설정</p>"
} else {
lang = "<p style=\'margin-left:5vw; color: #aeaeb0;\'>Setup Device</p>"
}
renderHTML() {
head {
"""
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width, height=device-height">
<style>
#레이어_1 { margin-left : 17vw; width : 50vw; height : 50vw;}
.st0{fill:#B5B6BB;}
</style>
"""
}
body {
"""
<div id="container">
<a id="st-deep-link" href="#">
<svg version="1.1" id="레이어_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve"><path class="st0" d="M20,0C9,0,0,9,0,20C0,30.5,8,39,18.2,40l3.8-4.8l-3.9-4.8c-4.9-0.9-8.6-5.2-8.6-10.4c0-5.8,4.7-10.5,10.5-10.5
S30.5,14.2,30.5,20c0,5.1-3.7,9.4-8.5,10.3l3.7,4.5L21.8,40C32,39.1,40,30.5,40,20C40,9,31,0,20,0z"/></svg>
</a>
${lang}
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
var ua = navigator.userAgent.toLowerCase();
var isAndroid = ua.indexOf("android") > -1;
if(!isAndroid) {
\$("#st-deep-link").attr("href", "https://itunes.apple.com/kr/app/enertalk-for-home/id1024660780?mt=8");
} else {
\$("#st-deep-link").attr("href", "market://details?id=com.ionicframework.enertalkhome874425");
}
</script>
"""
}
}
}

View File

@@ -1,163 +0,0 @@
/**
* PlantLink
*
* This device type takes sensor data and converts it into a json packet to send to myplantlink.com
* where its values will be computed for soil and plant type to show user readable values of how your
* specific plant is doing.
*
*
* Copyright 2015 Oso Technologies
*
* 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.
*
*/
import groovy.json.JsonBuilder
metadata {
definition (name: "PlantLink", namespace: "OsoTech", author: "Oso Technologies") {
capability "Sensor"
command "setStatusIcon"
command "setPlantFuelLevel"
command "setBatteryLevel"
command "setInstallSmartApp"
attribute "plantStatus","string"
attribute "plantFuelLevel","number"
attribute "linkBatteryLevel","string"
attribute "installSmartApp","string"
fingerprint profileId: "0104", inClusters: "0000,0001,0B04"
}
simulator {
status "battery": "read attr - raw: C0720100010A000021340A, dni: C072, endpoint: 01, cluster: 0001, size: 0A, attrId: 0000, encoding: 21, value: 0a34"
status "moisture": "read attr - raw: C072010B040A0001290000, dni: C072, endpoint: 01, cluster: 0B04, size: 0A, attrId: 0100, encoding: 29, value: 0000"
}
tiles {
standardTile("Title", "device.label") {
state("label", label:'PlantLink ${device.label}')
}
valueTile("plantMoistureTile", "device.plantFuelLevel", width: 1, height: 1) {
state("plantMoisture", label: '${currentValue}% Moisture')
}
valueTile("plantStatusTextTile", "device.plantStatus", decoration: "flat", width: 2, height: 2) {
state("plantStatusTextTile", label:'${currentValue}')
}
valueTile("battery", "device.linkBatteryLevel" ) {
state("battery", label:'${currentValue}% battery')
}
valueTile("installSmartApp","device.installSmartApp", decoration: "flat", width: 3, height: 1) {
state "needSmartApp", label:'Please install SmartApp "Required PlantLink Connector"', defaultState:true
state "connectedToSmartApp", label:'Connected to myplantlink.com'
}
main "plantStatusTextTile"
details(['plantStatusTextTile', "plantMoistureTile", "battery", "installSmartApp"])
}
}
def setStatusIcon(value){
def status = ''
switch (value) {
case '0':
status = 'Needs Water'
break
case '1':
status = 'Dry'
break
case '2':
case '3':
status = 'Good'
break
case '4':
status = 'Too Wet'
break
case 'No Soil':
status = 'Too Dry'
setPlantFuelLevel(0)
break
case 'Recently Watered':
status = 'Watered'
setPlantFuelLevel(100)
break
case 'Low Battery':
status = 'Low Battery'
break
case 'Waiting on First Measurement':
status = 'Calibrating'
break
default:
status = "?"
break
}
sendEvent("name":"plantStatus", "value":status, "description":statusText, displayed: true, isStateChange: true)
}
def setPlantFuelLevel(value){
sendEvent("name":"plantFuelLevel", "value":value, "description":statusText, displayed: true, isStateChange: true)
}
def setBatteryLevel(value){
sendEvent("name":"linkBatteryLevel", "value":value, "description":statusText, displayed: true, isStateChange: true)
}
def setInstallSmartApp(value){
sendEvent("name":"installSmartApp", "value":value)
}
def parse(String description) {
def description_map = parseDescriptionAsMap(description)
def event_name = ""
def measurement_map = [
type: "link",
signal: "0x00",
zigbeedeviceid: device.zigbeeId,
created: new Date().time /1000 as int
]
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"
measurement_map["battery"] = "0x${description_map.value}"
} else if (description_map.cluster == "0B04"){
/* raw moisture reading (needs to be sent to plantlink for soil/plant type conversion) */
log.debug "PlantLink - id ${device.zigbeeId} raw moisture ${description_map.value}"
measurement_map["moisture"] = "0x${description_map.value}"
event_name = "moisture_status"
} else{
log.debug "PlantLink - id ${device.zigbeeId} Unknown '${description}'"
return
}
def json_builder = new JsonBuilder(measurement_map)
def result = createEvent(name: event_name, value: json_builder.toString())
return result
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}

View File

@@ -1,627 +0,0 @@
/**
* Spruce Controller - Pre Release V2 10/11/2015
*
* Copyright 2015 Plaid Systems
*
* Author: NC
* Date: 2015-11
*
* 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.
*
-----------V3 updates-11-2015------------
-Start program button updated to signal schedule check in Scheduler
11/17 alarm "0" -> 0 (ln 305)
*/
metadata {
definition (name: "Spruce Controller", namespace: "plaidsystems", author: "NCauffman") {
capability "Switch"
capability "Configuration"
capability "Refresh"
capability "Actuator"
capability "Valve"
attribute "switch", "string"
attribute "switch1", "string"
attribute "switch2", "string"
attribute "switch8", "string"
attribute "switch5", "string"
attribute "switch3", "string"
attribute "switch4", "string"
attribute "switch6", "string"
attribute "switch7", "string"
attribute "switch9", "string"
attribute "switch10", "string"
attribute "switch11", "string"
attribute "switch12", "string"
attribute "switch13", "string"
attribute "switch14", "string"
attribute "switch15", "string"
attribute "switch16", "string"
attribute "status", "string"
command "programOn"
command "programOff"
command "on"
command "off"
command "z1on"
command "z1off"
command "z2on"
command "z2off"
command "z3on"
command "z3off"
command "z4on"
command "z4off"
command "z5on"
command "z5off"
command "z6on"
command "z6off"
command "z7on"
command "z7off"
command "z8on"
command "z8off"
command "z9on"
command "z9off"
command "z10on"
command "z10off"
command "z11on"
command "z11off"
command "z12on"
command "z12off"
command "z13on"
command "z13off"
command "z14on"
command "z14off"
command "z15on"
command "z15off"
command "z16on"
command "z16off"
command "offtime"
command "refresh"
command "rain"
command "manual"
command "setDisplay"
command "settingsMap"
command "writeTime"
command "writeType"
command "notify"
command "updated"
fingerprint endpointId: "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18", profileId: "0104", deviceId: "0002", deviceVersion: "00", inClusters: "0000,0003,0004,0005,0006,000F", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZ16-01"
}
// simulator metadata
simulator {
// status messages
// reply messages
}
preferences {
input description: "Press Configure button after making changes to these preferences", displayDuringSetup: true, type: "paragraph", element: "paragraph", title: ""
input "RainEnable", "bool", title: "Rain Sensor Attached?", required: false, displayDuringSetup: true
input "ManualTime", "number", title: "Automatic shutoff time when a zone is turned on manually?", required: false, displayDuringSetup: true
}
// UI tile definitions
tiles {
standardTile("status", "device.status") {
state "schedule", label: 'Schedule Set', icon: "http://www.plaidsystems.com/smartthings/st_spruce_leaf_225_t.png"
state "finished", label: 'Spruce Finished', icon: "st.Outdoor.outdoor5", backgroundColor: "#46c2e8"
state "raintoday", label: 'Rain Today', icon: "st.custom.wuk.nt_chancerain"
state "rainy", label: 'Previous Rain', icon: "st.custom.wuk.nt_chancerain"
state "raintom", label: 'Rain Tomorrow', icon: "st.custom.wuk.nt_chancerain"
state "donewweek", label: 'Spruce Finished', icon: "st.Outdoor.outdoor5", backgroundColor: "#52c435"
state "skipping", label: 'Skip Today', icon: "st.Outdoor.outdoor20", backgroundColor: "#36cfe3"
state "moisture", label: '', icon: "st.Weather.weather2", backgroundColor: "#36cfe3"
state "pause", label: 'PAUSE', icon: "st.contact.contact.open", backgroundColor: "#f2a51f"
state "active", label: 'Active', icon: "st.Outdoor.outdoor12", backgroundColor: "#3DC72E"
state "season", label: 'Seasonal Adjustment', icon: "st.Outdoor.outdoor17", backgroundColor: "#ffb900"
state "disable", label: 'Disabled', icon: "st.secondary.off", backgroundColor: "#888888"
state "warning", label: '', icon: "st.categories.damageAndDanger", backgroundColor: "#ffff7f"
state "alarm", label: 'Alarm', icon: "st.categories.damageAndDanger", backgroundColor: "#f9240c"
}
standardTile("switch", "device.switch") {
//state "programOff", label: 'Start Program', action: "programOn", icon: "st.sonos.play-icon", backgroundColor: "#a9a9a9"
state "off", label: 'Start Program', action: "programOn", icon: "st.sonos.play-icon", backgroundColor: "#a9a9a9"
state "programOn", label: 'Initialize Program', action: "programOff", icon: "st.contact.contact.open", backgroundColor: "#f6e10e"
state "on", label: 'Program Running', action: "off", icon: "st.Outdoor.outdoor12", backgroundColor: "#3DC72E"
}
standardTile("rainsensor", "device.rainsensor") {
state "rainSensrooff", label: 'Rain Sensor Clear', icon: "st.Weather.weather14", backgroundColor: "#a9a9a9"
state "rainSensoron", label: 'Rain Detected', icon: "st.Weather.weather10", backgroundColor: "#f6e10e"
}
standardTile("switch1", "device.switch1") {
state "z1off", label: '1', action: "z1on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z1on", label: '1', action: "z1off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch2", "device.switch2") {
state "z2off", label: '2', action: "z2on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z2on", label: '2', action: "z2off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch3", "device.switch3", inactiveLabel: false) {
state "z3off", label: '3', action: "z3on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z3on", label: '3', action: "z3off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch4", "device.switch4", inactiveLabel: false) {
state "z4off", label: '4', action: "z4on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z4on", label: '4', action: "z4off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch5", "device.switch5", inactiveLabel: false) {
state "z5off", label: '5', action: "z5on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z5on", label: '5', action: "z5off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch6", "device.switch6", inactiveLabel: false) {
state "z6off", label: '6', action: "z6on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z6on", label: '6', action: "z6off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch7", "device.switch7", inactiveLabel: false) {
state "z7off", label: '7', action: "z7on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z7on", label: '7', action: "z7off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch8", "device.switch8", inactiveLabel: false) {
state "z8off", label: '8', action: "z8on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z8on", label: '8', action: "z8off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch9", "device.switch9", inactiveLabel: false) {
state "z9off", label: '9', action: "z9on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z9on", label: '9', action: "z9off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch10", "device.switch10", inactiveLabel: false) {
state "z10off", label: '10', action: "z10on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z10on", label: '10', action: "z10off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch11", "device.switch11", inactiveLabel: false) {
state "z11off", label: '11', action: "z11on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z11on", label: '11', action: "z11off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch12", "device.switch12", inactiveLabel: false) {
state "z12off", label: '12', action: "z12on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z12on", label: '12', action: "z12off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch13", "device.switch13", inactiveLabel: false) {
state "z13off", label: '13', action: "z13on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z13on", label: '13', action: "z13off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch14", "device.switch14", inactiveLabel: false) {
state "z14off", label: '14', action: "z14on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z14on", label: '14', action: "z14off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch15", "device.switch15", inactiveLabel: false) {
state "z15off", label: '15', action: "z15on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z15on", label: '15', action: "z15off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("switch16", "device.switch16", inactiveLabel: false) {
state "z16off", label: '16', action: "z16on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
state "z16on", label: '16', action: "z16off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", action: "refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
main (["status"])
details(["status","rainsensor","switch","switch1","switch2","switch3","switch4","switch5","switch6","switch7","switch8","switch9","switch10","switch11","switch12","switch13","switch14","switch15","switch16","refresh","configure"])
}
}
def programOn(){
sendEvent(name: "switch", value: "programOn", descriptionText: "Program turned on")
}
def programOff(){
sendEvent(name: "switch", value: "off", descriptionText: "Program turned off")
off()
}
def updated(){
log.debug "updated"
}
// Parse incoming device messages to generate events
def parse(String description) {
//log.debug "Parse description $description"
def result = null
def map = [:]
if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
//log.debug "Desc Map: $descMap"
//using 000F cluster instead of 0006 (switch) because ST does not differentiate between EPs and processes all as switch
if (descMap.cluster == "000F" && descMap.attrId == "0055") {
log.debug "Zone"
map = getZone(descMap)
}
else if (descMap.cluster == "0009" && descMap.attrId == "0000") {
log.debug "Alarm"
map = getAlarm(descMap)
}
}
if (map) {
result = createEvent(map)
}
log.debug "Parse returned $map $result"
return result
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def getZone(descMap){
def map = [:]
def EP = Integer.parseInt(descMap.endpoint.trim(), 16)
String onoff
if(descMap.value == "00"){
onoff = "off"
}
else onoff = "on"
if (EP == 1){
map.name = "switch"
map.value = onoff
map.descriptionText = "${device.displayName} turned sprinkler program $onoff"
}
else if (EP == 18) {
map.name = "rainsensor"
map.value = "rainSensor" + onoff
map.descriptionText = "${device.displayName} rain sensor is $onoff"
}
else {
EP -= 1
map.name = "switch" + EP
map.value = "z" + EP + onoff
map.descriptionText = "${device.displayName} turned Zone $EP $onoff"
}
map.isStateChange = true
map.displayed = true
return map
}
def getAlarm(descMap){
def map = [:]
map.name = "status"
def alarmID = Integer.parseInt(descMap.value.trim(), 16)
log.debug "${alarmID}"
if(alarmID <= 0) map.descriptionText = "${device.displayName} has rebooted, no other alarms"
else map.descriptionText = "${device.displayName} rebooted, reported error on zone ${alarmID - 1}, please check zone is working correctly"
map.value = "alarm"
map.isStateChange = true
map.displayed = true
return map
}
//status notify and change status
def notify(value, text){
sendEvent(name:"status", value:"$value", descriptionText:"$text", isStateChange: true, display: false)
}
//prefrences - rain sensor, manual time
def rain() {
log.debug "Rain $RainEnable"
if (RainEnable) "st wattr 0x${device.deviceNetworkId} 18 0x0F 0x51 0x10 {01}"
else "st wattr 0x${device.deviceNetworkId} 18 0x0F 0x51 0x10 {00}"
}
def manual(){
log.debug "Time $ManualTime"
def mTime = 10
if (ManualTime) mTime = ManualTime
def manualTime = hex(mTime)
"st wattr 0x${device.deviceNetworkId} 1 6 0x4002 0x21 {00${manualTime}}"
}
//write switch time settings map
def settingsMap(WriteTimes, attrType){
log.debug WriteTimes
def i = 1
def runTime
def sendCmds = []
while(i <= 17){
if (WriteTimes."${i}"){
runTime = hex(Integer.parseInt(WriteTimes."${i}"))
log.debug "${i} : $runTime"
if (attrType == 4001) sendCmds.push("st wattr 0x${device.deviceNetworkId} ${i} 0x06 0x4001 0x21 {00${runTime}}")
else sendCmds.push("st wattr 0x${device.deviceNetworkId} ${i} 0x06 0x4002 0x21 {00${runTime}}")
sendCmds.push("delay 500")
}
i++
}
return sendCmds
}
//send switch time
def writeType(wEP, cycle){
log.debug "wt ${wEP} ${cycle}"
"st wattr 0x${device.deviceNetworkId} ${wEP} 0x06 0x4001 0x21 {00" + hex(cycle) + "}"
}
//send switch off time
def writeTime(wEP, runTime){
"st wattr 0x${device.deviceNetworkId} ${wEP} 0x06 0x4002 0x21 {00" + hex(runTime) + "}"
}
//set reporting and binding
def configure() {
String zigbeeId = swapEndianHex(device.hub.zigbeeId)
log.debug "Confuguring Reporting and Bindings ${device.deviceNetworkId} ${device.zigbeeId}"
sendEvent(name: 'configuration',value: 100, descriptionText: "Configuration initialized")
def configCmds = [
//program on/off
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x09 {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
//zones 1-8
"zdo bind 0x${device.deviceNetworkId} 2 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 3 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 4 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 5 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 6 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 7 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 8 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 9 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
//zones 9-16
"zdo bind 0x${device.deviceNetworkId} 10 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 11 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 12 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 13 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 14 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 15 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 16 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} 17 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
//rain sensor
"zdo bind 0x${device.deviceNetworkId} 18 1 0x0F {${device.zigbeeId}} {}",
"zcl global send-me-a-report 6 0 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 2", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 3", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 4", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 5", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 6", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 7", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 8", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 9", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 10", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 11", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 12", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 13", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 14", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 15", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 16", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 17", "delay 500",
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
"send 0x${device.deviceNetworkId} 1 18", "delay 500",
"zcl global send-me-a-report 0x09 0x00 0x21 1 0 {00}", "delay 500",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
return configCmds + rain() + manual()
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}
def refresh() {
log.debug "refresh"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 2 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 3 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 4 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 5 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 6 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 7 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 8 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 9 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 10 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 11 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 12 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 13 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 14 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 15 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 16 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 17 0x0F 0x55", "delay 500",
"st rattr 0x${device.deviceNetworkId} 18 0x0F 0x51","delay 500",
]
return refreshCmds + rain() + manual()
}
// Commands to device
//zones on - 8
def on() {
//sendEvent(name:"status", value:"active", descriptionText:"Program Running", isStateChange: true, display: false)
log.debug "on"
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
}
def off() {
log.debug "off"
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
}
def z1on() {
"st cmd 0x${device.deviceNetworkId} 2 6 1 {}"
}
def z1off() {
"st cmd 0x${device.deviceNetworkId} 2 6 0 {}"
}
def z2on() {
"st cmd 0x${device.deviceNetworkId} 3 6 1 {}"
}
def z2off() {
"st cmd 0x${device.deviceNetworkId} 3 6 0 {}"
}
def z3on() {
"st cmd 0x${device.deviceNetworkId} 4 6 1 {}"
}
def z3off() {
"st cmd 0x${device.deviceNetworkId} 4 6 0 {}"
}
def z4on() {
"st cmd 0x${device.deviceNetworkId} 5 6 1 {}"
}
def z4off() {
"st cmd 0x${device.deviceNetworkId} 5 6 0 {}"
}
def z5on() {
"st cmd 0x${device.deviceNetworkId} 6 6 1 {}"
}
def z5off() {
"st cmd 0x${device.deviceNetworkId} 6 6 0 {}"
}
def z6on() {
"st cmd 0x${device.deviceNetworkId} 7 6 1 {}"
}
def z6off() {
"st cmd 0x${device.deviceNetworkId} 7 6 0 {}"
}
def z7on() {
"st cmd 0x${device.deviceNetworkId} 8 6 1 {}"
}
def z7off() {
"st cmd 0x${device.deviceNetworkId} 8 6 0 {}"
}
def z8on() {
"st cmd 0x${device.deviceNetworkId} 9 6 1 {}"
}
def z8off() {
"st cmd 0x${device.deviceNetworkId} 9 6 0 {}"
}
//zones 9 - 16
def z9on() {
"st cmd 0x${device.deviceNetworkId} 10 6 1 {}"
}
def z9off() {
"st cmd 0x${device.deviceNetworkId} 10 6 0 {}"
}
def z10on() {
"st cmd 0x${device.deviceNetworkId} 11 6 1 {}"
}
def z10off() {
"st cmd 0x${device.deviceNetworkId} 11 6 0 {}"
}
def z11on() {
"st cmd 0x${device.deviceNetworkId} 12 6 1 {}"
}
def z11off() {
"st cmd 0x${device.deviceNetworkId} 12 6 0 {}"
}
def z12on() {
"st cmd 0x${device.deviceNetworkId} 13 6 1 {}"
}
def z12off() {
"st cmd 0x${device.deviceNetworkId} 13 6 0 {}"
}
def z13on() {
"st cmd 0x${device.deviceNetworkId} 14 6 1 {}"
}
def z13off() {
"st cmd 0x${device.deviceNetworkId} 14 6 0 {}"
}
def z14on() {
"st cmd 0x${device.deviceNetworkId} 15 6 1 {}"
}
def z14off() {
"st cmd 0x${device.deviceNetworkId} 15 6 0 {}"
}
def z15on() {
"st cmd 0x${device.deviceNetworkId} 16 6 1 {}"
}
def z15off() {
"st cmd 0x${device.deviceNetworkId} 16 6 0 {}"
}
def z16on() {
"st cmd 0x${device.deviceNetworkId} 17 6 1 {}"
}
def z16off() {
"st cmd 0x${device.deviceNetworkId} 17 6 0 {}"
}

View File

@@ -1,397 +0,0 @@
/**
* Spruce Sensor -Pre-release V2 10/8/2015
*
* Copyright 2014 Plaid Systems
*
* 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.
*
-------10/20/2015 Updates--------
-Fix/add battery reporting interval to update
-remove polling and/or refresh(?)
*/
metadata {
definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "NCauffman") {
capability "Configuration"
capability "Battery"
capability "Relative Humidity Measurement"
capability "Temperature Measurement"
capability "Sensor"
//capability "Polling"
attribute "maxHum", "string"
attribute "minHum", "string"
command "resetHumidity"
command "refresh"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01"
}
preferences {
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph", title: ""
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input "interval", "number", title: "Measurement Interval 1-120 minutes (default: 10 minutes)", description: "Set how often you would like to check soil moisture in minutes", range: "1..120", defaultValue: 10, displayDuringSetup: false
input "resetMinMax", "bool", title: "Reset Humidity min and max", required: false, displayDuringSetup: false
}
tiles {
valueTile("temperature", "device.temperature", canChangeIcon: false, canChangeBackground: false) {
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("humidity", "device.humidity", width: 2, height: 2, canChangeIcon: false, canChangeBackground: true) {
state "humidity", label:'${currentValue}%', unit:"",
backgroundColors:[
[value: 0, color: "#635C0C"],
[value: 16, color: "#EBEB21"],
[value: 22, color: "#C7DE6A"],
[value: 42, color: "#9AD290"],
[value: 64, color: "#44B621"],
[value: 80, color: "#3D79D9"],
[value: 96, color: "#0A50C2"]
]
}
valueTile("maxHum", "device.maxHum", canChangeIcon: false, canChangeBackground: false) {
state "maxHum", label:'High ${currentValue}%', unit:"",
backgroundColors:[
[value: 0, color: "#635C0C"],
[value: 16, color: "#EBEB21"],
[value: 22, color: "#C7DE6A"],
[value: 42, color: "#9AD290"],
[value: 64, color: "#44B621"],
[value: 80, color: "#3D79D9"],
[value: 96, color: "#0A50C2"]
]
}
valueTile("minHum", "device.minHum", canChangeIcon: false, canChangeBackground: false) {
state "minHum", label:'Low ${currentValue}%', unit:"",
backgroundColors:[
[value: 0, color: "#635C0C"],
[value: 16, color: "#EBEB21"],
[value: 22, color: "#C7DE6A"],
[value: 42, color: "#9AD290"],
[value: 64, color: "#44B621"],
[value: 80, color: "#3D79D9"],
[value: 96, color: "#0A50C2"]
]
}
valueTile("battery", "device.battery", decoration: "flat", canChangeIcon: false, canChangeBackground: false) {
state "battery", label:'${currentValue}% battery'
}
main (["humidity"])
details(["humidity","maxHum","minHum","temperature","battery"])
}
}
def parse(String description) {
log.debug "Parse description $description config: ${device.latestValue('configuration')} interval: $interval"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
map = parseReportAttributeMessage(description)
}
else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) {
map = parseCustomMessage(description)
}
def result = map ? createEvent(map) : null
//check in configuration change
if (!device.latestValue('configuration')) result = poll()
if (device.latestValue('configuration').toInteger() != interval && interval != null) {
result = poll()
}
log.debug "result: $result"
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def linkText = getLinkText(device)
//log.debug "Catchall"
def descMap = zigbee.parse(description)
//check humidity configuration is complete
if (descMap.command == 0x07 && descMap.clusterId == 0x0405){
def configInterval = 10
if (interval != null) configInterval = interval
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration Successful")
//setConfig()
log.debug "config complete"
//return resultMap = [name: 'configuration', value: configInterval, descriptionText: "Settings configured successfully"]
}
else if (descMap.command == 0x0001){
def hexString = "${hex(descMap.data[5])}" + "${hex(descMap.data[4])}"
def intString = Integer.parseInt(hexString, 16)
//log.debug "command: $descMap.command clusterid: $descMap.clusterId $hexString $intString"
if (descMap.clusterId == 0x0402){
def value = getTemperature(hexString)
resultMap = getTemperatureResult(value)
}
else if (descMap.clusterId == 0x0405){
def value = Math.round(new BigDecimal(intString / 100)).toString()
resultMap = getHumidityResult(value)
}
else return null
}
else return null
return resultMap
}
private Map parseReportAttributeMessage(String description) {
def descMap = parseDescriptionAsMap(description)
log.debug "Desc Map: $descMap"
log.debug "Report Attributes"
Map resultMap = [:]
if (descMap.cluster == "0001" && descMap.attrId == "0000") {
resultMap = getBatteryResult(descMap.value)
}
return resultMap
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
log.debug "parseCustom"
if (description?.startsWith('temperature: ')) {
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
resultMap = getTemperatureResult(value)
}
else if (description?.startsWith('humidity: ')) {
def pct = (description - "humidity: " - "%").trim()
if (pct.isNumber()) {
def value = Math.round(new BigDecimal(pct)).toString()
resultMap = getHumidityResult(value)
} else {
log.error "invalid humidity: ${pct}"
}
}
return resultMap
}
private Map getHumidityResult(value) {
def linkText = getLinkText(device)
def maxHumValue = 0
def minHumValue = 0
if (device.currentValue("maxHum") != null) maxHumValue = device.currentValue("maxHum").toInteger()
if (device.currentValue("minHum") != null) minHumValue = device.currentValue("minHum").toInteger()
log.debug "Humidity max: ${maxHumValue} min: ${minHumValue}"
def compare = value.toInteger()
if (compare > maxHumValue) {
sendEvent(name: 'maxHum', value: value, unit: '%', descriptionText: "${linkText} soil moisture high is ${value}%")
}
else if (((compare < minHumValue) || (minHumValue <= 2)) && (compare != 0)) {
sendEvent(name: 'minHum', value: value, unit: '%', descriptionText: "${linkText} soil moisture low is ${value}%")
}
return [
name: 'humidity',
value: value,
unit: '%',
descriptionText: "${linkText} soil moisture is ${value}%"
]
}
def getTemperature(value) {
def celsius = (Integer.parseInt(value, 16).shortValue()/100)
//log.debug "Report Temp $value : $celsius C"
if(getTemperatureScale() == "C"){
return celsius
} else {
return celsiusToFahrenheit(celsius) as Integer
}
}
private Map getTemperatureResult(value) {
log.debug "Temperature: $value"
def linkText = getLinkText(device)
if (tempOffset) {
def offset = tempOffset as int
def v = value as int
value = v + offset
}
def descriptionText = "${linkText} is ${value}°${temperatureScale}"
return [
name: 'temperature',
value: value,
descriptionText: descriptionText
]
}
private Map getBatteryResult(value) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [
name: 'battery'
]
def min = 2500
def percent = ((Integer.parseInt(value, 16) - min) / 5)
percent = Math.max(0, Math.min(percent, 100.0))
result.value = Math.round(percent)
def descriptionText
if (percent < 10) result.descriptionText = "${linkText} battery is getting low $percent %."
else result.descriptionText = "${linkText} battery is ${result.value}%"
return result
}
def resetHumidity(){
def linkText = getLinkText(device)
def minHumValue = 0
def maxHumValue = 0
sendEvent(name: 'minHum', value: minHumValue, unit: '%', descriptionText: "${linkText} min soil moisture reset to ${minHumValue}%")
sendEvent(name: 'maxHum', value: maxHumValue, unit: '%', descriptionText: "${linkText} max soil moisture reset to ${maxHumValue}%")
}
def setConfig(){
def configInterval = 100
if (interval != null) configInterval = interval
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration initialized")
}
//when device preferences are changed
def updated(){
log.debug "device updated"
if (!device.latestValue('configuration')) configure()
else{
if (resetMinMax == true) resetHumidity()
if (device.latestValue('configuration').toInteger() != interval && interval != null){
sendEvent(name: 'configuration',value: 0, descriptionText: "Settings changed and will update at next report. Measure interval set to ${interval} mins")
}
}
}
//poll
def poll() {
log.debug "poll called"
List cmds = []
if (!device.latestValue('configuration')) cmds += configure()
else if (device.latestValue('configuration').toInteger() != interval && interval != null) {
cmds += intervalUpdate()
}
//cmds += refresh()
log.debug "commands $cmds"
return cmds?.collect { new physicalgraph.device.HubAction(it) }
}
//update intervals
def intervalUpdate(){
log.debug "intervalUpdate"
def minReport = 10
def maxReport = 610
if (interval != null) {
minReport = interval
maxReport = interval * 61
}
[
"zcl global send-me-a-report 0x405 0x0000 0x21 $minReport $maxReport {6400}", "delay 500",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zcl global send-me-a-report 1 0x0000 0x21 0x0C 0 {0500}", "delay 500",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
]
}
def refresh() {
log.debug "refresh"
[
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} 1 0x405 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} 1 1 0"
]
}
//configure
def configure() {
//set minReport = measurement in minutes
def minReport = 10
def maxReport = 610
//String zigbeeId = swapEndianHex(device.hub.zigbeeId)
//log.debug "zigbeeid ${device.zigbeeId} deviceId ${device.deviceNetworkId}"
if (!device.zigbeeId) sendEvent(name: 'configuration',value: 0, descriptionText: "Device Zigbee Id not found, remove and attempt to rejoin device")
else sendEvent(name: 'configuration',value: 100, descriptionText: "Configuration initialized")
//log.debug "Configuring Reporting and Bindings. min: $minReport max: $maxReport "
[
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x405 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 1000",
//temperature
"zcl global send-me-a-report 0x402 0x0000 0x29 1 0 {3200}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
//min = soil measure interval
"zcl global send-me-a-report 0x405 0x0000 0x21 $minReport $maxReport {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
//min = battery measure interval 1 = 1 hour
"zcl global send-me-a-report 1 0x0000 0x21 0x0C 0 {0500}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
] + refresh()
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}

View File

@@ -1,161 +0,0 @@
/**
* Copyright 2016 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 HA", namespace: "smartthings", author: "SmartThings") {
capability "Tone"
capability "Actuator"
capability "Presence Sensor"
capability "Sensor"
capability "Battery"
capability "Configuration"
fingerprint inClusters: "0000,0001,0003,000F,0020", outClusters: "0003,0019",
manufacturer: "SmartThings", model: "tagv4", deviceJoinName: "Arrival Sensor"
}
preferences {
section {
image(name: 'educationalcontent', multiple: true, images: [
"http://cdn.device-gse.smartthings.com/Arrival/Arrival1.png",
"http://cdn.device-gse.smartthings.com/Arrival/Arrival2.png"
])
}
section {
input "checkInterval", "enum", title: "Presence timeout (minutes)",
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
}
}
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:"#ffffff"
}
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:""
}
main "presence"
details(["presence", "beep", "battery"])
}
}
def updated() {
startTimer()
}
def configure() {
def cmds = zigbee.configureReporting(0x0001, 0x0020, 0x20, 20, 20, 0x01)
log.debug "configure -- cmds: ${cmds}"
return cmds
}
def beep() {
log.debug "Sending Identify command to beep the sensor for 5 seconds"
return zigbee.command(0x0003, 0x00, "0500")
}
def parse(String description) {
state.lastCheckin = now()
handlePresenceEvent(true)
if (description?.startsWith('read attr -')) {
handleReportAttributeMessage(description)
}
return []
}
private handleReportAttributeMessage(String description) {
def descMap = zigbee.parseDescriptionAsMap(description)
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
handleBatteryEvent(Integer.parseInt(descMap.value, 16))
}
}
/**
* Create battery event from reported battery voltage.
*
* @param volts Battery voltage in .1V increments
*/
private handleBatteryEvent(volts) {
if (volts == 0 || volts == 255) {
log.debug "Ignoring invalid value for voltage (${volts/10}V)"
}
else {
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
def minVolts = 15
def maxVolts = 28
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
def linkText = getLinkText(device)
def eventMap = [
name: 'battery',
value: pct,
descriptionText: "${linkText} battery was ${pct}%"
]
log.debug "Creating battery event for voltage=${volts/10}V: ${eventMap}"
sendEvent(eventMap)
}
}
}
private handlePresenceEvent(present) {
def wasPresent = device.currentState("presence")?.value == "present"
if (!wasPresent && present) {
log.debug "Sensor is present"
startTimer()
} else if (!present) {
log.debug "Sensor is not present"
stopTimer()
}
def linkText = getLinkText(device)
def eventMap = [
name: "presence",
value: present ? "present" : "not present",
linkText: linkText,
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
]
log.debug "Creating presence event: ${eventMap}"
sendEvent(eventMap)
}
private startTimer() {
log.debug "Scheduling periodic timer"
schedule("0 * * * * ?", checkPresenceCallback)
}
private stopTimer() {
log.debug "Stopping periodic timer"
unschedule()
}
def checkPresenceCallback() {
def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000
def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60
log.debug "Sensor checked in ${timeSinceLastCheckin} seconds ago"
if (timeSinceLastCheckin >= theCheckInterval) {
handlePresenceEvent(false)
}
}

View File

@@ -15,7 +15,6 @@
* Author: SmartThings
* Date: 2013-12-04
*/
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "CentraLite Dimmer", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
@@ -26,6 +25,7 @@ metadata {
capability "Refresh"
capability "Sensor"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B04,0B05", outClusters: "0019"
}
// simulator metadata

View File

@@ -1,71 +0,0 @@
/**
* Ecobee Sensor
*
* Copyright 2015 Juan Risso
*
* 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: "Ecobee Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Sensor"
capability "Temperature Measurement"
capability "Motion Sensor"
capability "Refresh"
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",
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("motion", "device.motion") {
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main (["temperature","motion"])
details(["temperature","motion","refresh"])
}
}
def refresh() {
log.debug "refresh..."
poll()
}
void poll() {
log.debug "Executing 'poll' using parent SmartApp"
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

@@ -22,31 +22,31 @@ metadata {
capability "Polling"
capability "Sensor"
capability "Refresh"
command "generateEvent"
command "raiseSetpoint"
command "lowerSetpoint"
command "resumeProgram"
command "switchMode"
attribute "thermostatSetpoint","number"
attribute "thermostatStatus","string"
}
simulator { }
tiles {
tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F",
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"]
]
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") {
@@ -54,27 +54,27 @@ metadata {
state "heat", action:"switchMode", nextState: "updating", icon: "st.thermostat.heat"
state "cool", action:"switchMode", nextState: "updating", icon: "st.thermostat.cool"
state "auto", action:"switchMode", nextState: "updating", icon: "st.thermostat.auto"
state "auxHeatOnly", action:"switchMode", icon: "st.thermostat.emergency-heat"
state "updating", label:"Working", icon: "st.secondary.secondary"
state "auxHeatOnly", action:"switchMode", icon: "st.thermostat.emergency-heat"
state "updating", label:"Working", icon: "st.secondary.secondary"
}
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
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 "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"
standardTile("upButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") {
state "setpoint", action:"raiseSetpoint", backgroundColor:"#d04e00", icon:"st.thermostat.thermostat-up"
}
valueTile("thermostatSetpoint", "device.thermostatSetpoint", width: 1, height: 1, decoration: "flat") {
valueTile("thermostatSetpoint", "device.thermostatSetpoint", width: 1, height: 1, decoration: "flat") {
state "thermostatSetpoint", label:'${currentValue}'
}
valueTile("currentStatus", "device.thermostatStatus", height: 1, width: 2, decoration: "flat") {
state "thermostatStatus", label:'${currentValue}', backgroundColor:"#ffffff"
}
}
standardTile("downButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") {
state "setpoint", action:"lowerSetpoint", icon:"st.thermostat.thermostat-down"
}
state "setpoint", action:"lowerSetpoint", backgroundColor:"#d04e00", icon:"st.thermostat.thermostat-down"
}
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
state "setHeatingSetpoint", action:"thermostat.setHeatingSetpoint", backgroundColor:"#d04e00"
}
@@ -91,196 +91,218 @@ 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 Schedule', icon:"st.samsung.da.oven_ic_send"
state "updating", label:"Working", icon: "st.secondary.secondary"
state "resume", label:'Resume Program', action:"device.resumeProgram", icon:"st.sonos.play-icon"
}
main "temperature"
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh"])
}
preferences {
input "holdType", "enum", title: "Hold Type", description: "When changing temperature, use Temporary (Until next transition) or Permanent hold (default)", required: false, options:["Temporary", "Permanent"]
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh"])
}
}
/*
preferences {
input "highTemperature", "number", title: "Auto Mode High Temperature:", defaultValue: 80
input "lowTemperature", "number", title: "Auto Mode Low Temperature:", defaultValue: 70
input name: "holdType", type: "enum", title: "Hold Type", description: "When changing temperature, use Temporary or Permanent hold", required: true, options:["Temporary", "Permanent"]
}
*/
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
// TODO: handle '' attribute
}
def refresh() {
log.debug "refresh called"
poll()
log.debug "refresh ended"
def refresh()
{
log.debug "refresh called"
poll()
log.debug "refresh ended"
}
def go()
{
log.debug "before:go tile tapped"
poll()
log.debug "after"
}
void poll() {
log.debug "Executing 'poll' using parent SmartApp"
def results = parent.pollChild(this)
generateEvent(results) //parse received message from parent
parseEventData(results)
generateStatusEvent()
}
def generateEvent(Map results) {
def parseEventData(Map results)
{
log.debug "parsing data $results"
if(results) {
results.each { name, value ->
if(results)
{
results.each { name, value ->
def linkText = getLinkText(device)
def isChange = false
def isDisplayed = true
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name]
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") {
def sendValue = value? convertTemperatureIfNeeded(value.toDouble(), "F", 1): value //API return temperature value in F
def isChange = false
def isDisplayed = true
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") {
isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
} 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 {
isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange
event << [value: value.toString(), isStateChange: isChange, displayed: isDisplayed]
}
sendEvent(event)
isDisplayed = isChange
sendEvent(
name: name,
value: value,
unit: "F",
linkText: linkText,
descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name,
isStateChange: isChange,
displayed: isDisplayed)
}
else {
isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange
sendEvent(
name: name,
value: value.toString(),
linkText: linkText,
descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name,
isStateChange: isChange,
displayed: isDisplayed)
}
}
generateSetpointEvent ()
generateStatusEvent ()
}
}
void generateEvent(Map results)
{
log.debug "parsing data $results"
if(results)
{
results.each { name, value ->
def linkText = getLinkText(device)
def isChange = false
def isDisplayed = true
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") {
isChange = isTemperatureStateChange(device, name, value.toString())
isDisplayed = isChange
sendEvent(
name: name,
value: value,
unit: "F",
linkText: linkText,
descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name,
isStateChange: isChange,
displayed: isDisplayed)
}
else {
isChange = isStateChange(device, name, value.toString())
isDisplayed = isChange
sendEvent(
name: name,
value: value.toString(),
linkText: linkText,
descriptionText: getThermostatDescriptionText(name, value, linkText),
handlerName: name,
isStateChange: isChange,
displayed: isDisplayed)
}
}
generateSetpointEvent ()
generateStatusEvent ()
generateSetpointEvent ()
generateStatusEvent()
}
}
//return descriptionText to be shown on mobile activity feed
private getThermostatDescriptionText(name, value, linkText) {
if(name == "temperature") {
return "$linkText temperature is $value°F"
} else if(name == "heatingSetpoint") {
return "heating setpoint is $value°F"
} else if(name == "coolingSetpoint"){
return "cooling setpoint is $value°F"
} else if (name == "thermostatMode") {
return "thermostat mode is ${value}"
} else if (name == "thermostatFanMode") {
return "thermostat fan mode is ${value}"
} else {
return "${name} = ${value}"
private getThermostatDescriptionText(name, value, linkText)
{
if(name == "temperature")
{
return "$linkText was $value°F"
}
else if(name == "heatingSetpoint")
{
return "latest heating setpoint was $value°F"
}
else if(name == "coolingSetpoint")
{
return "latest cooling setpoint was $value°F"
}
else if (name == "thermostatMode")
{
return "thermostat mode is ${value}"
}
else
{
return "${name} = ${value}"
}
}
void setHeatingSetpoint(setpoint) {
setHeatingSetpoint(setpoint.toDouble())
void setHeatingSetpoint(degreesF) {
setHeatingSetpoint(degreesF.toDouble())
}
void setHeatingSetpoint(Double setpoint) {
// def mode = device.currentValue("thermostatMode")
def heatingSetpoint = setpoint
def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble()
def deviceId = device.deviceNetworkId.split(/\./).last()
//enforce limits of heatingSetpoint
if (heatingSetpoint > 79) {
heatingSetpoint = 79
} else if (heatingSetpoint < 45) {
heatingSetpoint = 45
}
//enforce limits of heatingSetpoint vs coolingSetpoint
if (heatingSetpoint >= coolingSetpoint) {
coolingSetpoint = heatingSetpoint
}
log.debug "Sending setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
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)" //This error is handled by the connect app
}
void setHeatingSetpoint(Double degreesF) {
log.debug "setHeatingSetpoint({$degreesF})"
sendEvent("name":"heatingSetpoint", "value":degreesF)
Double coolingSetpoint = device.currentValue("coolingSetpoint")
log.debug "coolingSetpoint: $coolingSetpoint"
parent.setHold(this, degreesF, coolingSetpoint)
}
void setCoolingSetpoint(setpoint) {
setCoolingSetpoint(setpoint.toDouble())
void setCoolingSetpoint(degreesF) {
setCoolingSetpoint(degreesF.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()
if (coolingSetpoint > 92) {
coolingSetpoint = 92
} else if (coolingSetpoint < 65) {
coolingSetpoint = 65
}
//enforce limits of heatingSetpoint vs coolingSetpoint
if (heatingSetpoint >= coolingSetpoint) {
heatingSetpoint = coolingSetpoint
}
log.debug "Sending setCoolingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
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)" //This error is handled by the connect app
}
void setCoolingSetpoint(Double degreesF) {
log.debug "setCoolingSetpoint({$degreesF})"
sendEvent("name":"coolingSetpoint", "value":degreesF)
Double heatingSetpoint = device.currentValue("heatingSetpoint")
parent.setHold(this, heatingSetpoint, degreesF)
}
void resumeProgram() {
log.debug "resumeProgram() is called"
sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false)
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.resumeProgram(this, deviceId)) {
sendEvent("name":"thermostatStatus", "value":"setpoint is updating", "description":statusText, displayed: false)
runIn(5, "poll")
log.debug "resumeProgram() is done"
sendEvent("name":"resumeProgram", "value":"resume", descriptionText: "resumeProgram is done", displayed: false, isStateChange: true)
} else {
sendEvent("name":"thermostatStatus", "value":"failed resume click refresh", "description":statusText, displayed: false)
log.error "Error resumeProgram() check parent.resumeProgram(this, deviceId)"
}
def configure() {
}
def resumeProgram() {
parent.resumeProgram(this)
}
def modes() {
if (state.modes) {
log.debug "Modes = ${state.modes}"
return state.modes
}
else {
state.modes = parent.availableModes(this)
log.debug "Modes = ${state.modes}"
return state.modes
}
log.debug "Modes = ${state.modes}"
return state.modes
}
else {
state.modes = parent.availableModes(this)
log.debug "Modes = ${state.modes}"
return state.modes
}
}
def fanModes() {
["off", "on", "auto", "circulate"]
}
def switchMode() {
log.debug "in switchMode"
def currentMode = device.currentState("thermostatMode")?.value
@@ -292,7 +314,7 @@ def switchMode() {
}
def switchToMode(nextMode) {
log.debug "In switchToMode = ${nextMode}"
log.debug "In switchToMode = ${nextMode}"
if (nextMode in modes()) {
state.lastTriedMode = nextMode
"$nextMode"()
@@ -354,326 +376,300 @@ def getDataByName(String name) {
def setThermostatMode(String value) {
log.debug "setThermostatMode({$value})"
}
def setThermostatFanMode(String value) {
log.debug "setThermostatFanMode({$value})"
}
def generateModeEvent(mode) {
sendEvent(name: "thermostatMode", value: mode, descriptionText: "$device.displayName is in ${mode} mode", displayed: true)
sendEvent(name: "thermostatMode", value: mode, descriptionText: "$device.displayName is in ${mode} mode", displayed: true, isStateChange: true)
}
def generateFanModeEvent(fanMode) {
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${mode} mode", displayed: true)
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${mode} mode", displayed: true, isStateChange: true)
}
def generateOperatingStateEvent(operatingState) {
sendEvent(name: "thermostatOperatingState", value: operatingState, descriptionText: "$device.displayName is ${operatingState}", displayed: true)
sendEvent(name: "thermostatOperatingState", value: operatingState, descriptionText: "$device.displayName is ${operatingState}", displayed: true, isStateChange: true)
}
def off() {
log.debug "off"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode (this,"off", deviceId))
generateModeEvent("off")
else {
log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back
}
generateSetpointEvent()
generateStatusEvent()
generateModeEvent("off")
if (parent.setMode (this,"off"))
generateModeEvent("off")
else {
log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back
}
generateSetpointEvent()
generateStatusEvent()
}
def heat() {
log.debug "heat"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode (this,"heat", deviceId))
generateModeEvent("heat")
else {
log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back
}
generateSetpointEvent()
generateStatusEvent()
generateModeEvent("heat")
if (parent.setMode (this,"heat"))
generateModeEvent("heat")
else {
log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back
}
generateSetpointEvent()
generateStatusEvent()
}
def auxHeatOnly() {
log.debug "auxHeatOnly"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode (this,"auxHeatOnly", deviceId))
generateModeEvent("auxHeatOnly")
else {
log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back
}
generateSetpointEvent()
generateStatusEvent()
generateModeEvent("auxHeatOnly")
if (parent.setMode (this,"auxHeatOnly"))
generateModeEvent("auxHeatOnly")
else {
log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back
}
generateSetpointEvent()
generateStatusEvent()
}
def cool() {
log.debug "cool"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode (this,"cool", deviceId))
generateModeEvent("cool")
else {
log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back
}
generateSetpointEvent()
generateStatusEvent()
generateModeEvent("cool")
if (parent.setMode (this,"cool"))
generateModeEvent("cool")
else {
log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back
}
generateSetpointEvent()
generateStatusEvent()
}
def auto() {
log.debug "auto"
def deviceId = device.deviceNetworkId.split(/\./).last()
if (parent.setMode (this,"auto", deviceId))
generateModeEvent("auto")
else {
log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back
}
generateSetpointEvent()
generateStatusEvent()
generateModeEvent("auto")
if (parent.setMode (this,"auto"))
generateModeEvent("auto")
else {
log.debug "Error setting new mode."
def currentMode = device.currentState("thermostatMode")?.value
generateModeEvent(currentMode) // reset the tile back
}
generateSetpointEvent()
generateStatusEvent()
}
def fanOn() {
log.debug "fanOn"
// parent.setFanMode (this,"on")
parent.setFanMode (this,"on")
}
def fanAuto() {
log.debug "fanAuto"
// parent.setFanMode (this,"auto")
parent.setFanMode (this,"auto")
}
def fanCirculate() {
log.debug "fanCirculate"
// parent.setFanMode (this,"circulate")
parent.setFanMode (this,"circulate")
}
def fanOff() {
log.debug "fanOff"
// parent.setFanMode (this,"off")
parent.setFanMode (this,"off")
}
def generateSetpointEvent() {
log.debug "Generate SetPoint Event"
log.debug "Generate SetPoint Event"
def mode = device.currentValue("thermostatMode")
log.debug "Current Mode = ${mode}"
log.debug "Current Mode = ${mode}"
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
log.debug "Heating Setpoint = ${heatingSetpoint}"
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
log.debug "Heating Setpoint = ${heatingSetpoint}"
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
log.debug "Cooling Setpoint = ${coolingSetpoint}"
log.debug "Cooling Setpoint = ${coolingSetpoint}"
if (mode == "heat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()+"°")
}
else if (mode == "cool") {
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString()+"°")
if (mode == "heat") {
} else if (mode == "auto") {
sendEvent("name":"thermostatSetpoint", "value":"Auto")
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
} else if (mode == "off") {
sendEvent("name":"thermostatSetpoint", "value":"Off")
}
else if (mode == "cool") {
} else if (mode == "emergencyHeat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()+"°")
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString())
} else if (mode == "auto") {
sendEvent("name":"thermostatSetpoint", "value":"Auto")
} else if (mode == "off") {
sendEvent("name":"thermostatSetpoint", "value":"Off")
} else if (mode == "emergencyHeat") {
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
}
}
}
void raiseSetpoint() {
log.debug "Raise SetPoint"
def mode = device.currentValue("thermostatMode")
def targetvalue
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
log.debug "Current Mode = ${mode}"
if (mode == "heat") {
heatingSetpoint++
if (heatingSetpoint > 99)
heatingSetpoint = 99
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()+"°")
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
parent.setHold (this, heatingSetpoint, coolingSetpoint)
log.debug "New Heating Setpoint = ${heatingSetpoint}"
}
else if (mode == "cool") {
coolingSetpoint++
if (coolingSetpoint > 99)
coolingSetpoint = 99
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString()+"°")
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
parent.setHold (this, heatingSetpoint, coolingSetpoint)
log.debug "New Cooling Setpoint = ${coolingSetpoint}"
}
generateStatusEvent()
if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow raiseSetpoint"
} else {
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 as Integer
} else {
targetvalue = 0
}
targetvalue = targetvalue + 1
if (mode == "heat" && targetvalue > 79) {
targetvalue = 79
} else if (mode == "cool" && targetvalue > 92) {
targetvalue = 92
}
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
log.info "In mode $mode raiseSetpoint() to $targetvalue"
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
}
}
//called by tile when user hit raise temperature button on UI
void lowerSetpoint() {
log.debug "Lower SetPoint"
def mode = device.currentValue("thermostatMode")
def targetvalue
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
log.debug "Current Mode = ${mode}, Current Heating Setpoint = ${heatingSetpoint}, Current Cooling Setpoint = ${coolingSetpoint}"
if (mode == "heat" || mode == "emergencyHeat") {
heatingSetpoint--
if (heatingSetpoint < 32)
heatingSetpoint = 32
if (mode == "off" || mode == "auto") {
log.warn "this mode: $mode does not allow lowerSetpoint"
} else {
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 as Integer
} else {
targetvalue = 0
}
targetvalue = targetvalue - 1
if (mode == "heat" && targetvalue.toInteger() < 45) {
targetvalue = 45
} else if (mode == "cool" && targetvalue.toInteger() < 65) {
targetvalue = 65
}
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
log.info "In mode $mode lowerSetpoint() to $targetvalue"
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
}
}
//called by raiseSetpoint() and lowerSetpoint()
void alterSetpoint(temp) {
def mode = device.currentValue("thermostatMode")
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"){
if (temp.value > coolingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = coolingSetpoint
}
} else if (mode == "cool") {
//enforce limits before sending request to cloud
if (temp.value < heatingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = heatingSetpoint
targetCoolingSetpoint = temp.value
}
}
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"
//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"){
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
} else if (mode == "cool") {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
}
}
generateStatusEvent()
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString()+"°")
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
parent.setHold (this, heatingSetpoint, coolingSetpoint)
log.debug "New Heating Setpoint = ${heatingSetpoint}"
}
else if (mode == "cool") {
coolingSetpoint--
if (coolingSetpoint < 32)
coolingSetpoint = 32
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString()+"°")
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
parent.setHold (this, heatingSetpoint, coolingSetpoint)
log.debug "New Cooling Setpoint = ${coolingSetpoint}"
}
generateStatusEvent()
}
def generateStatusEvent() {
def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def temperature = device.currentValue("temperature").toInteger()
def statusText
log.debug "Generate Status Event for Mode = ${mode}"
log.debug "Temperature = ${temperature}"
log.debug "Heating set point = ${heatingSetpoint}"
log.debug "Cooling set point = ${coolingSetpoint}"
log.debug "HVAC Mode = ${mode}"
if (mode == "heat") {
if (temperature >= heatingSetpoint)
statusText = "Right Now: Idle"
else
statusText = "Heating to ${heatingSetpoint}° F"
} else if (mode == "cool") {
if (temperature <= coolingSetpoint)
statusText = "Right Now: Idle"
else
statusText = "Cooling to ${coolingSetpoint}° F"
} else if (mode == "auto") {
statusText = "Right Now: Auto"
} else if (mode == "off") {
statusText = "Right Now: Off"
} else if (mode == "emergencyHeat") {
statusText = "Emergency Heat"
} else {
statusText = "?"
}
log.debug "Generate Status Event = ${statusText}"
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
def temperature = device.currentValue("temperature").toInteger()
def statusText
log.debug "Generate Status Event for Mode = ${mode}"
log.debug "Temperature = ${temperature}"
log.debug "Heating set point = ${heatingSetpoint}"
log.debug "Cooling set point = ${coolingSetpoint}"
log.debug "HVAC Mode = ${mode}"
if (mode == "heat") {
if (temperature >= heatingSetpoint)
statusText = "Right Now: Idle"
else
statusText = "Heating to ${heatingSetpoint}° F"
} else if (mode == "cool") {
if (temperature <= coolingSetpoint)
statusText = "Right Now: Idle"
else
statusText = "Cooling to ${coolingSetpoint}° F"
} else if (mode == "auto") {
statusText = "Right Now: Auto"
} else if (mode == "off") {
statusText = "Right Now: Off"
} else if (mode == "emergencyHeat") {
statusText = "Emergency Heat"
} else {
statusText = "?"
}
log.debug "Generate Status Event = ${statusText}"
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true, isStateChange: true)
}
//generate custom mobile activity feeds event
def generateActivityFeedsEvent(notificationMessage) {
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
}

View File

@@ -1,126 +0,0 @@
/**
* Copyright 2016 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: "Gentle Wake Up Controller", namespace: "smartthings", author: "SmartThings") {
capability "Switch"
capability "Timed Session"
attribute "percentComplete", "number"
command "setPercentComplete", ["number"]
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale: 2) {
multiAttributeTile(name: "richTile", type:"generic", width:6, height:4) {
tileAttribute("sessionStatus", key: "PRIMARY_CONTROL") {
attributeState "cancelled", action: "timed session.start", icon: "http://f.cl.ly/items/322n181j2K3f281r2s0A/playbutton.png", backgroundColor: "#ffffff", nextState: "running"
attributeState "stopped", action: "timed session.start", icon: "http://f.cl.ly/items/322n181j2K3f281r2s0A/playbutton.png", backgroundColor: "#ffffff", nextState: "cancelled"
attributeState "running", action: "timed session.stop", icon: "http://f.cl.ly/items/0B3y3p2V3X2l3P3y3W09/stopbutton.png", backgroundColor: "#79b821", nextState: "cancelled"
}
tileAttribute("timeRemaining", key: "SECONDARY_CONTROL") {
attributeState "timeRemaining", label:'${currentValue} remaining'
}
tileAttribute("percentComplete", key: "SLIDER_CONTROL") {
attributeState "percentComplete", action: "timed session.setTimeRemaining"
}
}
// start/stop
standardTile("sessionStatusTile", "sessionStatus", width: 1, height: 1, canChangeIcon: true) {
state "cancelled", label: "Stopped", action: "timed session.start", backgroundColor: "#ffffff", icon: "http://f.cl.ly/items/1J1g0H2P0S1G1f2O1s1s/icon.png"
state "stopped", label: "Stopped", action: "timed session.start", backgroundColor: "#ffffff", icon: "http://f.cl.ly/items/1J1g0H2P0S1G1f2O1s1s/icon.png"
state "running", label: "Running", action: "timed session.stop", backgroundColor: "#79b821", icon: "http://f.cl.ly/items/1J1g0H2P0S1G1f2O1s1s/icon.png"
}
// duration
valueTile("timeRemainingTile", "timeRemaining", decoration: "flat", width: 2) {
state "timeRemaining", label:'${currentValue} left'
}
controlTile("percentCompleteTile", "percentComplete", "slider", height: 1, width: 3) {
state "percentComplete", action: "timed session.setTimeRemaining"
}
main "sessionStatusTile"
details "richTile"
// details(["richTile", "sessionStatusTile", "timeRemainingTile", "percentCompleteTile"])
}
}
// parse events into attributes
def parse(description) {
log.debug "Parsing '${description}'"
// TODO: handle 'switch' attribute
// TODO: handle 'level' attribute
// TODO: handle 'sessionStatus' attribute
// TODO: handle 'timeRemaining' attribute
}
// handle commands
def on() {
log.debug "Executing 'on'"
startDimming()
}
def off() {
log.debug "Executing 'off'"
stopDimming()
}
def setTimeRemaining(percentComplete) {
log.debug "Executing 'setTimeRemaining' to ${percentComplete}% complete"
parent.jumpTo(percentComplete)
}
def start() {
log.debug "Executing 'start'"
startDimming()
}
def stop() {
log.debug "Executing 'stop'"
stopDimming()
}
def pause() {
log.debug "Executing 'pause'"
// TODO: handle 'pause' command
}
def cancel() {
log.debug "Executing 'cancel'"
stopDimming()
}
def startDimming() {
log.trace "startDimming"
log.debug "parent: ${parent}"
parent.start("controller")
}
def stopDimming() {
log.trace "stopDimming"
log.debug "parent: ${parent}"
parent.stop("controller")
}
def controllerEvent(eventData) {
log.trace "controllerEvent"
sendEvent(eventData)
}

View File

@@ -14,7 +14,7 @@
*
*/
metadata {
definition (name: "Harmony Activity", namespace: "smartthings", author: "Juan Risso") {
definition (name: "Logitech Harmony Activity", namespace: "smartthings", author: "Juan Risso") {
capability "Switch"
capability "Actuator"
capability "Refresh"

View File

@@ -80,12 +80,19 @@ def parse(String description) {
if (cmd) {
result = zwaveEvent(cmd)
}
log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
// log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.multiinstancev1.MultiInstanceCmdEncap cmd) {
def encapsulated = cmd.encapsulatedCommand([0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
def encapsulated = null
if (cmd.respondsTo("encapsulatedCommand")) {
encapsulated = cmd.encapsulatedCommand()
} else {
def hex1 = { n -> String.format("%02X", n) }
def sorry = "command: ${hex1(cmd.commandClass)}${hex1(cmd.command)}, payload: " + cmd.parameter.collect{ hex1(it) }.join(" ")
encapsulated = zwave.parse(sorry, [0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
}
return encapsulated ? zwaveEvent(encapsulated) : null
}

View File

@@ -17,50 +17,44 @@ metadata {
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666"
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"setColor"
}
tileAttribute ("device.model", key: "SECONDARY_CONTROL") {
attributeState "model", label: '${currentValue}'
}
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "unreachable", label: "?", action:"refresh.refresh", icon:"st.switches.light.off", backgroundColor:"#666666"
state "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'Turning on', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'Turning off', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
valueTile("null", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:''
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 2, width: 4, inactiveLabel: false, range:"(2700..9000)") {
state "colorTemp", action:"color temperature.setColorTemperature"
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
state "color", action:"setColor"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", height: 2, width: 2) {
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, icon: "st.illuminance.illuminance.light", decoration: "flat") {
state "level", label: '${currentValue}%'
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..9000)") {
state "colorTemp", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
state "colorTemp", label: '${currentValue}K'
}
main "switch"
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
main(["switch"])
details(["switch", "refresh", "level", "levelSliderControl", "rgbSelector", "colorTempSliderControl", "colorTemp"])
}
}
@@ -76,7 +70,7 @@ def parse(String description) {
def setHue(percentage) {
log.debug "setHue ${percentage}"
parent.logErrors(logObject: log) {
def resp = parent.apiPUT("/lights/${selector()}/state", [color: "hue:${percentage * 3.6}", power: "on"])
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "hue:${percentage * 3.6}"])
if (resp.status < 300) {
sendEvent(name: "hue", value: percentage)
sendEvent(name: "switch", value: "on")
@@ -89,7 +83,7 @@ def setHue(percentage) {
def setSaturation(percentage) {
log.debug "setSaturation ${percentage}"
parent.logErrors(logObject: log) {
def resp = parent.apiPUT("/lights/${selector()}/state", [color: "saturation:${percentage / 100}", power: "on"])
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "saturation:${percentage / 100}"])
if (resp.status < 300) {
sendEvent(name: "saturation", value: percentage)
sendEvent(name: "switch", value: "on")
@@ -120,7 +114,7 @@ def setColor(Map color) {
}
}
parent.logErrors(logObject:log) {
def resp = parent.apiPUT("/lights/${selector()}/state", [color: attrs.join(" "), power: "on"])
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: attrs.join(" ")])
if (resp.status < 300) {
sendEvent(name: "color", value: color.hex)
sendEvent(name: "switch", value: "on")
@@ -141,10 +135,9 @@ def setLevel(percentage) {
return off() // if the brightness is set to 0, just turn it off
}
parent.logErrors(logObject:log) {
def resp = parent.apiPUT("/lights/${selector()}/state", ["brightness": percentage / 100, "power": "on"])
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", ["color": "brightness:${percentage / 100}"])
if (resp.status < 300) {
sendEvent(name: "level", value: percentage)
sendEvent(name: "switch.setLevel", value: percentage)
sendEvent(name: "switch", value: "on")
} else {
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
@@ -155,7 +148,7 @@ def setLevel(percentage) {
def setColorTemperature(kelvin) {
log.debug "Executing 'setColorTemperature' to ${kelvin}"
parent.logErrors() {
def resp = parent.apiPUT("/lights/${selector()}/state", [color: "kelvin:${kelvin}", power: "on"])
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "kelvin:${kelvin}"])
if (resp.status < 300) {
sendEvent(name: "colorTemperature", value: kelvin)
sendEvent(name: "color", value: "#ffffff")
@@ -170,7 +163,7 @@ def setColorTemperature(kelvin) {
def on() {
log.debug "Device setOn"
parent.logErrors() {
if (parent.apiPUT("/lights/${selector()}/state", [power: "on"]) != null) {
if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "on"]) != null) {
sendEvent(name: "switch", value: "on")
}
}
@@ -179,7 +172,7 @@ def on() {
def off() {
log.debug "Device setOff"
parent.logErrors() {
if (parent.apiPUT("/lights/${selector()}/state", [power: "off"]) != null) {
if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "off"]) != null) {
sendEvent(name: "switch", value: "off")
}
}
@@ -187,26 +180,19 @@ def off() {
def poll() {
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
def resp = parent.apiGET("/lights/${selector()}")
if (resp.status == 404) {
sendEvent(name: "switch", value: "unreachable")
return []
} else if (resp.status != 200) {
def resp = parent.apiGET("/lights/${device.deviceNetworkId}")
if (resp.status != 200) {
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
return []
}
def data = resp.data[0]
log.debug("Data: ${data}")
def data = resp.data
sendEvent(name: "label", value: data.label)
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "level", value: sprintf("%.1f", (data.brightness ?: 1) * 100))
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
sendEvent(name: "color", value: colorUtil.hslToHex((data.color.hue / 3.6) as int, (data.color.saturation * 100) as int))
sendEvent(name: "hue", value: data.color.hue / 3.6)
sendEvent(name: "hue", value: data.color.hue / 3.6)
sendEvent(name: "saturation", value: data.color.saturation * 100)
sendEvent(name: "colorTemperature", value: data.color.kelvin)
sendEvent(name: "model", value: "${data.product.company} ${data.product.name}")
return []
}
@@ -215,11 +201,3 @@ def refresh() {
log.debug "Executing 'refresh'"
poll()
}
def selector() {
if (device.deviceNetworkId.contains(":")) {
return device.deviceNetworkId
} else {
return "id:${device.deviceNetworkId}"
}
}

View File

@@ -16,44 +16,41 @@ metadata {
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666"
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'Turning on', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'Turning off', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
state "unreachable", label: "?", action:"refresh.refresh", icon:"st.switches.light.off", backgroundColor:"#666666"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
valueTile("null", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:''
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 2, width: 4, inactiveLabel: false, range:"(2700..9000)") {
state "colorTemp", action:"color temperature.setColorTemperature"
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, icon: "st.illuminance.illuminance.light", decoration: "flat") {
state "level", label: '${currentValue}%'
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", height: 2, width: 2) {
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemp", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
state "colorTemp", label: '${currentValue}K'
}
main "switch"
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
main(["switch"])
details(["switch", "refresh", "level", "levelSliderControl", "colorTempSliderControl", "colorTemp"])
}
}
// parse events into attributes
@@ -75,10 +72,9 @@ def setLevel(percentage) {
return off() // if the brightness is set to 0, just turn it off
}
parent.logErrors(logObject:log) {
def resp = parent.apiPUT("/lights/${selector()}/state", [brightness: percentage / 100, power: "on"])
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", ["color": "brightness:${percentage / 100}"])
if (resp.status < 300) {
sendEvent(name: "level", value: percentage)
sendEvent(name: "switch.setLevel", value: percentage)
sendEvent(name: "switch", value: "on")
} else {
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
@@ -89,7 +85,7 @@ def setLevel(percentage) {
def setColorTemperature(kelvin) {
log.debug "Executing 'setColorTemperature' to ${kelvin}"
parent.logErrors() {
def resp = parent.apiPUT("/lights/${selector()}/state", [color: "kelvin:${kelvin}", power: "on"])
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "kelvin:${kelvin}"])
if (resp.status < 300) {
sendEvent(name: "colorTemperature", value: kelvin)
sendEvent(name: "color", value: "#ffffff")
@@ -104,7 +100,7 @@ def setColorTemperature(kelvin) {
def on() {
log.debug "Device setOn"
parent.logErrors() {
if (parent.apiPUT("/lights/${selector()}/state", [power: "on"]) != null) {
if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "on"]) != null) {
sendEvent(name: "switch", value: "on")
}
}
@@ -113,7 +109,7 @@ def on() {
def off() {
log.debug "Device setOff"
parent.logErrors() {
if (parent.apiPUT("/lights/${selector()}/state", [power: "off"]) != null) {
if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "off"]) != null) {
sendEvent(name: "switch", value: "off")
}
}
@@ -121,22 +117,16 @@ def off() {
def poll() {
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
def resp = parent.apiGET("/lights/${selector()}")
if (resp.status == 404) {
sendEvent(name: "switch", value: "unreachable")
return []
} else if (resp.status != 200) {
def resp = parent.apiGET("/lights/${device.deviceNetworkId}")
if (resp.status != 200) {
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
return []
}
def data = resp.data[0]
def data = resp.data
sendEvent(name: "label", value: data.label)
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
sendEvent(name: "level", value: sprintf("%f", (data.brightness ?: 1) * 100))
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
sendEvent(name: "colorTemperature", value: data.color.kelvin)
sendEvent(name: "model", value: data.product.name)
return []
}
@@ -145,11 +135,3 @@ def refresh() {
log.debug "Executing 'refresh'"
poll()
}
def selector() {
if (device.deviceNetworkId.contains(":")) {
return device.deviceNetworkId
} else {
return "id:${device.deviceNetworkId}"
}
}

View File

@@ -4,7 +4,6 @@
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
that issue by using state variables
*/
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") {
@@ -24,8 +23,8 @@ metadata {
command "setAdjustedColor"
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW"
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW"
}

View File

@@ -5,7 +5,7 @@ metadata {
capability "Switch"
capability "Sensor"
fingerprint profileId: "0104", inClusters: "0006, 0004, 0003, 0000, 0005", outClusters: "0019", manufacturer: "Compacta International, Ltd", model: "ZBMPlug15", deviceJoinName: "SmartPower Outlet V1"
fingerprint profileId: "0104", inClusters: "0000,0003,0006", outClusters: "0019"
}
// simulator metadata

View File

@@ -79,8 +79,8 @@ metadata {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
}

View File

@@ -88,8 +88,8 @@ metadata {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
}

View File

@@ -20,18 +20,18 @@ metadata {
capability "Refresh"
capability "Temperature Measurement"
capability "Water Sensor"
command "enrollResponse"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor"
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "moisturev4", deviceJoinName: "Water Leak Sensor"
}
simulator {
}
preferences {
@@ -43,11 +43,11 @@ metadata {
])
}
section {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
}
tiles(scale: 2) {
multiAttributeTile(name:"water", type: "generic", width: 6, height: 4){
tileAttribute ("device.water", key: "PRIMARY_CONTROL") {
@@ -78,7 +78,7 @@ metadata {
details(["water", "temperature", "battery", "refresh"])
}
}
def parse(String description) {
log.debug "description: $description"
@@ -92,59 +92,59 @@ def parse(String description) {
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
return resultMap
return resultMap
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private Map parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
@@ -153,10 +153,10 @@ private Map parseReportAttributeMessage(String description) {
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
@@ -167,42 +167,42 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMoistureResult('dry')
break
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMoistureResult('dry')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMoistureResult('wet')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMoistureResult('wet')
break
case '0x0022': // Tamper Alarm
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
log.debug 'dry with tamper alarm'
resultMap = getMoistureResult('dry')
break
case '0x0024': // Supervision Report
log.debug 'dry with tamper alarm'
resultMap = getMoistureResult('dry')
break
case '0x0025': // Restore Report
log.debug 'water with tamper alarm'
resultMap = getMoistureResult('wet')
break
case '0x0025': // Restore Report
log.debug 'water with tamper alarm'
resultMap = getMoistureResult('wet')
break
case '0x0026': // Trouble/Failure
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
case '0x0028': // Test Mode
break
}
return resultMap
}
def getTemperature(value) {
@@ -215,47 +215,24 @@ def getTemperature(value) {
}
private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}"
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [
name: 'battery',
value: '--'
]
def result = [
name: 'battery'
]
def volts = rawValue / 10
if (rawValue == 0 || rawValue == 255) {}
def descriptionText
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
def minVolts = 15
def maxVolts = 28
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
return result
@@ -290,7 +267,7 @@ private Map getMoistureResult(value) {
def refresh() {
log.debug "Refreshing Temperature and Battery"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
]
@@ -300,32 +277,32 @@ def refresh() {
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
return configCmds + refresh() // send refresh cmds as part of config
return configCmds + refresh() // send refresh cmds as part of config
}
def enrollResponse() {
log.debug "Sending enroll response"
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
[
[
//Resending the CIE in case the enroll request is sent before CIE is written
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
]
}
private getEndpointId() {
@@ -337,19 +314,19 @@ private hex(value) {
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}

View File

@@ -19,17 +19,16 @@ metadata {
capability "Motion Sensor"
capability "Configuration"
capability "Battery"
capability "Temperature Measurement"
capability "Temperature Measurement"
capability "Refresh"
command "enrollResponse"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305-S"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325-S", deviceJoinName: "Motion Sensor"
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,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325-S", deviceJoinName: "Motion Sensor"
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"
}
simulator {
@@ -46,8 +45,8 @@ metadata {
])
}
section {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
}
@@ -85,7 +84,7 @@ metadata {
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
@@ -96,55 +95,55 @@ def parse(String description) {
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
case 0x0402:
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
case 0x0406:
log.debug 'motion'
resultMap.name = 'motion'
break
}
}
log.debug 'motion'
resultMap.name = 'motion'
break
}
}
return resultMap
return resultMap
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private Map parseReportAttributeMessage(String description) {
@@ -153,7 +152,7 @@ private Map parseReportAttributeMessage(String description) {
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
@@ -162,14 +161,14 @@ private Map parseReportAttributeMessage(String description) {
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
def value = descMap.value.endsWith("01") ? "active" : "inactive"
resultMap = getMotionResult(value)
}
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
def value = descMap.value.endsWith("01") ? "active" : "inactive"
resultMap = getMotionResult(value)
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
@@ -180,44 +179,44 @@ private Map parseCustomMessage(String description) {
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMotionResult('inactive')
break
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMotionResult('inactive')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMotionResult('active')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMotionResult('active')
break
case '0x0022': // Tamper Alarm
log.debug 'motion with tamper alarm'
resultMap = getMotionResult('active')
break
case '0x0022': // Tamper Alarm
log.debug 'motion with tamper alarm'
resultMap = getMotionResult('active')
break
case '0x0023': // Battery Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap = getMotionResult('inactive')
break
case '0x0024': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap = getMotionResult('inactive')
break
case '0x0025': // Restore Report
break
case '0x0025': // Restore Report
break
case '0x0026': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap = getMotionResult('active')
break
case '0x0026': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap = getMotionResult('active')
break
case '0x0028': // Test Mode
break
}
return resultMap
case '0x0028': // Test Mode
break
}
return resultMap
}
def getTemperature(value) {
@@ -230,46 +229,30 @@ def getTemperature(value) {
}
private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}"
log.debug 'Battery'
def linkText = getLinkText(device)
log.debug rawValue
def result = [
name: 'battery',
value: '--'
]
def volts = rawValue / 10
def descriptionText
if (rawValue == 0 || rawValue == 255) {}
if (rawValue == 0) {}
else {
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
def minVolts = 15
def maxVolts = 28
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
else if (volts > 0){
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
@@ -354,19 +337,19 @@ private hex(value) {
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}

View File

@@ -37,8 +37,8 @@ metadata {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {

View File

@@ -14,28 +14,27 @@
*
*/
metadata {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Three Axis"
metadata {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Three Axis"
capability "Battery"
capability "Configuration"
capability "Sensor"
capability "Configuration"
capability "Sensor"
capability "Contact Sensor"
capability "Acceleration Sensor"
capability "Refresh"
capability "Temperature Measurement"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor"
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor"
attribute "status", "string"
}
}
simulator {
simulator {
status "open": "zone report :: type: 19 value: 0031"
status "closed": "zone report :: type: 19 value: 0030"
@@ -52,7 +51,7 @@ metadata {
status "x,y,z: 0,1000,0": "x: 0, y: 1000, z: 0"
status "x,y,z: 0,0,1000": "x: 0, y: 0, z: 1000"
}
preferences {
preferences {
section {
image(name: 'educationalcontent', multiple: true, images: [
"http://cdn.device-gse.smartthings.com/Multi/Multi1.jpg",
@@ -62,13 +61,13 @@ metadata {
])
}
section {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
section {
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
}
}
}
tiles(scale: 2) {
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
@@ -106,9 +105,9 @@ metadata {
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["status", "acceleration", "temperature"])
@@ -116,104 +115,91 @@ metadata {
}
}
def parse(String description) {
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
def parse(String description) {
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
map = parseReportAttributeMessage(description)
}
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(description)
}
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
def result = map ? createEvent(map) : null
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
else if (description?.startsWith('read attr -')) {
result = parseReportAttributeMessage(description).each { createEvent(it) }
}
return result
}
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
log.debug cluster
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
log.debug cluster
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
case 0xFC02:
log.debug 'ACCELERATION'
break
case 0xFC02:
log.debug 'ACCELERATION'
break
case 0x0402:
log.debug 'TEMP'
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
case 0x0402:
log.debug 'TEMP'
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
return resultMap
}
return resultMap
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private List parseReportAttributeMessage(String description) {
private Map parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
List result = []
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
result << getTemperatureResult(value)
resultMap = getTemperatureResult(value)
}
else if (descMap.cluster == "FC02" && descMap.attrId == "0010") {
if (descMap.value.size() == 32) {
// value will look like 00ae29001403e2290013001629001201
// breaking this apart and swapping byte order where appropriate, this breaks down to:
// X (0x0012) = 0x0016
// Y (0x0013) = 0x03E2
// Z (0x0014) = 0x00AE
// note that there is a known bug in that the x,y,z attributes are interpreted in the wrong order
// this will be fixed in a future update
def threeAxisAttributes = descMap.value[0..-9]
result << parseAxis(threeAxisAttributes)
descMap.value = descMap.value[-2..-1]
}
result << getAccelerationResult(descMap.value)
resultMap = getAccelerationResult(descMap.value)
}
else if (descMap.cluster == "FC02" && descMap.attrId == "0012" && descMap.value.size() == 24) {
// The size is checked to ensure the attribute report contains X, Y and Z values
// If all three axis are not included then the attribute report is ignored
result << parseAxis(descMap.value)
else if (descMap.cluster == "FC02" && descMap.attrId == "0012") {
resultMap = parseAxis(descMap.value)
}
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
result << getBatteryResult(Integer.parseInt(descMap.value, 16))
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
return result
return resultMap
}
private Map parseCustomMessage(String description) {
@@ -231,43 +217,43 @@ private Map parseIasMessage(String description) {
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
case '0x0020': // Closed/No Motion/Dry
if (garageSensor != "Yes"){
resultMap = getContactResult('closed')
}
break
break
case '0x0021': // Open/Motion/Wet
case '0x0021': // Open/Motion/Wet
if (garageSensor != "Yes"){
resultMap = getContactResult('open')
}
break
break
case '0x0022': // Tamper Alarm
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
case '0x0024': // Supervision Report
if (garageSensor != "Yes"){
resultMap = getContactResult('closed')
}
break
break
case '0x0025': // Restore Report
case '0x0025': // Restore Report
if (garageSensor != "Yes"){
resultMap = getContactResult('open')
}
break
break
case '0x0026': // Trouble/Failure
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
case '0x0028': // Test Mode
break
}
return resultMap
}
def updated() {
@@ -302,171 +288,146 @@ def getTemperature(value) {
}
}
private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device)
private Map getBatteryResult(rawValue) {
log.debug "Battery"
log.debug rawValue
def linkText = getLinkText(device)
def result = [
def result = [
name: 'battery',
value: '--'
]
value: '--'
]
def volts = rawValue / 10
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) {
def volts = rawValue / 10
def descriptionText
if (rawValue == 255) {}
else {
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
if (device.getDataValue("manufacturer") == "SmartThings") {
volts = rawValue // For the batteryMap to work the key needs to be an int
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
def minVolts = 15
def maxVolts = 28
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}}
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = batteryMap[volts]
if (pct != null) {
result.value = pct
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
return result
}
private Map getTemperatureResult(value) {
log.debug "Temperature"
def linkText = getLinkText(device)
if (tempOffset) {
def offset = tempOffset as int
def v = value as int
value = v + offset
}
}
return result
}
private Map getTemperatureResult(value) {
log.debug "Temperature"
def linkText = getLinkText(device)
if (tempOffset) {
def offset = tempOffset as int
def v = value as int
value = v + offset
}
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
return [
name: 'temperature',
value: value,
descriptionText: descriptionText
]
}
private Map getContactResult(value) {
log.debug "Contact"
def linkText = getLinkText(device)
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
}
private getAccelerationResult(numValue) {
log.debug "Acceleration"
def name = "acceleration"
def value = numValue.endsWith("1") ? "active" : "inactive"
def linkText = getLinkText(device)
def descriptionText = "$linkText was $value"
def isStateChange = isStateChange(device, name, value)
[
name: name,
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
return [
name: 'temperature',
value: value,
descriptionText: descriptionText,
isStateChange: isStateChange
]
}
def refresh() {
log.debug "Refreshing Values "
def refreshCmds = []
if (device.getDataValue("manufacturer") == "SmartThings") {
log.debug "Refreshing Values for manufacturer: SmartThings "
refreshCmds = refreshCmds + [
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
Separating these out in a separate if-else because I do not want to touch Centralite part
as of now.
*/
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
} else {
refreshCmds = refreshCmds + [
/* sensitivity - default value (8) */
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
descriptionText: descriptionText
]
}
//Common refresh commands
refreshCmds = refreshCmds + [
private Map getContactResult(value) {
log.debug "Contact"
def linkText = getLinkText(device)
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
}
private getAccelerationResult(numValue) {
log.debug "Acceleration"
def name = "acceleration"
def value = numValue.endsWith("1") ? "active" : "inactive"
def linkText = getLinkText(device)
def descriptionText = "$linkText was $value"
def isStateChange = isStateChange(device, name, value)
[
name: name,
value: value,
descriptionText: descriptionText,
isStateChange: isStateChange
]
}
def refresh() {
log.debug "Refreshing Values "
def refreshCmds = [
/* sensitivity - default value (8) */
"zcl mfg-code 0x104E", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
"zcl mfg-code 0x104E", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400",
"zcl mfg-code 0x104E", "delay 200",
"zcl global read 0xFC02 0x0012",
"send 0x${device.deviceNetworkId} 1 1","delay 400",
"zcl mfg-code 0x104E", "delay 200",
"zcl global read 0xFC02 0x0013",
"send 0x${device.deviceNetworkId} 1 1","delay 400",
"zcl mfg-code 0x104E", "delay 200",
"zcl global read 0xFC02 0x0014",
"send 0x${device.deviceNetworkId} 1 1", "delay 400"
]
return refreshCmds + enrollResponse()
}
return refreshCmds + enrollResponse()
}
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting"
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting"
def configCmds = [
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
"zcl mfg-code 0x104E",
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}", "delay 200",
"zcl mfg-code 0x104E",
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}", "delay 200",
"zcl mfg-code 0x104E",
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}", "delay 200",
"zcl mfg-code 0x104E",
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
return configCmds + refresh()
]
return configCmds + refresh()
}
private getEndpointId() {
@@ -481,43 +442,44 @@ def enrollResponse() {
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response
"raw 0x500 {01 23 00 00 00}", "delay 200",
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}
private Map parseAxis(String description) {
def z = hexToSignedInt(description[0..3])
def y = hexToSignedInt(description[10..13])
def x = hexToSignedInt(description[20..23])
def xyzResults = [x: x, y: y, z: z]
if (device.getDataValue("manufacturer") == "SmartThings") {
// This mapping matches the current behavior of the Device Handler for the Centralite sensors
xyzResults.x = z
xyzResults.y = y
xyzResults.z = -x
} else {
// The axises reported by the Device Handler differ from the axises reported by the sensor
// This may change in the future
xyzResults.x = z
xyzResults.y = x
xyzResults.z = y
}
log.debug "parseAxis -- ${xyzResults}"
if (garageSensor == "Yes")
garageEvent(xyzResults.z)
log.debug "parseAxis"
def xyzResults = [x: 0, y: 0, z: 0]
def parts = description.split("2900")
parts[0] = "12" + parts[0]
parts.each { part ->
part = part.trim()
if (part.startsWith("12")) {
def unsignedX = hexToInt(part.split("12")[1].trim())
def signedX = unsignedX > 32767 ? unsignedX - 65536 : unsignedX
xyzResults.x = signedX
log.debug "X Part: ${signedX}"
}
else if (part.startsWith("13")) {
def unsignedY = hexToInt(part.split("13")[1].trim())
def signedY = unsignedY > 32767 ? unsignedY - 65536 : unsignedY
xyzResults.y = signedY
log.debug "Y Part: ${signedY}"
}
else if (part.startsWith("14")) {
def unsignedZ = hexToInt(part.split("14")[1].trim())
def signedZ = unsignedZ > 32767 ? unsignedZ - 65536 : unsignedZ
xyzResults.z = signedZ
log.debug "Z Part: ${signedZ}"
if (garageSensor == "Yes")
garageEvent(signedZ)
}
}
getXyzResult(xyzResults, description)
}
private hexToSignedInt(hexVal) {
def unsignedVal = hexToInt(hexVal)
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
}
def garageEvent(zValue) {
def absValue = zValue.abs()
def contactValue = null
@@ -557,14 +519,6 @@ private Map getXyzResult(results, description) {
]
}
private getManufacturerCode() {
if (device.getDataValue("manufacturer") == "SmartThings") {
return "0x110A"
} else {
return "0x104E"
}
}
private hexToInt(value) {
new BigInteger(value, 16)
}
@@ -590,3 +544,4 @@ private byte[] reverseArray(byte[] array) {
}
return array
}

View File

@@ -43,8 +43,8 @@ metadata {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {

View File

@@ -32,8 +32,8 @@
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {

View File

@@ -34,8 +34,8 @@ metadata {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {

View File

@@ -33,8 +33,8 @@ metadata {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {

View File

@@ -45,8 +45,8 @@ metadata {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles {

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2015 SmartThings
* Copyright 2014 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:
@@ -15,7 +15,6 @@ metadata {
// Automatically generated. Make future change here.
definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") {
capability "Thermostat"
capability "Relative Humidity Measurement"
command "tempUp"
command "tempDown"
@@ -23,40 +22,11 @@ metadata {
command "heatDown"
command "coolUp"
command "coolDown"
command "setTemperature", ["number"]
command "setTemperature", ["number"]
}
tiles(scale: 2) {
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("default", action: "setTemperature")
}
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
attributeState("default", label:'${currentValue}%', unit:"%")
}
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621")
attributeState("heating", backgroundColor:"#ffa81e")
attributeState("cooling", backgroundColor:"#269bd2")
}
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
attributeState("off", label:'${name}')
attributeState("heat", label:'${name}')
attributeState("cool", label:'${name}')
attributeState("auto", label:'${name}')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
tiles {
valueTile("temperature", "device.temperature", width: 1, height: 1) {
state("temperature", label:'${currentValue}', unit:"dF",
backgroundColors:[
[value: 31, color: "#153591"],
@@ -69,51 +39,51 @@ metadata {
]
)
}
standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("tempDown", "device.temperature", inactiveLabel: false, decoration: "flat") {
state "default", label:'down', action:"tempDown"
}
standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("tempUp", "device.temperature", inactiveLabel: false, decoration: "flat") {
state "default", label:'up', action:"tempUp"
}
valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
}
standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("heatDown", "device.temperature", inactiveLabel: false, decoration: "flat") {
state "default", label:'down', action:"heatDown"
}
standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("heatUp", "device.temperature", inactiveLabel: false, decoration: "flat") {
state "default", label:'up', action:"heatUp"
}
valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") {
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
}
standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("coolDown", "device.temperature", inactiveLabel: false, decoration: "flat") {
state "default", label:'down', action:"coolDown"
}
standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("coolUp", "device.temperature", inactiveLabel: false, decoration: "flat") {
state "default", label:'up', action:"coolUp"
}
standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
state "off", label:'${name}', action:"thermostat.heat", backgroundColor:"#ffffff"
state "heat", label:'${name}', action:"thermostat.cool", backgroundColor:"#ffa81e"
state "cool", label:'${name}', action:"thermostat.auto", backgroundColor:"#269bd2"
state "auto", label:'${name}', action:"thermostat.off", backgroundColor:"#79b821"
}
standardTile("fanMode", "device.thermostatFanMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
state "fanAuto", label:'${name}', action:"thermostat.fanOn", backgroundColor:"#ffffff"
state "fanOn", label:'${name}', action:"thermostat.fanCirculate", backgroundColor:"#ffffff"
state "fanCirculate", label:'${name}', action:"thermostat.fanAuto", backgroundColor:"#ffffff"
}
standardTile("operatingState", "device.thermostatOperatingState", width: 2, height: 2) {
standardTile("operatingState", "device.thermostatOperatingState") {
state "idle", label:'${name}', backgroundColor:"#ffffff"
state "heating", label:'${name}', backgroundColor:"#ffa81e"
state "cooling", label:'${name}', backgroundColor:"#269bd2"
}
main("thermostatMulti")
main("temperature","operatingState")
details([
"temperature","tempDown","tempUp",
"mode", "fanMode", "operatingState",
@@ -131,7 +101,6 @@ def installed() {
sendEvent(name: "thermostatMode", value: "off")
sendEvent(name: "thermostatFanMode", value: "fanAuto")
sendEvent(name: "thermostatOperatingState", value: "idle")
sendEvent(name: "humidity", value: 53, unit: "%")
}
def parse(String description) {

View File

@@ -33,8 +33,8 @@ metadata {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles {

View File

@@ -29,18 +29,18 @@ metadata {
tiles {
valueTile("power", "device.power", canChangeIcon: true) {
valueTile("power", "device.power") {
state "power", label: '${currentValue} W'
}
htmlTile(name: "powerContent", attribute: "powerContent", type: "HTML", whitelist: "www.wattvision.com" , url: '${currentValue}', width: 3, height: 2)
tile(name: "powerChart", attribute: "powerContent", type: "HTML", url: '${currentValue}', width: 3, height: 2) { }
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat") {
state "default", label: '', action: "refresh.refresh", icon: "st.secondary.refresh"
}
main "power"
details(["powerContent", "power", "refresh"])
details(["powerChart", "power", "refresh"])
}
}
@@ -74,10 +74,10 @@ public addWattvisionData(json) {
log.trace "Adding data from Wattvision"
def data = parseJson(json.data.toString())
def data = json.data
def units = json.units ?: "watts"
if (data.size() > 0) {
if (data) {
def latestData = data[-1]
data.each {
sendPowerEvent(it.t, it.v, units, (latestData == it))
@@ -103,7 +103,3 @@ private sendPowerEvent(time, value, units, isLatest = false) {
sendEvent(eventData)
}
def parseJson(String s) {
new groovy.json.JsonSlurper().parseText(s)
}

View File

@@ -273,7 +273,7 @@ User-Agent: CyberGarage-HTTP/1.0
def poll() {
log.debug "Executing 'poll'"
if (device.currentValue("currentIP") != "Offline")
runIn(30, setOffline)
runIn(10, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277

View File

@@ -77,8 +77,9 @@ def parse(String description) {
def result = []
def bodyString = msg.body
if (bodyString) {
unschedule("setOffline")
unschedule("setOffline")
def body = new XmlSlurper().parseText(bodyString)
if (body?.property?.TimeSyncRequest?.text()) {
log.trace "Got TimeSyncRequest"
result << timeSyncResponse()
@@ -133,7 +134,7 @@ def refresh() {
def getStatus() {
log.debug "Executing WeMo Motion 'getStatus'"
if (device.currentValue("currentIP") != "Offline")
runIn(30, setOffline)
runIn(10, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277

View File

@@ -28,7 +28,6 @@
command "subscribe"
command "resubscribe"
command "unsubscribe"
command "setOffline"
}
// simulator metadata
@@ -208,7 +207,7 @@ def subscribe(ip, port) {
def existingIp = getDataValue("ip")
def existingPort = getDataValue("port")
if (ip && ip != existingIp) {
log.debug "Updating ip from $existingIp to $ip"
log.debug "Updating ip from $existingIp to $ip"
updateDataValue("ip", ip)
def ipvalue = convertHexToIP(getDataValue("ip"))
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
@@ -276,7 +275,7 @@ def setOffline() {
def poll() {
log.debug "Executing 'poll'"
if (device.currentValue("currentIP") != "Offline")
runIn(30, setOffline)
runIn(10, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277
@@ -291,4 +290,4 @@ User-Agent: CyberGarage-HTTP/1.0
</u:GetBinaryState>
</s:Body>
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
}
}

View File

@@ -25,8 +25,8 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-CIA19NAE26", deviceJoinName: "Sengled Element touch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "000A, 0019", manufacturer: "Jasco Products", model: "45852", deviceJoinName: "GE Zigbee Plug-In Dimmer"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "000A, 0019", manufacturer: "Jasco Products", model: "45857", deviceJoinName: "GE Zigbee In-Wall Dimmer"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45852", deviceJoinName: "GE Zigbee Plug-In Dimmer"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45857", deviceJoinName: "GE Zigbee In-Wall Dimmer"
}
tiles(scale: 2) {

View File

@@ -17,14 +17,15 @@ metadata {
capability "Actuator"
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Switch"
capability "Switch Level"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
}
tiles(scale: 2) {

View File

@@ -136,15 +136,15 @@ def parse(String description) {
// Lock capability commands
def lock() {
//def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_LOCK_DOOR}", "{}")
//log.info "lock() -- cmds: $cmds"
def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_LOCK_DOOR}", "{}")
log.info "lock() -- cmds: $cmds"
//return cmds
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_LOCK_DOOR} {}"
}
def unlock() {
//def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_UNLOCK_DOOR}", "{}")
//log.info "unlock() -- cmds: $cmds"
def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_UNLOCK_DOOR}", "{}")
log.info "unlock() -- cmds: $cmds"
//return cmds
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_UNLOCK_DOOR} {}"
}

View File

@@ -1,144 +0,0 @@
/**
* Copyright 2016 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.
*
* Author: SmartThings
* Date: 2016-01-19
*
* This DTH should serve as the generic DTH to handle RGBW ZigBee HA devices
*/
metadata {
definition (name: "ZigBee RGBW Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Color Control"
capability "Color Temperature"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Switch"
capability "Switch Level"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 RGBW", deviceJoinName: "OSRAM LIGHTIFY LED A19 RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR RGBW", deviceJoinName: "OSRAM LIGHTIFY LED BR30 RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT RGBW", deviceJoinName: "OSRAM LIGHTIFY LED RT 5/6 RGBW"
}
// UI tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"color control.setColor"
}
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
}
}
//Globals
private getATTRIBUTE_HUE() { 0x0000 }
private getATTRIBUTE_SATURATION() { 0x0001 }
private getHUE_COMMAND() { 0x00 }
private getSATURATION_COMMAND() { 0x03 }
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def finalResult = zigbee.getEvent(description)
if (finalResult) {
log.debug finalResult
sendEvent(finalResult)
}
else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
log.trace "zigbeeMap : $zigbeeMap"
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
sendEvent(name: "hue", value: hueValue, displayed:false)
}
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "saturation", value: saturationValue, displayed:false)
}
}
else {
log.info "DID NOT PARSE MESSAGE for description : $description"
}
}
}
def on() {
zigbee.on()
}
def off() {
zigbee.off()
}
def refresh() {
zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
}
def configure() {
log.debug "Configuring Reporting and Bindings."
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
}
def setColorTemperature(value) {
zigbee.setColorTemperature(value)
}
def setLevel(value) {
zigbee.setLevel(value)
}
def setColor(value){
log.trace "setColor($value)"
zigbee.on() + setHue(value.hue) + "delay 300" + setSaturation(value.saturation)
}
def setHue(value) {
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
}
def setSaturation(value) {
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") //payload-> sat value, transition time
}

View File

@@ -23,8 +23,8 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0B04"
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,0B05,0702", outClusters: "0003, 000A,0019", manufacturer: "Jasco Products", model: "45853", deviceJoinName: "GE ZigBee Plug-In Switch"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45856", deviceJoinName: "GE ZigBee In-Wall Switch"
}
tiles(scale: 2) {

View File

@@ -17,6 +17,7 @@ metadata {
capability "Actuator"
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"

View File

@@ -23,18 +23,18 @@ metadata {
capability "Color Temperature"
capability "Configuration"
capability "Refresh"
capability "Sensor"
capability "Switch"
capability "Switch Level"
attribute "colorName", "string"
command "setGenericName"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Flood BR30 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Flood BR30 Tunable White"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
}
// UI tile definitions

View File

@@ -66,20 +66,9 @@ metadata {
import physicalgraph.zwave.commands.doorlockv1.*
import physicalgraph.zwave.commands.usercodev1.*
def updated() {
try {
if (!state.init) {
state.init = true
response(secureSequence([zwave.doorLockV1.doorLockOperationGet(), zwave.batteryV1.batteryGet()]))
}
} catch (e) {
log.warn "updated() threw $e"
}
}
def parse(String description) {
def result = null
if (description.startsWith("Err 106")) {
if (description.startsWith("Err")) {
if (state.sec) {
result = createEvent(descriptionText:description, displayed:false)
} else {
@@ -91,8 +80,6 @@ def parse(String description) {
displayed: true,
)
}
} else if (description == "updated") {
return null
} else {
def cmd = zwave.parse(description, [ 0x98: 1, 0x72: 2, 0x85: 2, 0x86: 1 ])
if (cmd) {
@@ -275,7 +262,6 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
case 32:
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
allCodesDeleted()
break
case 33:
map = [ name: "codeReport", value: cmd.alarmLevel, data: [ code: "" ], isStateChange: true ]
map.descriptionText = "$device.displayName code $cmd.alarmLevel was deleted"
@@ -300,7 +286,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
}
break
case 167:
if (!state.lastbatt || now() - state.lastbatt > 12*60*60*1000) {
if (!state.lastbatt || (new Date().time) - state.lastbatt > 12*60*60*1000) {
map = [ descriptionText: "$device.displayName: battery low", isStateChange: true ]
result << response(secure(zwave.batteryV1.batteryGet()))
} else {
@@ -342,14 +328,14 @@ def zwaveEvent(UserCodeReport cmd) {
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: code ] ]
map.descriptionText = "$device.displayName code $cmd.userIdentifier is set"
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
map.isStateChange = true
map.isStateChange = (code != decrypt(state[name]))
}
result << createEvent(map)
} else {
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: "" ] ]
if (state.blankcodes && state["reset$name"]) { // we deleted this code so we can tell that our new code gets set
map.descriptionText = "$device.displayName code $cmd.userIdentifier was reset"
map.displayed = map.isStateChange = true
map.displayed = map.isStateChange = false
result << createEvent(map)
state["set$name"] = state["reset$name"]
result << response(setCode(cmd.userIdentifier, state["reset$name"]))
@@ -361,7 +347,7 @@ def zwaveEvent(UserCodeReport cmd) {
map.descriptionText = "$device.displayName code $cmd.userIdentifier is not set"
}
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
map.isStateChange = true
map.isStateChange = state[name] as Boolean
result << createEvent(map)
}
code = ""
@@ -445,7 +431,7 @@ def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
} else {
map.value = cmd.batteryLevel
}
state.lastbatt = now()
state.lastbatt = new Date().time
createEvent(map)
}
@@ -513,14 +499,15 @@ def refresh() {
cmds << "delay 4200"
cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format() // old Schlage locks use group 2 and don't secure the Association CC
cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1))
state.associationQuery = now()
} else if (secondsPast(state.associationQuery, 9)) {
state.associationQuery = new Date().time
} else if (new Date().time - state.associationQuery.toLong() > 9000) {
log.debug "setting association"
cmds << "delay 6000"
cmds << zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId).format()
cmds << secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))
cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format()
cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1))
state.associationQuery = now()
state.associationQuery = new Date().time
}
log.debug "refresh sending ${cmds.inspect()}"
cmds
@@ -528,22 +515,55 @@ def refresh() {
def poll() {
def cmds = []
// Only check lock state if it changed recently or we haven't had an update in an hour
def latest = device.currentState("lock")?.date?.time
if (!latest || !secondsPast(latest, 6 * 60) || secondsPast(state.lastPoll, 55 * 60)) {
cmds << secure(zwave.doorLockV1.doorLockOperationGet())
state.lastPoll = now()
} else if (!state.lastbatt || now() - state.lastbatt > 53*60*60*1000) {
cmds << secure(zwave.batteryV1.batteryGet())
state.lastbatt = now() //inside-214
}
if (cmds) {
log.debug "poll is sending ${cmds.inspect()}"
cmds
if (state.assoc != zwaveHubNodeId && secondsPast(state.associationQuery, 19 * 60)) {
log.debug "setting association"
cmds << zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId).format()
cmds << secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))
cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format()
cmds << "delay 6000"
cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1))
cmds << "delay 6000"
state.associationQuery = new Date().time
} else {
// workaround to keep polling from stopping due to lack of activity
sendEvent(descriptionText: "skipping poll", isStateChange: true, displayed: false)
null
// Only check lock state if it changed recently or we haven't had an update in an hour
def latest = device.currentState("lock")?.date?.time
if (!latest || !secondsPast(latest, 6 * 60) || secondsPast(state.lastPoll, 55 * 60)) {
cmds << secure(zwave.doorLockV1.doorLockOperationGet())
state.lastPoll = (new Date()).time
} else if (!state.MSR) {
cmds << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
} else if (!state.fw) {
cmds << zwave.versionV1.versionGet().format()
} else if (!state.codes) {
state.pollCode = 1
cmds << secure(zwave.userCodeV1.usersNumberGet())
} else if (state.pollCode && state.pollCode <= state.codes) {
cmds << requestCode(state.pollCode)
} else if (!state.lastbatt || (new Date().time) - state.lastbatt > 53*60*60*1000) {
cmds << secure(zwave.batteryV1.batteryGet())
} else if (!state.enc) {
encryptCodes()
state.enc = 1
}
}
log.debug "poll is sending ${cmds.inspect()}"
device.activity()
cmds ?: null
}
private def encryptCodes() {
def keys = new ArrayList(state.keySet().findAll { it.startsWith("code") })
keys.each { key ->
def match = (key =~ /^code(\d+)$/)
if (match) try {
def keynum = match[0][1].toInteger()
if (keynum > 30 && !state[key]) {
state.remove(key)
} else if (state[key] && !state[key].startsWith("~")) {
log.debug "encrypting $key: ${state[key].inspect()}"
state[key] = encrypt(state[key])
}
} catch (java.lang.NumberFormatException e) { }
}
}
@@ -652,7 +672,7 @@ private Boolean secondsPast(timestamp, seconds) {
return true
}
}
return (now() - timestamp) > (seconds * 1000)
return (new Date().time - timestamp) > (seconds * 1000)
}
private allCodesDeleted() {

View File

@@ -115,10 +115,6 @@ def strobe() {
]
}
def both() {
on()
}
def refresh() {
log.debug "sending battery refresh command"
zwave.batteryV1.batteryGet().format()

View File

@@ -1,243 +0,0 @@
metadata {
definition (name: "Timevalve Smart", namespace: "timevalve.gaslock.t-08", author: "ruinnel") {
capability "Valve"
capability "Refresh"
capability "Battery"
capability "Temperature Measurement"
command "setRemaining"
command "setTimeout"
command "setTimeout10"
command "setTimeout20"
command "setTimeout30"
command "setTimeout40"
command "remainingLevel"
attribute "remaining", "number"
attribute "remainingText", "String"
attribute "timeout", "number"
//raw desc : 0 0 0x1006 0 0 0 7 0x5E 0x86 0x72 0x5A 0x73 0x98 0x80
//fingerprint deviceId:"0x1006", inClusters:"0x5E, 0x86, 0x72, 0x5A, 0x73, 0x98, 0x80"
}
tiles (scale: 2) {
multiAttributeTile(name:"statusTile", type:"generic", width:6, height:4) {
tileAttribute("device.contact", key: "PRIMARY_CONTROL") {
attributeState "open", label: '${name}', action: "close", icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
attributeState "closed", label:'${name}', action: "", icon:"st.contact.contact.closed", backgroundColor:"#79b821"
}
tileAttribute("device.remainingText", key: "SECONDARY_CONTROL") {
attributeState "open", label: '${currentValue}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
attributeState "closed", label:'', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
}
}
standardTile("refreshTile", "command.refresh", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("remainingSliderTile", "device.remaining", "slider", inactiveLabel: false, range:"(0..590)", height: 2, width: 4) {
state "level", action:"remainingLevel"
}
valueTile("setRemaining", "device.remainingText", inactiveLabel: false, decoration: "flat", height: 2, width: 2){
state "remainingText", label:'${currentValue}\nRemaining'//, action: "setRemaining"//, icon: "st.Office.office6"
}
standardTile("setTimeout10", "device.remaining", inactiveLabel: false, decoration: "flat") {
state "default", label:'10Min', action: "setTimeout10", icon:"st.Health & Wellness.health7", defaultState: true
state "10", label:'10Min', action: "setTimeout10", icon:"st.Office.office13"
}
standardTile("setTimeout20", "device.remaining", inactiveLabel: false, decoration: "flat") {
state "default", label:'20Min', action: "setTimeout20", icon:"st.Health & Wellness.health7", defaultState: true
state "20", label:'20Min', action: "setTimeout20", icon:"st.Office.office13"
}
standardTile("setTimeout30", "device.remaining", inactiveLabel: false, decoration: "flat") {
state "default", label:'30Min', action: "setTimeout30", icon:"st.Health & Wellness.health7", defaultState: true
state "30", label:'30Min', action: "setTimeout30", icon:"st.Office.office13"
}
standardTile("setTimeout40", "device.remaining", inactiveLabel: false, decoration: "flat") {
state "default", label:'40Min', action: "setTimeout40", icon:"st.Health & Wellness.health7", defaultState: true
state "40", label:'40Min', action: "setTimeout40", icon:"st.Office.office13"
}
valueTile("batteryTile", "device.battery", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "battery", label:'${currentValue}% battery', unit:""
}
main (["statusTile"])
// details (["statusTile", "remainingSliderTile", "setRemaining", "setTimeout10", "setTimeout20", "batteryTile", "refreshTile", "setTimeout30", "setTimeout40"])
// details (["statusTile", "batteryTile", "setRemaining", "refreshTile"])
details (["statusTile", "batteryTile", "refreshTile"])
}
}
def parse(description) {
// log.debug "parse - " + description
def result = null
if (description.startsWith("Err 106")) {
state.sec = 0
result = createEvent(descriptionText: description, isStateChange: true)
} else if (description != "updated") {
def cmd = zwave.parse(description, [0x20: 1, 0x25: 1, 0x70: 1, 0x71: 1, 0x98: 1])
if (cmd) {
log.debug "parsed cmd = " + cmd
result = zwaveEvent(cmd)
//log.debug("'$description' parsed to $result")
} else {
log.debug("Couldn't zwave.parse '$description'")
}
}
return result
}
// 복호화 후 zwaveEvent() 호출
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
//log.debug "SecurityMessageEncapsulation - " + cmd
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x25: 1, 0x70: 1, 0x71: 1, 0x98: 1])
if (encapsulatedCommand) {
state.sec = 1
log.debug "encapsulatedCommand = " + encapsulatedCommand
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
//log.debug "switch status - " + cmd.value
createEvent(name:"contact", value: cmd.value ? "open" : "closed")
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [ name: "battery", unit: "%" ]
if (cmd.batteryLevel == 0xFF) { // Special value for low battery alert
map.value = 1
map.descriptionText = "${device.displayName} has a low battery"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
}
log.debug "battery - ${map.value}${map.unit}"
// Store time of last battery update so we don't ask every wakeup, see WakeUpNotification handler
state.lastbatt = new Date().time
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
//log.debug "zwaveEvent - ${device.displayName}: ${cmd}"
createEvent(descriptionText: "${device.displayName}: ${cmd}")
}
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
def result = []
log.info "zwave.configurationV1.configurationGet - " + cmd
def array = cmd.configurationValue
def value = ( (array[0] * 0x1000000) + (array[1] * 0x10000) + (array[2] * 0x100) + array[3] ).intdiv(60)
if (device.currentValue("contact") == "open") {
value = ( (array[0] * 0x1000000) + (array[1] * 0x10000) + (array[2] * 0x100) + array[3] ).intdiv(60)
} else {
value = 0
}
if (device.currentValue('contact') == 'open') {
def hour = value.intdiv(60);
def min = (value % 60).toString().padLeft(2, '0');
def text = "${hour}:${min}M"
log.info "remain - " + text
result.add( createEvent(name: "remaining", value: value, displayed: false, isStateChange: true) )
result.add( createEvent(name: "remainingText", value: text, displayed: false, isStateChange: true) )
} else {
result.add( createEvent(name: "timeout", value: value, displayed: false, isStateChange: true) )
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
def type = cmd.notificationType
if (type == cmd.NOTIFICATION_TYPE_HEAT) {
log.info "NotificationReport - ${type}"
createEvent(name: "temperature", value: 999, unit: "C", descriptionText: "${device.displayName} is over heat!", displayed: true, isStateChange: true)
}
}
def zwaveEvent(physicalgraph.zwave.commands.alarmv1.AlarmReport cmd) {
def type = cmd.alarmType
def level = cmd.alarmLevel
log.info "AlarmReport - type : ${type}, level : ${level}"
def msg = "${device.displayName} is over heat!"
def result = createEvent(name: "temperature", value: 999, unit: "C", descriptionText: msg, displayed: true, isStateChange: true)
if (sendPushMessage) {
sendPushMessage(msg)
}
return result
}
// remote open not allow
def open() {}
def close() {
// log.debug 'cmd - close()'
commands([
zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00),
zwave.switchBinaryV1.switchBinaryGet()
])
}
def setTimeout10() { setTimeout(10) }
def setTimeout20() { setTimeout(20) }
def setTimeout30() { setTimeout(30) }
def setTimeout40() { setTimeout(40) }
def setTimeout(value) {
// log.debug "setDefaultTime($value)"
commands([
zwave.configurationV1.configurationSet(parameterNumber: 0x01, size: 4, scaledConfigurationValue: value * 60),
zwave.configurationV1.configurationGet(parameterNumber: 0x01)
]);
}
def remainingLevel(value) {
// log.debug "remainingLevel($value)"
def hour = value.intdiv(60);
def min = (value % 60).toString().padLeft(2, '0');
def text = "${hour}:${min}M"
sendEvent(name: "remaining", value: value, displayed: false, isStateChange: true)
sendEvent(name: "remainingText", value: text, displayed: false, isStateChange: true)
}
def setRemaining() {
def remaining = device.currentValue("remaining")
// log.debug "setConfiguration() - remaining : $remaining"
commands([
zwave.configurationV1.configurationSet(parameterNumber: 0x03, size: 4, scaledConfigurationValue: remaining * 60),
zwave.configurationV1.configurationGet(parameterNumber: 0x03)
]);
}
private command(physicalgraph.zwave.Command cmd) {
if (state.sec != 0 && !(cmd instanceof physicalgraph.zwave.commands.batteryv1.BatteryGet)) {
log.debug "cmd = " + cmd + ", encapsulation"
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
} else {
log.debug "cmd = " + cmd + ", plain"
cmd.format()
}
}
private commands(commands, delay=200) {
delayBetween(commands.collect{ command(it) }, delay)
}
def refresh() {
// log.debug 'cmd - refresh()'
commands([
zwave.batteryV1.batteryGet(),
zwave.switchBinaryV1.switchBinaryGet(),
zwave.configurationV1.configurationGet(parameterNumber: 0x01),
zwave.configurationV1.configurationGet(parameterNumber: 0x03)
], 400)
}

View File

@@ -1,970 +0,0 @@
/**
* 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.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -1,189 +0,0 @@
/**
* Vinli Home Beta
*
* Copyright 2015 Daniel
*
*/
definition(
name: "Vinli Home Connect",
namespace: "com.vinli.smartthings",
author: "Daniel",
description: "Allows Vinli users to connect their car to SmartThings",
category: "SmartThings Labs",
iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/baeb2e5d-ebd0-49fe-a4ec-e92417ae20bb/images/vinli_oauth_60.png",
iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/baeb2e5d-ebd0-49fe-a4ec-e92417ae20bb/images/vinli_oauth_120.png",
iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/baeb2e5d-ebd0-49fe-a4ec-e92417ae20bb/images/vinli_oauth_120.png",
oauth: true)
preferences {
section ("Allow external service to control these things...") {
input "switches", "capability.switch", multiple: true, required: true
input "locks", "capability.lock", multiple: true, required: true
}
}
mappings {
path("/devices") {
action: [
GET: "listAllDevices"
]
}
path("/switches") {
action: [
GET: "listSwitches"
]
}
path("/switches/:command") {
action: [
PUT: "updateSwitches"
]
}
path("/switches/:id/:command") {
action: [
PUT: "updateSwitch"
]
}
path("/locks/:command") {
action: [
PUT: "updateLocks"
]
}
path("/locks/:id/:command") {
action: [
PUT: "updateLock"
]
}
path("/devices/:id/:command") {
action: [
PUT: "commandDevice"
]
}
}
// returns a list of all devices
def listAllDevices() {
def resp = []
switches.each {
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub.name]
}
locks.each {
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub.name]
}
return resp
}
// returns a list like
// [[name: "kitchen lamp", value: "off"], [name: "bathroom", value: "on"]]
def listSwitches() {
def resp = []
switches.each {
resp << [name: it.displayName, value: it.currentValue("switch"), type: "switch", id: it.id]
}
return resp
}
void updateLocks() {
// use the built-in request object to get the command parameter
def command = params.command
if (command) {
// check that the switch supports the specified command
// If not, return an error using httpError, providing a HTTP status code.
locks.each {
if (!it.hasCommand(command)) {
httpError(501, "$command is not a valid command for all switches specified")
}
}
// all switches have the comand
// execute the command on all switches
// (note we can do this on the array - the command will be invoked on every element
locks."$command"()
}
}
void updateLock() {
def command = params.command
locks.each {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for all lock specified")
}
if (it.id == params.id) {
it."$command"()
}
}
}
void updateSwitch() {
def command = params.command
switches.each {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for all switches specified")
}
if (it.id == params.id) {
it."$command"()
}
}
}
void commandDevice() {
def command = params.command
def devices = []
switches.each {
devices << it
}
locks.each {
devices << it
}
devices.each {
if (it.id == params.id) {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for specified device")
}
it."$command"()
}
}
}
void updateSwitches() {
// use the built-in request object to get the command parameter
def command = params.command
if (command) {
// check that the switch supports the specified command
// If not, return an error using httpError, providing a HTTP status code.
switches.each {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for all switches specified")
}
}
// all switches have the comand
// execute the command on all switches
// (note we can do this on the array - the command will be invoked on every element
switches."$command"()
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
}

View File

@@ -1,7 +1,7 @@
/**
* Initial State Event Streamer
*
* Copyright 2016 David Sulpy
* Copyright 2015 David Sulpy
*
* 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:
@@ -11,9 +11,9 @@
* 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.
*
*
* SmartThings data is sent from this SmartApp to Initial State. This is event data only for
* devices for which the user has authorized. Likewise, Initial State's services call this
* devices for which the user has authorized. Likewise, Initial State's services call this
* SmartApp on the user's behalf to configure Initial State specific parameters. The ToS and
* Privacy Policy for Initial State can be found here: https://www.initialstate.com/terms
*/
@@ -77,62 +77,6 @@ mappings {
}
}
def getAccessKey() {
log.trace "get access key"
if (atomicState.accessKey == null) {
httpError(404, "Access Key Not Found")
} else {
[
accessKey: atomicState.accessKey
]
}
}
def getBucketKey() {
log.trace "get bucket key"
if (atomicState.bucketKey == null) {
httpError(404, "Bucket key Not Found")
} else {
[
bucketKey: atomicState.bucketKey,
bucketName: atomicState.bucketName
]
}
}
def setBucketKey() {
log.trace "set bucket key"
def newBucketKey = request.JSON?.bucketKey
def newBucketName = request.JSON?.bucketName
log.debug "bucket name: $newBucketName"
log.debug "bucket key: $newBucketKey"
if (newBucketKey && (newBucketKey != atomicState.bucketKey || newBucketName != atomicState.bucketName)) {
atomicState.bucketKey = "$newBucketKey"
atomicState.bucketName = "$newBucketName"
atomicState.isBucketCreated = false
}
tryCreateBucket()
}
def setAccessKey() {
log.trace "set access key"
def newAccessKey = request.JSON?.accessKey
def newGrokerSubdomain = request.JSON?.grokerSubdomain
if (newGrokerSubdomain && newGrokerSubdomain != "" && newGrokerSubdomain != atomicState.grokerSubdomain) {
atomicState.grokerSubdomain = "$newGrokerSubdomain"
atomicState.isBucketCreated = false
}
if (newAccessKey && newAccessKey != atomicState.accessKey) {
atomicState.accessKey = "$newAccessKey"
atomicState.isBucketCreated = false
}
}
def subscribeToEvents() {
if (accelerometers != null) {
subscribe(accelerometers, "acceleration", genericHandler)
@@ -146,7 +90,7 @@ def subscribeToEvents() {
if (beacons != null) {
subscribe(beacons, "presence", genericHandler)
}
if (cos != null) {
subscribe(cos, "carbonMonoxide", genericHandler)
}
@@ -225,27 +169,85 @@ def subscribeToEvents() {
}
}
def getAccessKey() {
log.trace "get access key"
if (atomicState.accessKey == null) {
httpError(404, "Access Key Not Found")
} else {
[
accessKey: atomicState.accessKey
]
}
}
def getBucketKey() {
log.trace "get bucket key"
if (atomicState.bucketKey == null) {
httpError(404, "Bucket key Not Found")
} else {
[
bucketKey: atomicState.bucketKey,
bucketName: atomicState.bucketName
]
}
}
def setBucketKey() {
log.trace "set bucket key"
def newBucketKey = request.JSON?.bucketKey
def newBucketName = request.JSON?.bucketName
log.debug "bucket name: $newBucketName"
log.debug "bucket key: $newBucketKey"
if (newBucketKey && (newBucketKey != atomicState.bucketKey || newBucketName != atomicState.bucketName)) {
atomicState.bucketKey = "$newBucketKey"
atomicState.bucketName = "$newBucketName"
atomicState.isBucketCreated = false
}
tryCreateBucket()
}
def setAccessKey() {
log.trace "set access key"
def newAccessKey = request.JSON?.accessKey
def newGrokerSubdomain = request.JSON?.grokerSubdomain
if (newGrokerSubdomain && newGrokerSubdomain != "" && newGrokerSubdomain != atomicState.grokerSubdomain) {
atomicState.grokerSubdomain = "$newGrokerSubdomain"
atomicState.isBucketCreated = false
}
if (newAccessKey && newAccessKey != atomicState.accessKey) {
atomicState.accessKey = "$newAccessKey"
atomicState.isBucketCreated = false
}
}
def installed() {
atomicState.version = "1.1.0"
atomicState.isBucketCreated = false
atomicState.grokerSubdomain = "groker"
atomicState.version = "1.0.18"
subscribeToEvents()
atomicState.isBucketCreated = false
atomicState.grokerSubdomain = "groker"
atomicState.eventBuffer = []
runEvery15Minutes(flushBuffer)
log.debug "installed (version $atomicState.version)"
}
def updated() {
atomicState.version = "1.1.0"
atomicState.version = "1.0.18"
unsubscribe()
if (atomicState.bucketKey != null && atomicState.accessKey != null) {
atomicState.isBucketCreated = false
}
if (atomicState.eventBuffer == null) {
atomicState.eventBuffer = []
}
if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") {
atomicState.grokerSubdomain = "groker"
}
@@ -260,7 +262,7 @@ def uninstalled() {
}
def tryCreateBucket() {
// can't ship events if there is no grokerSubdomain
if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") {
log.error "streaming url is currently null"
@@ -325,40 +327,60 @@ def genericHandler(evt) {
eventHandler(key, value)
}
def eventHandler(name, value) {
def epoch = now() / 1000
def event = new JsonSlurper().parseText("{\"key\": \"$name\", \"value\": \"$value\", \"epoch\": \"$epoch\"}")
tryShipEvents(event)
log.debug "Shipped Event: " + event
// This is a handler function for flushing the event buffer
// after a specified amount of time to reduce the load on ST servers
def flushBuffer() {
log.trace "About to flush the buffer on schedule"
if (atomicState.eventBuffer != null && atomicState.eventBuffer.size() > 0) {
tryShipEvents()
}
}
def tryShipEvents(event) {
def eventHandler(name, value) {
log.debug atomicState.eventBuffer
def eventBuffer = atomicState.eventBuffer
def epoch = now() / 1000
// if for some reason this code block is being run
// but the SmartApp wasn't propery setup during install
// we need to set initialize the eventBuffer.
if (!atomicState.eventBuffer) {
atomicState.eventBuffer = []
}
eventBuffer << [key: "$name", value: "$value", epoch: "$epoch"]
log.debug eventBuffer
atomicState.eventBuffer = eventBuffer
if (eventBuffer.size() >= 10) {
tryShipEvents()
}
}
// a helper function for shipping the atomicState.eventBuffer to Initial State
def tryShipEvents() {
def grokerSubdomain = atomicState.grokerSubdomain
// can't ship events if there is no grokerSubdomain
if (grokerSubdomain == null || grokerSubdomain == "") {
if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") {
log.error "streaming url is currently null"
return
}
def accessKey = atomicState.accessKey
def bucketKey = atomicState.bucketKey
// can't ship if access key and bucket key are null, so finish trying
if (accessKey == null || bucketKey == null) {
if (atomicState.accessKey == null || atomicState.bucketKey == null) {
return
}
def eventPost = [
uri: "https://${grokerSubdomain}.initialstate.com/api/events",
uri: "https://${atomicState.grokerSubdomain}.initialstate.com/api/events",
headers: [
"Content-Type": "application/json",
"X-IS-BucketKey": "${bucketKey}",
"X-IS-AccessKey": "${accessKey}",
"X-IS-BucketKey": "${atomicState.bucketKey}",
"X-IS-AccessKey": "${atomicState.accessKey}",
"Accept-Version": "0.0.2"
],
body: event
body: atomicState.eventBuffer
]
try {
@@ -367,10 +389,13 @@ def tryShipEvents(event) {
log.debug "shipped events and got ${resp.status}"
if (resp.status >= 400) {
log.error "shipping failed... ${resp.data}"
} else {
// clear the buffer
atomicState.eventBuffer = []
}
}
} catch (e) {
log.error "shipping events failed: $e"
}
}

View File

@@ -13,7 +13,7 @@ preferences{
input "lock1", "capability.lock", required: true
}
section("Select the door contact sensor:") {
input "contact", "capability.contactSensor", required: true
input "contact", "capability.contactSensor", required: true
}
section("Automatically lock the door when closed...") {
input "minutesLater", "number", title: "Delay (in minutes):", required: true
@@ -22,10 +22,9 @@ preferences{
input "secondsLater", "number", title: "Delay (in seconds):", required: true
}
section( "Notifications" ) {
input("recipients", "contact", title: "Send notifications to", required: false) {
input "phoneNumber", "phone", title: "Warn with text message (optional)", description: "Phone Number", required: false
}
}
input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes", "No"]], required: false
input "phoneNumber", "phone", title: "Enter phone number to send text notification.", required: false
}
}
def installed(){
@@ -43,73 +42,55 @@ def initialize(){
subscribe(lock1, "lock", doorHandler, [filterEvents: false])
subscribe(lock1, "unlock", doorHandler, [filterEvents: false])
subscribe(contact, "contact.open", doorHandler)
subscribe(contact, "contact.closed", doorHandler)
subscribe(contact, "contact.closed", doorHandler)
}
def lockDoor(){
log.debug "Locking the door."
lock1.lock()
if(location.contactBookEnabled) {
if ( recipients ) {
log.debug ( "Sending Push Notification..." )
sendNotificationToContacts( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!", recipients)
}
}
if (phoneNumber) {
log.debug("Sending text message...")
sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!")
}
log.debug ( "Sending Push Notification..." )
if ( sendPushMessage != "No" ) sendPush( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!" )
log.debug("Sending text message...")
if ( phoneNumber != "0" ) sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!" )
}
def unlockDoor(){
log.debug "Unlocking the door."
lock1.unlock()
if(location.contactBookEnabled) {
if ( recipients ) {
log.debug ( "Sending Push Notification..." )
sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!", recipients)
}
}
if ( phoneNumber ) {
log.debug("Sending text message...")
sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!")
}
log.debug ( "Sending Push Notification..." )
if ( sendPushMessage != "No" ) sendPush( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!" )
log.debug("Sending text message...")
if ( phoneNumber != "0" ) sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!" )
}
def doorHandler(evt){
if ((contact.latestValue("contact") == "open") && (evt.value == "locked")) { // If the door is open and a person locks the door then...
//def delay = (secondsLater) // runIn uses seconds
runIn( secondsLater, unlockDoor ) // ...schedule (in minutes) to unlock... We don't want the door to be closed while the lock is engaged.
def delay = (secondsLater) // runIn uses seconds
runIn( delay, unlockDoor ) // ...schedule (in minutes) to unlock... We don't want the door to be closed while the lock is engaged.
}
else if ((contact.latestValue("contact") == "open") && (evt.value == "unlocked")) { // If the door is open and a person unlocks it then...
unschedule( unlockDoor ) // ...we don't need to unlock it later.
}
}
else if ((contact.latestValue("contact") == "closed") && (evt.value == "locked")) { // If the door is closed and a person manually locks it then...
unschedule( lockDoor ) // ...we don't need to lock it later.
}
else if ((contact.latestValue("contact") == "closed") && (evt.value == "unlocked")) { // If the door is closed and a person unlocks it then...
//def delay = (minutesLater * 60) // runIn uses seconds
runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock.
def delay = (minutesLater * 60) // runIn uses seconds
runIn( delay, lockDoor ) // ...schedule (in minutes) to lock.
}
else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "open")) { // If a person opens an unlocked door...
unschedule( lockDoor ) // ...we don't need to lock it later.
}
else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "closed")) { // If a person closes an unlocked door...
//def delay = (minutesLater * 60) // runIn uses seconds
runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock.
}
def delay = (minutesLater * 60) // runIn uses seconds
runIn( delay, lockDoor ) // ...schedule (in minutes) to lock.
}
else { //Opening or Closing door when locked (in case you have a handle lock)
log.debug "Unlocking the door."
lock1.unlock()
if(location.contactBookEnabled) {
if ( recipients ) {
log.debug ( "Sending Push Notification..." )
sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!", recipients)
}
}
if ( phoneNumber ) {
log.debug("Sending text message...")
sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!")
}
}
log.debug "Unlocking the door."
lock1.unlock()
log.debug ( "Sending Push Notification..." )
if ( sendPushMessage != "No" ) sendPush( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!" )
log.debug("Sending text message...")
if ( phoneNumber != "0" ) sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!" )
}
}

View File

@@ -1,389 +0,0 @@
/**
* Required PlantLink Connector
* This SmartApp forwards the raw data of the deviceType to myplantlink.com
* and returns it back to your device after calculating soil and plant type.
*
* Copyright 2015 Oso Technologies
*
* 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.
*
*/
import groovy.json.JsonBuilder
import java.util.regex.Matcher
import java.util.regex.Pattern
definition(
name: "PlantLink Connector",
namespace: "Osotech",
author: "Oso Technologies",
description: "This SmartApp connects to myplantlink.com and forwards the device data to it so it can calculate easy to read plant status for your specific plant's needs.",
category: "Convenience",
iconUrl: "https://dashboard.myplantlink.com/images/apple-touch-icon-76x76-precomposed.png",
iconX2Url: "https://dashboard.myplantlink.com/images/apple-touch-icon-120x120-precomposed.png",
iconX3Url: "https://dashboard.myplantlink.com/images/apple-touch-icon-152x152-precomposed.png"
) {
appSetting "client_id"
appSetting "client_secret"
appSetting "https_plantLinkServer"
}
preferences {
page(name: "auth", title: "Step 1 of 2", nextPage:"deviceList", content:"authPage")
page(name: "deviceList", title: "Step 2 of 2", install:true, uninstall:false){
section {
input "plantlinksensors", "capability.sensor", title: "Select PlantLink sensors", multiple: true
}
}
}
mappings {
path("/swapToken") {
action: [
GET: "swapToken"
]
}
}
def authPage(){
if(!atomicState.accessToken){
createAccessToken()
atomicState.accessToken = state.accessToken
}
def redirectUrl = oauthInitUrl()
def uninstallAllowed = false
def oauthTokenProvided = false
if(atomicState.authToken){
uninstallAllowed = true
oauthTokenProvided = true
}
if (!oauthTokenProvided) {
return dynamicPage(name: "auth", title: "Step 1 of 2", nextPage:null, uninstall:uninstallAllowed) {
section(){
href(name:"login",
url:redirectUrl,
style:"embedded",
title:"PlantLink",
image:"https://dashboard.myplantlink.com/images/PLlogo.png",
description:"Tap to login to myplantlink.com")
}
}
}else{
return dynamicPage(name: "auth", title: "Step 1 of 2 - Completed", nextPage:"deviceList", uninstall:uninstallAllowed) {
section(){
paragraph "You are logged in to myplantlink.com, tap next to continue", image: iconUrl
href(url:redirectUrl, title:"Or", description:"tap to switch accounts")
}
}
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def uninstalled() {
if (plantlinksensors){
plantlinksensors.each{ sensor_device ->
sensor_device.setInstallSmartApp("needSmartApp")
}
}
}
def initialize() {
atomicState.attached_sensors = [:]
if (plantlinksensors){
subscribe(plantlinksensors, "moisture_status", moistureHandler)
subscribe(plantlinksensors, "battery_status", batteryHandler)
plantlinksensors.each{ sensor_device ->
sensor_device.setStatusIcon("Waiting on First Measurement")
sensor_device.setInstallSmartApp("connectedToSmartApp")
}
}
}
def dock_sensor(device_serial, expected_plant_name) {
def docking_body_json_builder = new JsonBuilder([version: '1c', smartthings_device_id: device_serial])
def docking_params = [
uri : appSettings.https_plantLinkServer,
path : "/api/v1/smartthings/links",
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType: "application/json",
body: docking_body_json_builder.toString()
]
def plant_post_body_map = [
plant_type_key: 999999,
soil_type_key : 1000004
]
def plant_post_params = [
uri : appSettings.https_plantLinkServer,
path : "/api/v1/plants",
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType: "application/json",
]
log.debug "Creating new plant on myplantlink.com - ${expected_plant_name}"
httpPost(docking_params) { docking_response ->
if (parse_api_response(docking_response, "Docking a link")) {
if (docking_response.data.plants.size() == 0) {
log.debug "creating plant for - ${expected_plant_name}"
plant_post_body_map["name"] = expected_plant_name
plant_post_body_map['links_key'] = [docking_response.data.key]
def plant_post_body_json_builder = new JsonBuilder(plant_post_body_map)
plant_post_params["body"] = plant_post_body_json_builder.toString()
httpPost(plant_post_params) { plant_post_response ->
if(parse_api_response(plant_post_response, 'creating plant')){
def attached_map = atomicState.attached_sensors
attached_map[device_serial] = plant_post_response.data
atomicState.attached_sensors = attached_map
}
}
} else {
def plant = docking_response.data.plants[0]
def attached_map = atomicState.attached_sensors
attached_map[device_serial] = plant
atomicState.attached_sensors = attached_map
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
}
}
}
return true
}
def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
def plant_put_params = [
uri : appSettings.https_plantLinkServer,
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType : "application/json"
]
if (plant.name != expected_plant_name) {
log.debug "updating plant for - ${expected_plant_name}"
plant_put_params["path"] = "/api/v1/plants/${plant.key}"
def plant_put_body_map = [
name: expected_plant_name
]
def plant_put_body_json_builder = new JsonBuilder(plant_put_body_map)
plant_put_params["body"] = plant_put_body_json_builder.toString()
httpPut(plant_put_params) { plant_put_response ->
parse_api_response(plant_put_response, 'updating plant name')
}
}
}
def moistureHandler(event){
def expected_plant_name = "SmartThings - ${event.displayName}"
def device_serial = getDeviceSerialFromEvent(event)
if (!atomicState.attached_sensors.containsKey(device_serial)){
dock_sensor(device_serial, expected_plant_name)
}else{
def measurement_post_params = [
uri: appSettings.https_plantLinkServer,
path: "/api/v1/smartthings/links/${device_serial}/measurements",
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType: "application/json",
body: event.value
]
httpPost(measurement_post_params) { measurement_post_response ->
if (parse_api_response(measurement_post_response, 'creating moisture measurement') &&
measurement_post_response.data.size() >0){
def measurement = measurement_post_response.data[0]
def plant = measurement.plant
log.debug plant
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
plantlinksensors.each{ sensor_device ->
if (sensor_device.id == event.deviceId){
sensor_device.setStatusIcon(plant.status)
if (plant.last_measurements && plant.last_measurements[0].moisture){
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
}
if (plant.last_measurements && plant.last_measurements[0].battery){
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
}
}
}
}
}
}
}
def batteryHandler(event){
def expected_plant_name = "SmartThings - ${event.displayName}"
def device_serial = getDeviceSerialFromEvent(event)
if (!atomicState.attached_sensors.containsKey(device_serial)){
dock_sensor(device_serial, expected_plant_name)
}else{
def measurement_post_params = [
uri: appSettings.https_plantLinkServer,
path: "/api/v1/smartthings/links/${device_serial}/measurements",
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType: "application/json",
body: event.value
]
httpPost(measurement_post_params) { measurement_post_response ->
parse_api_response(measurement_post_response, 'creating battery measurement')
}
}
}
def getDeviceSerialFromEvent(event){
def pattern = /.*"zigbeedeviceid"\s*:\s*"(\w+)".*/
def match_result = (event.value =~ pattern)
return match_result[0][1]
}
def oauthInitUrl(){
atomicState.oauthInitState = UUID.randomUUID().toString()
def oauthParams = [
response_type: "code",
client_id: appSettings.client_id,
state: atomicState.oauthInitState,
redirect_uri: buildRedirectUrl()
]
return appSettings.https_plantLinkServer + "/oauth/oauth2/authorize?" + toQueryString(oauthParams)
}
def buildRedirectUrl(){
return getServerUrl() + "/api/token/${atomicState.accessToken}/smartapps/installations/${app.id}/swapToken"
}
def swapToken(){
def code = params.code
def oauthState = params.state
def stcid = appSettings.client_id
def postParams = [
method: 'POST',
uri: "https://oso-tech.appspot.com",
path: "/api/v1/oauth-token",
query: [grant_type:'authorization_code', code:params.code, client_id:stcid,
client_secret:appSettings.client_secret, redirect_uri: buildRedirectUrl()],
]
def jsonMap
httpPost(postParams) { resp ->
jsonMap = resp.data
}
atomicState.refreshToken = jsonMap.refresh_token
atomicState.authToken = jsonMap.access_token
def html = """
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
padding:25px;
}
.flex1 {
width:33%;
float:left;
text-align: center;
}
p {
font-size: 2em;
font-family: Verdana, Geneva, sans-serif;
text-align: center;
color: #777;
}
</style>
</head>
<body>
<div class="container">
<div class="flex1"><img src="https://dashboard.myplantlink.com/images/PLlogo.png" alt="PlantLink" height="75"/></div>
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected to" height="25" style="padding-top:25px;" /></div>
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings" height="75"/></div>
<br clear="all">
</div>
<div class="container">
<p>Your PlantLink Account is now connected to SmartThings!</p>
<p style="color:green;">Click <strong>Done</strong> at the top right to finish setup.</p>
</div>
</body>
</html>
"""
render contentType: 'text/html', data: html
}
private refreshAuthToken() {
def stcid = appSettings.client_id
def refreshParams = [
method: 'POST',
uri: "https://hardware-dot-oso-tech.appspot.com",
path: "/api/v1/oauth-token",
query: [grant_type:'refresh_token', code:"${atomicState.refreshToken}", client_id:stcid,
client_secret:appSettings.client_secret],
]
try{
def jsonMap
httpPost(refreshParams) { resp ->
if(resp.status == 200){
log.debug "OAuth Token refreshed"
jsonMap = resp.data
if (resp.data) {
atomicState.refreshToken = resp?.data?.refresh_token
atomicState.authToken = resp?.data?.access_token
if (data?.action && data?.action != "") {
log.debug data.action
"{data.action}"()
data.action = ""
}
}
data.action = ""
}else{
log.debug "refresh failed ${resp.status} : ${resp.status.code}"
}
}
}
catch(Exception e){
log.debug "caught exception refreshing auth token: " + e
}
}
def parse_api_response(resp, message) {
if (resp.status == 200) {
return true
} else {
log.error "sent ${message} Json & got http status ${resp.status} - ${resp.status.code}"
if (resp.status == 401) {
refreshAuthToken()
return false
} else {
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.", true)
return false
}
}
}
def getServerUrl() {
return "https://graph.api.smartthings.com"
}
def debugEvent(message, displayEvent) {
def results = [
name: "appdebug",
descriptionText: message,
displayed: displayEvent
]
log.debug "Generating AppDebug Event: ${results}"
sendEvent (results)
}
def toQueryString(Map m){
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
}

View File

@@ -10,7 +10,7 @@
* The timeout perid begins when the contact is closed, or motion stops, so leaving a door open won't start the timer until it's closed.
*
* Author: andersheie@gmail.com
* Date: 2014-08-31
* Date: 2015-10-30
*/
definition(
@@ -23,12 +23,18 @@ definition(
iconX2Url: "http://upload.wikimedia.org/wikipedia/commons/6/6a/Light_bulb_icon_tips.svg")
preferences {
section("Turn on when there's movement..."){
section("Turn on when there is movement..."){
input "motions", "capability.motionSensor", multiple: true, title: "Select motion detectors", required: false
}
section("Or, turn on when one of these contacts opened"){
input "contacts", "capability.contactSensor", multiple: true, title: "Select Contacts", required: false
}
section("Or, turn on when any of these people come home") {
input "peoplearrive", "capability.presenceSensor", multiple: true, required: false
}
section("Or, turn on when any of these people leave") {
input "peopleleave", "capability.presenceSensor", multiple: true, required: false
}
section("And off after no more triggers after..."){
input "minutes1", "number", title: "Minutes?", defaultValue: "5"
}
@@ -40,109 +46,232 @@ preferences {
def installed()
{
subscribe(switches, "switch", switchChange)
subscribe(motions, "motion", motionHandler)
subscribe(contacts, "contact", contactHandler)
schedule("0 * * * * ?", "scheduleCheck")
state.myState = "ready"
log.debug "installed()"
initialize()
}
def updated()
{
unsubscribe()
log.debug "updated()"
initialize()
}
def initialize()
{
log.debug "initialize()"
// Reset to new set of Switches, just in case.
unsubscribe()
state.switches = [:]
switches.each {
// log.debug "ID: $it.id"
// Set ready state for each switch
state.switches["$it.id"] = "ready"
}
logStates()
subscribe(motions, "motion", motionHandler)
subscribe(switches, "switch", switchChange)
subscribe(contacts, "contact", contactHandler)
subscribe(peoplearrive, "presence.present", presencePresentHandler)
subscribe(peopleleave, "presence.not present", presenceNotPresentHandler)
state.lastAction = null
schedule("0 * * * * ?", "scheduleCheck")
}
def logStates() {
if(state.switches == null) {
state.switches = [:]
}
state.switches.each {
log.debug "State: $it.key = $it.value"
}
}
def turnSwitchOn(id) {
for(S in switches) {
if(S.id == id) {
S.on()
}
}
}
def turnSwitchOff(id) {
for(S in switches) {
if(S.id == id) {
S.off()
}
}
}
def initState(deviceID) {
// Have to add this so existing apps that are neither updated nor initiated can slowly get 'reset' with states.
// Smartthings doesn't call any specifc method when new version is published.
if(state.switches == null) {
state.switches = [:]
}
if(state.switches[deviceID] == null) {
state.switches[deviceID] = "ready"
}
state.myState = "ready"
log.debug "state: " + state.myState
}
def switchChange(evt) {
log.debug "SwitchChange: $evt.name: $evt.value"
def deviceID = evt.device.id
log.debug "SwitchChange: $evt.name: $evt.value $evt.device.id current state = " + state.switches[deviceID]
initState(deviceID)
if(evt.value == "on") {
// Slight change of Race condition between motion or contact turning the switch on,
// versus user turning the switch on. Since we can't pass event parameters :-(, we rely
// on the state and hope the best.
if(state.myState == "activating") {
// OK, probably an event from Activating something, and not the switch itself. Go to Active mode.
state.myState = "active"
} else if(state.myState != "active") {
state.myState = "already on"
}
if(evt.value == "on") {
if(state.switches[deviceID] == "activating") {
// OK, probably an event from Activating something, and not the switch itself. Go to Active mode.
log.debug "$deviceID = " + state.switches[deviceID] + " -> active"
state.switches[deviceID] = "active"
} else if(state.switches[deviceID] != "active") {
log.debug "$deviceID = " + state.switches[deviceID] + " -> already on"
state.switches[deviceID] = "already on"
}
} else {
// If active and switch is turned of manually, then stop the schedule and go to ready state
if(state.myState == "active" || state.myState == "activating") {
unschedule()
}
state.myState = "ready"
// If active and switch is turned of manually, then stop the schedule and go to ready state
state.switches[deviceID] = "ready"
}
log.debug "state: " + state.myState
logStates()
}
def contactHandler(evt) {
log.debug "contactHandler: $evt.name: $evt.value"
if (evt.value == "open") {
if(state.myState == "ready") {
log.debug "Turning on lights by contact opening"
switches.on()
state.inactiveAt = null
state.myState = "activating"
}
} else if (evt.value == "closed") {
if (!state.inactiveAt && state.myState == "active" || state.myState == "activating") {
// When contact closes, we reset the timer if not already set
setActiveAndSchedule()
state.switches.each { thisswitch ->
initState(thisswitch.key)
//log.debug "Looking for $thisswitch.key"
if (evt.value == "open") {
if(state.switches[thisswitch.key] == "ready") {
log.debug "Turning on lights by contact opening: $thisswitch"
log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> activating"
state.switches[thisswitch.key] = "activating"
turnSwitchOn(thisswitch.key)
setActiveAndSchedule()
}
} else if (evt.value == "closed") {
if (!state.lastAction && (state.switches[thisswitch.key] == "active"
|| state.switches[thisswitch.key] == "activating")) {
// When contact closes, we reset the timer if not already set
log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> active"
state.switches[thisswitch.key] = "active"
setActiveAndSchedule()
}
}
}
log.debug "state: " + state.myState
logStates()
}
def motionHandler(evt) {
log.debug "motionHandler: $evt.name: $evt.value"
if (evt.value == "active") {
if(state.myState == "ready" || state.myState == "active" || state.myState == "activating" ) {
log.debug "turning on lights"
switches.on()
state.inactiveAt = null
state.myState = "activating"
}
} else if (evt.value == "inactive") {
if (!state.inactiveAt && state.myState == "active" || state.myState == "activating") {
// When Motion ends, we reset the timer if not already set
setActiveAndSchedule()
def scheduleCheck() {
log.debug "schedule check, ts = ${state.lastAction}"
boolean resetInactive = false
state.switches.each { thisswitch ->
initState(thisswitch.key)
if(state.switches[thisswitch.key] != "already on") {
if(state.lastAction != null) {
def elapsed = now() - state.lastAction
log.debug "${elapsed / 1000} sec since events stopped"
def threshold = 1000 * 60 * minutes1
if (elapsed >= threshold) {
if (state.switches[thisswitch.key] == "active"
|| state.switches[thisswitch.key] == "activating") {
state.switches[thisswitch.key] = "ready"
log.debug "Turning off lights by switch closing: $thisswitch"
turnSwitchOff(thisswitch.key)
}
resetInactive = true
}
}
}
}
log.debug "state: " + state.myState
if(resetInactive) {
state.lastAction = null
unschedule()
}
logStates()
}
/**********************************************************************/
def motionHandler(evt) {
log.debug "motionHandler: $evt.name: $evt.value (current state: " + state.myState + ")"
state.switches.each { thisswitch ->
initState(thisswitch.key)
if (evt.value == "active") {
if(state.switches[thisswitch.key] == "ready"
|| state.switches[thisswitch.key] == "active"
|| state.switches[thisswitch.key] == "activating" ) {
log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> activating"
state.switches[thisswitch.key] = "activating"
turnSwitchOn(thisswitch.key)
setActiveAndSchedule()
}
} else if (evt.value == "inactive") {
if (state.switches[thisswitch.key] == "active" || state.switches[thisswitch.key] == "activating") {
// When Motion ends, we reset the timer if not already set
log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> active"
state.switches[thisswitch.key] = "active"
setActiveAndSchedule()
}
}
}
logStates()
}
def presencePresentHandler(evt) {
log.debug "presence: $evt.linkText is now $evt.value"
state.switches.each { thisswitch ->
initState(thisswitch.key)
if (evt.value == "present") {
if(state.switches[thisswitch.key] == "ready"
|| state.switches[thisswitch.key] == "active"
|| state.switches[thisswitch.key] == "activating" ) {
log.debug "Presence turning on switch $thisswitch"
state.switches[thisswitch.key] = "active"
turnSwitchOn(thisswitch.key)
// We don't wait until the person leave, but instead start timer immediately.
setActiveAndSchedule()
}
}
}
logStates()
}
def presenceNotPresentHandler(evt) {
log.debug "presence: $evt.linkText is now $evt.value"
state.switches.each { thisswitch ->
initState(thisswitch.key)
if (evt.value == "not present") {
if(state.switches[thisswitch.key] == "ready"
|| state.switches[thisswitch.key] == "active"
|| state.switches[thisswitch.key] == "activating" ) {
log.debug "No Presence turning on lights $thisswitch"
state.switches[thisswitch.key] = "active"
turnSwitchOn(thisswitch.key)
// We don't wait until the person arrive back, but instead start timer immediately.
setActiveAndSchedule()
}
}
}
logStates()
}
def setActiveAndSchedule() {
unschedule()
state.myState = "active"
state.inactiveAt = now()
state.lastAction = now()
schedule("0 * * * * ?", "scheduleCheck")
log.debug "Scheduled new timer"
}
def scheduleCheck() {
log.debug "schedule check, ts = ${state.inactiveAt}"
if(state.myState != "already on") {
if(state.inactiveAt != null) {
def elapsed = now() - state.inactiveAt
log.debug "${elapsed / 1000} sec since motion stopped"
def threshold = 1000 * 60 * minutes1
if (elapsed >= threshold) {
if (state.myState == "active") {
log.debug "turning off lights"
switches.off()
}
state.inactiveAt = null
state.myState = "ready"
}
}
}
log.debug "state: " + state.myState
}

View File

@@ -1,341 +0,0 @@
/**
* 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.
*
* Bose® SoundTouch® Control
*
* Author: SmartThings & Joe Geiger
*
* Date: 2015-30-09
*/
definition(
name: "Bose® SoundTouch® Control",
namespace: "smartthings",
author: "SmartThings & Joe Geiger",
description: "Control your Bose® SoundTouch® when certain actions take place in your home.",
category: "SmartThings Labs",
iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon.png",
iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x.png",
iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/fcf1d93a-ba0b-4324-b96f-e5b5487dfaf5/images/BoseST_icon@2x-1.png"
)
preferences {
page(name: "mainPage", title: "Control your Bose® SoundTouch® when something happens", install: true, uninstall: true)
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 mainPage() {
dynamicPage(name: "mainPage") {
def anythingSet = anythingSet()
if (anythingSet) {
section("When..."){
ifSet "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
ifSet "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
}
}
section(anythingSet ? "Select additional triggers" : "When...", hideable: anythingSet, hidden: true){
ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
ifUnset "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
}
section("Perform this action"){
input "actionType", "enum", title: "Action?", required: true, defaultValue: "play", options: [
"Turn On & Play",
"Turn Off",
"Toggle Play/Pause",
"Skip to Next Track",
"Skip to Beginning/Previous Track",
"Play Preset 1",
"Play Preset 2",
"Play Preset 3",
"Play Preset 4",
"Play Preset 5",
"Play Preset 6"
]
}
section {
input "bose", "capability.musicPlayer", title: "Bose® SoundTouch® music player", required: true
}
section("More options", hideable: true, hidden: true) {
input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false
input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
if (settings.modes) {
input "modes", "mode", title: "Only when mode is", multiple: true, required: false
}
input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
}
section([mobileOnly:true]) {
label title: "Assign a name", required: false
mode title: "Set for specific mode(s)"
}
}
}
private anythingSet() {
for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","triggerModes","timeOfDay"]) {
if (settings[name]) {
return true
}
}
return false
}
private ifUnset(Map options, String name, String capability) {
if (!settings[name]) {
input(options, name, capability)
}
}
private ifSet(Map options, String name, String capability) {
if (settings[name]) {
input(options, name, capability)
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
subscribeToEvents()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
unschedule()
subscribeToEvents()
}
def subscribeToEvents() {
log.trace "subscribeToEvents()"
subscribe(app, appTouchHandler)
subscribe(contact, "contact.open", eventHandler)
subscribe(contactClosed, "contact.closed", eventHandler)
subscribe(acceleration, "acceleration.active", eventHandler)
subscribe(motion, "motion.active", eventHandler)
subscribe(mySwitch, "switch.on", eventHandler)
subscribe(mySwitchOff, "switch.off", eventHandler)
subscribe(arrivalPresence, "presence.present", eventHandler)
subscribe(departurePresence, "presence.not present", eventHandler)
subscribe(smoke, "smoke.detected", eventHandler)
subscribe(smoke, "smoke.tested", eventHandler)
subscribe(smoke, "carbonMonoxide.detected", eventHandler)
subscribe(water, "water.wet", eventHandler)
subscribe(button1, "button.pushed", eventHandler)
if (triggerModes) {
subscribe(location, modeChangeHandler)
}
if (timeOfDay) {
schedule(timeOfDay, scheduledTimeHandler)
}
}
def eventHandler(evt) {
if (allOk) {
def lastTime = state[frequencyKey(evt)]
if (oncePerDayOk(lastTime)) {
if (frequency) {
if (lastTime == null || now() - lastTime >= frequency * 60000) {
takeAction(evt)
}
else {
log.debug "Not taking action because $frequency minutes have not elapsed since last action"
}
}
else {
takeAction(evt)
}
}
else {
log.debug "Not taking action because it was already taken today"
}
}
}
def modeChangeHandler(evt) {
log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
if (evt.value in triggerModes) {
eventHandler(evt)
}
}
def scheduledTimeHandler() {
eventHandler(null)
}
def appTouchHandler(evt) {
takeAction(evt)
}
private takeAction(evt) {
log.debug "takeAction($actionType)"
def options = [:]
if (volume) {
bose.setLevel(volume as Integer)
options.delay = 1000
}
switch (actionType) {
case "Turn On & Play":
options ? bose.on(options) : bose.on()
break
case "Turn Off":
options ? bose.off(options) : bose.off()
break
case "Toggle Play/Pause":
def currentStatus = bose.currentValue("playpause")
if (currentStatus == "play") {
options ? bose.pause(options) : bose.pause()
}
else if (currentStatus == "pause") {
options ? bose.play(options) : bose.play()
}
break
case "Skip to Next Track":
options ? bose.nextTrack(options) : bose.nextTrack()
break
case "Skip to Beginning/Previous Track":
options ? bose.previousTrack(options) : bose.previousTrack()
break
case "Play Preset 1":
options ? bose.preset1(options) : bose.preset1()
break
case "Play Preset 2":
options ? bose.preset2(options) : bose.preset2()
break
case "Play Preset 3":
options ? bose.preset3(options) : bose.preset3()
break
case "Play Preset 4":
options ? bose.preset4(options) : bose.preset4()
break
case "Play Preset 5":
options ? bose.preset5(options) : bose.preset5()
break
case "Play Preset 6":
options ? bose.preset6(options) : bose.preset6()
break
default:
log.error "Action type '$actionType' not defined"
}
if (frequency) {
state.lastActionTimeStamp = now()
}
}
private frequencyKey(evt) {
//evt.deviceId ?: evt.value
"lastActionTimeStamp"
}
private dayString(Date date) {
def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
if (location.timeZone) {
df.setTimeZone(location.timeZone)
}
else {
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
}
df.format(date)
}
private oncePerDayOk(Long lastTime) {
def result = true
if (oncePerDay) {
result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
log.trace "oncePerDayOk = $result"
}
result
}
// TODO - centralize somehow
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, location?.timeZone).time
def stop = timeToday(ending, location?.timeZone).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 timeIntervalLabel()
{
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
}
// TODO - End Centralize

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -246,9 +246,6 @@ def toggle(devices) {
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()
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
/**
* Every Element
*
* Copyright 2015 SmartThings
* Copyright 2014 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:
@@ -14,555 +14,349 @@
*
*/
definition(
name: "Every Element",
namespace: "smartthings/examples",
author: "SmartThings",
description: "Every element demonstration app",
category: "SmartThings Internal",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
name: "Every Element",
namespace: "smartthings/examples",
author: "SmartThings",
description: "Every element demonstration app",
category: "SmartThings Internal",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
)
preferences {
// landing page
page(name: "firstPage")
// PageKit
page(name: "buttonsPage")
page(name: "imagePage")
page(name: "inputPage")
page(name: "inputBooleanPage")
page(name: "inputIconPage")
page(name: "inputImagePage")
page(name: "inputDevicePage")
page(name: "inputCapabilityPage")
page(name: "inputRoomPage")
page(name: "inputModePage")
page(name: "inputSelectionPage")
page(name: "inputHubPage")
page(name: "inputContactBookPage")
page(name: "inputTextPage")
page(name: "inputTimePage")
page(name: "appPage")
page(name: "hrefPage")
page(name: "paragraphPage")
page(name: "videoPage")
page(name: "labelPage")
page(name: "modePage")
// Every element helper pages
page(name: "deadEnd", title: "Nothing to see here, move along.", content: "foo")
page(name: "flattenedPage")
page(name: "firstPage")
page(name: "inputPage")
page(name: "appPage")
page(name: "labelPage")
page(name: "modePage")
page(name: "paragraphPage")
page(name: "iconPage")
page(name: "hrefPage")
page(name: "buttonsPage")
page(name: "imagePage")
page(name: "videoPage")
page(name: "deadEnd", title: "Nothing to see here, move along.", content: "foo")
page(name: "flattenedPage")
}
def firstPage() {
dynamicPage(name: "firstPage", title: "Where to first?", install: true, uninstall: true) {
section {
href(page: "appPage", title: "Element: 'app'")
href(page: "buttonsPage", title: "Element: 'buttons'")
href(page: "hrefPage", title: "Element: 'href'")
href(page: "imagePage", title: "Element: 'image'")
href(page: "inputPage", title: "Element: 'input'")
href(page: "labelPage", title: "Element: 'label'")
href(page: "modePage", title: "Element: 'mode'")
href(page: "paragraphPage", title: "Element: 'paragraph'")
href(page: "videoPage", title: "Element: 'video'")
}
section {
href(page: "flattenedPage", title: "All of the above elements on a single page")
}
}
dynamicPage(name: "firstPage", title: "Where to first?", install: true, uninstall: true) {
section() {
href(page: "inputPage", title: "Element: 'input'")
href(page: "appPage", title: "Element: 'app'")
href(page: "labelPage", title: "Element: 'label'")
href(page: "modePage", title: "Element: 'mode'")
href(page: "paragraphPage", title: "Element: 'paragraph'")
href(page: "iconPage", title: "Element: 'icon'")
href(page: "hrefPage", title: "Element: 'href'")
href(page: "buttonsPage", title: "Element: 'buttons'")
href(page: "imagePage", title: "Element: 'image'")
href(page: "videoPage", title: "Element: 'video'")
}
section() {
href(page: "flattenedPage", title: "All of the above elements on a single page")
}
}
}
def inputPage() {
dynamicPage(name: "inputPage", title: "Links to every 'input' element") {
section {
href(page: "inputBooleanPage", title: "to boolean page")
href(page: "inputIconPage", title: "to icon page")
href(page: "inputImagePage", title: "to image page")
href(page: "inputSelectionPage", title: "to selection page")
href(page: "inputTextPage", title: "to text page")
href(page: "inputTimePage", title: "to time page")
}
section("subsets of selection input") {
href(page: "inputDevicePage", title: "to device selection page")
href(page: "inputCapabilityPage", title: "to capability selection page")
href(page: "inputRoomPage", title: "to room selection page")
href(page: "inputModePage", title: "to mode selection page")
href(page: "inputHubPage", title: "to hub selection page")
href(page: "inputContactBookPage", title: "to contact-book selection page")
}
}
}
def inputBooleanPage() {
dynamicPage(name: "inputBooleanPage") {
section {
paragraph "The `required` and `multiple` attributes have no effect because the value will always be either `true` or `false`"
}
section {
input(type: "boolean", name: "booleanWithoutDescription", title: "without description", description: null)
input(type: "boolean", name: "booleanWithDescription", title: "with description", description: "This has a description")
}
section("defaultValue: 'true'") {
input(type: "boolean", name: "booleanWithDefaultValue", title: "", description: "", defaultValue: "true")
}
section("with image") {
input(type: "boolean", name: "booleanWithoutDescriptionWithImage", title: "without description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", description: null)
input(type: "boolean", name: "booleanWithDescriptionWithImage", title: "with description", description: "This has a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
}
def inputIconPage() {
dynamicPage(name: "inputIconPage") {
section {
paragraph "`description` is not displayed for icon elements"
paragraph "`multiple` has no effect because you can only choose a single icon"
}
section("required: true") {
input(type: "icon", name: "iconRequired", title: "without description", required: true)
input(type: "icon", name: "iconRequiredWithDescription", title: "with description", description: "this is a description", required: true)
}
section("with image") {
paragraph "The image specified will be replaced after an icon is selected"
input(type: "icon", name: "iconwithImage", title: "without description", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
}
def inputImagePage() {
dynamicPage(name: "inputImagePage") {
section {
paragraph "This only exists in DeviceTypes. Someone should do something about that. (glares at MikeDave)"
paragraph "Go to the device preferences of a Mobile Presence device to see it in action"
paragraph "If you try to set the value of this, it will not behave as it would in Device Preferences"
input(type: "image", title: "This is kind of what it looks like", required: false)
}
}
}
def optionsGroup(List groups, String title) {
def group = [values:[], order: groups.size()]
group.title = title ?: ""
groups << group
return groups
}
def addValues(List groups, String key, String value) {
def lastGroup = groups[-1]
lastGroup["values"] << [
key: key,
value: value,
order: lastGroup["values"].size()
]
return groups
}
def listToMap(List original) {
original.inject([:]) { result, v ->
result[v] = v
return result
}
}
def addGroup(List groups, String title, values) {
if (values instanceof List) {
values = listToMap(values)
}
values.inject(optionsGroup(groups, title)) { result, k, v ->
return addValues(result, k, v)
}
return groups
}
def addGroup(values) {
addGroup([], null, values)
}
/* Example usage of options builder
// Creating grouped options
def newGroups = []
addGroup(newGroups, "first group", ["foo", "bar", "baz"])
addGroup(newGroups, "second group", [zero: "zero", one: "uno", two: "dos", three: "tres"])
// simple list
addGroup(["a", "b", "c"])
// simple map
addGroup(["a": "yes", "b": "no", "c": "maybe"])
*/
def inputSelectionPage() {
def englishOptions = ["One", "Two", "Three"]
def spanishOptions = ["Uno", "Dos", "Tres"]
def groupedOptions = []
addGroup(groupedOptions, "English", englishOptions)
addGroup(groupedOptions, "Spanish", spanishOptions)
dynamicPage(name: "inputSelectionPage") {
section("options variations") {
paragraph "tap these elements and look at the differences when selecting an option"
input(type: "enum", name: "selectionSimple", title: "Simple options", description: "no separators in the selectable options", groupedOptions: addGroup(englishOptions + spanishOptions))
input(type: "enum", name: "selectionGrouped", title: "Grouped options", description: "separate groups of options with headers", groupedOptions: groupedOptions)
}
section("list vs map") {
paragraph "These should be identical in UI, but are different in code and will produce different settings"
input(type: "enum", name: "selectionList", title: "Choose a device", description: "settings will be something like ['Device1 Label']", groupedOptions: addGroup(["Device1 Label", "Device2 Label"]))
input(type: "enum", name: "selectionMap", title: "Choose a device", description: "settings will be something like ['device1-id']", groupedOptions: addGroup(["device1-id": "Device1 Label", "device2-id": "Device2 Label"]))
}
section("segmented") {
paragraph "segmented should only work if there are either 2 or 3 options to choose from"
input(type: "enum", name: "selectionSegmented1", style: "segmented", title: "1 option", groupedOptions: addGroup(["One"]))
input(type: "enum", name: "selectionSegmented4", style: "segmented", title: "4 options", groupedOptions: addGroup(["One", "Two", "Three", "Four"]))
paragraph "multiple and required will have no effect on segmented selection elements. There will always be exactly 1 option selected"
input(type: "enum", name: "selectionSegmented2", style: "segmented", title: "2 options", options: ["One", "Two"])
input(type: "enum", name: "selectionSegmented3", style: "segmented", title: "3 options", options: ["One", "Two", "Three"])
paragraph "specifying defaultValue still works with segmented selection elements"
input(type: "enum", name: "selectionSegmentedWithDefault", title: "defaulted to 'two'", groupedOptions: addGroup(["One", "Two", "Three"]), defaultValue: "Two")
}
section("required: true") {
input(type: "enum", name: "selectionRequired", title: "This is required", description: "It should look different when nothing is selected", groupedOptions: addGroup(["only option"]), required: true)
}
section("multiple: true") {
input(type: "enum", name: "selectionMultiple", title: "This allows multiple selections", description: "It should look different when nothing is selected", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), multiple: true)
}
section("with image") {
input(type: "enum", name: "selectionWithImage", title: "This has an image", description: "and a description", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
}
def inputTextPage() {
dynamicPage(name: "inputTextPage", title: "Every 'text' variation") {
section("style and functional differences") {
input(type: "text", name: "textRequired", title: "required: true", description: "This should look different when nothing has been entered", required: true)
input(type: "text", name: "textWithImage", title: "with image", description: "This should look different when nothing has been entered", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", required: false)
}
section("text") {
input(type: "text", name: "text", title: "This has an alpha-numeric keyboard", description: "no special formatting", required: false)
}
section("password") {
input(type: "password", name: "password", title: "This has an alpha-numeric keyboard", description: "masks value", required: false)
}
section("email") {
input(type: "email", name: "email", title: "This has an email-specific keyboard", description: "no special formatting", required: false)
}
section("phone") {
input(type: "phone", name: "phone", title: "This has a numeric keyboard", description: "formatted for phone numbers", required: false)
}
section("decimal") {
input(type: "decimal", name: "decimal", title: "This has an numeric keyboard with decimal point", description: "no special formatting", required: false)
}
section("number") {
input(type: "number", name: "number", title: "This has an numeric keyboard without decimal point", description: "no special formatting", required: false)
}
section("specified ranges") {
paragraph "You can limit number and decimal inputs to a specific range."
input(range: "50..150", type: "decimal", name: "decimalRange50..150", title: "only values between 50 and 150 will pass validation", description: "no special formatting", required: false)
paragraph "Negative limits will add a negative symbol to the keyboard."
input(range: "-50..50", type: "number", name: "numberRange-50..50", title: "only values between -50 and 50 will pass validation", description: "no special formatting", required: false)
paragraph "Specify * to not limit one side or the other."
input(range: "*..0", type: "decimal", name: "decimalRange*..0", title: "only negative values will pass validation", description: "no special formatting", required: false)
input(range: "*..*", type: "number", name: "numberRange*..*", title: "only positive values will pass validation", description: "no special formatting", required: false)
paragraph "If you don't specify a range, it defaults to 0..*"
}
}
}
def inputTimePage() {
dynamicPage(name: "inputTimePage") {
section {
input(type: "time", name: "timeWithDescription", title: "a time picker", description: "with a description", required: false)
input(type: "time", name: "timeWithoutDescription", title: "without a description", description: null, required: false)
input(type: "time", name: "timeRequired", title: "required: true", required: true)
}
}
}
/// selection subsets
def inputDevicePage() {
dynamicPage(name: "inputDevicePage") {
section("required: true") {
input(type: "device.switch", name: "deviceRequired", title: "This is required", description: "It should look different when nothing is selected")
}
section("multiple: true") {
input(type: "device.switch", name: "deviceMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true)
}
section("with image") {
input(type: "device.switch", name: "deviceRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
}
def inputCapabilityPage() {
dynamicPage(name: "inputCapabilityPage") {
section("required: true") {
input(type: "capability.switch", name: "capabilityRequired", title: "This is required", description: "It should look different when nothing is selected")
}
section("multiple: true") {
input(type: "capability.switch", name: "capabilityMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true)
}
section("with image") {
input(type: "capability.switch", name: "capabilityRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
}
def inputRoomPage() {
dynamicPage(name: "inputRoomPage") {
section("required: true") {
input(type: "room", name: "roomRequired", title: "This is required", description: "It should look different when nothing is selected")
}
section("multiple: true") {
input(type: "room", name: "roomMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true)
}
section("with image") {
input(type: "room", name: "roomRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
}
def inputModePage() {
dynamicPage(name: "inputModePage") {
section("required: true") {
input(type: "mode", name: "modeRequired", title: "This is required", description: "It should look different when nothing is selected")
}
section("multiple: true") {
input(type: "mode", name: "modeMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true)
}
section("with image") {
input(type: "mode", name: "modeRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
}
def inputHubPage() {
dynamicPage(name: "inputHubPage") {
section("required: true") {
input(type: "hub", name: "hubRequired", title: "This is required", description: "It should look different when nothing is selected")
}
section("multiple: true") {
input(type: "hub", name: "hubMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true)
}
section("with image") {
input(type: "hub", name: "hubRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
}
def inputContactBookPage() {
dynamicPage(name: "inputContactBookPage") {
section("required: true") {
input(type: "contact", name: "contactRequired", title: "This is required", description: "It should look different when nothing is selected")
}
section("multiple: true") {
input(type: "contact", name: "contactMultiple", title: "This is required", description: "It should look different when nothing is selected", multiple: true)
}
section("with image") {
input(type: "contact", name: "contactRequired", title: "This has an image", description: "and a description", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
dynamicPage(name: "inputPage", title: "Every 'input' type") {
section("enum") {
input(type: "enum", name: "enumRefresh", title: "submitOnChange:true", required: false, multiple: true, options: ["one", "two", "three"], submitOnChange: true)
if (enumRefresh) {
paragraph "${enumRefresh}"
}
input(type: "enum", name: "enumSegmented", title: "style:segmented", required: false, multiple: true, options: ["one", "two", "three"], style: "segmented")
input(type: "enum", name: "enum", title: "required:false, multiple:false", required: false, multiple: false, options: ["one", "two", "three"])
input(type: "enum", name: "enumRequired", title: "required:true", required: true, multiple: false, options: ["one", "two", "three"])
input(type: "enum", name: "enumMultiple", title: "multiple:true", required: false, multiple: true, options: ["one", "two", "three"])
input(type: "enum", name: "enumWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, options: ["one", "two", "three"], image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
input(type: "enum", name: "enumWithGroupedOptions", title: "groupedOptions", description: "This enum has grouped options", required: false, multiple: true, groupedOptions: [
[
title : "the group title that is displayed",
order : 0, // the order of the group; 0-based
image : "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", // not yet supported
values: [
[
key : "the value that will be placed in SmartApp settings.", // such as a device id
value: "the title of the selectable option that is displayed", // such as a device name
order: 0 // the order of the option
]
]
],
[
title : "the second group title that is displayed",
order : 1, // the order of the group; 0-based
image : null, // not yet supported
values: [
[
key : "some_device_id",
value: "some_device_name",
order: 1 // the order of the option. This option will appear second in the list even though it is the first option defined in this map
],
[
key : "some_other_device_id",
value: "some_other_device_name",
order: 0 // the order of the option. This option will appear first in the list even though it is not the first option defined in this map
]
]
]
])
}
section("text") {
input(type: "text", name: "text", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "text", name: "textRequired", title: "required:true", required: true, multiple: false)
input(type: "text", name: "textWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("number") {
input(type: "number", name: "number", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "number", name: "numberRequired", title: "required:true", required: true, multiple: false)
input(type: "number", name: "numberWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("boolean") {
input(type: "boolean", name: "boolean", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "boolean", name: "booleanWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("password") {
input(type: "password", name: "password", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "password", name: "passwordRequired", title: "required:true", required: true, multiple: false)
input(type: "password", name: "passwordWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("phone") {
input(type: "phone", name: "phone", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "phone", name: "phoneRequired", title: "required:true", required: true, multiple: false)
input(type: "phone", name: "phoneWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("email") {
input(type: "email", name: "email", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "email", name: "emailRequired", title: "required:true", required: true, multiple: false)
input(type: "email", name: "emailWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("decimal") {
input(type: "decimal", name: "decimal", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "decimal", name: "decimalRequired", title: "required:true", required: true, multiple: false)
input(type: "decimal", name: "decimalWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("mode") {
input(type: "mode", name: "mode", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "mode", name: "modeRequired", title: "required:true", required: true, multiple: false)
input(type: "mode", name: "modeMultiple", title: "multiple:true", required: false, multiple: true)
input(type: "mode", name: "iconWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("icon") {
input(type: "icon", name: "icon", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "icon", name: "iconRequired", title: "required:true", required: true, multiple: false)
input(type: "icon", name: "iconWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("capability") {
input(type: "capability.switch", name: "capability", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "capability.switch", name: "capabilityRequired", title: "required:true", required: true, multiple: false)
input(type: "capability.switch", name: "capabilityMultiple", title: "multiple:true", required: false, multiple: true)
input(type: "capability.switch", name: "capabilityWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("hub") {
input(type: "hub", name: "hub", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "hub", name: "hubRequired", title: "required:true", required: true, multiple: false)
input(type: "hub", name: "hubMultiple", title: "multiple:true", required: false, multiple: true)
input(type: "hub", name: "hubWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("device") {
input(type: "device.switch", name: "device", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "device.switch", name: "deviceRequired", title: "required:true", required: true, multiple: false)
input(type: "device.switch", name: "deviceMultiple", title: "multiple:true", required: false, multiple: true)
input(type: "device.switch", name: "deviceWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("time") {
input(type: "time", name: "time", title: "required:false, multiple:false", required: false, multiple: false)
input(type: "time", name: "timeRequired", title: "required:true", required: true, multiple: false)
input(type: "time", name: "timeWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
section("contact-book") {
input("recipients", "contact", title: "Notify", description: "Send notifications to") {
input(type: "phone", name: "phone", title: "Send text message to", required: false, multiple: false)
input(type: "boolean", name: "boolean", title: "Send push notification", required: false, multiple: false)
}
}
}
}
def appPage() {
dynamicPage(name: "appPage", title: "Every 'app' type") {
section {
paragraph "These won't work unless you create a child SmartApp to link to... Sorry."
}
section("app") {
app(
name: "app",
title: "required:false, multiple:false",
required: false,
multiple: false,
namespace: "Steve",
appName: "Child SmartApp"
)
app(name: "appRequired", title: "required:true", required: true, multiple: false, namespace: "Steve", appName: "Child SmartApp")
app(name: "appComplete", title: "state:complete", required: false, multiple: false, namespace: "Steve", appName: "Child SmartApp", state: "complete")
app(name: "appWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", namespace: "Steve", appName: "Child SmartApp")
}
section("multiple:true") {
app(name: "appMultiple", title: "multiple:true", required: false, multiple: true, namespace: "Steve", appName: "Child SmartApp")
}
section("multiple:true with image") {
app(name: "appMultipleWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", namespace: "Steve", appName: "Child SmartApp")
}
}
dynamicPage(name: "appPage", title: "Every 'app' type") {
section {
paragraph "These won't work unless you create a child SmartApp to link to... Sorry."
}
section("app") {
app(
name: "app",
title: "required:false, multiple:false",
required: false,
multiple: false,
namespace: "Steve",
appName: "Child SmartApp"
)
app(name: "appRequired", title: "required:true", required: true, multiple: false, namespace: "Steve", appName: "Child SmartApp")
app(name: "appComplete", title: "state:complete", required: false, multiple: false, namespace: "Steve", appName: "Child SmartApp", state: "complete")
app(name: "appWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", namespace: "Steve", appName: "Child SmartApp")
}
section("multiple:true") {
app(name: "appMultiple", title: "multiple:true", required: false, multiple: true, namespace: "Steve", appName: "Child SmartApp")
}
section("multiple:true with image") {
app(name: "appMultipleWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", namespace: "Steve", appName: "Child SmartApp")
}
}
}
def labelPage() {
dynamicPage(name: "labelPage", title: "Every 'Label' type") {
section("label") {
paragraph "The difference between a label element and a text input element is that the label element will effect the SmartApp directly by setting the label. An input element will place the set value in the SmartApp's settings."
paragraph "There are 3 here as an example. Never use more than 1 label element on a page."
label(name: "label", title: "required:false, multiple:false", required: false, multiple: false)
label(name: "labelRequired", title: "required:true", required: true, multiple: false)
label(name: "labelWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
dynamicPage(name: "labelPage", title: "Every 'Label' type") {
section("label") {
label(name: "label", title: "required:false, multiple:false", required: false, multiple: false)
label(name: "labelRequired", title: "required:true", required: true, multiple: false)
label(name: "labelWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
}
def modePage() {
dynamicPage(name: "modePage", title: "Every 'mode' type") { // TODO: finish this
section("mode") {
paragraph "The difference between a mode element and a mode input element is that the mode element will effect the SmartApp directly by setting the modes it executes in. A mode input element will place the set value in the SmartApp's settings."
paragraph "Another difference is that you can select 'All Modes' when choosing which mode the SmartApp should execute in. This is the same as selecting no modes. When a SmartApp does not have modes specified, it will execute in all modes."
paragraph "There are 4 here as an example. Never use more than 1 mode element on a page."
mode(name: "mode", title: "required:false, multiple:false", required: false, multiple: false)
mode(name: "modeRequired", title: "required:true", required: true, multiple: false)
mode(name: "modeMultiple", title: "multiple:true", required: false, multiple: true)
mode(name: "modeWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
dynamicPage(name: "modePage", title: "Every 'mode' type") { // TODO: finish this
section("mode") {
mode(name: "mode", title: "required:false, multiple:false", required: false, multiple: false)
mode(name: "modeRequired", title: "required:true", required: true, multiple: false)
mode(name: "modeMultiple", title: "multiple:true", required: false, multiple: true)
mode(name: "modeWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, multiple: true, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
}
def paragraphPage() {
dynamicPage(name: "paragraphPage", title: "Every 'paragraph' type") {
section("paragraph") {
paragraph "This is how you should make a paragraph element"
paragraph image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", "This is a long description, blah, blah, blah."
}
}
dynamicPage(name: "paragraphPage", title: "Every 'paragraph' type") {
section("paragraph") {
paragraph "This us how you should make a paragraph element"
paragraph image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", "This is a long description, blah, blah, blah."
}
}
}
def iconPage() {
dynamicPage(name: "iconPage", title: "Every 'icon' type") { // TODO: finish this
section("icon") {
icon(name: "icon", title: "required:false, multiple:false", required: false, multiple: false)
icon(name: "iconRequired", title: "required:true", required: true, multiple: false)
icon(name: "iconWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
}
def hrefPage() {
dynamicPage(name: "hrefPage", title: "Every 'href' variation") {
section("stylistic differences") {
href(page: "deadEnd", title: "state: 'complete'", description: "gives the appearance of an input that has been filled out", state: "complete")
href(page: "deadEnd", title: "with image", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
href(page: "deadEnd", title: "with image and description", description: "and state: 'complete'", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", state: "complete")
}
section("functional differences") {
href(page: "deadEnd", title: "to a page within the app")
href(url: "http://www.google.com", title: "to a url using all defaults")
href(url: "http://www.google.com", title: "external: true", description: "takes you outside the app", external: true)
}
}
dynamicPage(name: "hrefPage", title: "Every 'href' type") {
section("page") {
href(name: "hrefPage", title: "required:false, multiple:false", required: false, multiple: false, page: "deadEnd")
href(name: "hrefPageRequired", title: "required:true", required: true, multiple: false, page: "deadEnd", description: "Don't make hrefs required")
href(name: "hrefPageComplete", title: "state:complete", required: false, multiple: false, page: "deadEnd", state: "complete")
href(name: "hrefPageWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", page: "deadEnd",)
}
section("external") {
href(name: "hrefExternal", title: "required:false, multiple:false", required: false, multiple: false, style: "external", url: "http://smartthings.com/")
href(name: "hrefExternalRequired", title: "required:true", required: true, multiple: false, style: "external", url: "http://smartthings.com/", description: "Don't make hrefs required")
href(name: "hrefExternalComplete", title: "state:complete", required: false, multiple: true, style: "external", url: "http://smartthings.com/", state: "complete")
href(name: "hrefExternalWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", url: "http://smartthings.com/")
}
section("embedded") {
href(name: "hrefEmbedded", title: "required:false, multiple:false", required: false, multiple: false, style: "embedded", url: "http://smartthings.com/")
href(name: "hrefEmbeddedRequired", title: "required:true", required: true, multiple: false, style: "embedded", url: "http://smartthings.com/", description: "Don't make hrefs required")
href(name: "hrefEmbeddedComplete", title: "state:complete", required: false, multiple: true, style: "embedded", url: "http://smartthings.com/", state: "complete")
href(name: "hrefEmbeddedWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", url: "http://smartthings.com/")
}
}
}
def buttonsPage() {
dynamicPage(name: "buttonsPage", title: "Every 'button' type") {
section("Simple Buttons") {
paragraph "If there are an odd number of buttons, the last button will span the entire view area."
buttons(name: "buttons1", title: "1 button", buttons: [
[label: "foo", action: "foo"]
])
buttons(name: "buttons2", title: "2 buttons", buttons: [
[label: "foo", action: "foo"],
[label: "bar", action: "bar"]
])
buttons(name: "buttons3", title: "3 buttons", buttons: [
[label: "foo", action: "foo"],
[label: "bar", action: "bar"],
[label: "baz", action: "baz"]
])
buttons(name: "buttonsWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", buttons: [
[label: "foo", action: "foo"],
[label: "bar", action: "bar"]
])
}
section("Colored Buttons") {
buttons(name: "buttonsColoredSpecial", title: "special strings", description: "SmartThings highly recommends using these colors", buttons: [
[label: "complete", action: "bar", backgroundColor: "complete"],
[label: "required", action: "bar", backgroundColor: "required"]
])
buttons(name: "buttonsColoredHex", title: "hex values work", buttons: [
[label: "bg: #000dff", action: "foo", backgroundColor: "#000dff"],
[label: "fg: #ffac00", action: "foo", color: "#ffac00"],
[label: "both fg and bg", action: "foo", color: "#ffac00", backgroundColor: "#000dff"]
])
buttons(name: "buttonsColoredString", title: "strings work too", buttons: [
[label: "green", action: "foo", backgroundColor: "green"],
[label: "red", action: "foo", backgroundColor: "red"],
[label: "both fg and bg", action: "foo", color: "red", backgroundColor: "green"]
])
}
}
dynamicPage(name: "buttonsPage", title: "Every 'button' type") {
section("buttons") {
buttons(name: "buttons", title: "required:false, multiple:false", required: false, multiple: false, buttons: [
[label: "foo", action: "foo"],
[label: "bar", action: "bar"]
])
buttons(name: "buttonsRequired", title: "required:true", required: true, multiple: false, buttons: [
[label: "foo", action: "foo"],
[label: "bar", action: "bar"]
])
buttons(name: "buttonsWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", buttons: [
[label: "foo", action: "foo"],
[label: "bar", action: "bar"]
])
}
section("Colored Buttons") {
buttons(name: "buttonsColoredSpecial", title: "special strings", description: "SmartThings highly recommends using these colors", buttons: [
[label: "complete", action: "bar", backgroundColor: "complete"],
[label: "required", action: "bar", backgroundColor: "required"]
])
buttons(name: "buttonsColoredHex", title: "hex values work", buttons: [
[label: "bg: #000dff", action: "foo", backgroundColor: "#000dff"],
[label: "fg: #ffac00", action: "foo", color: "#ffac00"],
[label: "both fg and bg", action: "foo", color: "#ffac00", backgroundColor: "#000dff"]
])
buttons(name: "buttonsColoredString", title: "strings work too", buttons: [
[label: "green", action: "foo", backgroundColor: "green"],
[label: "red", action: "foo", backgroundColor: "red"],
[label: "both fg and bg", action: "foo", color: "red", backgroundColor: "green"]
])
}
}
}
def imagePage() {
dynamicPage(name: "imagePage", title: "Every 'image' type") { // TODO: finish thise
section("image") {
image "http://f.cl.ly/items/1k1S0A0m3805402o3O12/20130915-191127.jpg"
image(name: "imageWithMultipleImages", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, images: ["https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", "http://f.cl.ly/items/1k1S0A0m3805402o3O12/20130915-191127.jpg"])
}
}
dynamicPage(name: "imagePage", title: "Every 'image' type") { // TODO: finish thise
section("image") {
image "http://f.cl.ly/items/1k1S0A0m3805402o3O12/20130915-191127.jpg"
image(name: "imageWithImage", title: "This element has an image and a long title.", description: "I am setting long title and descriptions to test the offset", required: false, image: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png")
}
}
}
def videoPage() {
dynamicPage(name: "videoPage", title: "Every 'video' type") { // TODO: finish this
section("video") {
// TODO: update this when there is a videoElement method
element(name: "videoElement", element: "video", type: "video", title: "this is a video!", description: "I am setting long title and descriptions to test the offset", required: false, image: "http://f.cl.ly/items/0w0D1p0K2D0d190F3H3N/Image%202015-12-14%20at%207.57.27%20AM.jpg", video: "http://f.cl.ly/items/3O2L03471l2K3E3l3K1r/Zombie%20Kid%20Likes%20Turtles.mp4")
}
}
dynamicPage(name: "imagePage", title: "Every 'image' type") { // TODO: finish this
section("video") {
// TODO: update this when there is a videoElement method
element(name: "videoElement", element: "video", type: "video", title: "this is a video!", description: "I am setting long title and descriptions to test the offset", required: false, image: "http://ec2-54-161-144-215.compute-1.amazonaws.com:8081/jesse/cam1/54aafcd1c198347511c26321.jpg", video: "http://ec2-54-161-144-215.compute-1.amazonaws.com:8081/jesse/cam1/54aafcd1c198347511c2631f.mp4")
}
}
}
def flattenedPage() {
def allSections = []
firstPage().sections[0].body.each { hrefElement ->
if (hrefElement.name != "inputPage") {
// inputPage is a bunch of hrefs
allSections += "${hrefElement.page}"().sections
}
}
// collect the input elements
inputPage().sections.each { section ->
section.body.each { hrefElement ->
allSections += "${hrefElement.page}"().sections
}
}
def flattenedPage = dynamicPage(name: "flattenedPage", title: "All elements in one page!") {}
flattenedPage.sections = allSections
return flattenedPage
def allSections = []
firstPage().sections.each { section ->
section.body.each { hrefElement ->
if (hrefElement.page != "flattenedPage") {
allSections += "${hrefElement.page}"().sections
}
}
}
def flattenedPage = dynamicPage(name: "flattenedPage", title: "All elements in one page!") {}
flattenedPage.sections = allSections
return flattenedPage
}
def foo() {
dynamicPage(name: "deadEnd") {
section { }
}
dynamicPage(name: "deadEnd") {
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
log.debug "Installed with settings: ${settings}"
initialize()
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
unsubscribe()
initialize()
}
def initialize() {
// TODO: subscribe to attributes, devices, locations, etc.
// TODO: subscribe to attributes, devices, locations, etc.
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2016 SmartThings
* 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:
@@ -38,75 +38,37 @@ preferences {
page(name: "schedulingPage")
page(name: "completionPage")
page(name: "numbersPage")
page(name: "controllerExplanationPage")
}
def rootPage() {
dynamicPage(name: "rootPage", title: "", install: true, uninstall: true) {
section("What to dim") {
section {
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
if (dimmers) {
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
}
}
if (dimmers) {
section("Gentle Wake Up Has A Controller") {
href(title: "Learn how to control Gentle Wake Up", page: "controllerExplanationPage", description: null)
section {
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
}
section("Rules For Dimming") {
href(name: "toSchedulingPage", page: "schedulingPage", title: "Automation", description: schedulingHrefDescription() ?: "Set rules for when to start", state: schedulingHrefDescription() ? "complete" : "")
input(name: "manualOverride", type: "enum", options: ["cancel": "Cancel dimming", "jumpTo": "Jump to the end"], title: "When one of the dimmers is manually turned off…", description: "dimming will continue", required: false, multiple: false)
href(name: "toCompletionPage", title: "Completion Actions", page: "completionPage", state: completionHrefDescription() ? "complete" : "", description: completionHrefDescription() ?: "Set rules for what to do when dimming completes")
section {
href(name: "toSchedulingPage", page: "schedulingPage", title: "Rules For Automatically Dimming Your Lights", description: schedulingHrefDescription(), state: schedulingHrefDescription() ? "complete" : "")
}
section {
href(name: "toCompletionPage", title: "Completion Actions (Optional)", page: "completionPage", state: completionHrefDescription() ? "complete" : "", description: completionHrefDescription())
}
section {
// TODO: fancy label
label(title: "Label This SmartApp", required: false, defaultValue: "", description: "Highly recommended", submitOnChange: true)
label(title: "Label this SmartApp", required: false, defaultValue: "")
}
}
}
}
def controllerExplanationPage() {
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
section("With other SmartApps", hideable: true, hidden: false) {
paragraph "When this SmartApp is installed, it will create a controller device which you can use in other SmartApps for even more customizable automation!"
paragraph "The controller acts like a switch so any SmartApp that can control a switch can control Gentle Wake Up, too!"
paragraph "Routines and 'Smart Lighting' are great ways to automate Gentle Wake Up."
}
section("More about the controller", hideable: true, hidden: true) {
paragraph "You can find the controller with your other 'Things'. It will look like this."
image "http://f.cl.ly/items/2O0v0h41301U14042z3i/GentleWakeUpController-tile-stopped.png"
paragraph "You can start and stop Gentle Wake up by tapping the control on the right."
image "http://f.cl.ly/items/3W323J3M1b3K0k0V3X3a/GentleWakeUpController-tile-running.png"
paragraph "If you look at the device details screen, you will find even more information about Gentle Wake Up and more fine grain controls."
image "http://f.cl.ly/items/291s3z2I2Q0r2q0x171H/GentleWakeUpController-richTile-stopped.png"
paragraph "The slider allows you to jump to any point in the dimming process. Think of it as a percentage. If Gentle Wake Up is set to dim down as you fall asleep, but your book is just too good to put down; simply drag the slider to the left and Gentle Wake Up will give you more time to finish your chapter and drift off to sleep."
image "http://f.cl.ly/items/0F0N2G0S3v1q0L0R3J3Y/GentleWakeUpController-richTile-running.png"
paragraph "In the lower left, you will see the amount of time remaining in the dimming cycle. It does not count down evenly. Instead, it will update whenever the slider is updated; typically every 6-18 seconds depending on the duration of your dimming cycle."
paragraph "Of course, you may also tap the middle to start or stop the dimming cycle at any time."
}
section("Starting and stopping the SmartApp itself", hideable: true, hidden: true) {
paragraph "Tap the 'play' button on the SmartApp to start or stop dimming."
image "http://f.cl.ly/items/0R2u1Z2H30393z2I2V3S/GentleWakeUp-appTouch2.png"
}
section("Turning off devices while dimming", hideable: true, hidden: true) {
paragraph "It's best to use other Devices and SmartApps for triggering the Controller device. However, that isn't always an option."
paragraph "If you turn off a switch that is being dimmed, it will either continue to dim, stop dimming, or jump to the end of the dimming cycle depending on your settings."
paragraph "Unfortunately, some switches take a little time to turn off and may not finish turning off before Gentle Wake Up sets its dim level again. You may need to try a few times to get it to stop."
paragraph "That's why it's best to use devices that aren't currently dimming. Remember that you can use other SmartApps to toggle the controller. :)"
}
}
}
def numbersPage() {
dynamicPage(name:"numbersPage", title:"") {
@@ -166,33 +128,24 @@ def endLevelLabel() {
return "${endLevel}%"
}
def weekdays() {
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}
def weekends() {
["Saturday", "Sunday"]
}
def schedulingPage() {
dynamicPage(name: "schedulingPage", title: "Rules For Automatically Dimming Your Lights") {
section("Use Other SmartApps!") {
href(title: "Learn how to control Gentle Wake Up", page: "controllerExplanationPage", description: null)
section {
input(name: "days", type: "enum", title: "Allow Automatic Dimming On These Days", description: "Every day", required: false, multiple: true, options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"])
}
section("Allow Automatic Dimming") {
input(name: "days", type: "enum", title: "On These Days", description: "Every day", required: false, multiple: true, options: weekdays() + weekends())
}
section("Start Dimming...") {
input(name: "startTime", type: "time", title: "At This Time", description: null, required: false)
input(name: "modeStart", title: "When Entering This Mode", type: "mode", required: false, mutliple: false, submitOnChange: true, description: null)
section {
input(name: "modeStart", title: "Start when entering this mode", type: "mode", required: false, mutliple: false, submitOnChange: true)
if (modeStart) {
input(name: "modeStop", title: "Stop when leaving '${modeStart}' mode", type: "bool", required: false)
}
}
section {
input(name: "startTime", type: "time", title: "Start Dimming At This Time", description: null, required: false)
}
}
}
@@ -241,16 +194,11 @@ def updated() {
log.debug "Updating 'Gentle Wake Up' with settings: ${settings}"
unschedule()
def controller = getController()
if (controller) {
controller.label = app.label
}
initialize()
}
private initialize() {
stop("settingsChange")
stop()
if (startTime) {
log.debug "scheduling dimming routine to run at $startTime"
@@ -261,27 +209,15 @@ private initialize() {
subscribe(app, appHandler)
subscribe(location, locationHandler)
if (manualOverride) {
subscribe(dimmers, "switch.off", stopDimmersHandler)
}
if (!getAllChildDevices()) {
// create controller device and set name to the label used here
def dni = "${new Date().getTime()}"
log.debug "app.label: ${app.label}"
addChildDevice("smartthings", "Gentle Wake Up Controller", dni, null, ["label": app.label])
state.controllerDni = dni
}
}
def appHandler(evt) {
log.debug "appHandler evt: ${evt.value}"
if (evt.value == "touch") {
if (atomicState.running) {
stop("appTouch")
stop()
} else {
start("appTouch")
start()
}
}
}
@@ -297,47 +233,26 @@ def locationHandler(evt) {
def modeStopIsTrue = (modeStop && modeStop != "false")
if (isSpecifiedMode && canStartAutomatically()) {
start("modeChange")
start()
} else if (!isSpecifiedMode && modeStopIsTrue) {
stop("modeChange")
stop()
}
}
def stopDimmersHandler(evt) {
log.trace "stopDimmersHandler evt: ${evt.value}"
def percentComplete = completionPercentage()
// Often times, the first thing we do is turn lights on or off so make sure we don't stop as soon as we start
if (percentComplete > 2 && percentComplete < 98) {
if (manualOverride == "cancel") {
log.debug "STOPPING in stopDimmersHandler"
stop("manualOverride")
} else if (manualOverride == "jumpTo") {
def end = dynamicEndLevel()
log.debug "Jumping to 99% complete in stopDimmersHandler"
jumpTo(99)
}
} else {
log.debug "not stopping in stopDimmersHandler"
}
}
// ========================================================
// Scheduling
// ========================================================
def scheduledStart() {
if (canStartAutomatically()) {
start("schedule")
start()
}
}
public def start(source) {
def start() {
log.trace "START"
sendStartEvent(source)
setLevelsInState()
atomicState.running = true
@@ -348,11 +263,9 @@ public def start(source) {
increment()
}
public def stop(source) {
def stop() {
log.trace "STOP"
sendStopEvent(source)
atomicState.running = false
atomicState.start = 0
@@ -369,110 +282,6 @@ private healthCheck() {
increment()
}
// ========================================================
// Controller
// ========================================================
def sendStartEvent(source) {
log.trace "sendStartEvent(${source})"
def eventData = [
name: "sessionStatus",
value: "running",
descriptionText: "${app.label} has started dimming",
displayed: true,
linkText: app.label,
isStateChange: true
]
if (source == "modeChange") {
eventData.descriptionText += " because of a mode change"
} else if (source == "schedule") {
eventData.descriptionText += " as scheduled"
} else if (source == "appTouch") {
eventData.descriptionText += " because you pressed play on the app"
} else if (source == "controller") {
eventData.descriptionText += " because you pressed play on the controller"
}
sendControllerEvent(eventData)
}
def sendStopEvent(source) {
log.trace "sendStopEvent(${source})"
def eventData = [
name: "sessionStatus",
value: "stopped",
descriptionText: "${app.label} has stopped dimming",
displayed: true,
linkText: app.label,
isStateChange: true
]
if (source == "modeChange") {
eventData.descriptionText += " because of a mode change"
eventData.value += "cancelled"
} else if (source == "schedule") {
eventData.descriptionText = "${app.label} has finished dimming"
} else if (source == "appTouch") {
eventData.descriptionText += " because you pressed play on the app"
eventData.value += "cancelled"
} else if (source == "controller") {
eventData.descriptionText += " because you pressed stop on the controller"
eventData.value += "cancelled"
} else if (source == "settingsChange") {
eventData.descriptionText += " because the settings have changed"
eventData.value += "cancelled"
} else if (source == "manualOverride") {
eventData.descriptionText += " because the dimmer was manually turned off"
eventData.value += "cancelled"
}
sendControllerEvent(eventData)
sendTimeRemainingEvent(0)
}
def sendTimeRemainingEvent(percentComplete) {
log.trace "sendTimeRemainingEvent(${percentComplete})"
def percentCompleteEventData = [
name: "percentComplete",
value: percentComplete as int,
displayed: true,
isStateChange: true
]
sendControllerEvent(percentCompleteEventData)
def duration = sanitizeInt(duration, 30)
def timeRemaining = duration - (duration * (percentComplete / 100))
def timeRemainingEventData = [
name: "timeRemaining",
value: displayableTime(timeRemaining),
displayed: true,
isStateChange: true
]
sendControllerEvent(timeRemainingEventData)
}
def sendControllerEvent(eventData) {
def controller = getController()
if (controller) {
controller.controllerEvent(eventData)
}
}
def getController() {
def dni = state.controllerDni
if (!dni) {
log.warn "no controller dni"
return null
}
def controller = getChildDevice(dni)
if (!controller) {
log.warn "no controller"
return null
}
log.debug "controller: ${controller}"
return controller
}
// ========================================================
// Setting levels
// ========================================================
@@ -540,8 +349,6 @@ def updateDimmers(percentComplete) {
}
}
sendTimeRemainingEvent(percentComplete)
}
int dynamicLevel(dimmer, percentComplete) {
@@ -570,7 +377,7 @@ private completion() {
return
}
stop("schedule")
stop()
handleCompletionSwitches()
@@ -578,7 +385,6 @@ private completion() {
handleCompletionModesAndPhrases()
sendTimeRemainingEvent(100)
}
private handleCompletionSwitches() {
@@ -687,65 +493,22 @@ def completionPercentage() {
return
}
def now = new Date().getTime()
def timeElapsed = now - atomicState.start
def totalRunTime = totalRunTimeMillis()
def percentComplete = timeElapsed / totalRunTime * 100
log.debug "percentComplete: ${percentComplete}"
int now = new Date().getTime()
int diff = now - atomicState.start
int totalRunTime = totalRunTimeMillis()
int percentOfRunTime = (diff / totalRunTime) * 100
log.debug "percentOfRunTime: ${percentOfRunTime}"
return percentComplete
percentOfRunTime
}
int totalRunTimeMillis() {
int minutes = sanitizeInt(duration, 30)
convertToMillis(minutes)
}
int convertToMillis(minutes) {
def seconds = minutes * 60
def millis = seconds * 1000
return millis
return millis as int
}
def timeRemaining(percentComplete) {
def normalizedPercentComplete = percentComplete / 100
def duration = sanitizeInt(duration, 30)
def timeElapsed = duration * normalizedPercentComplete
def timeRemaining = duration - timeElapsed
return timeRemaining
}
int millisToEnd(percentComplete) {
convertToMillis(timeRemaining(percentComplete))
}
String displayableTime(timeRemaining) {
def timeString = "${timeRemaining}"
def parts = timeString.split(/\./)
if (!parts.size()) {
return "0:00"
}
def minutes = parts[0]
if (parts.size() == 1) {
return "${minutes}:00"
}
def fraction = "0.${parts[1]}" as double
def seconds = "${60 * fraction as int}".padRight(2, "0")
return "${minutes}:${seconds}"
}
def jumpTo(percentComplete) {
def millisToEnd = millisToEnd(percentComplete)
def endTime = new Date().getTime() + millisToEnd
def duration = sanitizeInt(duration, 30)
def durationMillis = convertToMillis(duration)
def shiftedStart = endTime - durationMillis
atomicState.start = shiftedStart
updateDimmers(percentComplete)
sendTimeRemainingEvent(percentComplete)
}
int dynamicEndLevel() {
if (usesOldSettings()) {
if (direction && direction == "Down") {
@@ -910,13 +673,7 @@ def schedulingHrefDescription() {
def descriptionParts = []
if (days) {
if (days == weekdays()) {
descriptionParts << "On weekdays,"
} else if (days == weekends()) {
descriptionParts << "On weekends,"
} else {
descriptionParts << "On ${fancyString(days)},"
}
descriptionParts << "On ${fancyString(days)},"
}
descriptionParts << "${fancyDeviceString(dimmers)} will start dimming"
@@ -1002,15 +759,15 @@ def completionHrefDescription() {
def numbersPageHrefDescription() {
def title = "All dimmers will dim for ${duration ?: '30'} minutes from ${startLevelLabel()} to ${endLevelLabel()}"
if (colorize) {
def colorDimmers = dimmersWithSetColorCommand()
if (colorDimmers == dimmers) {
title += " and will gradually change color."
} else {
title += ".\n${fancyDeviceString(colorDimmers)} will gradually change color."
}
}
return title
if (colorize) {
def colorDimmers = dimmersWithSetColorCommand()
if (colorDimmers == dimmers) {
title += " and will gradually change color."
} else {
title += ".\n${fancyDeviceString(colorDimmers)} will gradually change color."
}
}
return title
}
def hueSatToHex(h, s) {

View File

@@ -258,8 +258,8 @@ def installed() {
def updated() {
log.trace "Updated with settings: ${settings}"
unschedule()
unsubscribe()
unschedule()
initialize()
}
@@ -325,8 +325,6 @@ def addBulbs() {
} else {
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
}
log.debug "created ${d.displayName} with id $dni"
d.refresh()
} else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
}
@@ -334,8 +332,10 @@ def addBulbs() {
//backwards compatable
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
d.refresh()
}
log.debug "created ${d.displayName} with id $dni"
d.refresh()
} else {
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
if (bulbs instanceof java.util.Map) {

View File

@@ -592,7 +592,7 @@ def updated() {
// log.debug "External Id=${app.id}:${member.id}"
// create the device
def childDevice = addChildDevice("smartthings", "Life360 User", "${app.id}.${member.id}",null,[name:member.firstName, completedSetup: true])
def childDevice = addChildDevice("smartthings", "life360-user", "${app.id}.${member.id}",null,[name:member.firstName, completedSetup: true])
// childDevice.setMemberId(member.id)
if (childDevice)

View File

@@ -5,23 +5,23 @@
*
*/
definition(
name: "LIFX (Connect)",
namespace: "smartthings",
author: "LIFX",
description: "Allows you to use LIFX smart light bulbs with SmartThings.",
category: "Convenience",
iconUrl: "https://cloud.lifx.com/images/lifx.png",
iconX2Url: "https://cloud.lifx.com/images/lifx.png",
iconX3Url: "https://cloud.lifx.com/images/lifx.png",
oauth: true,
singleInstance: true) {
appSetting "clientId"
appSetting "clientSecret"
}
name: "LIFX (Connect)",
namespace: "smartthings",
author: "LIFX",
description: "Allows you to use LIFX smart light bulbs with SmartThings.",
category: "Convenience",
iconUrl: "https://cloud.lifx.com/images/lifx.png",
iconX2Url: "https://cloud.lifx.com/images/lifx.png",
iconX3Url: "https://cloud.lifx.com/images/lifx.png",
oauth: true,
singleInstance: true) {
appSetting "clientId"
appSetting "clientSecret"
}
preferences {
page(name: "Credentials", title: "LIFX", content: "authPage", install: true)
page(name: "Credentials", title: "LIFX", content: "authPage", install: false)
}
mappings {
@@ -33,29 +33,29 @@ mappings {
path("/test") { action: [ GET: "oauthSuccess" ] }
}
def getServerUrl() { return "https://graph.api.smartthings.com" }
def getCallbackUrl() { return "https://graph.api.smartthings.com/oauth/callback"}
def apiURL(path = '/') { return "https://api.lifx.com/v1${path}" }
def getSecretKey() { return appSettings.secretKey }
def getClientId() { return appSettings.clientId }
def getServerUrl() { return "https://graph.api.smartthings.com" }
def apiURL(path = '/') { return "https://api.lifx.com/v1beta1${path}" }
def buildRedirectUrl(page) {
return "${serverUrl}/api/token/${state.accessToken}/smartapps/installations/${app.id}/${page}"
}
def authPage() {
log.debug "authPage test1"
log.debug "authPage"
if (!state.lifxAccessToken) {
log.debug "no LIFX access token"
// This is the SmartThings access token
if (!state.accessToken) {
log.debug "no access token, create access token"
state.accessToken = createAccessToken() // predefined method
createAccessToken() // predefined method
}
def description = "Tap to enter LIFX credentials"
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" // this triggers oauthInit() below
// def redirectUrl = "${apiServerUrl}"
log.debug "app id: ${app.id}"
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}" // this triggers oauthInit() below
log.debug "app id: ${app.id}"
log.debug "redirect url: ${redirectUrl}"
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) {
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:false) {
section {
href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account")
// href(url:buildRedirectUrl("test"), title: "Message test")
}
}
} else {
@@ -63,15 +63,17 @@ def authPage() {
def options = locationOptions() ?: []
def count = options.size()
def refreshInterval = 3
return dynamicPage(name:"Credentials", title:"", nextPage:"", install:true, uninstall: true) {
return dynamicPage(name:"Credentials", title:"Select devices...", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Select your location") {
input "selectedLocationId", "enum", required:true, title:"Select location (${count} found)", multiple:false, options:options, submitOnChange: true
input "selectedLocationId", "enum", required:true, title:"Select location (${count} found)", multiple:false, options:options
}
}
}
}
// OAuth
def oauthInit() {
@@ -110,7 +112,7 @@ def oauthCallback() {
}
def oauthReceiveToken(redirectUrl = null) {
// Not sure what redirectUrl is for
log.debug "receiveToken - params: ${params}"
def oauthParams = [ client_id: "${appSettings.clientId}", client_secret: "${appSettings.clientSecret}", grant_type: "authorization_code", code: params.code, scope: params.scope ] // how is params.code valid here?
def params = [
@@ -133,25 +135,25 @@ def oauthReceiveToken(redirectUrl = null) {
def oauthSuccess() {
def message = """
<p>Your LIFX Account is now connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p>
"""
<p>Your LIFX Account is now connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p>
"""
oauthConnectionStatus(message)
}
def oauthFailure() {
def message = """
<p>The connection could not be established!</p>
<p>Click 'Done' to return to the menu.</p>
"""
<p>The connection could not be established!</p>
<p>Click 'Done' to return to the menu.</p>
"""
oauthConnectionStatus(message)
}
def oauthReceivedToken() {
def message = """
<p>Your LIFX Account is already connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p>
"""
<p>Your LIFX Account is already connected to SmartThings!</p>
<p>Click 'Done' to finish setup.</p>
"""
oauthConnectionStatus(message)
}
@@ -159,74 +161,74 @@ def oauthConnectionStatus(message, redirectUrl = null) {
def redirectHtml = ""
if (redirectUrl) {
redirectHtml = """
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
"""
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
"""
}
def html = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<title>SmartThings Connection</title>
<style type="text/css">
@font-face {
font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal;
font-style: normal;
}
.container {
width: 280;
padding: 20px;
text-align: center;
}
img {
vertical-align: middle;
}
img:nth-child(2) {
margin: 0 15px;
}
p {
font-size: 1.2em;
font-family: 'Swiss 721 W01 Thin';
text-align: center;
color: #666666;
padding: 0 20px;
margin-bottom: 0;
}
span {
font-family: 'Swiss 721 W01 Light';
}
</style>
${redirectHtml}
</head>
<body>
<div class="container">
<img src='https://cloud.lifx.com/images/lifx.png' alt='LIFX icon' width='100'/>
<img src='https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png' alt='connected device icon' width="40"/>
<img src='https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png' alt='SmartThings logo' width="100"/>
<p>
${message}
</p>
</div>
</body>
</html>
"""
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<title>SmartThings Connection</title>
<style type="text/css">
@font-face {
font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal;
font-style: normal;
}
.container {
width: 280;
padding: 20px;
text-align: center;
}
img {
vertical-align: middle;
}
img:nth-child(2) {
margin: 0 15px;
}
p {
font-size: 1.2em;
font-family: 'Swiss 721 W01 Thin';
text-align: center;
color: #666666;
padding: 0 20px;
margin-bottom: 0;
}
span {
font-family: 'Swiss 721 W01 Light';
}
</style>
${redirectHtml}
</head>
<body>
<div class="container">
<img src='https://cloud.lifx.com/images/lifx.png' alt='LIFX icon' width='100'/>
<img src='https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png' alt='connected device icon' width="40"/>
<img src='https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png' alt='SmartThings logo' width="100"/>
<p>
${message}
</p>
</div>
</body>
</html>
"""
render contentType: 'text/html', data: html
}
@@ -237,6 +239,7 @@ String toQueryString(Map m) {
// App lifecycle hooks
def installed() {
enableCallback() // wtf does this do?
if (!state.accessToken) {
createAccessToken()
} else {
@@ -248,6 +251,7 @@ def installed() {
// called after settings are changed
def updated() {
enableCallback() // not sure what this does
if (!state.accessToken) {
createAccessToken()
} else {
@@ -301,36 +305,27 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) {
state.remove("lifxAccessToken")
options.logObject.warn "Access token is not valid"
}
return options.errorReturn
return options.errerReturn
} catch (java.net.SocketTimeoutException e) {
options.logObject.warn "Connection timed out, not much we can do here"
return options.errorReturn
return options.errerReturn
}
}
def apiGET(path) {
try {
httpGet(uri: apiURL(path), headers: apiRequestHeaders()) {response ->
logResponse(response)
return response
}
} catch (groovyx.net.http.HttpResponseException e) {
logResponse(e.response)
return e.response
httpGet(uri: apiURL(path), headers: apiRequestHeaders()) {response ->
logResponse(response)
return response
}
}
def apiPUT(path, body = [:]) {
try {
log.debug("Beginning API PUT: ${path}, ${body}")
httpPutJson(uri: apiURL(path), body: new groovy.json.JsonBuilder(body).toString(), headers: apiRequestHeaders(), ) {response ->
logResponse(response)
return response
}
} catch (groovyx.net.http.HttpResponseException e) {
logResponse(e.response)
return e.response
}}
log.debug("Beginning API PUT: ${path}, ${body}")
httpPutJson(uri: apiURL(path), body: new groovy.json.JsonBuilder(body).toString(), headers: apiRequestHeaders(), ) {response ->
logResponse(response)
return response
}
}
def devicesList(selector = '') {
logErrors([]) {
@@ -345,12 +340,12 @@ def devicesList(selector = '') {
}
Map locationOptions() {
def options = [:]
def devices = devicesList()
devices.each { device ->
options[device.location.id] = device.location.name
}
log.debug("Locations: ${options}")
return options
}
@@ -364,32 +359,28 @@ def updateDevices() {
state.devices = [:]
}
def devices = devicesInLocation()
def selectors = []
log.debug("All selectors: ${selectors}")
def deviceIds = devices*.id
devices.each { device ->
def childDevice = getChildDevice(device.id)
selectors.add("${device.id}")
if (!childDevice) {
log.info("Adding device ${device.id}: ${device.product}")
log.info("Adding device ${device.id}: ${device.capabilities}")
def data = [
label: device.label,
level: Math.round((device.brightness ?: 1) * 100),
level: sprintf("%f", (device.brightness ?: 1) * 100),
switch: device.connected ? device.power : "unreachable",
colorTemperature: device.color.kelvin
]
if (device.product.capabilities.has_color) {
if (device.capabilities.has_color) {
data["color"] = colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int)
data["hue"] = device.color.hue / 3.6
data["saturation"] = device.color.saturation * 100
childDevice = addChildDevice(app.namespace, "LIFX Color Bulb", device.id, null, data)
childDevice = addChildDevice("smartthings", "LIFX Color Bulb", device.id, null, data)
} else {
childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, data)
childDevice = addChildDevice("smartthings", "LIFX White Bulb", device.id, null, data)
}
}
}
getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each {
getChildDevices().findAll { !deviceIds.contains(it.deviceNetworkId) }.each {
log.info("Deleting ${it.deviceNetworkId}")
deleteChildDevice(it.deviceNetworkId)
}
@@ -401,4 +392,4 @@ def refreshDevices() {
getChildDevices().each { device ->
device.refresh()
}
}
}

View File

@@ -49,7 +49,6 @@ preferences {
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"]
}
}

View File

@@ -316,40 +316,60 @@ private def parseEventMessage(String description) {
parts.each { part ->
part = part.trim()
if (part.startsWith('devicetype:')) {
part -= "devicetype:"
event.devicetype = part.trim()
def valueString = part.split(":")[1].trim()
event.devicetype = valueString
}
else if (part.startsWith('mac:')) {
part -= "mac:"
event.mac = part.trim()
def valueString = part.split(":")[1].trim()
if (valueString) {
event.mac = valueString
}
}
else if (part.startsWith('networkAddress:')) {
part -= "networkAddress:"
event.ip = part.trim()
def valueString = part.split(":")[1].trim()
if (valueString) {
event.ip = valueString
}
}
else if (part.startsWith('deviceAddress:')) {
part -= "deviceAddress:"
event.port = part.trim()
def valueString = part.split(":")[1].trim()
if (valueString) {
event.port = valueString
}
}
else if (part.startsWith('ssdpPath:')) {
part -= "ssdpPath:"
event.ssdpPath = part.trim()
def valueString = part.split(":")[1].trim()
if (valueString) {
event.ssdpPath = valueString
}
}
else if (part.startsWith('ssdpUSN:')) {
part -= "ssdpUSN:"
event.ssdpUSN = part.trim()
def valueString = part.trim()
if (valueString) {
event.ssdpUSN = valueString
}
}
else if (part.startsWith('ssdpTerm:')) {
part -= "ssdpTerm:"
event.ssdpTerm = part.trim()
def valueString = part.trim()
if (valueString) {
event.ssdpTerm = valueString
}
}
else if (part.startsWith('headers')) {
part -= "headers:"
event.headers = part.trim()
def valueString = part.trim()
if (valueString) {
event.headers = valueString
}
}
else if (part.startsWith('body')) {
part -= "body:"
event.body = part.trim()
def valueString = part.trim()
if (valueString) {
event.body = valueString
}
}
}
event

View File

@@ -10,24 +10,24 @@
* 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.
*
* Speaker Control
* Sonos Control
*
* Author: SmartThings
*
* Date: 2013-12-10
*/
definition(
name: "Speaker Control",
name: "Sonos Control",
namespace: "smartthings",
author: "SmartThings",
description: "Play or pause your Speaker when certain actions take place in your home.",
description: "Play or pause your Sonos when certain actions take place in your home.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
)
preferences {
page(name: "mainPage", title: "Control your Speaker when something happens", install: true, uninstall: true)
page(name: "mainPage", title: "Control your Sonos when something happens", install: true, uninstall: true)
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
input "starting", "time", title: "Starting", required: false
@@ -81,7 +81,7 @@ def mainPage() {
]
}
section {
input "sonos", "capability.musicPlayer", title: "Speaker music player", required: true
input "sonos", "capability.musicPlayer", title: "Sonos music player", required: true
}
section("More options", hideable: true, hidden: true) {
input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false

View File

@@ -10,7 +10,7 @@
* 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.
*
* Speaker Mood Music
* Sonos Mood Music
*
* Author: SmartThings
* Date: 2014-02-12
@@ -65,7 +65,7 @@ private saveSelectedSong() {
}
definition(
name: "Speaker Mood Music",
name: "Sonos Mood Music",
namespace: "smartthings",
author: "SmartThings",
description: "Plays a selected song or station.",
@@ -75,7 +75,7 @@ definition(
)
preferences {
page(name: "mainPage", title: "Play a selected song or station on your Speaker when something happens", nextPage: "chooseTrack", uninstall: true)
page(name: "mainPage", title: "Play a selected song or station on your Sonos when something happens", nextPage: "chooseTrack", uninstall: true)
page(name: "chooseTrack", title: "Select a song", install: true)
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
@@ -125,7 +125,7 @@ def mainPage() {
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
}
section {
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
}
section("More options", hideable: true, hidden: true) {
input "volume", "number", title: "Set the volume", description: "0-100%", required: false

View File

@@ -10,23 +10,23 @@
* 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.
*
* Speaker Custom Message
* Sonos Custom Message
*
* Author: SmartThings
* Date: 2014-1-29
*/
definition(
name: "Speaker Notify with Sound",
name: "Sonos Notify with Sound",
namespace: "smartthings",
author: "SmartThings",
description: "Play a sound or custom message through your Speaker when the mode changes or other events occur.",
description: "Play a sound or custom message through your Sonos when the mode changes or other events occur.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
)
preferences {
page(name: "mainPage", title: "Play a message on your Speaker when something happens", install: true, uninstall: true)
page(name: "mainPage", title: "Play a message on your Sonos when something happens", install: true, uninstall: true)
page(name: "chooseTrack", title: "Select a song or station")
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
@@ -75,8 +75,8 @@ def mainPage() {
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
}
section{
input "actionType", "enum", title: "Action?", required: true, defaultValue: "Bell 1", options: [
//"Custom Message",
input "actionType", "enum", title: "Action?", required: true, defaultValue: "Custom Message", options: [
"Custom Message",
"Bell 1",
"Bell 2",
"Dogs Barking",
@@ -89,10 +89,10 @@ def mainPage() {
"Someone is arriving",
"Piano",
"Lightsaber"]
//input "message","text",title:"Play this message", required:false, multiple: false
input "message","text",title:"Play this message", required:false, multiple: false
}
section {
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
}
section("More options", hideable: true, hidden: true) {
input "resumePlaying", "bool", title: "Resume currently playing music after notification", required: false, defaultValue: true
@@ -409,13 +409,12 @@ private loadText() {
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/lightsaber.mp3", duration: "10"]
break;
default:
/*if (message) {
if (message) {
state.sound = textToSpeech(message instanceof List ? message[0] : message) // not sure why this is (sometimes) needed)
}
else {
state.sound = textToSpeech("You selected the custom message option but did not enter a message in the $app.label Smart App")
}*/
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/bell1.mp3", duration: "10"]
}
break;
}
}

View File

@@ -10,23 +10,23 @@
* 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.
*
* Speaker Weather Forecast
* Sonos Weather Forecast
*
* Author: SmartThings
* Date: 2014-1-29
*/
definition(
name: "Speaker Weather Forecast",
name: "Sonos Weather Forecast",
namespace: "smartthings",
author: "SmartThings",
description: "Play a weather report through your Speaker when the mode changes or other events occur",
description: "Play a weather report through your Sonos when the mode changes or other events occur",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
)
preferences {
page(name: "mainPage", title: "Play the weather report on your speaker", install: true, uninstall: true)
page(name: "mainPage", title: "Play the weather report on your sonos", install: true, uninstall: true)
page(name: "chooseTrack", title: "Select a song or station")
page(name: "timeIntervalInput", title: "Only during a certain time") {
section {
@@ -85,7 +85,7 @@ def mainPage() {
)
}
section {
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
}
section("More options", hideable: true, hidden: true) {
input "resumePlaying", "bool", title: "Resume currently playing music after weather report finishes", required: false, defaultValue: true

View File

@@ -78,7 +78,7 @@ def firstPage()
def motionsDiscovered = motionsDiscovered()
def lightSwitchesDiscovered = lightSwitchesDiscovered()
return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: selectedSwitches != null || selectedMotions != null || selectedLightSwitches != null) {
section("Select a device...") {
input "selectedSwitches", "enum", required:false, title:"Select Wemo Switches \n(${switchesDiscovered.size() ?: 0} found)", multiple:true, options:switchesDiscovered
input "selectedMotions", "enum", required:false, title:"Select Wemo Motions \n(${motionsDiscovered.size() ?: 0} found)", multiple:true, options:motionsDiscovered
@@ -473,48 +473,68 @@ private def parseXmlBody(def body) {
}
private def parseDiscoveryMessage(String description) {
def event = [:]
def device = [:]
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('devicetype:')) {
part -= "devicetype:"
event.devicetype = part.trim()
def valueString = part.split(":")[1].trim()
device.devicetype = valueString
}
else if (part.startsWith('mac:')) {
part -= "mac:"
event.mac = part.trim()
def valueString = part.split(":")[1].trim()
if (valueString) {
device.mac = valueString
}
}
else if (part.startsWith('networkAddress:')) {
part -= "networkAddress:"
event.ip = part.trim()
def valueString = part.split(":")[1].trim()
if (valueString) {
device.ip = valueString
}
}
else if (part.startsWith('deviceAddress:')) {
part -= "deviceAddress:"
event.port = part.trim()
def valueString = part.split(":")[1].trim()
if (valueString) {
device.port = valueString
}
}
else if (part.startsWith('ssdpPath:')) {
part -= "ssdpPath:"
event.ssdpPath = part.trim()
def valueString = part.split(":")[1].trim()
if (valueString) {
device.ssdpPath = valueString
}
}
else if (part.startsWith('ssdpUSN:')) {
part -= "ssdpUSN:"
event.ssdpUSN = part.trim()
def valueString = part.trim()
if (valueString) {
device.ssdpUSN = valueString
}
}
else if (part.startsWith('ssdpTerm:')) {
part -= "ssdpTerm:"
event.ssdpTerm = part.trim()
def valueString = part.trim()
if (valueString) {
device.ssdpTerm = valueString
}
}
else if (part.startsWith('headers')) {
part -= "headers:"
event.headers = part.trim()
def valueString = part.trim()
if (valueString) {
device.headers = valueString
}
}
else if (part.startsWith('body')) {
part -= "body:"
event.body = part.trim()
def valueString = part.trim()
if (valueString) {
device.body = valueString
}
}
}
event
device
}
def doDeviceSync(){