Compare commits

...

56 Commits

Author SHA1 Message Date
Vinay Rao
1977b5c2cc Merge pull request #2208 from SmartThingsCommunity/staging
Rolling up staging to production
2017-08-01 12:24:56 -07:00
Vinay Rao
8bd02707f4 Merge pull request #2196 from varzac/smartsense-battery-smoothing
Smooth battery values for smartsense devices
2017-07-27 13:11:04 -07:00
Zach Varberg
c86d61a862 Smooth battery values for smartsense devices
Since we have been receiving a lot of reports and complaints about the
battery readings on the smartsense devices that received battery
updates (multi and motion), we are adding this code to smooth out the
readings so it doesn't constantly oscillate between two values.  The
basic logic is, only create an event if we have two readings in a row
that are the same, or a reading is more than 100 millivolts off of the
current battery percentage.
2017-07-27 11:19:31 -05:00
Vinay Rao
8126203164 Merge pull request #2156 from dkirker/ICP-1267
ICP-1267 Move lock state check code from updated() to installed() to reduce chance that hub has gone back to pjoin mode thus blocking Z-Wave.
2017-07-26 14:27:39 -07:00
Vinay Rao
04f56060c9 Merge pull request #2194 from SmartThingsCommunity/master
Rolling up master to staging
2017-07-25 16:47:54 -07:00
Vinay Rao
215253d6d3 Merge pull request #2193 from SmartThingsCommunity/staging
Rolling down staging to master
2017-07-25 16:46:35 -07:00
Vinay Rao
f6e99745ce Merge pull request #2192 from SmartThingsCommunity/staging
Rolling up staging to production
2017-07-25 15:39:20 -07:00
Jack Chi
47eedf5770 Merge pull request #2170 from pchomal/hc_changes
[DHF-14][DHF-15] Updated comments & rectified typo
2017-07-25 12:25:38 +05:30
Luke Ness
3d0dbc57a1 Merge pull request #2100 from gausnes/ZwaveSirenFix
BatteryGet has a format method not property
2017-07-24 16:05:36 -05:00
Vinay Rao
d2b16ab9c4 Merge pull request #2186 from larsfinander/DVCSMP-2864_OpenT2T_Update_to_7_17_submission_staging
DVCSMP-2864 OpenT2T: Update to 7/17 submission
2017-07-24 10:42:47 -07:00
Lars Finander
7297ad2622 DVCSMP-2864 OpenT2T: Update to 7/17 submission 2017-07-24 11:21:11 -06:00
Donald Kirker
f1a8d58c40 ICP-1267 Call lock state check code from installed() with precise timing to threduce chance that hub has gone back to pjoin mode thus blocking Z-Wave. 2017-07-21 21:44:44 -07:00
Ingvar Marstorp
6b4309fa95 ICP-1103, ICP-1125, ICP-1194 CT Thermostat issues (#2181)
* ICP-1103, ICP-1125, ICP-1194 CT Thermostat issues
- Added modes and changed mode names to reflect specification in thermostat capability
- Changed how device is initialized poll by sending all commands separated by 3 seconds
  instead of sending a request and on its response send the next. This to give all
  commands a chance in case one is dropped by the network.
- Reduced the time from 30 to 1 second between setting and requesting a setpoint
  using the setHeating/setCooling method. This is now the same as changing using
  the ST DTH tiles.
- Also modified the UI to use the multi attribute tile and using arrows instead of sliders
  for changing setpoints.

* Update ct100-thermostat.groovy
2017-07-21 17:27:46 -07:00
Vinay Rao
3d0fb9cdde Merge pull request #2169 from jackchi/health-aeon
[DHF-31] [DHF-32] Device Health Aeon MultiSensor checkInterval fix
2017-07-21 15:39:10 -07:00
Vinay Rao
f0f72b2bce Merge pull request #2130 from dkirker/ICP-1075-ICP-1215
ICP-1075, ICP-1215 Separate ZSMOKE from ZCOMBO and default detector DTH (assume supports smoke+CO if no explicit fingerprint), set initial states to clear
2017-07-21 13:44:01 -07:00
Vinay Rao
19de3e0145 Merge pull request #2157 from jackchi/spruce-sensor-icon
[MOB-3453] Update Spruce sensor icon
2017-07-20 23:13:01 -07:00
jackchi
a06aff2bbb [MOB-3453] Update Spruce Sensor icon 2017-07-21 11:31:26 +05:30
Donald Kirker
dfad749e3c ICP-1075, ICP-1215 Separate ZSMOKE from ZCOMBO and default detector DTH (assume supports smoke+CO if no explicit fingerprint), set initial states to clear 2017-07-20 00:54:58 -07:00
piyush.c
f96ae94d12 [DHF-14][DHF-15] Health Check Aeon Key Fob and Aeon Minimote 2017-07-19 14:18:32 +05:30
jackchi
748529b81b [DHF-31] [DHF-32] Device Health Aeon MultiSensor checkInterval fix 2017-07-18 12:46:04 -07:00
Vinay Rao
de5f0683d3 Merge pull request #2168 from SmartThingsCommunity/staging
Rolling down staging to master
2017-07-18 12:24:01 -07:00
Vinay Rao
fc70b5ce55 Merge pull request #2167 from SmartThingsCommunity/staging
Rolling up staging to production
2017-07-18 11:43:24 -07:00
Vinay Rao
36e63133fc Merge pull request #2146 from marstorp/icp1148HoneywellZwave
ICP-1148 Support Thermostat Dynamic data
2017-07-17 15:45:19 -07:00
Vinay Rao
838c466312 Merge pull request #2158 from varzac/smartsense-battery-updates
[DVCSMP-2811] Update ranges for centralite battery values
2017-07-14 14:37:45 -07:00
Zach Varberg
97bfe61baa Update ranges for centralite battery values
This just updates the range to be more conservative as well as match the
battery curve better.
2017-07-14 11:39:48 -05:00
Vinay Rao
34df40d5b4 Merge pull request #2155 from jackchi/health-aeon6
[DHF-24] Fix Aeon MultiSensor6 OFFLINE issue
2017-07-12 14:29:03 -07:00
jackchi
545be046f0 [DHF-24] Update Aeon Multi6 to 2hr2min 2017-07-11 17:05:42 -07:00
Vinay Rao
a5041e0fcb Merge pull request #2154 from SmartThingsCommunity/master
Rolling up staging to master
2017-07-11 14:03:19 -07:00
Vinay Rao
6996a07969 Merge pull request #2153 from SmartThingsCommunity/staging
Rolling up staging to production
2017-07-11 13:59:29 -07:00
Vinay Rao
5beae1d9fc Merge pull request #2150 from SmartThingsCommunity/staging
Rolling down staging to master
2017-07-07 16:06:50 -07:00
Vinay Rao
e5738978b0 Merge pull request #850 from workingmonk/feature/new_button_methods
DVCSMP-1741 Changes to button controller to read number of buttons from the dth
2017-07-07 14:38:39 -07:00
Jack Chi
07064eb8cc Merge pull request #2148 from natec007/MSA-2084-5
MSA-2084: Spruce Sensor DTH new model and health check
2017-07-07 12:14:09 -07:00
natec007
f519a2d828 Update spruce-sensor.groovy
Removed checkInterval attribute
sendEvent(name: "checkInterval" already in updated and installed
2017-07-06 14:27:33 -07:00
Nathan Cauffman
4da49283bf MSA-2084: Updated with new model number and health check 2017-07-06 13:22:12 -07:00
Bob Florian
7389edf795 Merge pull request #2140 from juano2310/lifx_not_null
ICP-507 - Replace location count null with 0
2017-07-06 08:29:58 -07:00
Brian Steere
23154372d2 Merge pull request #2141 from SmartThingsCommunity/every-element-update
Update Every Element with more accurate enum settings
2017-07-06 09:02:42 -05:00
juano2310
1b0437c633 ICP-507 - Replace location count null with 0
Updated with def count = options.size().toString()
2017-07-06 09:43:47 -04:00
Vinay Rao
b3d9578140 Merge pull request #2147 from dkirker/CSING-26
CSING-26 Send IAS Zone update request when refreshing for 1st party zigbee sensors
2017-07-05 22:43:38 -07:00
Donald Kirker
7549979be5 CSING-26 Send IAS Zone update request when refreshing for 1st party zigbee sensors. 2017-07-05 20:35:18 -07:00
marstorp
771926c337 ICP-1148 Support Thermostat Dynamic data
Adding support for dynamic thermostat and fan modes to TCC DTH.
Also replaced capability "Polling" with "Refresh" and runEvery5Minutes("refresh") as polling capability is unreliable.
Also removed capability "Relative Humidity Measurement" as Honeywell Z-Wave Thermostat (YTH8320ZW1007/U) doesn't support humidity.
2017-07-05 16:50:30 -07:00
Vinay Rao
f647be3a62 Merge pull request #2144 from SmartThingsCommunity/staging
Rolling down staging to master
2017-07-05 14:22:13 -07:00
Vinay Rao
728b169a08 Merge pull request #2143 from SmartThingsCommunity/staging
Rolling up staging to production
2017-07-05 14:16:16 -07:00
Vinay Rao
d2f981fd34 Merge pull request #2142 from SmartThingsCommunity/production
Rolling down production to staging
2017-07-05 14:13:37 -07:00
Brian Steere
2536c69083 Update Every Element with more accurate enum settings
Added a couple of extra enum inputs to demonstrate additional states
2017-07-05 16:01:16 -05:00
Vinay Rao
a58dd2094d Merge pull request #2137 from twack/update-gopher-water-valve-initail-state
(ICP-1050) Added check gopher valve initial state in installed()
2017-07-05 13:57:44 -07:00
twack
c549a5bed0 Added check valve initial state in installed() 2017-07-04 08:10:09 -07:00
Vinay Rao
ef5fffc4bc Merge pull request #2133 from jackchi/health-dhf-16
[DHF-16] ZigBee Button Device Health Update
2017-06-30 11:17:31 -07:00
jackchi
c370e88a6b [DHF-16] ZigBee Button Device Health Update 2017-06-30 10:44:20 -07:00
Vinay Rao
ce8c50c630 Merge pull request #2128 from marstorp/icp1148EcobeeHotfix
ICP-1148 Support Thermostat Dynamic data
2017-06-27 16:29:03 -07:00
marstorp
b069669c11 ICP-1148 Support Thermostat Dynamic data
Adding default "off" thermostat mode as it is not provided by the thermostat.
2017-06-27 16:19:49 -07:00
Vinay Rao
4d61d28b42 Merge pull request #2127 from SmartThingsCommunity/master
Rolling up master to staging
2017-06-27 16:05:32 -07:00
Vinay Rao
4547d0543b Merge pull request #2126 from SmartThingsCommunity/staging
Rolling down staging to master
2017-06-27 16:03:00 -07:00
Jack Chi
59e6f1251b Merge pull request #2097 from skt123/zigbee_button
[DHF-16] Added Health Check Implementation for Zigbee Button.
2017-06-27 10:28:47 -07:00
sushant.k1
e8939a77d7 [DHF-16]
Added Health Check Implementation for Zigbee Button.
2017-06-27 19:12:35 +05:30
Luke Ness
5e787dd0e3 BatteryGet has a format method not property 2017-06-19 11:59:13 -05:00
Vinay Rao
45663ffb86 changes to button controller to read number of buttons from the dth 2016-04-01 16:17:03 -07:00
29 changed files with 918 additions and 517 deletions

View File

@@ -1,5 +1,5 @@
/** /**
* Spruce Sensor -Pre-release V2 10/8/2015 * Spruce Sensor -updated with SLP model number 5/2017
* *
* Copyright 2014 Plaid Systems * Copyright 2014 Plaid Systems
* *
@@ -14,25 +14,33 @@
* *
-------10/20/2015 Updates-------- -------10/20/2015 Updates--------
-Fix/add battery reporting interval to update -Fix/add battery reporting interval to update
-remove polling and/or refresh(?) -remove polling and/or refresh
-------5/2017 Updates--------
-Add fingerprints for SLP
-add device health, check every 60mins + 2mins
*/ */
metadata { metadata {
definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "NCauffman") { definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "Plaid Systems") {
capability "Configuration" capability "Configuration"
capability "Battery" capability "Battery"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Sensor" capability "Sensor"
capability "Health Check"
//capability "Polling" //capability "Polling"
attribute "maxHum", "string" attribute "maxHum", "string"
attribute "minHum", "string" attribute "minHum", "string"
command "resetHumidity" command "resetHumidity"
command "refresh" command "refresh"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01" fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01", deviceJoinName: "Spruce Sensor"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-SLP1", deviceJoinName: "Spruce Sensor"
} }
preferences { preferences {
@@ -65,7 +73,7 @@ metadata {
[value: 64, color: "#44B621"], [value: 64, color: "#44B621"],
[value: 80, color: "#3D79D9"], [value: 80, color: "#3D79D9"],
[value: 96, color: "#0A50C2"] [value: 96, color: "#0A50C2"]
] ], icon:"st.Weather.weather12"
} }
valueTile("maxHum", "device.maxHum", canChangeIcon: false, canChangeBackground: false) { valueTile("maxHum", "device.maxHum", canChangeIcon: false, canChangeBackground: false) {
@@ -293,6 +301,11 @@ def setConfig(){
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration initialized") sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration initialized")
} }
def installed(){
//check every 1 hour + 2mins
sendEvent(name: "checkInterval", value: 1 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
//when device preferences are changed //when device preferences are changed
def updated(){ def updated(){
log.debug "device updated" log.debug "device updated"
@@ -303,6 +316,8 @@ def updated(){
sendEvent(name: 'configuration',value: 0, descriptionText: "Settings changed and will update at next report. Measure interval set to ${interval} mins") sendEvent(name: 'configuration',value: 0, descriptionText: "Settings changed and will update at next report. Measure interval set to ${interval} mins")
} }
} }
//check every 1 hour + 2mins
sendEvent(name: "checkInterval", value: 1 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
} }
//poll //poll

View File

@@ -133,8 +133,8 @@ def updated() {
} }
def initialize() { def initialize() {
// Arrival sensors only goes OFFLINE when Hub is off // Device only goes OFFLINE when Hub is off
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false) sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zwave", scheme:"untracked"]), displayed: false)
def zwMap = getZwaveInfo() def zwMap = getZwaveInfo()
def buttons = 4 // Default for Key Fob def buttons = 4 // Default for Key Fob

View File

@@ -111,7 +111,6 @@ def configure() {
return cmds return cmds
} }
def installed() { def installed() {
initialize() initialize()
} }
@@ -121,7 +120,7 @@ def updated() {
} }
def initialize() { def initialize() {
// Arrival sensors only goes OFFLINE when Hub is off // Device only goes OFFLINE when Hub is off
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false) sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zwave", scheme:"untracked"]), displayed: false)
sendEvent(name: "numberOfButtons", value: 4) sendEvent(name: "numberOfButtons", value: 4)
} }

