Compare commits

..

33 Commits

Author SHA1 Message Date
Vinay Rao
01a36696d8 Merge pull request #291 from kwarodom/LiFXProd
LiFX - LiFX: update oauth/callback url using getApiServerUrl() for shard proxying and change shardUrl param to apiServerUrl
2015-11-17 15:00:30 -08:00
Yaima Valdivia
797def2935 Merge branch 'master' of github.com:SmartThingsCommunity/SmartThingsPublic 2015-11-17 10:40:04 -08:00
Yaima Valdivia
1034cd06e6 Var names reversed 2015-11-17 10:38:01 -08:00
Vinay Rao
c37729242e temporary changes to the lock DTH to account for the upcoming zigbee library changes 2015-11-17 10:38:01 -08:00
Vinay Rao
d29c3ec557 Cree DTH refresh 2015-11-17 10:38:01 -08:00
Vinay Rao
17be85b846 Moving re-certified device to generic zigbee DTH. 2015-11-17 10:38:01 -08:00
Vinay Rao
da06104563 Adding fingerprint for new bulbs and transition old color temperature osram bulbs to new one 2015-11-17 10:38:01 -08:00
Vinay Rao
5d37ac8515 new fingerprints for Yale. battery issue resolution for Yale 2015-11-17 10:38:01 -08:00
Vinay Rao
2a739fda07 adding fingerprints for osram and sengled dimmable bulbs 2015-11-17 10:38:01 -08:00
Juan Pablo Risso
f627fb4fac Reverted to previously working getBridgeIP() 2015-11-17 10:38:01 -08:00
Juan Pablo Risso
1d30a718b2 Replaced atomicState with State 2015-11-17 10:38:01 -08:00
Juan Pablo Risso
303ca7117c Added a "," 2015-11-17 10:38:01 -08:00
Juan Pablo Risso
1c96645b9f removed function ShardUrl() 2015-11-17 10:38:00 -08:00
Juan Pablo Risso
af4dc0640a added singleInstance: true 2015-11-17 10:38:00 -08:00
juano2310
80b46153dc Harmony Global Oauth 2015-11-17 10:38:00 -08:00
Mike Robinet
b92cd9c637 CREX-1094 Delete stale device subscriptions on IFTTT app update 2015-11-17 10:38:00 -08:00
Juan Pablo Risso
1039b65c81 Code Cleanup 2015-11-17 10:38:00 -08:00
juano2310
4e203e8e13 buildActionUrl("hookCallback") 2015-11-17 10:38:00 -08:00
juano2310
61398105d1 Jawbone Global Oauth 2015-11-17 10:38:00 -08:00
bflorian
505efc5463 Deleted lights on when door opens after sundown 2015-11-17 10:38:00 -08:00
bflorian
3ddc82f996 More name/namespace changes 2015-11-17 10:38:00 -08:00
Juan Pablo Risso
13a324069d Force Level = 1% to 1
This ensures that the bulb will be able to dim to it minimum
2015-11-17 10:38:00 -08:00
bflorian
2fd5859326 Misc filename and namespace changes. 2015-11-17 10:38:00 -08:00
bflorian
bbedbddf9d Filename corrections 2015-11-17 10:38:00 -08:00
bflorian
e424e7abdd Corrected filename of Z-Wave Device Multichannel 2015-11-17 10:38:00 -08:00
Tom Manley
47fbdabf6b Fix 'Low Battery Handler' exception caused by non-integer battery events
ZigBee locks report battery percentage remaining in .5% increments. However
the Low Battery Handler Smart App in Hello Home expects it to be an integer.
2015-11-17 10:37:59 -08:00
Mike Robinet
7defe1cc61 CREX-3129 Update parent and service manager apps to be singleton 2015-11-17 10:37:59 -08:00
Mike Cousins
a78459347b update keen home smart vent device handler 2015-11-17 10:37:59 -08:00
juano2310
c473745e47 Wemo refactor final (DVCSMP-1189)
https://smartthings.atlassian.net/browse/DVCSMP-1189

Detect and mark device offline within 5 minutes.
Show Device offline in device tile.
Show Device offline in Recent Activity.
Log the current IP address to Recent Activity.
Log the changed IP address to Recent Activity.
Support 'Turning on' and 'Turning off' (blindly changing the state of
device to ON or OFF without confirming bulb responded correctly)
Turn on / off through Wemo-App reflected timely in SmartThings
App/Ecosystem.
Manual turn on / off of device is reflected timely in SmartThings
App/Ecosystem.

Lower case createEvent

Bug Fixes

Bug fixes

setOffline