View File

@@ -27,13 +27,9 @@ Works with:
## Device Health ## Device Health
Aeon Labs MultiSensor 6 is polled by the hub. Aeon Labs MultiSensor 6 is polled by the hub.
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed. Aeon MultiSensor reports in once every hour.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
* __32min__ checkInterval * __122min__ checkInterval
## Troubleshooting ## Troubleshooting

View File

@@ -130,13 +130,13 @@ metadata {
} }
def installed(){ def installed(){
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 122min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
} }
def updated() { def updated() {
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 122min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
log.debug "Updated with settings: ${settings}" log.debug "Updated with settings: ${settings}"
log.debug "${device.displayName} is now ${device.latestValue("powerSupply")}" log.debug "${device.displayName} is now ${device.latestValue("powerSupply")}"

View File

@@ -27,13 +27,9 @@ Works with:
## Device Health ## Device Health
Aeon Labs MultiSensor (Gen 5) is polled by the hub. Aeon Labs MultiSensor (Gen 5) is polled by the hub.
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed. Aeon MultiSensor Gen5 reports in once every hour.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
* __32min__ checkInterval * __122min__ checkInterval
## Troubleshooting ## Troubleshooting

View File

@@ -100,12 +100,12 @@ metadata {
def installed(){ def installed(){
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
} }
def updated(){ def updated(){
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
} }
def parse(String description) def parse(String description)

View File

@@ -28,13 +28,9 @@ Works with:
## Device Health ## Device Health
Aeon Labs MultiSensor is polled by the hub. Aeon Labs MultiSensor is polled by the hub.
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed. Aeon MultiSensor reports in once every hour.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
* __32min__ checkInterval * __122min__ checkInterval
## Battery Specification ## Battery Specification

View File

@@ -96,12 +96,12 @@ metadata {
def installed(){ def installed(){
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
} }
def updated(){ def updated(){
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
} }
// Parse incoming device messages to generate events // Parse incoming device messages to generate events

View File

@@ -6,7 +6,6 @@ metadata {
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Thermostat" capability "Thermostat"
capability "Battery" capability "Battery"
capability "Configuration"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
capability "Health Check" capability "Health Check"
@@ -15,161 +14,173 @@ metadata {
command "switchMode" command "switchMode"
command "switchFanMode" command "switchFanMode"
command "quickSetCool" command "lowerHeatingSetpoint"
command "quickSetHeat" command "raiseHeatingSetpoint"
command "lowerCoolSetpoint"
command "raiseCoolSetpoint"
fingerprint deviceId: "0x08", inClusters: "0x43,0x40,0x44,0x31,0x80,0x85,0x60" fingerprint deviceId: "0x08", inClusters: "0x43,0x40,0x44,0x31,0x80,0x85,0x60"
fingerprint mfr:"0098", prod:"6401", model:"0107", deviceJoinName: "2Gig CT100 Programmable Thermostat" fingerprint mfr:"0098", prod:"6401", model:"0107", deviceJoinName: "2Gig CT100 Programmable Thermostat"
} }
// simulator metadata
simulator {
status "off" : "command: 4003, payload: 00"
status "heat" : "command: 4003, payload: 01"
status "cool" : "command: 4003, payload: 02"
status "auto" : "command: 4003, payload: 03"
status "emergencyHeat" : "command: 4003, payload: 04"
status "fanAuto" : "command: 4403, payload: 00"
status "fanOn" : "command: 4403, payload: 01"
status "fanCirculate" : "command: 4403, payload: 06"
status "heat 60" : "command: 4303, payload: 01 09 3C"
status "heat 72" : "command: 4303, payload: 01 09 48"
status "cool 76" : "command: 4303, payload: 02 09 4C"
status "cool 80" : "command: 4303, payload: 02 09 50"
status "temp 58" : "command: 3105, payload: 01 2A 02 44"
status "temp 62" : "command: 3105, payload: 01 2A 02 6C"
status "temp 78" : "command: 3105, payload: 01 2A 03 0C"
status "temp 86" : "command: 3105, payload: 01 2A 03 34"
status "idle" : "command: 4203, payload: 00"
status "heating" : "command: 4203, payload: 01"
status "cooling" : "command: 4203, payload: 02"
// reply messages
reply "2502": "command: 2503, payload: FF"
}
tiles { tiles {
valueTile("temperature", "device.temperature", width: 2, height: 2) { multiAttributeTile(name:"temperature", type:"generic", width:3, height:2, canChangeIcon: true) {
state("temperature", label:'${currentValue}°', tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
backgroundColors:[ attributeState("temperature", label:'${currentValue}°', icon: "st.alarm.temperature.normal",
[value: 32, color: "#153591"], backgroundColors:[
[value: 44, color: "#1e9cbb"], // Celsius
[value: 59, color: "#90d2a7"], [value: 0, color: "#153591"],
[value: 74, color: "#44b621"], [value: 7, color: "#1e9cbb"],
[value: 84, color: "#f1d801"], [value: 15, color: "#90d2a7"],
[value: 92, color: "#d04e00"], [value: 23, color: "#44b621"],
[value: 98, color: "#bc2323"] [value: 28, color: "#f1d801"],
] [value: 35, color: "#d04e00"],
) [value: 37, color: "#bc2323"],
// Fahrenheit
[value: 40, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
)
}
tileAttribute("device.batteryIcon", key: "SECONDARY_CONTROL") {
attributeState "ok_battery", label:'${currentValue}%', icon:"st.arlo.sensor_battery_4"
attributeState "low_battery", label:'Low Battery', icon:"st.arlo.sensor_battery_0"
}
} }
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { standardTile("mode", "device.thermostatMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "off", label:'${name}', action:"switchMode", nextState:"to_heat" state "off", action:"switchMode", nextState:"to_heat", icon: "st.thermostat.heating-cooling-off"
state "heat", label:'${name}', action:"switchMode", nextState:"to_cool" state "heat", action:"switchMode", nextState:"to_cool", icon: "st.thermostat.heat"
state "cool", label:'${name}', action:"switchMode", nextState:"..." state "cool", action:"switchMode", nextState:"...", icon: "st.thermostat.cool"
state "auto", label:'${name}', action:"switchMode", nextState:"..." state "auto", action:"switchMode", nextState:"...", icon: "st.thermostat.auto"
state "emergency heat", label:'${name}', action:"switchMode", nextState:"..." state "emergency heat", action:"switchMode", nextState:"...", icon: "st.thermostat.emergency-heat"
state "to_heat", label: "heat", action:"switchMode", nextState:"to_cool" state "to_heat", action:"switchMode", nextState:"to_cool", icon: "st.secondary.secondary"
state "to_cool", label: "cool", action:"switchMode", nextState:"..." state "to_cool", action:"switchMode", nextState:"...", icon: "st.secondary.secondary"
state "...", label: "...", action:"off", nextState:"off" state "...", label: "...", action:"off", nextState:"off", icon: "st.secondary.secondary"
} }
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") { standardTile("fanMode", "device.thermostatFanMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "fanAuto", label:'${name}', action:"switchFanMode" state "auto", action:"switchFanMode", icon: "st.thermostat.fan-auto"
state "fanOn", label:'${name}', action:"switchFanMode" state "on", action:"switchFanMode", icon: "st.thermostat.fan-on"
state "fanCirculate", label:'${name}', action:"switchFanMode" state "circulate", action:"switchFanMode", icon: "st.thermostat.fan-circulate"
} }
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) { valueTile("humidity", "device.humidity", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "setHeatingSetpoint", action:"quickSetHeat", backgroundColor:"#e86d13" state "humidity", label:'${currentValue}%', icon:"st.Weather.weather12"
} }
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") { standardTile("lowerHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue}° heat', backgroundColor:"#ffffff" state "heatingSetpoint", action:"lowerHeatingSetpoint", icon:"st.thermostat.thermostat-left"
} }
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) { valueTile("heatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "setCoolingSetpoint", action:"quickSetCool", backgroundColor: "#00a0dc" state "heatingSetpoint", label:'${currentValue}° heat', backgroundColor:"#ffffff"
} }
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") { standardTile("raiseHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "cool", label:'${currentValue}° cool', backgroundColor:"#ffffff" state "heatingSetpoint", action:"raiseHeatingSetpoint", icon:"st.thermostat.thermostat-right"
} }
valueTile("humidity", "device.humidity", inactiveLabel: false, decoration: "flat") { standardTile("lowerCoolSetpoint", "device.coolingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "humidity", label:'${currentValue}% humidity', unit:"" state "coolingSetpoint", action:"lowerCoolSetpoint", icon:"st.thermostat.thermostat-left"
} }
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") { valueTile("coolingSetpoint", "device.coolingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "battery", label:'${currentValue}% battery', unit:"" state "coolingSetpoint", label:'${currentValue}° cool', backgroundColor:"#ffffff"
} }
standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { standardTile("raiseCoolSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "heatingSetpoint", action:"raiseCoolSetpoint", icon:"st.thermostat.thermostat-right"
}
standardTile("refresh", "device.thermostatMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main "temperature" main "temperature"
details(["temperature", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "refresh", "humidity", "battery"]) details(["temperature", "mode", "fanMode", "humidity", "lowerHeatingSetpoint", "heatingSetpoint", "raiseHeatingSetpoint", "lowerCoolSetpoint", "coolingSetpoint", "raiseCoolSetpoint", "refresh"])
} }
} }
def updated() { def updated() {
// Device-Watch simply pings if no device events received for 32min(checkInterval) // If not set update ManufacturerSpecific data
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) if (!getDataValue("manufacturer")) {
sendHubCommand(new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()))
}
initialize()
} }
def installed() { def installed() {
// Configure device
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format())
cmds << new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())
sendHubCommand(cmds)
initialize()
}
def initialize() {
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
// Poll device for additional data that will be updated by refresh tile
refresh()
} }
def parse(String description) def parse(String description)
{ {
def result = [] def result = null
if (description == "updated") { if (description == "updated") {
} else { } else {
def zwcmd = zwave.parse(description, [0x42:2, 0x43:2, 0x31: 2, 0x60: 3]) def zwcmd = zwave.parse(description, [0x42:2, 0x43:2, 0x31: 2, 0x60: 3])
if (zwcmd) { if (zwcmd) {
result += zwaveEvent(zwcmd) result = zwaveEvent(zwcmd)
// Check battery level at least once every 2 days
if (!state.lastbatt || now() - state.lastbatt > 48*60*60*1000) {
sendHubCommand(new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format()))
}
} else { } else {
log.debug "$device.displayName couldn't parse $description" log.debug "$device.displayName couldn't parse $description"
} }
} }
if (!result) { if (!result) {
return null return []
} }
if (result.size() == 1 && (!state.lastbatt || now() - state.lastbatt > 48*60*60*1000)) { return [result]
result << response(zwave.batteryV1.batteryGet().format())
}
log.debug "$device.displayName parsed '$description' to $result"
result
} }
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) { def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiInstanceCmdEncap cmd) {
def result = null def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 3])
def encapsulatedCommand = cmd.encapsulatedCommand([0x42:2, 0x43:2, 0x31: 2]) log.debug ("multiinstancev1.MultiInstanceCmdEncap: command from instance ${cmd.instance}: ${encapsulatedCommand}")
log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}")
if (encapsulatedCommand) { if (encapsulatedCommand) {
result = zwaveEvent(encapsulatedCommand) zwaveEvent(encapsulatedCommand)
if (cmd.sourceEndPoint == 1) { // indicates a response to refresh() vs an unrequested update
def event = ([] + result)[0] // in case zwaveEvent returns a list
def resp = nextRefreshQuery(event?.name)
if (resp) {
log.debug("sending next refresh query: $resp")
result = [] + result + response(["delay 200", resp])
}
}
} }
result
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd) def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd)
{ {
def cmdScale = cmd.scale == 1 ? "F" : "C" def sendCmd = []
def temp = convertTemperatureIfNeeded(cmd.scaledValue, cmdScale, cmd.precision)
def unit = getTemperatureScale() def unit = getTemperatureScale()
def map1 = [ value: temp, unit: unit, displayed: false ] def cmdScale = cmd.scale == 1 ? "F" : "C"
def setpoint = getTempInLocalScale(cmd.scaledValue, cmdScale)
switch (cmd.setpointType) { switch (cmd.setpointType) {
case 1: case 1:
map1.name = "heatingSetpoint" //map1.name = "heatingSetpoint"
sendEvent(name: "heatingSetpoint", value: setpoint, unit: unit, displayed: false)
updateThermostatSetpoint("heatingSetpoint", setpoint)
// Enforce coolingSetpoint limits, as device doesn't
if (setpoint > getTempInLocalScale("coolingSetpoint")) {
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 2, scale: cmd.scale, precision: cmd.precision, scaledValue: cmd.scaledValue).format())
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format())
sendHubCommand(sendCmd)
}
break; break;
case 2: case 2:
map1.name = "coolingSetpoint" //map1.name = "coolingSetpoint"
sendEvent(name: "coolingSetpoint", value: setpoint, unit: unit, displayed: false)
updateThermostatSetpoint("coolingSetpoint", setpoint)
// Enforce heatingSetpoint limits, as device doesn't
if (setpoint < getTempInLocalScale("heatingSetpoint")) {
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 1, scale: cmd.scale, precision: cmd.precision, scaledValue: cmd.scaledValue).format())
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format())
sendHubCommand(sendCmd)
}
break; break;
default: default:
log.debug "unknown setpointType $cmd.setpointType" log.debug "unknown setpointType $cmd.setpointType"
@@ -180,33 +191,55 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpo
state.size = cmd.size state.size = cmd.size
state.scale = cmd.scale state.scale = cmd.scale
state.precision = cmd.precision state.precision = cmd.precision
def mode = device.latestValue("thermostatMode")
if (mode && map1.name.startsWith(mode) || (mode == "emergency heat" && map1.name == "heatingSetpoint")) {
def map2 = [ name: "thermostatSetpoint", value: temp, unit: unit ]
[ createEvent(map1), createEvent(map2) ]
} else {
createEvent(map1)
}
} }
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) // thermostatSetpoint is not displayed by any tile as it can't be predictable calculated due to
{ // the device's quirkiness but it is defined by the capability so it must be set, set it to the most likely value
def updateThermostatSetpoint(setpoint, value) {
def scale = getTemperatureScale()
def heatingSetpoint = (setpoint == "heatingSetpoint") ? value : getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = (setpoint == "coolingSetpoint") ? value : getTempInLocalScale("coolingSetpoint")
def mode = device.currentValue("thermostatMode")
def thermostatSetpoint = heatingSetpoint // corresponds to (mode == "heat" || mode == "emergency heat")
if (mode == "cool") {
thermostatSetpoint = coolingSetpoint
}
// Just set to average of heating + cooling for mode off and auto
if (mode == "off" || mode == "auto") {
thermostatSetpoint = getTempInLocalScale((heatingSetpoint + coolingSetpoint)/2, scale)
}
sendEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: scale)
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) {
def map = [:] def map = [:]
if (cmd.sensorType == 1) { if (cmd.sensorType == 1) {
map.name = "temperature" map.name = "temperature"
map.unit = getTemperatureScale() map.unit = getTemperatureScale()
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision) map.value = getTempInLocalScale(cmd.scaledSensorValue, (cmd.scale == 1 ? "F" : "C"))
} else if (cmd.sensorType == 5) { } else if (cmd.sensorType == 5) {
map.name = "humidity" map.name = "humidity"
map.unit = "%" map.unit = "%"
map.value = cmd.scaledSensorValue map.value = cmd.scaledSensorValue
} }
createEvent(map) sendEvent(map)
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport cmd) def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelReport cmd) {
{ def map = [:]
if (cmd.sensorType == 1) {
map.name = "temperature"
map.unit = getTemperatureScale()
map.value = getTempInLocalScale(cmd.scaledSensorValue, (cmd.scale == 1 ? "F" : "C"))
} else if (cmd.sensorType == 5) {
map.value = cmd.scaledSensorValue
map.unit = "%"
map.name = "humidity"
}
sendEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport cmd) {
def map = [name: "thermostatOperatingState" ] def map = [name: "thermostatOperatingState" ]
switch (cmd.operatingState) { switch (cmd.operatingState) {
case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_IDLE: case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_IDLE:
@@ -231,12 +264,7 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev2.Thermosta
map.value = "vent economizer" map.value = "vent economizer"
break break
} }
def result = createEvent(map) sendEvent(map)
if (result.isStateChange && device.latestValue("thermostatMode") == "auto" && (result.value == "heating" || result.value == "cooling")) {
def thermostatSetpoint = device.latestValue("${result.value}Setpoint")
result = [result, createEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: getTemperatureScale())]
}
result
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanstatev1.ThermostatFanStateReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatfanstatev1.ThermostatFanStateReport cmd) {
@@ -252,203 +280,256 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatfanstatev1.ThermostatFanSt
map.value = "running high" map.value = "running high"
break break
} }
createEvent(map) sendEvent(map)
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) {
def map = [name: "thermostatMode"] def map = [name: "thermostatMode", data:[supportedThermostatModes: state.supportedModes]]
def thermostatSetpoint = null
switch (cmd.mode) { switch (cmd.mode) {
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF: case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF:
map.value = "off" map.value = "off"
break break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_HEAT: case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_HEAT:
map.value = "heat" map.value = "heat"
thermostatSetpoint = device.latestValue("heatingSetpoint")
break break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUXILIARY_HEAT: case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUXILIARY_HEAT:
map.value = "emergency heat" map.value = "emergency heat"
thermostatSetpoint = device.latestValue("heatingSetpoint")
break break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_COOL: case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_COOL:
map.value = "cool" map.value = "cool"
thermostatSetpoint = device.latestValue("coolingSetpoint")
break break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUTO: case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUTO:
map.value = "auto" map.value = "auto"
def temp = device.latestValue("temperature")
def heatingSetpoint = device.latestValue("heatingSetpoint")
def coolingSetpoint = device.latestValue("coolingSetpoint")
if (temp && heatingSetpoint && coolingSetpoint) {
if (temp < (heatingSetpoint + coolingSetpoint) / 2.0) {
thermostatSetpoint = heatingSetpoint
} else {
thermostatSetpoint = coolingSetpoint
}
}
break break
} }
state.lastTriedMode = map.value state.lastTriedMode = map.value
if (thermostatSetpoint) { sendEvent(map)
[ createEvent(map), createEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: getTemperatureScale()) ] updateThermostatSetpoint(null, null)
} else {
createEvent(map)
}
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) {
def map = [name: "thermostatFanMode", displayed: false] def map = [name: "thermostatFanMode", data:[supportedThermostatFanModes: state.supportedFanModes]]
switch (cmd.fanMode) { switch (cmd.fanMode) {
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW: case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW:
map.value = "fanAuto" map.value = "auto"
break break
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_LOW: case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_LOW:
map.value = "fanOn" map.value = "on"
break break
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_CIRCULATION: case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_CIRCULATION:
map.value = "fanCirculate" map.value = "circulate"
break break
} }
state.lastTriedFanMode = map.value state.lastTriedFanMode = map.value
createEvent(map) sendEvent(map)
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) {
def supportedModes = "" def supportedModes = []
if(cmd.off) { supportedModes += "off " } if(cmd.heat) { supportedModes << "heat" }
if(cmd.heat) { supportedModes += "heat " } if(cmd.cool) { supportedModes << "cool" }
if(cmd.auxiliaryemergencyHeat) { supportedModes += "emergency heat " } // Make sure off is before auto, this ensures the right setpoint is used based on current temperature when auto is set
if(cmd.cool) { supportedModes += "cool " } if(cmd.off) { supportedModes << "off" }
if(cmd.auto) { supportedModes += "auto " } if(cmd.auto) { supportedModes << "auto" }
if(cmd.auxiliaryemergencyHeat) { supportedModes << "emergency heat" }
state.supportedModes = supportedModes state.supportedModes = supportedModes
[ createEvent(name:"supportedModes", value: supportedModes, displayed: false), sendEvent(name: "supportedThermostatModes", value: supportedModes, isStateChange: true, displayed: false)
response(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet()) ]
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) {
def supportedFanModes = "" def supportedFanModes = []
if(cmd.auto) { supportedFanModes += "fanAuto " } if(cmd.auto) { supportedFanModes << "auto" }
if(cmd.low) { supportedFanModes += "fanOn " } if(cmd.low) { supportedFanModes << "on" }
if(cmd.circulation) { supportedFanModes += "fanCirculate " } if(cmd.circulation) { supportedFanModes << "circulate" }
state.supportedFanModes = supportedFanModes state.supportedFanModes = supportedFanModes
[ createEvent(name:"supportedFanModes", value: supportedModes, displayed: false), sendEvent(name: "supportedThermostatFanModes", value: supportedFanModes, isStateChange: true, displayed: false)
response(refresh()) ]
} }
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
log.debug "Zwave event received: $cmd" log.debug "Zwave BasicReport: $cmd"
} }
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [ name: "battery", unit: "%" ] def batteryState = cmd.batteryLevel
if (cmd.batteryLevel == 0xFF) { def map = [name: "battery", unit: "%", value: cmd.batteryLevel]
if ((cmd.batteryLevel == 0xFF) || (cmd.batteryLevel == 0x00)) { // Special value for low battery alert
map.value = 1 map.value = 1
map.descriptionText = "${device.displayName} battery is low" map.descriptionText = "${device.displayName} battery is low"
map.isStateChange = true map.isStateChange = true
} else { batteryState = "low_battery"
map.value = cmd.batteryLevel
} }
state.lastbatt = now() state.lastbatt = now()
createEvent(map) sendEvent(name: "batteryIcon", value: batteryState, displayed: false)
sendEvent(map)
} }
def zwaveEvent(physicalgraph.zwave.Command cmd) { def zwaveEvent(physicalgraph.zwave.Command cmd) {
log.warn "Unexpected zwave command $cmd" log.warn "Unexpected zwave command $cmd"
} }
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
log.debug "ManufacturerSpecificReport ${cmd}: value:${cmd}"
if (cmd.manufacturerName) {
updateDataValue("manufacturer", cmd.manufacturerName)
}
if (cmd.productTypeId) {
updateDataValue("productTypeId", cmd.productTypeId.toString())
}
if (cmd.productId) {
updateDataValue("productId", cmd.productId.toString())
}
}
def refresh() { def refresh() {
// Use encapsulation to differentiate refresh cmds from what the thermostat sends proactively on change // Only allow refresh every 2 minutes to prevent flooding the Zwave network
def cmd = zwave.sensorMultilevelV2.sensorMultilevelGet() def timeNow = now()
zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:1).encapsulate(cmd).format() if (!state.refreshTriggeredAt || (2 * 60 * 1000 < (timeNow - state.refreshTriggeredAt))) {
} state.refreshTriggeredAt = timeNow
// refresh will request battery, prevent multiple request by setting lastbatt now
def nextRefreshQuery(name) { state.lastbatt = timeNow
def cmd = null // use runIn with overwrite to prevent multiple DTH instances run before state.refreshTriggeredAt has been saved
switch (name) { runIn(2, "poll", [overwrite: true])
case "temperature":
cmd = zwave.thermostatModeV2.thermostatModeGet()
break
case "thermostatMode":
cmd = zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1)
break
case "heatingSetpoint":
cmd = zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2)
break
case "coolingSetpoint":
cmd = zwave.thermostatFanModeV3.thermostatFanModeGet()
break
case "thermostatFanMode":
cmd = zwave.thermostatOperatingStateV2.thermostatOperatingStateGet()
break
case "thermostatOperatingState":
// get humidity, multilevel sensor get to endpoint 2
cmd = zwave.sensorMultilevelV2.sensorMultilevelGet()
return zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:2).encapsulate(cmd).format()
default: return null
} }
zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:1).encapsulate(cmd).format()
} }
def quickSetHeat(degrees) { def poll() {
setHeatingSetpoint(degrees, 1000) def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 1).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // temperature
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()) // HeatingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()) // CoolingSetpoint
cmds << new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format())
cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 2).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // humidity
def time = getTimeAndDay()
log.debug "time: $time"
if (time) {
cmds << new physicalgraph.device.HubAction(zwave.clockV1.clockSet(time).format())
}
// Add 3 seconds delay between each command to avoid flooding the Z-Wave network choking the hub
sendHubCommand(cmds, 3000)
} }
def setHeatingSetpoint(degrees, delay = 30000) { def raiseHeatingSetpoint() {
setHeatingSetpoint(degrees.toDouble(), delay) alterSetpoint(null, true, "heatingSetpoint")
} }
def setHeatingSetpoint(Double degrees, Integer delay = 30000) { def lowerHeatingSetpoint() {
log.trace "setHeatingSetpoint($degrees, $delay)" alterSetpoint(null, false, "heatingSetpoint")
def deviceScale = state.scale ?: 1 }
def deviceScaleString = deviceScale == 2 ? "C" : "F"
def raiseCoolSetpoint() {
alterSetpoint(null, true, "coolingSetpoint")
}
def lowerCoolSetpoint() {
alterSetpoint(null, false, "coolingSetpoint")
}
// Adjusts nextHeatingSetpoint either .5° C/1° F) if raise true/false
def alterSetpoint(degrees, raise, setpoint) {
def locationScale = getTemperatureScale() def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
def convertedDegrees def targetvalue = (setpoint == "heatingSetpoint") ? heatingSetpoint : coolingSetpoint
if (locationScale == "C" && deviceScaleString == "F") { def delta = (locationScale == "F") ? 1 : 0.5
convertedDegrees = celsiusToFahrenheit(degrees) if (raise != null) {
} else if (locationScale == "F" && deviceScaleString == "C") { targetvalue += raise ? delta : - delta
convertedDegrees = fahrenheitToCelsius(degrees) } else if (degrees) {
targetvalue = degrees
} else { } else {
convertedDegrees = degrees log.warn "alterSetpoint called with neither up/down/degree information"
return
}
def data = enforceSetpointLimits(setpoint, [targetvalue: targetvalue, heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
// update UI without waiting for the device to respond, this to give user a smoother UI experience
// also, as runIn's have to overwrite and user can change heating/cooling setpoint separately separate runIn's have to be used
if (data.targetHeatingSetpoint) {
sendEvent("name": "heatingSetpoint", "value": data.targetHeatingSetpoint, unit: locationScale, eventType: "ENTITY_UPDATE")//, displayed: false)
runIn(4, "updateHeatingSetpoint", [data: data, overwrite: true])
}
if (data.targetCoolingSetpoint) {
sendEvent("name": "coolingSetpoint", "value": data.targetCoolingSetpoint, unit: locationScale, eventType: "ENTITY_UPDATE")//, displayed: false)
runIn(4, "updateCoolingSetpoint", [data: data, overwrite: true])
}
}
def updateHeatingSetpoint(data) {
updateSetpoints(data)
}
def updateCoolingSetpoint(data) {
updateSetpoints(data)
}
def enforceSetpointLimits(setpoint, data) {
// Enforce max/min for setpoints
def maxSetpoint = getTempInLocalScale(95, "F")
def minSetpoint = getTempInLocalScale(35, "F")
def targetvalue = data.targetvalue
def heatingSetpoint = null
def coolingSetpoint = null
if (targetvalue > maxSetpoint) {
targetvalue = maxSetpoint
} else if (targetvalue < minSetpoint) {
targetvalue = minSetpoint
}
// Enforce limits, for now make sure heating <= cooling, and cooling >= heating
if (setpoint == "heatingSetpoint") {
heatingSetpoint = targetvalue
coolingSetpoint = (heatingSetpoint > data.coolingSetpoint) ? heatingSetpoint : null
}
if (setpoint == "coolingSetpoint") {
coolingSetpoint = targetvalue
heatingSetpoint = (coolingSetpoint < data.heatingSetpoint) ? coolingSetpoint : null
}
return [targetHeatingSetpoint: heatingSetpoint, targetCoolingSetpoint: coolingSetpoint]
}
def setHeatingSetpoint(degrees) {
if (degrees) {
def data = enforceSetpointLimits("heatingSetpoint",
[targetvalue: degrees.toDouble(), heatingSetpoint: getTempInLocalScale("heatingSetpoint"), coolingSetpoint: getTempInLocalScale("coolingSetpoint")])
updateSetpoints(data)
}
}
def setCoolingSetpoint(degrees) {
if (degrees) {
def data = enforceSetpointLimits("coolingSetpoint",
[targetvalue: degrees.toDouble(), heatingSetpoint: getTempInLocalScale("heatingSetpoint"), coolingSetpoint: getTempInLocalScale("coolingSetpoint")])
updateSetpoints(data)
}
}
def updateSetpoints(data) {
def cmds = []
if (data.targetHeatingSetpoint) {
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 1, scale: state.scale, precision: state.precision, scaledValue: convertToDeviceScale(data.targetHeatingSetpoint)).format())
}
if (data.targetCoolingSetpoint) {
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
setpointType: 2, scale: state.scale, precision: state.precision, scaledValue: convertToDeviceScale(data.targetCoolingSetpoint)).format())
} }
delayBetween([ // Always request both setpoints in case thermostat changed both
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 1, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(), cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format())
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format() cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format())
], delay) sendHubCommand(cmds)
} }
def quickSetCool(degrees) { def convertToDeviceScale(setpoint) {
setCoolingSetpoint(degrees, 1000)
}
def setCoolingSetpoint(degrees, delay = 30000) {
setCoolingSetpoint(degrees.toDouble(), delay)
}
def setCoolingSetpoint(Double degrees, Integer delay = 30000) {
log.trace "setCoolingSetpoint($degrees, $delay)"
def deviceScale = state.scale ?: 1
def deviceScaleString = deviceScale == 2 ? "C" : "F"
def locationScale = getTemperatureScale() def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision def deviceScale = (state.scale == 1) ? "F" : "C"
return (deviceScale == locationScale) ? setpoint :
def convertedDegrees (deviceScale == "F" ? celsiusToFahrenheit(setpoint.toBigDecimal()) : roundC(fahrenheitToCelsius(setpoint.toBigDecimal())))
if (locationScale == "C" && deviceScaleString == "F") {
convertedDegrees = celsiusToFahrenheit(degrees)
} else if (locationScale == "F" && deviceScaleString == "C") {
convertedDegrees = fahrenheitToCelsius(degrees)
} else {
convertedDegrees = degrees
}
delayBetween([
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 2, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()
], delay)
} }
/** /**
@@ -456,78 +537,56 @@ def setCoolingSetpoint(Double degrees, Integer delay = 30000) {
* */ * */
def ping() { def ping() {
log.debug "ping() called" log.debug "ping() called"
refresh() // Just get Operating State as it is not reported when it chnages and there's no need to flood more commands
} sendHubCommand(new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format()))
def configure() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSupportedGet().format(),
], 2300)
}
def modes() {
["off", "heat", "cool", "auto", "emergency heat"]
} }
def switchMode() { def switchMode() {
def currentMode = device.currentState("thermostatMode")?.value def currentMode = device.currentValue("thermostatMode")
def lastTriedMode = state.lastTriedMode ?: currentMode ?: "off" def lastTriedMode = state.lastTriedMode ?: currentMode ?: "off"
def supportedModes = getDataByName("supportedModes") def supportedModes = state.supportedModes
def modeOrder = modes() if (supportedModes) {
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] } def next = { supportedModes[supportedModes.indexOf(it) + 1] ?: supportedModes[0] }
def nextMode = next(lastTriedMode) def nextMode = next(lastTriedMode)
if (supportedModes?.contains(currentMode)) { setThermostatMode(nextMode)
while (!supportedModes.contains(nextMode) && nextMode != "off") { state.lastTriedMode = nextMode
nextMode = next(nextMode) } else {
} log.warn "supportedModes not defined"
} }
state.lastTriedMode = nextMode
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[nextMode]).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], 1000)
} }
def switchToMode(nextMode) { def switchToMode(nextMode) {
def supportedModes = getDataByName("supportedModes") def supportedModes = state.supportedModes
if(supportedModes && !supportedModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported" if (supportedModes && supportedModes.contains(nextMode)) {
if (nextMode in modes()) { setThermostatMode(nextMode)
state.lastTriedMode = nextMode state.lastTriedMode = nextMode
"$nextMode"()
} else { } else {
log.debug("no mode method '$nextMode'") log.debug("ThermostatMode $nextMode is not supported by ${device.displayName}")
} }
} }
def switchFanMode() { def switchFanMode() {
def currentMode = device.currentState("thermostatFanMode")?.value def currentMode = device.currentState("thermostatFanMode")?.value
def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: "off" def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: "off"
def supportedModes = getDataByName("supportedFanModes") ?: "fanAuto fanOn" def supportedFanModes = state.supportedFanModes
def modeOrder = ["fanAuto", "fanCirculate", "fanOn"] if (supportedFanModes) {
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] } def next = { supportedFanModes[supportedFanModes.indexOf(it) + 1] ?: supportedFanModes[0] }
def nextMode = next(lastTriedMode) def nextMode = next(lastTriedMode)
while (!supportedModes?.contains(nextMode) && nextMode != "fanAuto") { setThermostatFanMode(nextMode)
nextMode = next(nextMode) state.lastTriedFanMode = nextMode
} else {
log.warn "supportedFanModes not defined"
} }
switchToFanMode(nextMode)
} }
def switchToFanMode(nextMode) { def switchToFanMode(nextMode) {
def supportedFanModes = getDataByName("supportedFanModes") def supportedFanModes = state.supportedFanModes
if(supportedFanModes && !supportedFanModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported" if (supportedFanModes && supportedFanModes.contains(nextMode)) {
setThermostatFanMode(nextMode)
def returnCommand state.lastTriedFanMode = nextMode
if (nextMode == "fanAuto") {
returnCommand = fanAuto()
} else if (nextMode == "fanOn") {
returnCommand = fanOn()
} else if (nextMode == "fanCirculate") {
returnCommand = fanCirculate()
} else { } else {
log.debug("no fan mode '$nextMode'") log.debug("FanMode $nextMode is not supported by ${device.displayName}")
} }
if(returnCommand) state.lastTriedFanMode = nextMode
returnCommand
} }
def getDataByName(String name) { def getDataByName(String name) {
@@ -543,10 +602,10 @@ def getModeMap() { [
]} ]}
def setThermostatMode(String value) { def setThermostatMode(String value) {
delayBetween([ def cmds = []
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format(), cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format())
zwave.thermostatModeV2.thermostatModeGet().format() cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
], standardDelay) sendHubCommand(cmds)
} }
def getFanModeMap() { [ def getFanModeMap() { [
@@ -556,69 +615,70 @@ def getFanModeMap() { [
]} ]}
def setThermostatFanMode(String value) { def setThermostatFanMode(String value) {
delayBetween([ def cmds = []
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[value]).format(), cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[value]).format())
zwave.thermostatFanModeV3.thermostatFanModeGet().format() cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
], standardDelay) sendHubCommand(cmds)
} }
def off() { def off() {
delayBetween([ switchToMode("off")
zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
} }
def heat() { def heat() {
delayBetween([ switchToMode("heat")
zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
} }
def emergencyHeat() { def emergencyHeat() {
delayBetween([ switchToMode("emergency heat")
zwave.thermostatModeV2.thermostatModeSet(mode: 4).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
} }
def cool() { def cool() {
delayBetween([ switchToMode("cool")
zwave.thermostatModeV2.thermostatModeSet(mode: 2).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
} }
def auto() { def auto() {
delayBetween([ switchToMode("auto")
zwave.thermostatModeV2.thermostatModeSet(mode: 3).format(),
zwave.thermostatModeV2.thermostatModeGet().format()
], standardDelay)
} }
def fanOn() { def fanOn() {
delayBetween([ switchToFanMode("on")
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 1).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
} }
def fanAuto() { def fanAuto() {
delayBetween([ switchToFanMode("auto")
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 0).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
} }
def fanCirculate() { def fanCirculate() {
delayBetween([ switchToFanMode("circulate")
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 6).format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
], standardDelay)
} }
private getStandardDelay() { private getTimeAndDay() {
1000 def timeNow = now()
// Need to check that location have timeZone as SC may have created the location without setting it
// Don't update clock more than once a day
if (location.timeZone && (!state.timeClockSet || (24 * 60 * 60 * 1000 < (timeNow - state.timeClockSet)))) {
def currentDate = Calendar.getInstance(location.timeZone)
state.timeClockSet = timeNow
return [hour: currentDate.get(Calendar.HOUR_OF_DAY), minute: currentDate.get(Calendar.MINUTE), weekday: currentDate.get(Calendar.DAY_OF_WEEK)]
}
} }
// Get stored temperature from currentState in current local scale
def getTempInLocalScale(state) {
def temp = device.currentState(state)
if (temp && temp.value && temp.unit) {
return getTempInLocalScale(temp.value.toBigDecimal(), temp.unit)
}
return 0
}
// get/convert temperature to current local scale
def getTempInLocalScale(temp, scale) {
def scaledTemp = convertTemperatureIfNeeded(temp.toBigDecimal(), scale).toDouble()
return (getTemperatureScale() == "F" ? scaledTemp.round(0).toInteger() : roundC(scaledTemp))
}
def roundC (tempC) {
return (Math.round(tempC.toDouble() * 2))/2
}

View File

@@ -158,7 +158,7 @@ def generateEvent(Map results) {
if(results) { if(results) {
def linkText = getLinkText(device) def linkText = getLinkText(device)
def supportedThermostatModes = [] def supportedThermostatModes = ["off"]
def thermostatMode = null def thermostatMode = null
results.each { name, value -> results.each { name, value ->

View File

@@ -105,6 +105,8 @@ def parse(String description) {
} else { } else {
log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}"
} }
} else if (descMap?.clusterInt == zigbee.IAS_ZONE_CLUSTER && descMap.attrInt == zigbee.ATTRIBUTE_IAS_ZONE_STATUS && descMap?.value) {
map = translateZoneStatus(new ZoneStatus(zigbee.convertToInt(descMap?.value)))
} }
} }
} else if (map.name == "temperature") { } else if (map.name == "temperature") {
@@ -129,6 +131,10 @@ def parse(String description) {
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) ZoneStatus zs = zigbee.parseZoneStatus(description)
translateZoneStatus(zs)
}
private Map translateZoneStatus(ZoneStatus zs) {
return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry') return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry')
} }
@@ -197,7 +203,8 @@ def ping() {
def refresh() { def refresh() {
log.debug "Refreshing Temperature and Battery" log.debug "Refreshing Temperature and Battery"
def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS)
return refreshCmds + zigbee.enrollResponse() return refreshCmds + zigbee.enrollResponse()
} }

View File

@@ -111,6 +111,8 @@ def parse(String description) {
def value = descMap.value.endsWith("01") ? "active" : "inactive" def value = descMap.value.endsWith("01") ? "active" : "inactive"
log.debug "Doing a read attr motion event" log.debug "Doing a read attr motion event"
map = getMotionResult(value) map = getMotionResult(value)
} else if (descMap?.clusterInt == zigbee.IAS_ZONE_CLUSTER && descMap.attrInt == zigbee.ATTRIBUTE_IAS_ZONE_STATUS && descMap?.value) {
map = translateZoneStatus(new ZoneStatus(zigbee.convertToInt(descMap?.value)))
} }
} }
} else if (map.name == "temperature") { } else if (map.name == "temperature") {
@@ -135,6 +137,10 @@ def parse(String description) {
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) ZoneStatus zs = zigbee.parseZoneStatus(description)
translateZoneStatus(zs)
}
private Map translateZoneStatus(ZoneStatus zs) {
// Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion // Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive') return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
} }
@@ -165,13 +171,29 @@ private Map getBatteryResult(rawValue) {
def pct = batteryMap[volts] def pct = batteryMap[volts]
result.value = pct result.value = pct
} else { } else {
def minVolts = 2.1 def minVolts = 2.4
def maxVolts = 3.0 def maxVolts = 2.7
def pct = (volts - minVolts) / (maxVolts - minVolts) // Get the current battery percentage as a multiplier 0 - 1
def roundedPct = Math.round(pct * 100) def curValVolts = Integer.parseInt(device.currentState("battery")?.value ?: "100") / 100.0
if (roundedPct <= 0) // Find the corresponding voltage from our range
roundedPct = 1 curValVolts = curValVolts * (maxVolts - minVolts) + minVolts
result.value = Math.min(100, roundedPct) // Round to the nearest 10th of a volt
curValVolts = Math.round(10 * curValVolts) / 10.0
// Only update the battery reading if we don't have a last reading,
// OR we have received the same reading twice in a row
// OR we don't currently have a battery reading
// OR the value we just received is at least 2 steps off from the last reported value
if(state?.lastVolts == null || state?.lastVolts == volts || device.currentState("battery")?.value == null || Math.abs(curValVolts - volts) > 0.1) {
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
} else {
// Don't update as we want to smooth the battery values
result = null
}
state.lastVolts = volts
} }
} }
@@ -200,7 +222,8 @@ def refresh() {
log.debug "refresh called" log.debug "refresh called"
def refreshCmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + def refreshCmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS)
return refreshCmds + zigbee.enrollResponse() return refreshCmds + zigbee.enrollResponse()
} }

View File

@@ -134,8 +134,9 @@ def parse(String description) {
} else { } else {
log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}"
} }
} else if (descMap?.clusterInt == zigbee.IAS_ZONE_CLUSTER && descMap.attrInt == zigbee.ATTRIBUTE_IAS_ZONE_STATUS && descMap?.value) {
maps += translateZoneStatus(new ZoneStatus(zigbee.convertToInt(descMap?.value)))
} else { } else {
maps += handleAcceleration(descMap) maps += handleAcceleration(descMap)
} }
} }
@@ -229,6 +230,11 @@ private List<Map> parseAxis(List<Map> attrData) {
private List<Map> parseIasMessage(String description) { private List<Map> parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) ZoneStatus zs = zigbee.parseZoneStatus(description)
translateZoneStatus(zs)
}
private List<Map> translateZoneStatus(ZoneStatus zs) {
List<Map> results = [] List<Map> results = []
if (garageSensor != "Yes") { if (garageSensor != "Yes") {
@@ -268,12 +274,28 @@ private Map getBatteryResult(rawValue) {
result.value = pct result.value = pct
} else { } else {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 2.7
def pct = (volts - minVolts) / (maxVolts - minVolts) // Get the current battery percentage as a multiplier 0 - 1
def roundedPct = Math.round(pct * 100) def curValVolts = Integer.parseInt(device.currentState("battery")?.value ?: "100") / 100.0
if (roundedPct <= 0) // Find the corresponding voltage from our range
roundedPct = 1 curValVolts = curValVolts * (maxVolts - minVolts) + minVolts
result.value = Math.min(100, roundedPct) // Round to the nearest 10th of a volt
curValVolts = Math.round(10 * curValVolts) / 10.0
// Only update the battery reading if we don't have a last reading,
// OR we have received the same reading twice in a row
// OR we don't currently have a battery reading
// OR the value we just received is at least 2 steps off from the last reported value
if(state?.lastVolts == null || state?.lastVolts == volts || device.currentState("battery")?.value == null || Math.abs(curValVolts - volts) > 0.1) {
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
} else {
// Don't update as we want to smooth the battery values
result = null
}
state.lastVolts = volts
} }
} }
@@ -313,7 +335,7 @@ def refresh() {
def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) +
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode]) + zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode]) +
zigbee.enrollResponse() zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS) + zigbee.enrollResponse()
return refreshCmds return refreshCmds
} }