Minor cosmetic fixes
2015-11-17 10:37:59 -08:00
Juan Pablo Risso
fc587ef15a Fix to Hue reverts dimmer settings (DVCSMP-1227)
if you use the hue native app to adjust the dimmer setting, smartthings will reset the dimmer to previous value when toggling from ST app (and automations)
2015-11-17 10:37:59 -08:00
Warodom Khamphanchai
0f3b730f26 DVCSMP-668
- Show batteryStatus tile insteady of battery tile to be able to display both when sensor is USB powered or battery powered
- Remove background for illuminance. This can be added when we have best practice of showing colors for lux.
- Instead of using powerSupply:failed, configurationGet cmd is sent and then the configure() is triggered by configurationReport to determine powerSupply (USB Cable/Battery)
- Instead of querying battery level on wake up, battery report is put in association group 2 that is configured to report every 6 hours by default
- Update configure() to  send both unsecure and secure configuration commands when sensor is joined normally or securely
2015-11-17 10:37:59 -08:00
Warodom Khamphanchai
11df2f31b3 DVCSMP-668
The following changes has been made to the original Aeon Multisensor device type handler to improve and modernize it:
1. Add "powerSupply" attribute to be able to tell power source (USB Cable/Battery)
2. Add preference page for user to customize "motion delay time", "motion sensitivity", and "sensor report interval"
3. Add color backgroud of "illuminance" value tile
4. Add tile for "ultravioletIndex"
5. Add tile for "powerSupply"
6. Modify updated() to be able to send configuration commands to sensor whether it is powered by USB cable or battery
7. When battery operated, send command to get update battery level if it hasn't been reported for a while
8. Report MSR of the sensor
9. Add handle for ConfigurationReport command class to update the "powerSupply" tile and change opetion mode of the sensor accordingly
10. Update configure() to configure parameters changed by user in preference page
11. Take out the "Configure" tile and instead send the configuration commands on every wakeup (sensor is battey powered)
2015-11-17 10:37:59 -08:00
Yaima Valdivia
4d243bf44d Rename Sonos SmartApps
https://smartthings.atlassian.net/browse/DVCSMP-607
2015-10-27 12:29:05 -07:00
58 changed files with 2050 additions and 6019 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,177 +0,0 @@
/**
* Nest
*
* Copyright 2016 John Lister
*
* 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: "Nest", namespace: "smartthings-users", author: "John Lister") {
capability "Polling"
capability "Presence Sensor"
capability "Relative Humidity Measurement"
capability "Sensor"
capability "Thermostat"
attribute "temperatureUnit", "string"
command "away"
command "present"
command "setPresence"
command "heatingSetpointUp"
command "heatingSetpointDown"
command "coolingSetpointUp"
command "coolingSetpointDown"
command "setFahrenheit"
command "setCelsius"
}
simulator {
// TODO: define status and reply messages here
}
tiles {
// TODO: define your main and details tiles here
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
// TODO: handle 'presence' attribute
// TODO: handle 'humidity' attribute
// TODO: handle 'temperature' attribute
// TODO: handle 'heatingSetpoint' attribute
// TODO: handle 'coolingSetpoint' attribute
// TODO: handle 'thermostatSetpoint' attribute
// TODO: handle 'thermostatMode' attribute
// TODO: handle 'thermostatFanMode' attribute
// TODO: handle 'thermostatOperatingState' attribute
// TODO: handle 'schedule' attribute
// TODO: handle 'temperatureUnit' attribute
}
// handle commands
def poll() {
log.debug "Executing 'poll'"
// TODO: handle 'poll' command
}
def setHeatingSetpoint() {
log.debug "Executing 'setHeatingSetpoint'"
// TODO: handle 'setHeatingSetpoint' command
}
def setCoolingSetpoint() {
log.debug "Executing 'setCoolingSetpoint'"
// TODO: handle 'setCoolingSetpoint' command
}
def off() {
log.debug "Executing 'off'"
// TODO: handle 'off' command
}
def heat() {
log.debug "Executing 'heat'"
// TODO: handle 'heat' command
}
def emergencyHeat() {
log.debug "Executing 'emergencyHeat'"
// TODO: handle 'emergencyHeat' command
}
def cool() {
log.debug "Executing 'cool'"
// TODO: handle 'cool' command
}
def setThermostatMode() {
log.debug "Executing 'setThermostatMode'"
// TODO: handle 'setThermostatMode' command
}
def fanOn() {
log.debug "Executing 'fanOn'"
// TODO: handle 'fanOn' command
}
def fanAuto() {
log.debug "Executing 'fanAuto'"
// TODO: handle 'fanAuto' command
}
def fanCirculate() {
log.debug "Executing 'fanCirculate'"
// TODO: handle 'fanCirculate' command
}
def setThermostatFanMode() {
log.debug "Executing 'setThermostatFanMode'"
// TODO: handle 'setThermostatFanMode' command
}
def auto() {
log.debug "Executing 'auto'"
// TODO: handle 'auto' command
}
def setSchedule() {
log.debug "Executing 'setSchedule'"
// TODO: handle 'setSchedule' command
}
def away() {
log.debug "Executing 'away'"
// TODO: handle 'away' command
}
def present() {
log.debug "Executing 'present'"
// TODO: handle 'present' command
}
def setPresence() {
log.debug "Executing 'setPresence'"
// TODO: handle 'setPresence' command
}
def heatingSetpointUp() {
log.debug "Executing 'heatingSetpointUp'"
// TODO: handle 'heatingSetpointUp' command
}
def heatingSetpointDown() {
log.debug "Executing 'heatingSetpointDown'"
// TODO: handle 'heatingSetpointDown' command
}
def coolingSetpointUp() {
log.debug "Executing 'coolingSetpointUp'"
// TODO: handle 'coolingSetpointUp' command
}
def coolingSetpointDown() {
log.debug "Executing 'coolingSetpointDown'"
// TODO: handle 'coolingSetpointDown' command
}
def setFahrenheit() {
log.debug "Executing 'setFahrenheit'"
// TODO: handle 'setFahrenheit' command
}
def setCelsius() {
log.debug "Executing 'setCelsius'"
// TODO: handle 'setCelsius' command
}

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

@@ -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,141 +288,115 @@ 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",
@@ -449,24 +409,25 @@ def configure() {
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
"zcl mfg-code ${manufacturerCode}",
"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}",
"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}",
"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}",
"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() {
@@ -486,34 +447,35 @@ def enrollResponse() {
]
}
private Map parseAxis(String description) {
def hexToSignedInt = { hexVal ->
def unsignedVal = hexToInt(hexVal)
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
}
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)
}
@@ -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) {
@@ -299,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 {
@@ -444,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)
}
@@ -512,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
@@ -527,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) { }
}
}
@@ -651,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)
}

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

@@ -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
*/
@@ -90,7 +90,7 @@ def subscribeToEvents() {
if (beacons != null) {
subscribe(beacons, "presence", genericHandler)
}
if (cos != null) {
subscribe(cos, "carbonMonoxide", genericHandler)
}
@@ -218,7 +218,7 @@ def setAccessKey() {
atomicState.grokerSubdomain = "$newGrokerSubdomain"
atomicState.isBucketCreated = false
}
if (newAccessKey && newAccessKey != atomicState.accessKey) {
atomicState.accessKey = "$newAccessKey"
atomicState.isBucketCreated = false
@@ -262,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"
@@ -330,57 +330,57 @@ def genericHandler(evt) {
// 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() {
def eventBuffer = atomicState.eventBuffer
log.trace "About to flush the buffer on schedule"
if (eventBuffer != null && eventBuffer.size() > 0) {
atomicState.eventBuffer = []
tryShipEvents(eventBuffer)
if (atomicState.eventBuffer != null && atomicState.eventBuffer.size() > 0) {
tryShipEvents()
}
}
def eventHandler(name, value) {
log.debug atomicState.eventBuffer
def eventBuffer = atomicState.eventBuffer
def epoch = now() / 1000
def eventBuffer = atomicState.eventBuffer ?: []
// 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) {
// Clear eventBuffer right away since we've already pulled it off of atomicState to reduce the risk of missing
// events. This assumes the grokerSubdomain, accessKey, and bucketKey are set correctly to avoid the eventBuffer
// from growing unbounded.
atomicState.eventBuffer = []
tryShipEvents(eventBuffer)
} else {
// Make sure we persist the updated eventBuffer with the new event added back to atomicState
atomicState.eventBuffer = eventBuffer
tryShipEvents()
}
log.debug "Event added to buffer: " + eventBuffer
}
// a helper function for shipping the atomicState.eventBuffer to Initial State
def tryShipEvents(eventBuffer) {
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: eventBuffer
body: atomicState.eventBuffer
]
try {
@@ -389,10 +389,13 @@ def tryShipEvents(eventBuffer) {
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

@@ -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,7 +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"
} else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
}
@@ -334,6 +333,8 @@ def addBulbs() {
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
}
log.debug "created ${d.displayName} with id $dni"
d.refresh()
} else {
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
@@ -774,4 +775,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it }
}
}

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

@@ -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

@@ -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,7 +89,7 @@ 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
@@ -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

@@ -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