View File

@@ -0,0 +1,2 @@
.st-ignore
README.md

View File

@@ -0,0 +1,42 @@
# ZigBee Button
Cloud Execution
Works with:
* [OSRAM LIGHTIFY Dimming Switch](https://support.smartthings.com/hc/en-us/articles/115000236823-SYLVANIA-Dimming-Switch)
* [Iris Smart Button](https://support.smartthings.com/hc/en-us/articles/115000190186-Iris-Smart-Button)
* [Iris KeyFob](https://support.smartthings.com/hc/en-us/articles/217409686-Iris-Smart-Fob)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Troubleshooting](#troubleshooting)
## Capabilities
* **Actuator** - It represents that a device has commands.
* **Battery** - It defines that the device has a battery
* **Button** - It defines that a device has one or more buttons
* **Holdable Button** - It defines that a device has one or more holdable buttons
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Refresh** - _refresh()_ command for status updates
* **Sensor** - it represents that a Device has attributes.
* **Health Check** - indicates ability to get device health notifications
## Device Health
ZigBee Button is marked offline only in the case when Hub is offline.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
It may also happen that you need to reset the device.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [OSRAM LIGHTIFY Dimming Switch Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/115000236823-SYLVANIA-Dimming-Switch)
* [Iris Smart Button Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/115000190186-Iris-Smart-Button)
* [Iris KeyFob Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/217409686-Iris-Smart-Fob)

View File

@@ -13,6 +13,8 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
import groovy.json.JsonOutput
import physicalgraph.zigbee.zcl.DataType import physicalgraph.zigbee.zcl.DataType
metadata { metadata {
@@ -24,6 +26,7 @@ metadata {
capability "Configuration" capability "Configuration"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
capability "Health Check"
command "enrollResponse" command "enrollResponse"
@@ -249,6 +252,8 @@ def updated() {
} }
def initialize() { def initialize() {
// Arrival sensors only goes OFFLINE when Hub is off
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
if ((device.getDataValue("manufacturer") == "OSRAM") && (device.getDataValue("model") == "LIGHTIFY Dimming Switch")) { if ((device.getDataValue("manufacturer") == "OSRAM") && (device.getDataValue("model") == "LIGHTIFY Dimming Switch")) {
sendEvent(name: "numberOfButtons", value: 2) sendEvent(name: "numberOfButtons", value: 2)
} }

View File

@@ -0,0 +1,2 @@
.st-ignore
README.md

View File

@@ -0,0 +1,39 @@
# Z-wave Basic Smoke Alarm
Cloud Execution
Works with:
* [First Alert Smoke Detector (ZSMOKE)](https://www.smartthings.com/products/first-alert-smoke-detector)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
* [Troubleshooting](#troubleshooting)
## Capabilities
* **Smoke Detector** - measure smoke and optionally carbon monoxide levels
* **Sensor** - detects sensor events
* **Battery** - defines device uses a battery
* **Health Check** - indicates ability to get device health notifications
## Device Health
First Alert Smoke Detector (ZSMOKE) is a Z-wave sleepy device and checks in every 1 hour.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*60 + 2)mins = 122 mins.
* __122min__ checkInterval
## Battery Specification
Two AA 1.5V batteries are required.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [First Alert Smoke Detector (ZSMOKE) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/207150556-First-Alert-Smoke-Detector-ZSMOKE-)

View File

@@ -0,0 +1,181 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Z-Wave Basic Smoke Alarm", namespace: "smartthings", author: "SmartThings") {
capability "Smoke Detector"
capability "Sensor"
capability "Battery"
capability "Health Check"
fingerprint deviceId: "0xA100", inClusters: "0x20,0x80,0x70,0x85,0x71,0x72,0x86"
fingerprint mfr:"0138", prod:"0001", model:"0001", deviceJoinName: "First Alert Smoke Detector"
}
simulator {
status "smoke": "command: 7105, payload: 01 FF"
status "clear": "command: 7105, payload: 01 00"
status "test": "command: 7105, payload: 0C FF"
status "battery 100%": "command: 8003, payload: 64"
status "battery 5%": "command: 8003, payload: 05"
}
tiles (scale: 2){
multiAttributeTile(name:"smoke", type: "lighting", width: 6, height: 4){
tileAttribute ("device.smoke", key: "PRIMARY_CONTROL") {
attributeState("clear", label:"clear", icon:"st.alarm.smoke.clear", backgroundColor:"#ffffff")
attributeState("detected", label:"SMOKE", icon:"st.alarm.smoke.smoke", backgroundColor:"#e86d13")
attributeState("tested", label:"TEST", icon:"st.alarm.smoke.test", backgroundColor:"#e86d13")
}
}
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
main "smoke"
details(["smoke", "battery"])
}
}
def installed() {
// Device checks in every hour, this interval allows us to miss one check-in notification before marking offline
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
def cmds = []
createSmokeEvents("smokeClear", cmds)
cmds.each { cmd -> sendEvent(cmd) }
}
def updated() {
// Device checks in every hour, this interval allows us to miss one check-in notification before marking offline
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
}
def parse(String description) {
def results = []
if (description.startsWith("Err")) {
results << createEvent(descriptionText:description, displayed:true)
} else {
def cmd = zwave.parse(description, [ 0x80: 1, 0x84: 1, 0x71: 2, 0x72: 1 ])
if (cmd) {
zwaveEvent(cmd, results)
}
}
log.debug "'$description' parsed to ${results.inspect()}"
return results
}
def createSmokeEvents(name, results) {
def text = null
switch (name) {
case "smoke":
text = "$device.displayName smoke was detected!"
// these are displayed:false because the composite event is the one we want to see in the app
results << createEvent(name: "smoke", value: "detected", descriptionText: text)
break
case "tested":
text = "$device.displayName was tested"
results << createEvent(name: "smoke", value: "tested", descriptionText: text)
break
case "smokeClear":
text = "$device.displayName smoke is clear"
results << createEvent(name: "smoke", value: "clear", descriptionText: text)
name = "clear"
break
case "testClear":
text = "$device.displayName test cleared"
results << createEvent(name: "smoke", value: "clear", descriptionText: text)
name = "clear"
break
}
}
def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
if (cmd.zwaveAlarmType == physicalgraph.zwave.commands.alarmv2.AlarmReport.ZWAVE_ALARM_TYPE_SMOKE) {
if (cmd.zwaveAlarmEvent == 3) {
createSmokeEvents("tested", results)
} else {
createSmokeEvents((cmd.zwaveAlarmEvent == 1 || cmd.zwaveAlarmEvent == 2) ? "smoke" : "smokeClear", results)
}
} else switch(cmd.alarmType) {
case 1:
createSmokeEvents(cmd.alarmLevel ? "smoke" : "smokeClear", results)
break
case 12: // test button pressed
createSmokeEvents(cmd.alarmLevel ? "tested" : "testClear", results)
break
case 13: // sent every hour -- not sure what this means, just a wake up notification?
if (cmd.alarmLevel == 255) {
results << createEvent(descriptionText: "$device.displayName checked in", isStateChange: false)
} else {
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", isStateChange:true, displayed:false)
}
// Clear smoke in case they pulled batteries and we missed the clear msg
if(device.currentValue("smoke") != "clear") {
createSmokeEvents("smokeClear", results)
}
// Check battery if we don't have a recent battery event
if (!state.lastbatt || (now() - state.lastbatt) >= 48*60*60*1000) {
results << response(zwave.batteryV1.batteryGet())
}
break
default:
results << createEvent(displayed: true, descriptionText: "Alarm $cmd.alarmType ${cmd.alarmLevel == 255 ? 'activated' : cmd.alarmLevel ?: 'deactivated'}".toString())
break
}
}
// SensorBinary and SensorAlarm aren't tested, but included to preemptively support future smoke alarms
//
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd, results) {
if (cmd.sensorType == physicalgraph.zwave.commandclasses.SensorBinaryV2.SENSOR_TYPE_SMOKE) {
createSmokeEvents(cmd.sensorValue ? "smoke" : "smokeClear", results)
}
}
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd, results) {
if (cmd.sensorType == 1) {
createSmokeEvents(cmd.sensorState ? "smoke" : "smokeClear", results)
}
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd, results) {
results << createEvent(descriptionText: "$device.displayName woke up", isStateChange: false)
if (!state.lastbatt || (now() - state.lastbatt) >= 56*60*60*1000) {
results << response(zwave.batteryV1.batteryGet(), "delay 2000", zwave.wakeUpV1.wakeUpNoMoreInformation())
} else {
results << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
}
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd, results) {
def map = [ name: "battery", unit: "%", isStateChange: true ]
state.lastbatt = now()
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = "$device.displayName battery is low!"
} else {
map.value = cmd.batteryLevel
}
results << createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.Command cmd, results) {
def event = [ displayed: false ]
event.linkText = device.label ?: device.name
event.descriptionText = "$event.linkText: $cmd"
results << createEvent(event)
}

View File

@@ -88,19 +88,21 @@ import physicalgraph.zwave.commands.usercodev1.*
def installed() { def installed() {
// Device-Watch pings if no device events received for 1 hour (checkInterval) // Device-Watch pings if no device events received for 1 hour (checkInterval)
sendEvent(name: "checkInterval", value: 1 * 60 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 1 * 60 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
try {
if (!state.init) {
state.init = true
// Wait long enough for behind-the-scenes z-wave magic to finish, but be quick enough before hub goes back into inclusion and blocks us
response(["delay 2000"] + secureSequence([zwave.doorLockV1.doorLockOperationGet(), zwave.batteryV1.batteryGet()], 2200))
}
} catch (e) {
log.warn "installed() threw $e"
}
} }
def updated() { def updated() {
// Device-Watch pings if no device events received for 1 hour (checkInterval) // Device-Watch pings if no device events received for 1 hour (checkInterval)
sendEvent(name: "checkInterval", value: 1 * 60 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 1 * 60 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
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 parse(String description) {

View File

@@ -72,7 +72,7 @@ def createEvents(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def poll() { def poll() {
if (secondsPast(state.lastbatt, 36*60*60)) { if (secondsPast(state.lastbatt, 36*60*60)) {
return zwave.batteryV1.batteryGet().format return zwave.batteryV1.batteryGet().format()
} else { } else {
return null return null
} }

View File

@@ -21,8 +21,6 @@ metadata {
attribute "alarmState", "string" attribute "alarmState", "string"
fingerprint deviceId: "0xA100", inClusters: "0x20,0x80,0x70,0x85,0x71,0x72,0x86"
fingerprint mfr:"0138", prod:"0001", model:"0001", deviceJoinName: "First Alert Smoke Detector"
fingerprint mfr:"0138", prod:"0001", model:"0002", deviceJoinName: "First Alert Smoke Detector and Carbon Monoxide Alarm (ZCOMBO)" fingerprint mfr:"0138", prod:"0001", model:"0002", deviceJoinName: "First Alert Smoke Detector and Carbon Monoxide Alarm (ZCOMBO)"
} }
@@ -57,6 +55,10 @@ metadata {
def installed() { def installed() {
// Device checks in every hour, this interval allows us to miss one check-in notification before marking offline // Device checks in every hour, this interval allows us to miss one check-in notification before marking offline
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
def cmds = []
createSmokeOrCOEvents("allClear", cmds) // allClear to set inital states for smoke and CO
cmds.each { cmd -> sendEvent(cmd) }
} }
def updated() { def updated() {
@@ -105,6 +107,12 @@ def createSmokeOrCOEvents(name, results) {
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false) results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
name = "clear" name = "clear"
break break
case "allClear":
text = "$device.displayName all clear"
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
name = "clear"
break
case "testClear": case "testClear":
text = "$device.displayName test cleared" text = "$device.displayName test cleared"
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false) results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)

View File

@@ -15,10 +15,9 @@ metadata {
definition (name: "Z-Wave Thermostat", namespace: "smartthings", author: "SmartThings") { definition (name: "Z-Wave Thermostat", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Actuator"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Thermostat" capability "Thermostat"
capability "Configuration" capability "Configuration"
capability "Polling" capability "Refresh"
capability "Sensor" capability "Sensor"
capability "Health Check" capability "Health Check"
@@ -117,7 +116,7 @@ metadata {
state "cool", label:'${currentValue}° cool', backgroundColor:"#ffffff" state "cool", label:'${currentValue}° cool', backgroundColor:"#ffffff"
} }
standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
state "default", action:"polling.poll", icon:"st.secondary.refresh" state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main "temperature" main "temperature"
details(["temperature", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "refresh"]) details(["temperature", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "refresh"])
@@ -125,13 +124,20 @@ metadata {
} }
def installed(){ def installed(){
// Device-Watch simply pings if no device events received for 32min(checkInterval) sendHubCommand(new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format()))
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) initialize()
} }
def updated(){ def updated(){
initialize()
}
def initialize() {
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
unschedule()
runEvery5Minutes("refresh")
refresh()
} }
def parse(String description) def parse(String description)
@@ -149,6 +155,7 @@ def parse(String description)
] ]
if (map.name == "thermostatMode") { if (map.name == "thermostatMode") {
state.lastTriedMode = map.value state.lastTriedMode = map.value
map.data = [supportedThermostatModes:state.supportedThermostatModes]
if (map.value == "cool") { if (map.value == "cool") {
map2.value = device.latestValue("coolingSetpoint") map2.value = device.latestValue("coolingSetpoint")
log.info "THERMOSTAT, latest cooling setpoint = ${map2.value}" log.info "THERMOSTAT, latest cooling setpoint = ${map2.value}"
@@ -172,6 +179,7 @@ def parse(String description)
} }
} else if (map.name == "thermostatFanMode" && map.isStateChange) { } else if (map.name == "thermostatFanMode" && map.isStateChange) {
state.lastTriedFanMode = map.value state.lastTriedFanMode = map.value
map.data = [supportedThermostatFanModes: state.supportedThermostatFanModes]
} }
log.debug "Parse returned $result" log.debug "Parse returned $result"
result result
@@ -305,26 +313,26 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanMod
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) {
def supportedModes = "" def supportedModes = []
if(cmd.off) { supportedModes += "off " } if(cmd.off) { supportedModes << "off" }
if(cmd.heat) { supportedModes += "heat " } if(cmd.heat) { supportedModes << "heat" }
if(cmd.auxiliaryemergencyHeat) { supportedModes += "emergency heat " } if(cmd.cool) { supportedModes << "cool" }
if(cmd.cool) { supportedModes += "cool " } if(cmd.auto) { supportedModes << "auto" }
if(cmd.auto) { supportedModes += "auto " } if(cmd.auxiliaryemergencyHeat) { supportedModes << "emergency heat" }
state.supportedModes = supportedModes state.supportedThermostatModes = supportedModes
// No events to be generated, return empty map sendEvent(name: "supportedThermostatModes", value: supportedModes, displayed: false)
return [:] return [:]
} }
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) {
def supportedFanModes = "" def supportedFanModes = []
if(cmd.auto) { supportedFanModes += "auto " } // "fanAuto " if(cmd.auto) { supportedFanModes << "auto" } // "fanAuto "
if(cmd.low) { supportedFanModes += "on " } // "fanOn" if(cmd.circulation) { supportedFanModes << "circulate" } // "fanCirculate"
if(cmd.circulation) { supportedFanModes += "circulate " } // "fanCirculate" if(cmd.low) { supportedFanModes << "on" } // "fanOn"
state.supportedFanModes = supportedFanModes state.supportedThermostatFanModes = supportedFanModes
// No events to be generated, return empty map sendEvent(name: "supportedThermostatFanModes", value: supportedFanModes, displayed: false)
return [:] return [:]
} }
@@ -337,15 +345,17 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
} }
// Command Implementations // Command Implementations
def poll() { def refresh() {
delayBetween([ def cmds = []
zwave.sensorMultilevelV3.sensorMultilevelGet().format(), // current temperature cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format(), cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format(), cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
zwave.thermostatModeV2.thermostatModeGet().format(), cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
zwave.thermostatFanModeV3.thermostatFanModeGet().format(), cmds << new physicalgraph.device.HubAction(zwave.sensorMultilevelV2.sensorMultilevelGet().format()) // current temperature
zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format() cmds << new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format())
], 2300) cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format())
sendHubCommand(cmds)
} }
def quickSetHeat(degrees) { def quickSetHeat(degrees) {
@@ -416,28 +426,14 @@ def ping() {
poll() poll()
} }
def configure() {
delayBetween([
zwave.thermostatModeV2.thermostatModeSupportedGet().format(),
zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format(),
zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format(),
zwave.sensorMultilevelV3.sensorMultilevelGet().format(), // current temperature
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format(),
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format(),
zwave.thermostatModeV2.thermostatModeGet().format(),
zwave.thermostatFanModeV3.thermostatFanModeGet().format(),
zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format()
], 2300)
}
def modes() { def modes() {
["off", "heat", "cool", "auto", "emergency heat"] return state.supportedThermostatModes
} }
def switchMode() { def switchMode() {
def currentMode = device.currentState("thermostatMode")?.value def currentMode = device.currentState("thermostatMode")?.value
def lastTriedMode = state.lastTriedMode ?: currentMode ?: "off" def lastTriedMode = state.lastTriedMode ?: currentMode ?: ["off"]
def supportedModes = getDataByName("supportedModes") def supportedModes = getDataByName("supportedThermostatModes")
def modeOrder = modes() def modeOrder = modes()
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] } def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(lastTriedMode) def nextMode = next(lastTriedMode)
@@ -454,7 +450,7 @@ def switchMode() {
} }
def switchToMode(nextMode) { def switchToMode(nextMode) {
def supportedModes = getDataByName("supportedModes") def supportedModes = getDataByName("supportedThermostatModes")
if(supportedModes && !supportedModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported" if(supportedModes && !supportedModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
if (nextMode in modes()) { if (nextMode in modes()) {
state.lastTriedMode = nextMode state.lastTriedMode = nextMode
@@ -466,9 +462,9 @@ def switchToMode(nextMode) {
def switchFanMode() { def switchFanMode() {
def currentMode = device.currentState("thermostatFanMode")?.value def currentMode = device.currentState("thermostatFanMode")?.value
def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: "off" def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: ["off"]
def supportedModes = getDataByName("supportedFanModes") ?: "auto on" // "fanAuto fanOn" def supportedModes = getDataByName("supportedThermostatFanModes") ?: ["auto", "on"]
def modeOrder = ["auto", "circulate", "on"] // "fanAuto", "fanCirculate", "fanOn" def modeOrder = state.supportedThermostatFanModes
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] } def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(lastTriedMode) def nextMode = next(lastTriedMode)
while (!supportedModes?.contains(nextMode) && nextMode != "auto") { // "fanAuto" while (!supportedModes?.contains(nextMode) && nextMode != "auto") { // "fanAuto"
@@ -478,7 +474,7 @@ def switchFanMode() {
} }
def switchToFanMode(nextMode) { def switchToFanMode(nextMode) {
def supportedFanModes = getDataByName("supportedFanModes") def supportedFanModes = getDataByName("supportedThermostatFanModes")
if(supportedFanModes && !supportedFanModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported" if(supportedFanModes && !supportedFanModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
def returnCommand def returnCommand

View File

@@ -58,6 +58,7 @@ metadata {
def installed() { def installed() {
// Device-Watch simply pings if no device events received for 32min(checkInterval) // Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
response(refresh())
} }
def updated() { def updated() {

View File

@@ -2,26 +2,11 @@ import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
/**
* OpenT2T SmartApp Test
*
* Copyright 2016 OpenT2T
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition( definition(
name: "OpenT2T SmartApp Test", name: "OpenT2T SmartApp Test",
namespace: "opent2t", namespace: "opent2t",
author: "OpenT2T", author: "Microsoft",
description: "Test app to test end to end SmartThings scenarios via OpenT2T", description: "SmartApp for end to end SmartThings scenarios via OpenT2T",
category: "SmartThings Labs", category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
@@ -55,16 +40,16 @@ definition(
//Device Inputs //Device Inputs
preferences { preferences {
section("Allow OpenT2T to control these things...") { section("Allow Microsoft to control these things...") {
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false, hideWhenEmpty: true // input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false, hideWhenEmpty: true
input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false, hideWhenEmpty: true // input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false, hideWhenEmpty: true
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false, hideWhenEmpty: true // input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false, hideWhenEmpty: true
input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false, hideWhenEmpty: true // input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false, hideWhenEmpty: true
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false, hideWhenEmpty: true // input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false, hideWhenEmpty: true
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false, hideWhenEmpty: true // input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false, hideWhenEmpty: true
input "switches", "capability.switch", title: "Which Switches and Lights?", multiple: true, required: false, hideWhenEmpty: true input "switches", "capability.switch", title: "Which Switches and Lights?", multiple: true, required: false, hideWhenEmpty: true
input "thermostats", "capability.thermostat", title: "Which Thermostat?", multiple: true, required: false, hideWhenEmpty: true input "thermostats", "capability.thermostat", title: "Which Thermostat?", multiple: true, required: false, hideWhenEmpty: true
input "waterSensors", "capability.waterSensor", title: "Which Water Leak Sensors?", multiple: true, required: false, hideWhenEmpty: true // input "waterSensors", "capability.waterSensor", title: "Which Water Leak Sensors?", multiple: true, required: false, hideWhenEmpty: true
} }
} }
@@ -82,36 +67,32 @@ def getInputs() {
return inputList return inputList
} }
//API external Endpoints //API external Endpoints
mappings { mappings {
path("/devices") { path("/devices") {
action: action: [
[
GET: "getDevices" GET: "getDevices"
] ]
} }
path("/devices/:id") { path("/devices/:id") {
action: action: [
[
GET: "getDevice" GET: "getDevice"
] ]
} }
path("/update/:id") { path("/update/:id") {
action: action: [
[
PUT: "updateDevice" PUT: "updateDevice"
] ]
} }
path("/deviceSubscription") { path("/deviceSubscription") {
action: action: [
[
POST : "registerDeviceChange", POST : "registerDeviceChange",
DELETE: "unregisterDeviceChange" DELETE: "unregisterDeviceChange"
] ]
} }
path("/locationSubscription") { path("/locationSubscription") {
action: action: [
[
POST : "registerDeviceGraph", POST : "registerDeviceGraph",
DELETE: "unregisterDeviceGraph" DELETE: "unregisterDeviceGraph"
] ]
@@ -196,7 +177,7 @@ def registerDeviceChange() {
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}" log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
} else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)) { } else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)) {
// state.deviceSubscriptionMap[deviceId] << subscriptionEndpt // state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
// For now, we will only have one subscription endpoint per device // For now, we will only have one subscription endpoint per device
state.deviceSubscriptionMap.remove(deviceId) state.deviceSubscriptionMap.remove(deviceId)
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt]) state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}" log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
@@ -311,17 +292,17 @@ def deviceEventHandler(evt) {
def evtDeviceType = getDeviceType(evtDevice) def evtDeviceType = getDeviceType(evtDevice)
def deviceData = []; def deviceData = [];
if (evt.data != null) {
def evtData = parseJson(evt.data)
log.info "Received event for ${evtDevice.displayName}, data: ${evtData}, description: ${evt.descriptionText}"
}
if (evtDeviceType == "thermostat") { if (evtDeviceType == "thermostat") {
deviceData = [name: evtDevice.displayName, id: evtDevice.id, status: evtDevice.status, deviceType: evtDeviceType, manufacturer: evtDevice.manufacturerName, model: evtDevice.modelName, attributes: deviceAttributeList(evtDevice, evtDeviceType), locationMode: getLocationModeInfo(), locationId: location.id] deviceData = [name: evtDevice.displayName, id: evtDevice.id, status: evtDevice.status, deviceType: evtDeviceType, manufacturer: evtDevice.manufacturerName, model: evtDevice.modelName, attributes: deviceAttributeList(evtDevice, evtDeviceType), locationMode: getLocationModeInfo(), locationId: location.id]
} else { } else {
deviceData = [name: evtDevice.displayName, id: evtDevice.id, status: evtDevice.status, deviceType: evtDeviceType, manufacturer: evtDevice.manufacturerName, model: evtDevice.modelName, attributes: deviceAttributeList(evtDevice, evtDeviceType), locationId: location.id] deviceData = [name: evtDevice.displayName, id: evtDevice.id, status: evtDevice.status, deviceType: evtDeviceType, manufacturer: evtDevice.manufacturerName, model: evtDevice.modelName, attributes: deviceAttributeList(evtDevice, evtDeviceType), locationId: location.id]
} }
if(evt.data != null){
def evtData = parseJson(evt.data)
log.info "Received event for ${evtDevice.displayName}, data: ${evtData}, description: ${evt.descriptionText}"
}
def params = [body: deviceData] def params = [body: deviceData]
//send event to all subscriptions urls //send event to all subscriptions urls
@@ -330,10 +311,10 @@ def deviceEventHandler(evt) {
params.uri = "${it}" params.uri = "${it}"
if (state.verificationKeyMap[it] != null) { if (state.verificationKeyMap[it] != null) {
def key = state.verificationKeyMap[it] def key = state.verificationKeyMap[it]
params.header = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))] params.headers = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
} }
log.trace "POST URI: ${params.uri}" log.trace "POST URI: ${params.uri}"
log.trace "Header: ${params.header}" log.trace "Headers: ${params.headers}"
log.trace "Payload: ${params.body}" log.trace "Payload: ${params.body}"
try { try {
httpPostJson(params) { resp -> httpPostJson(params) { resp ->
@@ -363,10 +344,10 @@ def locationEventHandler(evt) {
params.uri = "${it}" params.uri = "${it}"
if (state.verificationKeyMap[it] != null) { if (state.verificationKeyMap[it] != null) {
def key = state.verificationKeyMap[it] def key = state.verificationKeyMap[it]
params.header = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))] params.headers = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
} }
log.trace "POST URI: ${params.uri}" log.trace "POST URI: ${params.uri}"
log.trace "Header: ${params.header}" log.trace "Headers: ${params.headers}"
log.trace "Payload: ${params.body}" log.trace "Payload: ${params.body}"
try { try {
httpPostJson(params) { resp -> httpPostJson(params) { resp ->
@@ -385,6 +366,7 @@ def locationEventHandler(evt) {
private ComputHMACValue(key, data) { private ComputHMACValue(key, data) {
try { try {
log.debug "data hased: ${data}"
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA1") SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA1")
Mac mac = Mac.getInstance("HmacSHA1") Mac mac = Mac.getInstance("HmacSHA1")
mac.init(secretKeySpec) mac.init(secretKeySpec)
@@ -507,7 +489,8 @@ private getDeviceType(device) {
//Loop through the device capability list to determine the device type. //Loop through the device capability list to determine the device type.
capabilities.each { capability -> capabilities.each { capability ->
switch (capability.name.toLowerCase()) { switch(capability.name.toLowerCase())
{
case "switch": case "switch":
deviceType = "switch" deviceType = "switch"
@@ -652,7 +635,8 @@ private mapDeviceCommands(command, value) {
if (value == 1 || value == "1" || value == "lock") { if (value == 1 || value == "1" || value == "lock") {
resultCommand = "lock" resultCommand = "lock"
resultValue = "" resultValue = ""
} else if (value == 0 || value == "0" || value == "unlock") { }
else if (value == 0 || value == "0" || value == "unlock") {
resultCommand = "unlock" resultCommand = "unlock"
resultValue = "" resultValue = ""
} }

View File

@@ -27,10 +27,9 @@ definition(
preferences { preferences {
page(name: "selectButton") page(name: "selectButton")
page(name: "configureButton1") for (def i=1; i<=8; i++) {
page(name: "configureButton2") page(name: "configureButton$i")
page(name: "configureButton3") }
page(name: "configureButton4")
page(name: "timeIntervalInput", title: "Only during a certain time") { page(name: "timeIntervalInput", title: "Only during a certain time") {
section { section {
@@ -60,22 +59,45 @@ def selectButton() {
} }
} }
def createPage(pageNum) {
if ((state.numButton == pageNum) || (pageNum == 8))
state.installCondition = true
dynamicPage(name: "configureButton$pageNum", title: "Set up button $pageNum here",
nextPage: "configureButton${pageNum+1}", install: state.installCondition, uninstall: configured(), getButtonSections(pageNum))
}
def configureButton1() { def configureButton1() {
dynamicPage(name: "configureButton1", title: "Now let's decide how to use the first button", state.numButton = buttonDevice.currentState("numberOfButtons")?.longValue ?: 4
nextPage: "configureButton2", uninstall: configured(), getButtonSections(1)) log.debug "state variable numButton: ${state.numButton}"
state.installCondition = false
createPage(1)
} }
def configureButton2() { def configureButton2() {
dynamicPage(name: "configureButton2", title: "If you have a second button, set it up here", createPage(2)
nextPage: "configureButton3", uninstall: configured(), getButtonSections(2))
} }
def configureButton3() { def configureButton3() {
dynamicPage(name: "configureButton3", title: "If you have a third button, you can do even more here", createPage(3)
nextPage: "configureButton4", uninstall: configured(), getButtonSections(3))
} }
def configureButton4() { def configureButton4() {
dynamicPage(name: "configureButton4", title: "If you have a fourth button, you rule, and can set it up here", createPage(4)
install: true, uninstall: true, getButtonSections(4)) }
def configureButton5() {
createPage(5)
}
def configureButton6() {
createPage(6)
}
def configureButton7() {
createPage(7)
}
def configureButton8() {
createPage(8)
} }
def getButtonSections(buttonNumber) { def getButtonSections(buttonNumber) {

View File

@@ -202,7 +202,8 @@ def inputSelectionPage() {
section("options variations") { section("options variations") {
paragraph "tap these elements and look at the differences when selecting an option" 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: "selectionSimple", title: "Simple options", description: "no separators in the selectable options", options: ["Thing 1", "Thing 2", "(Complicated) Thing 3"])
input(type: "enum", name: "selectionSimpleGrouped", title: "Simple (Grouped) 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) input(type: "enum", name: "selectionGrouped", title: "Grouped options", description: "separate groups of options with headers", groupedOptions: groupedOptions)
} }
@@ -214,15 +215,15 @@ def inputSelectionPage() {
section("segmented") { section("segmented") {
paragraph "segmented should only work if there are either 2 or 3 options to choose from" 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: "selectionSegmented1", style: "segmented", title: "1 option", options: ["One"])
input(type: "enum", name: "selectionSegmented4", style: "segmented", title: "4 options", groupedOptions: addGroup(["One", "Two", "Three", "Four"])) input(type: "enum", name: "selectionSegmented4", style: "segmented", title: "4 options", options: ["One", "Two", "Three", "Four"])
paragraph "multiple and required will have no effect on segmented selection elements. There will always be exactly 1 option selected" 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: "selectionSegmented2", style: "segmented", title: "2 options", options: ["One", "Two"])
input(type: "enum", name: "selectionSegmented3", style: "segmented", title: "3 options", options: ["One", "Two", "Three"]) input(type: "enum", name: "selectionSegmented3", style: "segmented", title: "3 options", options: ["One", "Two", "Three"])
paragraph "specifying defaultValue still works with segmented selection elements" 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") input(type: "enum", name: "selectionSegmentedWithDefault", style: "segmented", title: "defaulted to 'two'", options: ["One", "Two", "Three"], defaultValue: "Two")
} }
section("required: true") { section("required: true") {
@@ -231,6 +232,8 @@ def inputSelectionPage() {
section("multiple: 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) 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)
input(type: "enum", name: "selectionMultipleDefault1", title: "This allows multiple selections with a single default", description: "It should look different when nothing is selected", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), multiple: true, defaultValue: "an option")
input(type: "enum", name: "selectionMultipleDefault2", title: "This allows multiple selections with multiple defaults", description: "It should look different when nothing is selected", groupedOptions: addGroup(["an option", "another option", "no way, one more?"]), multiple: true, defaultValue: ["an option", "another option"])
} }
section("with image") { section("with image") {

View File

@@ -72,7 +72,7 @@ def authPage() {
log.debug "have LIFX access token" log.debug "have LIFX access token"
def options = locationOptions() ?: [] def options = locationOptions() ?: []
def count = options.size() def count = options.size().toString()
return dynamicPage(name:"Credentials", title:"", nextPage:"", install:true, uninstall: true) { return dynamicPage(name:"Credentials", title:"", nextPage:"", install:true, uninstall: true) {
section("Select your location") { section("Select your location") {