Compare commits

...

98 Commits

Author SHA1 Message Date
Vinay Rao
a7cd9e072b Merge pull request #1078 from SmartThingsCommunity/staging
Rolling up staging to production for deploy
2016-07-26 13:45:49 -07:00
Vinay Rao
b5843acc38 Merge pull request #1067 from SmartThingsCommunity/master
Rolling up master to staging
2016-07-19 16:00:31 -07:00
Vinay Rao
863c49ffd4 Merge pull request #1066 from SmartThingsCommunity/staging
Rolling down staging hotfix to production
2016-07-19 15:55:53 -07:00
Vinay Rao
4e5d1f6ad0 Merge pull request #1065 from SmartThingsCommunity/staging
Rolling up staging to production for deploy
2016-07-19 15:09:07 -07:00
Vinay Rao
2f0d8d814b Merge pull request #1064 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-07-19 15:06:43 -07:00
Vinay Rao
a86eba494f Merge pull request #1063 from tslagle13/add-capability-SS-open/closed
Add capability sensor to SS Open/Closed
2016-07-18 15:46:21 -07:00
tslagle13
2549372bb7 Add capability sensor to SS Open/Closed
There are some integrations out there using the "Actuator" and "Sensor" Capabilities and this doesn't show up for them.
2016-07-18 15:20:06 -07:00
Vinay Rao
38cdde7479 Merge pull request #1057 from SmartThingsCommunity/DVCSMP-1892
DVCSMP-1892 Fix error in Z-Wave Door/Window Sensor wake up handler
2016-07-14 10:54:36 -07:00
Duncan McKee
853f616cc8 DVCSMP-1892 Fix error in Z-Wave Door/Window Sensor wake up handler 2016-07-14 13:51:36 -04:00
Vinay Rao
2b3a4e1278 Merge pull request #1055 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-07-13 19:45:29 -07:00
Vinay Rao
825e693efd Merge pull request #1054 from SmartThingsCommunity/production
Rolling down prod hotfix to staging
2016-07-13 19:44:57 -07:00
Vinay Rao
686c8f7337 Merge pull request #1053 from rappleg/FixFalseBatteryNotificationOnNewerGroovyVersion
PRP-192 Fix false low battery notifications on Groovy 2.4.4
2016-07-13 19:40:55 -07:00
rappleg
11f4e42fe9 PRP-192 Fix false low battery notifications on Groovy 2.4.4 2016-07-13 21:22:57 -05:00
Steve Vlaminck
bc459ae178 Merge pull request #1051 from vlaminck/GWU-exception-fix
fix divide by zero exception [DVCSMP-1891]
2016-07-13 10:01:10 -05:00
vlaminck
1392b6e1a5 fix divide by zero exception 2016-07-13 09:27:23 -05:00
Vinay Rao
3db96faa00 Merge pull request #1049 from SmartThingsCommunity/master
Rolling up master to staging
2016-07-12 15:04:26 -07:00
Vinay Rao
399fbcb676 Merge pull request #1048 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-07-12 15:03:28 -07:00
Vinay Rao
eed1ced71b Merge pull request #1047 from SmartThingsCommunity/staging
Rolling up staging for production deploy
2016-07-12 14:33:19 -07:00
Vinay Rao
d080833d5c Merge pull request #1046 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-07-12 14:26:10 -07:00
Vinay Rao
e998528e8e Merge pull request #1041 from rappleg/ProductionFixJsonSlurperHashMap
PRP-172 Fix Hue Connect parse errors on newer versions of Groovy
2016-07-11 23:49:10 -07:00
Jack Chi
3472ee329d Merge pull request #1036 from jackchi/healthcheck-zwave-aeon
[CHF-138] Adds HealthCheck to Z-Wave Sensors
2016-07-11 11:21:30 -07:00
Juan Pablo Risso
577b127287 PRP-151 Harmony cloud fix + PENG-161 - Allow setLevel (#1035)
* PRP-151 - Harmony Cloud

Added is IP

Try around Integer.parseInt

declare int p outside try

* Remove unsubscribe & unschedule

* PENG-161 - Allow setLevel for switch capability

Last version
2016-07-11 10:04:32 -04:00
rappleg
d85566bb98 PRP-172 Fix Hue Connect parse errors on newer versions of Groovy 2016-07-10 15:35:12 -05:00
jackchi
5f41af35e2 [CHF-138] Adds HealthCheck to Z-Wave Sensors 2016-07-08 15:36:58 -07:00
Duncan McKee
e1a5b4dd27 Merge pull request #1034 from SmartThingsCommunity/DVCSMP-1879
DVCSMP-1879 Fix error in Z-Wave Door/Window Sensor throwing exceptions on update
2016-07-06 17:43:42 -04:00
Duncan McKee
a2baa37901 DVCSMP-1879 Fix error in Z-Wave Door/Window Sensor throwing exceptions on update 2016-07-06 17:08:15 -04:00
Vinay Rao
922ab45343 Merge pull request #1033 from SmartThingsCommunity/master
Rolling up master to staging for deploy
2016-07-06 13:59:03 -07:00
Vinay Rao
962774996e Merge pull request #1032 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-07-06 13:57:35 -07:00
Vinay Rao
d79594cbcb Merge pull request #1031 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-07-06 13:56:39 -07:00
Vinay Rao
bf8fe4cad7 Merge pull request #1030 from rappleg/RevertLazyMapNewerGroovyVersion
Revert "PRP-172 Fix Hue Connect parse errors on newer versions of Groovy"
2016-07-06 13:53:32 -07:00
rappleg
65752ce378 Revert "PRP-172 Fix Hue Connect parse errors on newer versions of Groovy"
This reverts commit be7f6a76a9.
2016-07-06 14:44:58 -05:00
spurohit1
95f08aeb3d Update logitech-harmony-connect.groovy (#1009)
If port is present in the callbackUrl then send command to hub else post the request through http.
Removed isIP function since it is no longer required.
2016-07-06 09:40:29 -04:00
Vinay Rao
cd7bc1b262 Merge pull request #1023 from rappleg/FixHueConnectParseErrorsGrailsUpgradeProduction
PRP-172 Fix Hue Connect parse errors on newer versions of Groovy
2016-06-30 00:46:24 -07:00
rappleg
be7f6a76a9 PRP-172 Fix Hue Connect parse errors on newer versions of Groovy 2016-06-30 02:42:24 -05:00
Vinay Rao
10e5b7e9d7 Merge pull request #1021 from SmartThingsCommunity/master
Rolling up master to staging
2016-06-28 22:57:32 -07:00
Vinay Rao
fc38c534f9 Merge pull request #1020 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-06-28 21:04:33 -07:00
Vinay Rao
90e6dc91eb Merge pull request #1018 from SmartThingsCommunity/staging
Rolling up staging to production
2016-06-28 14:16:10 -07:00
Donald C. Kirker
2e502024a6 Merge pull request #1015 from SmartThingsCommunity/DVCSMP-1826
New handlers for Vision ZW+ Motion and Door/Window DVCSMP-1826 DVCSMP-1831
2016-06-28 10:13:59 -07:00
Steve Vlaminck
fd47bcb8a8 Merge pull request #1011 from vlaminck/gwu-exception-handling
Gwu exception handling
2016-06-23 12:26:42 -05:00
vlaminck
972599b1b5 Provide a way to remove unsupported devices from settings 2016-06-23 12:19:54 -05:00
vlaminck
f5d3cca6a0 Guard against devices that don't support necessary commands 2016-06-23 09:27:54 -05:00
Jack Chi
6d3ae11e44 Merge pull request #1008 from jackchi/healthcheck-open-close
[CHF-131] Adding Health Check to V1 SmartSense Open/Close Sensor
2016-06-22 11:03:45 -07:00
jackchi
dd63e76dfb [CHF-131] Adding Health Check to V1 SmartSense Open/Close Sensor 2016-06-22 10:19:40 -07:00
Duncan McKee
7d6f37d98f Z-Wave Plus Secure Sensors: reduce configure() delay for join v2 2016-06-22 11:12:10 -04:00
Vinay Rao
a015742d65 Merge pull request #1005 from SmartThingsCommunity/master
Rolling up master to staging
2016-06-21 13:42:00 -07:00
Vinay Rao
3ee8f86aa3 Merge pull request #1004 from SmartThingsCommunity/staging
Rolling up staging to prod
2016-06-21 13:19:59 -07:00
Juan Pablo Risso
9b285ec93b PRP-151 - Harmony Cloud (#1003)
- Added isIP() to check if a local or cloud callback URL
- Added try around Integer.parseInt
2016-06-21 13:19:53 -04:00
Duncan McKee
23f66e3caa Z-Wave Plus Door/Window Sensor DVCSMP-1831 2016-06-20 17:35:31 -04:00
Duncan McKee
7a44c59581 Z-Wave Plus Motion/Temp Sensor device type DVCSMP-1826 2016-06-18 15:48:56 -04:00
Duncan McKee
4d343d9bcf Z-Wave D/W: send requests secure encapsulated when necessary DVCSMP-1826 2016-06-17 21:49:08 -04:00
Juan Pablo Risso
777f8f7e20 PENG-161 - Logitech Harmony don't allow undefined commands (#965)
https://smartthings.atlassian.net/browse/PENG-161

extra )

New getCapabilityName()

Small fixes

Extra colon

capName

Added .id
2016-06-16 15:26:39 -04:00
Vinay Rao
de6d84acd2 Merge pull request #994 from SmartThingsCommunity/master
Rolling up master to staging
2016-06-14 15:59:24 -07:00
Vinay Rao
eac48382e8 Merge pull request #993 from SmartThingsCommunity/staging
Rolling down hotfix to master
2016-06-14 15:55:53 -07:00
Vinay Rao
d1a910f11f Merge pull request #992 from SmartThingsCommunity/staging
Rolling up staging to prod
2016-06-14 13:11:06 -07:00
Vinay Rao
9f09a4b0b2 Merge pull request #991 from SmartThingsCommunity/production
Rolling down hotfix
2016-06-14 13:10:03 -07:00
Donald C. Kirker
3c47fe7b60 Merge pull request #990 from dkirker/astra-devices
Add re-type for Vision Motion Sensor Plus.
2016-06-14 11:41:04 -07:00
Donald Kirker
45a0822e9b Add re-type for Vision Motion Sensor Plus. 2016-06-14 11:07:31 -07:00
Rohan Desai
8cc87f3858 Merge pull request #966 from rohandesai/PENG-160
PENG-160 Alfred workflow should not allow undefined commands
2016-06-13 14:17:10 -07:00
Vinay Rao
e818695947 Merge pull request #987 from SmartThingsCommunity/staging
Roll down hotfix
2016-06-13 10:50:29 -07:00
Vinay Rao
bbba20288e Merge pull request #986 from larsfinander/DVCSMP-1819_Philips_Hue_Incorrect_folder_name_for_Ambiance_bulb_DTH_staging
DVCSMP-1819 Philips Hue: Incorrect folder name for Ambiance bulb DTH
2016-06-13 10:49:34 -07:00
Lars Finander
ff9dd3f6e2 DVCSMP-1819 Philips Hue: Incorrect folder name for Ambiance bulb DTH
-Renamed folder to .src
2016-06-13 10:46:25 -07:00
Vinay Rao
a94a62d34c Merge pull request #982 from rohandesai/netatmo-hotfix
NPE fix for NetAtmo
2016-06-09 11:30:51 -07:00
Rohan Desai
e861d3c256 added fix for NPE 2016-06-09 11:04:55 -07:00
Vinay Rao
7adff88d0f Merge pull request #978 from SmartThingsCommunity/master
Rolling up master to staging
2016-06-07 16:47:52 -07:00
Vinay Rao
ad1f1b2dc9 Merge pull request #977 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-06-07 16:44:35 -07:00
Vinay Rao
8d8b039dda Merge pull request #975 from SmartThingsCommunity/staging
Rolling up staging to production
2016-06-07 12:23:55 -07:00
Duncan McKee
c875547942 Merge pull request #947 from SmartThingsCommunity/DPROT-110
DPROT-110 Add manufacturer fingerprints to Z-Wave Motion Sensor
2016-06-07 12:11:17 -04:00
Zach Varberg
1676a9c381 Merge pull request #942 from varzac/update-smartsense-multi
DVCSMP-1801: mfgCode in the ZigBee library for smartsense-multi-sensor
2016-06-07 09:50:39 -05:00
Rohan Desai
49d293e749 PENG-160 UBI should not allow undefined commands
- added some more changes

added changes to alfred workflow
2016-06-03 15:25:50 -07:00
Lars Finander
5d1b033486 Merge pull request #962 from larsfinander/DVCSMP-1676_Philips_Hue_timeout_message_search
DVCSMP-1676 Philips Hue: Need to provide timeout message if search ta…
2016-06-03 10:26:29 -07:00
Lars Finander
31f77513da DVCSMP-1676 Philips Hue: Need to provide timeout message if search takes too long.
DVCSMP-1675 Changed to show bridge serial number instead of IP
-Shows timeout screen after 5 minutes
-Removed old image code that didnt work for one of the pages
-Handled null pointer when adding unsupported devices
2016-06-02 21:13:00 -07:00
rohandesai
98d7829d1a Merge pull request #957 from rohandesai/peng-158-staging
PENG-158 UBI should not allow undefined commands
2016-06-02 14:33:49 -07:00
Vinay Rao
c6f706e47a Merge pull request #960 from SmartThingsCommunity/staging
Rolling down Staging to master
2016-06-02 14:30:49 -07:00
Vinay Rao
532afd7336 Merge pull request #959 from rohandesai/delete-prempoint
removed Prempoint from staging
2016-06-02 14:30:02 -07:00
Rohan Desai
ac7f1a0c66 removed Prempoint from staging 2016-06-02 14:24:29 -07:00
Lars Finander
cb26f055d7 Merge pull request #950 from larsfinander/DVCSMP-400_Philips_Hue_Not_Yet_Configured
DVCSMP-400 Philips Hue: Hue bridge & Bulbs displaying as Not Yet Conf…
2016-06-02 12:14:44 -07:00
Rohan Desai
34107f935e PENG-158 UBI should not allow undefined commands
- now validating commands per capability of the device in the smartapp

removed commented out code
2016-06-02 11:18:54 -07:00
Lars Finander
cc2d19e951 DVCSMP-400 Philips Hue: Hue bridge & Bulbs displaying as Not Yet Configured 2016-06-01 18:06:46 -07:00
rohandesai
031a15ec86 Merge pull request #897 from rohandesai/PENG-158
PENG-158 UBI should not allow undefined commands
2016-06-01 15:22:45 -07:00
Vinay Rao
fc2db2575d Merge pull request #949 from SmartThingsCommunity/staging
Rolling down hotfix to master
2016-06-01 12:18:54 -07:00
Vinay Rao
fd549631e6 Merge pull request #948 from SmartThingsCommunity/revert-828-stage2_v1_multi_garage
Revert "DVCSMP-1107 Stage 2 of adding preferences to support garage s…
2016-06-01 12:11:49 -07:00
Vinay Rao
417c246d61 Revert "DVCSMP-1107 Stage 2 of adding preferences to support garage sensor in v1 safari mutli sensor" 2016-06-01 12:09:38 -07:00
Zach Varberg
038d770691 Use mfgCode in the ZigBee library for smartsense-multi-sensor
With the change to the ZigBee library in appengine to add
the optional manufacturers code, the custom writing of
ZigBee commands can be replaced with calls to the library

This resolves https://smartthings.atlassian.net/browse/DVCSMP-1801
2016-06-01 10:23:30 -05:00
Duncan McKee
ce28ec2039 DPROT-110 Add manufacturer fingerprints to Z-Wave Motion Sensor 2016-05-31 18:29:40 -04:00
Vinay Rao
0f1781c02e Merge pull request #946 from SmartThingsCommunity/master
Rolling up master to staging
2016-05-31 14:22:14 -07:00
Vinay Rao
4ce6ee0890 Merge pull request #945 from SmartThingsCommunity/staging
Rolling down staging hotfixes to master
2016-05-31 14:14:13 -07:00
Vinay Rao
f8050a5cd5 Merge pull request #944 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-05-31 14:07:40 -07:00
Jack Chi
d44dac448b Merge pull request #920 from jackchi/smartsense-motion-power
[CHF-74] Add Smartsense device-health related documentation
2016-05-31 11:24:42 -07:00
Vinay Rao
67c20abcba Merge pull request #828 from workingmonk/stage2_v1_multi_garage
DVCSMP-1107 Stage 2 of adding preferences to support garage sensor in v1 safari mutli sensor
2016-05-31 11:13:58 -07:00
Duncan McKee
d56e132896 Merge pull request #941 from SmartThingsCommunity/INTL-626-prod
INTL-626 Add mfr fingerprint for Fibaro Smoke Sensor
2016-05-31 14:08:48 -04:00
Duncan McKee
009ec2539d INTL-626 Add mfr fingerprint for Fibaro Smoke Sensor 2016-05-31 14:00:59 -04:00
Lars Finander
ff0860cbe1 Merge pull request #934 from larsfinander/DVCSMP-1795_Philips_Hue_Hue_Ambience_bulb
DVCSMP-1795 Philips Hue: Add support for Hue Ambience bulb
2016-05-27 09:57:24 -07:00
Lars Finander
ecfb99974b DVCSMP-1795 Philips Hue: Add support for Hue Ambience bulb
-Add support for Hue White Ambiance Bulb (white,dimmable+color temp)
2016-05-27 09:45:26 -07:00
Vinay Rao
c2f18a91be Merge pull request #906 from SmartThingsCommunity/master
Rolling up master to staging for next week's deploy
2016-05-24 16:28:51 -07:00
jackchi
0c1208928f [CHF-74] Add Smartsense device-health related documentation for motion sensor 2016-05-24 15:17:40 -07:00
Vinay Rao
02d9963fab Merge pull request #919 from SmartThingsCommunity/staging
Rolling up staging to production for release May 24
2016-05-24 13:59:11 -07:00
Rohan Desai
32f0385e30 PENG-158 UBI should not allow undefined commands
- now validating commands per capability of the device in the smartapp

removed commented out code
2016-05-19 11:59:13 -07:00
Vinay Rao
d17cadc4c7 stage 2 of adding preferences to support garage sensor in mutli sensor 2016-04-22 17:41:57 -07:00
26 changed files with 1245 additions and 614 deletions

View File

@@ -22,6 +22,7 @@ metadata {
capability "Configuration"
capability "Sensor"
capability "Battery"
capability "Health Check"
attribute "tamper", "enum", ["detected", "clear"]
attribute "batteryStatus", "string"
@@ -326,6 +327,9 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
}
def configure() {
// allow device user configured or default 16 min to check in; double the periodic reporting interval
sendEvent(name: "checkInterval", value: 2* timeOptionValueMap[reportInterval] ?: 2*8*60, displayed: false)
// This sensor joins as a secure device if you double-click the button to include it
log.debug "${device.displayName} is configuring its settings"
def request = []

View File

@@ -20,6 +20,9 @@ metadata {
capability "Configuration"
capability "Sensor"
capability "Battery"
capability "Health Check"
command "configureAfterSecure"
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A"
}
@@ -245,6 +248,8 @@ def configureAfterSecure() {
def configure() {
// log.debug "configure()"
//["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet())
// allow device 16 min to check in; double the periodic reporting interval
sendEvent(name: "checkInterval", value: 2*8*60, displayed: false)
}
private setConfigured() {

View File

@@ -20,6 +20,7 @@ metadata {
capability "Illuminance Measurement"
capability "Sensor"
capability "Battery"
capability "Health Check"
fingerprint deviceId: "0x2001", inClusters: "0x30,0x31,0x80,0x84,0x70,0x85,0x72,0x86"
}
@@ -180,6 +181,9 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
}
def configure() {
// allow device 10 min to check in; double the periodic reporting interval
sendEvent(name: "checkInterval", value: 2*5*60, displayed: false)
delayBetween([
// send binary sensor report instead of basic set for motion
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2).format(),

View File

@@ -21,6 +21,7 @@ metadata {
attribute "tamper", "enum", ["detected", "clear"]
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
fingerprint mfr:"010F", prod:"0C02", model:"1002"
}
simulator {
//battery

View File

@@ -0,0 +1,110 @@
/**
* Hue White Ambiance Bulb
*
* Philips Hue Type "Color Temperature Light"
*
* Author: SmartThings
*/
// for the UI
metadata {
// Automatically generated. Make future change here.
definition (name: "Hue White Ambiance Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Color Temperature"
capability "Switch"
capability "Refresh"
command "refresh"
}
simulator {
// TODO: define status and reply messages here
}
tiles (scale: 2){
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
}
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["rich-control"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "refresh"])
}
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
def results = []
def map = description
if (description instanceof String) {
log.debug "Hue Ambience Bulb stringToMap - ${map}"
map = stringToMap(description)
}
if (map?.name && map?.value) {
results << createEvent(name: "${map?.name}", value: "${map?.value}")
}
results
}
// handle commands
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void setLevel(percent) {
log.debug "Executing 'setLevel'"
if (percent != null && percent >= 0 && percent <= 100) {
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
sendEvent(name: "switch", value: "on")
} else {
log.warn "$percent is not 0-100"
}
}
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else {
log.warn "Invalid color temperature"
}
}
void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}

View File

@@ -255,7 +255,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}

View File

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

View File

@@ -0,0 +1,30 @@
# Smartsense Motion Sensor
Works with:
* [Samsung SmartThings Motion Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-motion-sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health]($health)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Motion Sensor** - can detect motion
* **Battery** - defines device uses a battery
* **Refresh** - _refresh()_ command for status updates
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C2 motion sensor that has 120min check-in interval

View File

@@ -271,7 +271,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}

View File

@@ -24,7 +24,7 @@ metadata {
capability "Temperature Measurement"
capability "Refresh"
capability "Sensor"
command "enrollResponse"
}
@@ -73,7 +73,7 @@ metadata {
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
@@ -87,10 +87,10 @@ def parse(String description) {
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
@@ -128,7 +128,7 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
@@ -141,7 +141,7 @@ private Map parseReportAttributeMessage(String description) {
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
@@ -153,11 +153,11 @@ private Map parseReportAttributeMessage(String description) {
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
def value = descMap.value.endsWith("01") ? "active" : "inactive"
resultMap = getMotionResult(value)
}
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
@@ -170,7 +170,7 @@ private Map parseCustomMessage(String description) {
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
@@ -240,7 +240,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
}

View File

@@ -338,7 +338,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
}
}
@@ -403,39 +404,21 @@ def refresh() {
if (device.getDataValue("manufacturer") == "SmartThings") {
log.debug "Refreshing Values for manufacturer: SmartThings "
refreshCmds = refreshCmds + [
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
Separating these out in a separate if-else because I do not want to touch Centralite part
as of now.
*/
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
/* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276)
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
Separating these out in a separate if-else because I do not want to touch Centralite part
as of now.
*/
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode])
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
} else {
refreshCmds = refreshCmds + [
/* sensitivity - default value (8) */
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
}
//Common refresh commands
refreshCmds = refreshCmds + [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
zigbee.readAttribute(0x0001, 0x0020) +
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
return refreshCmds + enrollResponse()
}
@@ -443,38 +426,15 @@ def refresh() {
def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting"
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
def configCmds = enrollResponse() +
zigbee.batteryConfig() +
zigbee.temperatureConfig() +
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
return configCmds + refresh()
}

View File

@@ -234,7 +234,8 @@ def getTemperature(value) {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
}

View File

@@ -13,32 +13,34 @@
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
capability "Battery"
capability "Configuration"
capability "Contact Sensor"
capability "Refresh"
capability "Temperature Measurement"
capability "Health Check"
capability "Sensor"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor"
}
simulator {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles(scale: 2) {
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
@@ -71,10 +73,10 @@ metadata {
details(["contact","temperature","battery","refresh"])
}
}
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
@@ -88,10 +90,10 @@ def parse(String description) {
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
@@ -99,7 +101,7 @@ def parse(String description) {
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
@@ -125,7 +127,7 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
@@ -135,14 +137,14 @@ private boolean shouldProcessMessage(cluster) {
private int getHumidity(value) {
return Math.round(Double.parseDouble(value))
}
private Map parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
@@ -151,10 +153,10 @@ private Map parseReportAttributeMessage(String description) {
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
@@ -167,7 +169,7 @@ private Map parseCustomMessage(String description) {
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
@@ -200,7 +202,7 @@ private Map parseIasMessage(String description) {
}
return resultMap
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
@@ -213,11 +215,11 @@ def getTemperature(value) {
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [
name: 'battery'
]
def volts = rawValue / 10
def descriptionText
if (rawValue == 0 || rawValue == 255) {}
@@ -228,7 +230,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
@@ -273,6 +276,7 @@ def refresh() {
}
def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -205,7 +205,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
}

View File

@@ -13,7 +13,7 @@
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Battery"
@@ -21,28 +21,28 @@ metadata {
capability "Contact Sensor"
capability "Refresh"
capability "Temperature Measurement"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "Visonic", model: "MCT-340 SMA"
}
simulator {
}
preferences {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
tiles {
standardTile("contact", "device.contact", width: 2, height: 2) {
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
}
valueTile("temperature", "device.temperature", inactiveLabel: false) {
state "temperature", label:'${currentValue}°',
backgroundColors:[
@@ -58,23 +58,23 @@ metadata {
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
main (["contact", "temperature"])
details(["contact","temperature","battery","refresh","configure"])
}
}
def parse(String description) {
log.debug "description: $description"
Map map = [:]
if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description)
@@ -88,10 +88,10 @@ def parse(String description) {
else if (description?.startsWith('zone status')) {
map = parseIasMessage(description)
}
log.debug "Parse returned $map"
def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
@@ -99,7 +99,7 @@ def parse(String description) {
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
@@ -125,20 +125,20 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private Map parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug "Desc Map: $descMap"
Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value)
@@ -147,10 +147,10 @@ private Map parseReportAttributeMessage(String description) {
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ')) {
@@ -163,7 +163,7 @@ private Map parseCustomMessage(String description) {
private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
@@ -196,7 +196,7 @@ private Map parseIasMessage(String description) {
}
return resultMap
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
@@ -209,11 +209,11 @@ def getTemperature(value) {
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [
name: 'battery'
]
def volts = rawValue / 10
def descriptionText
if (volts > 3.5) {
@@ -223,7 +223,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
@@ -261,7 +262,7 @@ def refresh()
{
log.debug "Refreshing Temperature and Battery"
[
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
@@ -274,24 +275,24 @@ def configure() {
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
"delay 1000",
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
//"raw 0x500 {01 23 00 00 00}", "delay 200",
//"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}",
"delay 500"
]
return configCmds + enrollResponse() + refresh() // send refresh cmds as part of config
@@ -299,11 +300,11 @@ def configure() {
def enrollResponse() {
log.debug "Sending enroll response"
[
[
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1"
]
}
private hex(value) {

View File

@@ -28,7 +28,6 @@ metadata {
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98"
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82"
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x80,0x71,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20" // Philio multi+
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x72,0x5A,0x80,0x73,0x86,0x84,0x85,0x59,0x71,0x70,0x7A,0x98" // Vision door/window
}
// simulator metadata
@@ -36,6 +35,7 @@ metadata {
// status messages
status "open": "command: 2001, payload: FF"
status "closed": "command: 2001, payload: 00"
status "wake up": "command: 8407, payload: "
}
// UI tile definitions
@@ -81,7 +81,7 @@ def updated() {
def cmds = []
if (!state.MSR) {
cmds = [
zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()),
"delay 1200",
zwave.wakeUpV1.wakeUpNoMoreInformation().format()
]
@@ -94,9 +94,9 @@ def updated() {
}
def configure() {
delayBetween([
zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
batteryGetCommand()
commands([
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
zwave.batteryV1.batteryGet()
], 6000)
}
@@ -147,12 +147,11 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
result << sensorValueEvent(1)
} else if (cmd.event == 0x03) {
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
if(!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
} else if (cmd.event == 0x07) {
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
if(!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
result << createEvent(name: "motion", value: "active", descriptionText:"$device.displayName detected motion")
}
} else if (cmd.notificationType) {
@@ -170,12 +169,11 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
def cmds = []
if (!state.MSR) {
cmds << zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId).format()
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()
cmds << command(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
cmds << "delay 1200"
}
if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) {
cmds << batteryGetCommand()
cmds << command(zwave.batteryV1.batteryGet())
} else {
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
}
@@ -212,7 +210,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
if (msr == "0086-0102-0059") {
result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format())
} else {
result << response(batteryGetCommand())
result << response(command(zwave.batteryV1.batteryGet()))
}
}
@@ -220,7 +218,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x85: 2, 0x70: 1])
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1])
// log.debug "encapsulated: $encapsulatedCommand"
if (encapsulatedCommand) {
state.sec = 1
@@ -232,12 +230,16 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)
}
def batteryGetCommand() {
def cmd = zwave.batteryV1.batteryGet()
if (state.sec) {
cmd = zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd)
private command(physicalgraph.zwave.Command cmd) {
if (state.sec == 1) {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
} else {
cmd.format()
}
cmd.format()
}
private commands(commands, delay=200) {
delayBetween(commands.collect{ command(it) }, delay)
}
def retypeBasedOnMSR() {
@@ -260,8 +262,12 @@ def retypeBasedOnMSR() {
setDeviceType("3-in-1 Multisensor Plus (SG)")
break
case "0109-2001-0106": // Vision door/window
log.debug "Changing device type to Door / Window Sensor Plus (SG)"
setDeviceType("Door / Window Sensor Plus (SG)")
log.debug "Changing device type to Z-Wave Plus Door/Window Sensor"
setDeviceType("Z-Wave Plus Door/Window Sensor")
break
case "0109-2002-0205": // Vision Motion
log.debug "Changing device type to Z-Wave Plus Motion/Temp Sensor"
setDeviceType("Z-Wave Plus Motion/Temp Sensor")
break
}
}

View File

@@ -21,6 +21,13 @@ metadata {
capability "Motion Sensor"
capability "Sensor"
capability "Battery"
fingerprint mfr: "011F", prod: "0001", model: "0001", deviceJoinName: "Schlage Motion Sensor" // Schlage motion
fingerprint mfr: "014A", prod: "0001", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion
fingerprint mfr: "014A", prod: "0004", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion +
fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814
fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02
fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC
}
simulator {
@@ -125,9 +132,9 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
}
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
result << response(zwave.batteryV1.batteryGet())
result << response("delay 1200")
} else {
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
}
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
result
}

View File

@@ -0,0 +1,270 @@
/**
* Copyright 2016 SmartThings
* Copyright 2015 AstraLink
*
* 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.
*
* Z-Wave Plus Door/Window Sensor, ZD2102*-5
*
*/
metadata {
definition (name: "Z-Wave Plus Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Contact Sensor"
capability "Configuration"
capability "Battery"
capability "Sensor"
// for Astralink
attribute "ManufacturerCode", "string"
attribute "ProduceTypeCode", "string"
attribute "ProductCode", "string"
attribute "WakeUp", "string"
attribute "WirelessConfig", "string"
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x98, 0x86, 0x72, 0x5A, 0x85, 0x59, 0x73, 0x80, 0x71, 0x70, 0x84, 0x7A"
fingerprint type:"8C07", inClusters: "5E,98,86,72,5A,71"
fingerprint mfr:"0109", prod:"2001", model:"0106" // not using deviceJoinName because it's sold under different brand names
}
tiles(scale: 2) {
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
}
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
main (["contact"])
details(["contact","battery"])
}
simulator {
// messages the device returns in response to commands it receives
status "open (basic)" : "command: 9881, payload: 00 20 01 FF"
status "closed (basic)" : "command: 9881 payload: 00 20 01 00"
status "open (notification)" : "command: 9881, payload: 00 71 05 06 FF 00 FF 06 16 00 00"
status "closed (notification)" : "command: 9881, payload: 00 71 05 06 00 00 FF 06 17 00 00"
status "tamper: enclosure opened" : "command: 9881, payload: 00 71 05 07 FF 00 FF 07 03 00 00"
status "tamper: enclosure replaced" : "command: 9881, payload: 00 71 05 07 00 00 FF 07 00 00 00"
status "wake up" : "command: 9881, payload: 00 84 07"
status "battery (100%)" : "command: 9881, payload: 00 80 03 64"
status "battery low" : "command: 9881, payload: 00 80 03 FF"
}
}
def configure() {
log.debug "configure()"
def cmds = []
if (state.sec != 1) {
// secure inclusion may not be complete yet
cmds << "delay 1000"
}
cmds += secureSequence([
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
zwave.batteryV1.batteryGet(),
], 500)
cmds << "delay 8000"
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
return cmds
}
private getCommandClassVersions() {
[
0x71: 3, // Notification
0x5E: 2, // ZwaveplusInfo
0x59: 1, // AssociationGrpInfo
0x85: 2, // Association
0x20: 1, // Basic
0x80: 1, // Battery
0x70: 1, // Configuration
0x5A: 1, // DeviceResetLocally
0x7A: 2, // FirmwareUpdateMd
0x72: 2, // ManufacturerSpecific
0x73: 1, // Powerlevel
0x98: 1, // Security
0x84: 2, // WakeUp
0x86: 1, // Version
]
}
// Parse incoming device messages to generate events
def parse(String description) {
def result = []
def cmd
if (description.startsWith("Err 106")) {
state.sec = 0
result = createEvent( name: "secureInclusion", value: "failed", eventType: "ALERT",
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
} else if (description.startsWith("Err")) {
result = createEvent(descriptionText: "$device.displayName $description", isStateChange: true)
} else {
cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
result = zwaveEvent(cmd)
}
}
if (result instanceof List) {
result = result.flatten()
}
log.debug "Parsed '$description' to $result"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
log.debug "encapsulated: $encapsulatedCommand"
if (encapsulatedCommand) {
state.sec = 1
return zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
return [createEvent(descriptionText: cmd.toString())]
}
}
def sensorValueEvent(value) {
if (value) {
createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open")
} else {
createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed")
}
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
return sensorValueEvent(cmd.value)
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
return sensorValueEvent(cmd.value)
}
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
return sensorValueEvent(cmd.sensorValue)
}
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
return sensorValueEvent(cmd.sensorState)
}
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
def result = []
if (cmd.notificationType == 0x06 && cmd.event == 0x16) {
result << sensorValueEvent(1)
} else if (cmd.notificationType == 0x06 && cmd.event == 0x17) {
result << sensorValueEvent(0)
} else if (cmd.notificationType == 0x07) {
if (cmd.event == 0x00) {
if (cmd.eventParametersLength == 0 || cmd.eventParameter[0] != 3) {
result << createEvent(descriptionText: "$device.displayName covering replaced", isStateChange: true, displayed: false)
} else {
result << sensorValueEvent(0)
}
} else if (cmd.event == 0x01 || cmd.event == 0x02) {
result << sensorValueEvent(1)
} else if (cmd.event == 0x03) {
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
if (!device.currentState("ManufacturerCode")) {
result << response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
}
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
} else {
result << createEvent(descriptionText: "$device.displayName event $cmd.event ${cmd.eventParameter.inspect()}", isStateChange: true, displayed: false)
}
} else if (cmd.notificationType) {
result << createEvent(descriptionText: "$device.displayName notification $cmd.notificationType event $cmd.event ${cmd.eventParameter.inspect()}", isStateChange: true, displayed: false)
} else {
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, isStateChange: true, displayed: false)
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
def event = createEvent(name: "WakeUp", value: "wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: false) // for Astralink
def cmds = []
if (!device.currentState("ManufacturerCode")) {
cmds << secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
cmds << "delay 2000"
}
if (!state.lastbat || now() - state.lastbat > 10*60*60*1000) {
event.descriptionText += ", requesting battery"
cmds << secure(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1))
cmds << "delay 800"
cmds << secure(zwave.batteryV1.batteryGet())
cmds << "delay 2000"
} else {
log.debug "not checking battery, was updated ${(now() - state.lastbat)/60000 as int} min ago"
}
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
return [event, response(cmds)]
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [ name: "battery", unit: "%" ]
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = "${device.displayName} has a low battery"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
}
def event = createEvent(map)
// Save at least one battery report in events list every few days
if (!event.isStateChange && (now() - 3*24*60*60*1000) > device.latestState("battery")?.date?.time) {
map.isStateChange = true
}
state.lastbat = now()
return [event]
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
def result = []
def manufacturerCode = String.format("%04X", cmd.manufacturerId)
def productTypeCode = String.format("%04X", cmd.productTypeId)
def productCode = String.format("%04X", cmd.productId)
def wirelessConfig = "ZWP"
log.debug "MSR ${manufacturerCode} ${productTypeCode} ${productCode}"
result << createEvent(name: "ManufacturerCode", value: manufacturerCode)
result << createEvent(name: "ProduceTypeCode", value: productTypeCode)
result << createEvent(name: "ProductCode", value: productCode)
result << createEvent(name: "WirelessConfig", value: wirelessConfig)
return result
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
return [createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)]
}
private secure(physicalgraph.zwave.Command cmd) {
if (state.sec == 0) { // default to secure
cmd.format()
} else {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
}
private secureSequence(commands, delay=200) {
delayBetween(commands.collect{ secure(it) }, delay)
}

View File

@@ -0,0 +1,327 @@
/**
* Copyright 2016 SmartThings
* Copyright 2015 AstraLink
*
* 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.
*
* Z-Wave Plus Motion Sensor with Temperature Measurement, ZP3102*-5
*
*/
metadata {
definition (name: "Z-Wave Plus Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Motion Sensor"
capability "Temperature Measurement"
capability "Configuration"
capability "Battery"
capability "Sensor"
// for Astralink
attribute "ManufacturerCode", "string"
attribute "ProduceTypeCode", "string"
attribute "ProductCode", "string"
attribute "WakeUp", "string"
attribute "WirelessConfig", "string"
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x98, 0x86, 0x72, 0x5A, 0x85, 0x59, 0x73, 0x80, 0x71, 0x31, 0x70, 0x84, 0x7A"
fingerprint type:"8C07", inClusters: "5E,98,86,72,5A,31,71"
fingerprint mfr:"0109", prod:"2002", model:"0205" // not using deviceJoinName because it's sold under different brand names
}
tiles {
standardTile("motion", "device.motion", width: 3, height: 2) {
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
}
valueTile("temperature", "device.temperature", inactiveLabel: false) {
state "temperature", label:'${currentValue}°',
backgroundColors:[
[value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],
[value: 84, color: "#f1d801"],
[value: 95, color: "#d04e00"],
[value: 96, color: "#bc2323"]
]
}
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
state "battery", label:'${currentValue}% battery', unit:"%"
}
main(["motion", "temperature"])
details(["motion", "temperature", "battery"])
}
}
def updated() {
if (!device.currentState("ManufacturerCode")) {
response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
}
}
def configure() {
log.debug "configure()"
def cmds = []
if (state.sec != 1) {
// secure inclusion may not be complete yet
cmds << "delay 1000"
}
cmds += secureSequence([
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
zwave.batteryV1.batteryGet(),
zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1)
], 500)
cmds << "delay 8000"
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
return cmds
}
private getCommandClassVersions() {
[
0x71: 3, // Notification
0x5E: 2, // ZwaveplusInfo
0x59: 1, // AssociationGrpInfo
0x85: 2, // Association
0x20: 1, // Basic
0x80: 1, // Battery
0x70: 1, // Configuration
0x5A: 1, // DeviceResetLocally
0x7A: 2, // FirmwareUpdateMd
0x72: 2, // ManufacturerSpecific
0x73: 1, // Powerlevel
0x98: 1, // Security
0x31: 5, // SensorMultilevel
0x84: 2 // WakeUp
]
}
// Parse incoming device messages to generate events
def parse(String description) {
def result = []
def cmd
if (description.startsWith("Err 106")) {
state.sec = 0
result = createEvent( name: "secureInclusion", value: "failed", eventType: "ALERT",
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
} else if (description.startsWith("Err")) {
result = createEvent(descriptionText: "$device.displayName $description", isStateChange: true)
} else {
cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
result = zwaveEvent(cmd)
}
}
if (result instanceof List) {
result = result.flatten()
}
log.debug "Parsed '$description' to $result"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
log.debug "encapsulated: $encapsulatedCommand"
if (encapsulatedCommand) {
state.sec = 1
return zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
return [createEvent(descriptionText: cmd.toString())]
}
}
def sensorValueEvent(value) {
def result = []
if (value) {
log.debug "sensorValueEvent($value) : active"
result << createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
} else {
log.debug "sensorValueEvent($value) : inactive"
result << createEvent(name: "motion", value: "inactive", descriptionText: "$device.displayName motion has stopped")
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
return sensorValueEvent(cmd.value)
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
return sensorValueEvent(cmd.value)
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
return sensorValueEvent(cmd.value)
}
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
return sensorValueEvent(cmd.sensorValue)
}
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
return sensorValueEvent(cmd.sensorState)
}
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
def result = []
if (cmd.notificationType == 0x07) {
if (cmd.event == 0x01 || cmd.event == 0x02) {
result << sensorValueEvent(1)
} else if (cmd.event == 0x03) {
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
result << response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
} else if (cmd.event == 0x07) {
result << sensorValueEvent(1)
} else if (cmd.event == 0x08) {
result << sensorValueEvent(1)
} else if (cmd.event == 0x00) {
if (cmd.eventParametersLength && cmd.eventParameter[0] == 3) {
result << createEvent(descriptionText: "$device.displayName covering replaced", isStateChange: true, displayed: false)
} else {
result << sensorValueEvent(0)
}
} else if (cmd.event == 0xFF) {
result << sensorValueEvent(1)
} else {
result << createEvent(descriptionText: "$device.displayName sent event $cmd.event")
}
} else if (cmd.notificationType) {
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false)
} else {
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false)
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
def event = createEvent(name: "WakeUp", value: "wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: false) // for Astralink
def cmds = []
if (!device.currentState("ManufacturerCode")) {
cmds << secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
cmds << "delay 2000"
}
if (!state.lastbat || now() - state.lastbat > 10*60*60*1000) {
event.descriptionText += ", requesting battery"
cmds << secure(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1))
cmds << "delay 800"
cmds << secure(zwave.batteryV1.batteryGet())
cmds << "delay 2000"
} else {
log.debug "not checking battery, was updated ${(now() - state.lastbat)/60000 as int} min ago"
}
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
return [event, response(cmds)]
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def result = []
def map = [ name: "battery", unit: "%" ]
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = "${device.displayName} has a low battery"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
}
def event = createEvent(map)
// Save at least one battery report in events list every few days
if (!event.isStateChange && (now() - 3*24*60*60*1000) > device.latestState("battery")?.date?.time) {
map.isStateChange = true
}
state.lastbat = now()
return [event]
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
def result = []
def map = [:]
switch (cmd.sensorType) {
case 1:
def cmdScale = cmd.scale == 1 ? "F" : "C"
map.name = "temperature"
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
map.unit = getTemperatureScale()
break;
case 3:
map.name = "illuminance"
map.value = cmd.scaledSensorValue.toInteger().toString()
map.unit = "lux"
break;
case 5:
map.name = "humidity"
map.value = cmd.scaledSensorValue.toInteger().toString()
map.unit = cmd.scale == 0 ? "%" : ""
break;
case 0x1E:
map.name = "loudness"
map.unit = cmd.scale == 1 ? "dBA" : "dB"
map.value = cmd.scaledSensorValue.toString()
break;
default:
map.descriptionText = cmd.toString()
}
result << createEvent(map)
return result
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
def result = []
def manufacturerCode = String.format("%04X", cmd.manufacturerId)
def productTypeCode = String.format("%04X", cmd.productTypeId)
def productCode = String.format("%04X", cmd.productId)
def wirelessConfig = "ZWP"
log.debug "MSR ${manufacturerCode} ${productTypeCode} ${productCode}"
result << createEvent(name: "ManufacturerCode", value: manufacturerCode)
result << createEvent(name: "ProduceTypeCode", value: productTypeCode)
result << createEvent(name: "ProductCode", value: productCode)
result << createEvent(name: "WirelessConfig", value: wirelessConfig)
if (manufacturerCode == "0109" && productTypeCode == "2002") {
result << response(secureSequence([
// Change re-trigger duration to 1 minute
zwave.configurationV1.configurationSet(parameterNumber: 1, configurationValue: [1], size: 1),
zwave.batteryV1.batteryGet(),
zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1)
], 400))
}
return result
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
return [createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)]
}
private secure(physicalgraph.zwave.Command cmd) {
if (state.sec == 0) { // default to secure
cmd.format()
} else {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
}
}
private secureSequence(commands, delay=200) {
delayBetween(commands.collect{ secure(it) }, delay)
}

View File

@@ -337,10 +337,10 @@ def initialize() {
settings.devices.each {
def deviceId = it
def detail = state.deviceDetail[deviceId]
def detail = state?.deviceDetail[deviceId]
try {
switch(detail.type) {
switch(detail?.type) {
case 'NAMain':
log.debug "Base station"
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
@@ -487,12 +487,12 @@ def poll() {
log.debug "State: ${state.deviceState}"
settings.devices.each { deviceId ->
def detail = state.deviceDetail[deviceId]
def data = state.deviceState[deviceId]
def child = children.find { it.deviceNetworkId == deviceId }
def detail = state?.deviceDetail[deviceId]
def data = state?.deviceState[deviceId]
def child = children?.find { it.deviceNetworkId == deviceId }
log.debug "Update: $child";
switch(detail.type) {
switch(detail?.type) {
case 'NAMain':
log.debug "Updating NAMain $data"
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())

View File

@@ -1,345 +0,0 @@
/**
* SmartThings service for Prempoint
*
* Author: Prempoint Inc. (c) 2016
*
*/
definition(
name: "Prempoint",
namespace: "prempoint.com",
author: "Prempoint Inc.",
description: "SmartThings service for Prempoint",
category: "Connections",
iconUrl: "http://www.prempoint.com/images/social_app_emblem_50x50.png",
iconX2Url: "http://www.prempoint.com/images/social_app_emblem_100x100.png",
iconX3Url: "http://www.prempoint.com/images/social_app_emblem_150x150.png",
oauth: [displayName: "Prempoint", displayLink: "http://www.prempoint.com/"])
preferences {
section("Allow Prempoint to Control & Access These Things...") {
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
input "garagedoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false
//input "doors", "capability.doorControl", title: "Which Doors?", multiple: true, required: false
input "cameras", "capability.imageCapture", title: "Which Cameras?", multiple: true, required: false
}
}
mappings {
path("/list") {
action: [
GET: "listDevices"
]
}
path("/switches") {
action: [
GET: "listSwitches"
]
}
path("/switches/:id") {
action: [
GET: "showSwitch"
]
}
path("/switches/:id/:command") {
action: [
GET: "updateSwitch"
]
}
path("/switches/:id/:command/:level") {
action: [
GET: "updateSwitch"
]
}
path("/locks") {
action: [
GET: "listLocks"
]
}
path("/locks/:id") {
action: [
GET: "showLock"
]
}
path("/locks/:id/:command") {
action: [
GET: "updateLock"
]
}
path("/doors/:id") {
action: [
GET: "showDoor"
]
}
path("/doors/:id/:command") {
action: [
GET: "updateDoor"
]
}
path("/garagedoors/:id") {
action: [
GET: "showGarageDoor"
]
}
path("/garagedoors/:id/:command") {
action: [
GET: "updateGarageDoor"
]
}
path("/cameras/:id") {
action: [
GET: "showCamera"
]
}
path("/cameras/:id/:command") {
action: [
GET: "updateCamera"
]
}
}
def installed() {}
def updated() {}
def listDevices() {
log.debug "entering listDevices"
//return listSwitches() + listLocks() + listGarageDoors() + listDoors() + listCameras()
return listSwitches() + listLocks() + listGarageDoors() + listCameras()
}
//switches
def listSwitches() {
log.debug "entering listSwitches"
switches.collect{showDevice(it,"switch")}
}
def showSwitch() {
log.debug "entering showSwitches"
show(switches, "switch")
}
def updateSwitch() {
log.debug "entering updateSwitches"
update(switches, "switch")
}
//locks
def listLocks() {
log.debug "entering listLocks"
locks.collect{showDevice(it,"lock")}
}
def showLock() {
log.debug "entering showLock"
show(locks, "lock")
}
def updateLock() {
log.debug "entering updateLock"
update(locks, "lock")
}
//doors
def listDoors() {
log.debug "entering listDoors"
locks.collect{showDevice(it,"door")}
}
def showDoor() {
log.debug "entering showDoors"
show(doors, "door")
}
def updateDoor() {
log.debug "entering updateDoor"
update(doors, "door")
}
//garagedoors
def listGarageDoors() {
log.debug "entering listGarageDoors"
locks.collect{showDevice(it,"garagedoor")}
}
def showGarageDoor() {
log.debug "entering showGarageDoors"
show(garagedoors, "garagedoor")
}
def updateGarageDoor() {
log.debug "entering updateGarageDoor"
update(gargedoors, "garagedoor")
}
//cameras
def listCameras() {
log.debug "entering listCameras"
cameras.collect{showDevice(it,"image")}
}
def showCamera() {
log.debug "entering showCameras"
show(cameras, "camera")
}
def updateCamera() {
log.debug "entering updateCamera"
update(cameras, "camera")
}
def deviceHandler(evt) {}
private update(devices, type) {
def rc = null
//def command = request.JSON?.command
def command = params.command
log.debug "update, request: params: ${params}, devices: $devices.id type=$type command=$command"
// Process the command.
if (command)
{
def dev = devices.find { it.id == params.id }
if (!dev) {
httpError(404, "Device not found: $params.id")
} else if (type == "switch") {
switch(command) {
case "on":
rc = dev.on()
break
case "off":
rc = dev.off()
break
default:
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
}
} else if (type == "lock") {
switch(command) {
case "lock":
rc = dev.lock()
break
case "unlock":
rc = dev.unlock()
break
default:
httpError(400, "Device command=$command is not a valid for device:=$it.id $dev")
}
} else if (type == "door") {
switch(command) {
case "open":
rc = dev.open()
break
case "close":
rc = dev.close()
break
default:
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
}
} else if (type == "garagedoor") {
switch(command) {
case "open":
rc = dev.open()
break
case "close":
rc = dev.close()
break
default:
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
}
} else if (type == "camera") {
switch(command) {
case "take":
rc = dev.take()
log.debug "Device command=$command device=$it.id $dev current image=$it.currentImage"
break
default:
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
}
}
log.debug "executed device=$it.id $dev command=$command rc=$rc"
// Check that the device is a switch that is currently on, supports 'setLevel"
// and that a level was specified.
int level = params.level ? params.level as int : -1;
if ((type == "switch") && (dev.currentValue('switch') == "on") && hasLevel(dev) && (level != -1)) {
log.debug "device about to setLevel=$level"
dev.setLevel(level);
}
// Show the device info if necessary.
if (rc == null) {
rc = showDevice(dev, type)
}
}
return rc
}
private show(devices, type) {
def dev = devices.find { it.id == params.id }
if (!dev) {
httpError(404, "Device not found")
} else {
// Show the device info.
showDevice(dev, type)
}
}
private showDevice(it, type) {
def props = null
// Get the current state for the device type.
def state = [it.currentState(type)]
// Check that whether the a switch device with level support is located and update the returned device type.
def devType = type
if (type == "switch" && hasLevel(it)) {
// Assign "switchWithLevel" to device type.
devType = "switchWithLevel"
// Add the level state.
def levelState = it.currentState("level")
if (levelState) {
state.add(levelState)
}
}
log.debug "device label=$it.label type=$devType"
// Assign the device item properties if appropriate.
if (it) {
props = [id: it.id, label: it.label, type: devType, state: state]
// Add the hub information to the device properties
// if appropriate.
if (it.hub) {
props.put("location", it.hub.hub.location)
}
if (it.currentImage) {
props.put("currentImage", it.currentImage)
}
}
return props
}
private hasLevel(device) {
// Default return value.
def rc = false;
// Get the device supported commands.
def supportedCommands = device.supportedCommands
// Check to see if the "setLevel" was found and assign
// the appropriate return value.
if (supportedCommands) {
// Find the "setLevel" command.
rc = supportedCommands.toString().indexOf("setLevel") != -1
}
log.debug "hasLevel device label=$device.label supportedCommands=$supportedCommands rc=$rc"
return rc
}

View File

@@ -39,6 +39,7 @@ preferences {
page(name: "completionPage")
page(name: "numbersPage")
page(name: "controllerExplanationPage")
page(name: "unsupportedDevicesPage")
}
def rootPage() {
@@ -47,6 +48,9 @@ def rootPage() {
section("What to dim") {
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
if (dimmers) {
if (dimmersContainUnsupportedDevices()) {
href(name: "toUnsupportedDevicesPage", page: "unsupportedDevicesPage", title: "Some of your selected dimmers don't seem to be supported", description: "Tap here to fix it", required: true)
}
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
}
}
@@ -71,6 +75,31 @@ def rootPage() {
}
}
def unsupportedDevicesPage() {
def unsupportedDimmers = dimmers.findAll { !hasSetLevelCommand(it) }
dynamicPage(name: "unsupportedDevicesPage") {
if (unsupportedDimmers) {
section("These devices do not support the setLevel command") {
unsupportedDimmers.each {
paragraph deviceLabel(it)
}
}
section {
input(name: "dimmers", type: "capability.sensor", title: "Please remove the above devices from this list.", submitOnChange: true, multiple: true)
}
section {
paragraph "If you think there is a mistake here, please contact support."
}
} else {
section {
paragraph "You're all set. You can hit the back button, now. Thanks for cleaning up your settings :)"
}
}
}
}
def controllerExplanationPage() {
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
@@ -528,14 +557,16 @@ def updateDimmers(percentComplete) {
} else {
def shouldChangeColors = (colorize && colorize != "false")
def canChangeColors = hasSetColorCommand(dimmer)
log.debug "Setting ${deviceLabel(dimmer)} to ${nextLevel}"
if (shouldChangeColors && canChangeColors) {
dimmer.setColor([hue: getHue(dimmer, nextLevel), saturation: 100, level: nextLevel])
} else {
if (shouldChangeColors && hasSetColorCommand(dimmer)) {
def hue = getHue(dimmer, nextLevel)
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel} and hue to ${hue}"
dimmer.setColor([hue: hue, saturation: 100, level: nextLevel])
} else if (hasSetLevelCommand(dimmer)) {
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel}"
dimmer.setLevel(nextLevel)
} else {
log.warn "${deviceLabel(dimmer)} does not have setColor or setLevel commands."
}
}
@@ -689,7 +720,7 @@ def completionPercentage() {
def now = new Date().getTime()
def timeElapsed = now - atomicState.start
def totalRunTime = totalRunTimeMillis()
def totalRunTime = totalRunTimeMillis() ?: 1
def percentComplete = timeElapsed / totalRunTime * 100
log.debug "percentComplete: ${percentComplete}"
@@ -817,24 +848,21 @@ private getRedHue(level) {
if (level >= 96) return 17
}
private dimmersContainUnsupportedDevices() {
def found = dimmers.find { hasSetLevelCommand(it) == false }
return found != null
}
private hasSetLevelCommand(device) {
def isDimmer = false
device.supportedCommands.each {
if (it.name.contains("setLevel")) {
isDimmer = true
}
}
return isDimmer
return hasCommand(device, "setLevel")
}
private hasSetColorCommand(device) {
def hasColor = false
device.supportedCommands.each {
if (it.name.contains("setColor")) {
hasColor = true
}
}
return hasColor
return hasCommand(device, "setColor")
}
private hasCommand(device, String command) {
return (device.supportedCommands.find { it.name == command } != null)
}
private dimmersWithSetColorCommand() {
@@ -1073,4 +1101,4 @@ def hasStartLevel() {
def hasEndLevel() {
return (endLevel != null && endLevel != "")
}
}

View File

@@ -30,6 +30,7 @@ definition(
preferences {
page(name:"mainPage", title:"Hue Device Setup", content:"mainPage", refreshTimeout:5)
page(name:"bridgeDiscovery", title:"Hue Bridge Discovery", content:"bridgeDiscovery", refreshTimeout:5)
page(name:"bridgeDiscoveryFailed", title:"Bridge Discovery Failed", content:"bridgeDiscoveryFailed", refreshTimeout:0)
page(name:"bridgeBtnPush", title:"Linking with your Hue", content:"bridgeLinking", refreshTimeout:5)
page(name:"bulbDiscovery", title:"Hue Device Setup", content:"bulbDiscovery", refreshTimeout:5)
}
@@ -53,12 +54,21 @@ def bridgeDiscovery(params=[:])
def options = bridges ?: []
def numFound = options.size() ?: 0
if (numFound == 0 && state.bridgeRefreshCount > 25) {
log.trace "Cleaning old bridges memory"
state.bridges = [:]
state.bridgeRefreshCount = 0
app.updateSetting("selectedHue", "")
}
if (numFound == 0) {
if (state.bridgeRefreshCount == 25) {
log.trace "Cleaning old bridges memory"
state.bridges = [:]
app.updateSetting("selectedHue", "")
} else if (state.bridgeRefreshCount > 100) {
// five minutes have passed, give up
// there seems to be a problem going back from discovey failed page in some instances (compared to pressing next)
// however it is probably a SmartThings settings issue
state.bridges = [:]
app.updateSetting("selectedHue", "")
state.bridgeRefreshCount = 0
return bridgeDiscoveryFailed()
}
}
ssdpSubscribe()
@@ -79,6 +89,13 @@ def bridgeDiscovery(params=[:])
}
}
def bridgeDiscoveryFailed() {
return dynamicPage(name:"bridgeDiscoveryFailed", title: "Bridge Discovery Failed", nextPage: "bridgeDiscovery") {
section("Failed to discover any Hue Bridges. Please confirm that the Hue Bridge is connected to the same network as your SmartThings Hub, and that it has power.") {
}
}
}
def bridgeLinking()
{
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
@@ -88,19 +105,15 @@ def bridgeLinking()
def nextPage = ""
def title = "Linking with your Hue"
def paragraphText
def hueimage = null
if (selectedHue) {
paragraphText = "Press the button on your Hue Bridge to setup a link. "
hueimage = "http://huedisco.mediavibe.nl/wp-content/uploads/2013/09/pair-bridge.png"
} else {
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
hueimage = null
}
if (state.username) { //if discovery worked
nextPage = "bulbDiscovery"
title = "Success!"
paragraphText = "Linking to your hub was a success! Please click 'Next'!"
hueimage = null
}
if((linkRefreshcount % 2) == 0 && !state.username) {
@@ -110,8 +123,6 @@ def bridgeLinking()
return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) {
section("") {
paragraph """${paragraphText}"""
if (hueimage != null)
image "${hueimage}"
}
}
}
@@ -135,13 +146,14 @@ def bulbDiscovery() {
if((bulbRefreshCount % 5) == 0) {
discoverHueBulbs()
}
def selectedBridge = state.bridges.find { key, value -> value?.serialNumber?.equalsIgnoreCase(selectedHue) }
def title = selectedBridge?.value?.name ?: "Find bridges"
return dynamicPage(name:"bulbDiscovery", title:"Bulb Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions
}
section {
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
}
@@ -323,6 +335,8 @@ private getDeviceType(hueType) {
return "Hue Bulb"
else if (hueType?.equalsIgnoreCase("Color Light"))
return "Hue Bloom"
else if (hueType?.equalsIgnoreCase("Color Temperature Light"))
return "Hue White Ambiance Bulb"
else
return null
}
@@ -346,26 +360,29 @@ def addBulbs() {
def newHueBulb
if (bulbs instanceof java.util.Map) {
newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
if (newHueBulb != null) {
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
if (newHueBulb != null) {
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
if (d) {
log.debug "created ${d.displayName} with id $dni"
d.completedSetup = true
d.refresh()
}
} else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
}
} else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
}
} else {
//backwards compatable
//backwards compatable
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
d = addChildBulb(dni, "Extended Color Light", newHueBulb?.value?.name, newHueBulb?.value?.hub)
d?.completedSetup = true
d?.refresh()
}
} else {
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
if (bulbs instanceof java.util.Map) {
// Update device type if incorrect
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
upgradeDeviceType(d, newHueBulb?.value?.type)
}
}
@@ -397,6 +414,7 @@ def addBridge() {
}
if (newbridge) {
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
d?.completedSetup = true
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
def childDevice = getChildDevice(d.deviceNetworkId)
childDevice.sendEvent(name: "serialNumber", value: vbridge.value.serialNumber)
@@ -484,7 +502,21 @@ void bridgeDescriptionHandler(physicalgraph.device.HubResponse hubResponse) {
def bridges = getHueBridges()
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
if (bridge) {
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
// serialNumber from API is in format of 0017882413ad (mac address), however on the actual bridge only last six
// characters are printed on the back so using that to identify bridge
def idNumber = body?.device?.serialNumber?.text()
if (idNumber?.size() >= 6)
idNumber = idNumber[-6..-1].toUpperCase()
// usually in form of bridge name followed by (ip), i.e. defaults to Philips Hue (192.168.1.2)
// replace IP with serial number to make it easier for user to identify
def name = body?.device?.friendlyName?.text()
def index = name?.indexOf('(')
if (index != -1) {
name = name.substring(0,index)
name += " ($idNumber)"
}
bridge.value << [name:name, serialNumber:body?.device?.serialNumber?.text(), verified: true]
} else {
log.error "/description.xml returned a bridge that didn't exist"
}
@@ -657,7 +689,7 @@ def parse(childDevice, description) {
log.warn "Parsing Body failed - trying again..."
poll()
}
if (body instanceof java.util.HashMap) {
if (body instanceof java.util.Map) {
//poll response
def bulbs = getChildDevices()
for (bulb in body) {
@@ -798,22 +830,22 @@ def setColorTemperature(childDevice, huesettings) {
def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'"
def value = [:]
def hue = null
def sat = null
def xy = null
if (huesettings.hex != null) {
value.xy = getHextoXY(huesettings.hex)
} else {
if (huesettings.hue != null)
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
if (huesettings.saturation != null)
if (huesettings.saturation != null)
value.sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
}
// Default behavior is to turn light on
// Default behavior is to turn light on
value.on = true
if (huesettings.level != null) {
@@ -821,7 +853,7 @@ def setColor(childDevice, huesettings) {
value.on = false
else if (huesettings.level == 1)
value.bri = 1
else
else
value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
}
value.alert = huesettings.alert ? huesettings.alert : "none"

View File

@@ -658,29 +658,73 @@ def updateDevice() {
def data = request.JSON
def command = data.command
def arguments = data.arguments
log.debug "updateDevice, params: ${params}, request: ${data}"
if (!command) {
render status: 400, data: '{"msg": "command is required"}'
} else {
def device = allDevices.find { it.id == params.id }
if (device) {
if (device.hasCommand("$command")) {
if (arguments) {
device."$command"(*arguments)
} else {
device."$command"()
}
render status: 204, data: "{}"
} else {
render status: 404, data: '{"msg": "Command not supported by this Device"}'
}
} else {
render status: 404, data: '{"msg": "Device not found"}'
}
if (device) {
if (validateCommand(device, command)) {
if (arguments) {
device."$command"(*arguments)
} else {
device."$command"()
}
render status: 204, data: "{}"
} else {
render status: 403, data: '{"msg": "Access denied. This command is not supported by current capability."}'
}
} else {
render status: 404, data: '{"msg": "Device not found"}'
}
}
}
/**
* Validating the command passed by the user based on capability.
* @return boolean
*/
def validateCommand(device, command) {
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
def currentDeviceCapability = getCapabilityName(device)
if (currentDeviceCapability != "" && capabilityCommands[currentDeviceCapability]) {
return (command in capabilityCommands[currentDeviceCapability] || (currentDeviceCapability == "Switch" && command == "setLevel" && device.hasCommand("setLevel"))) ? true : false
} else {
// Handling other device types here, which don't accept commands
httpError(400, "Bad request.")
}
}
/**
* Need to get the attribute name to do the lookup. Only
* doing it for the device types which accept commands
* @return attribute name of the device type
*/
def getCapabilityName(device) {
def capName = ""
if (switches.find{it.id == device.id})
capName = "Switch"
else if (alarms.find{it.id == device.id})
capName = "Alarm"
else if (locks.find{it.id == device.id})
capName = "Lock"
log.trace "Device: $device - Capability Name: $capName"
return capName
}
/**
* Constructing the map over here of
* supported commands by device capability
* @return a map of device capability -> supported commands
*/
def getDeviceCapabilityCommands(deviceCapabilities) {
def map = [:]
deviceCapabilities.collect {
map[it.name] = it.commands.collect{ it.name.toString() }
}
return map
}
def listSubscriptions() {
log.debug "listSubscriptions()"
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
@@ -779,18 +823,33 @@ def deviceHandler(evt) {
}
def sendToHarmony(evt, String callbackUrl) {
def callback = new URI(callbackUrl)
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
sendHubCommand(new physicalgraph.device.HubAction(
method: "POST",
path: path,
headers: [
"Host": host,
"Content-Type": "application/json"
],
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
))
def callback = new URI(callbackUrl)
if (callback.port != -1) {
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
sendHubCommand(new physicalgraph.device.HubAction(
method: "POST",
path: path,
headers: [
"Host": host,
"Content-Type": "application/json"
],
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
))
} else {
def params = [
uri: callbackUrl,
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
]
try {
log.debug "Sending data to Harmony Cloud: $params"
httpPostJson(params) { resp ->
log.debug "Harmony Cloud - Response: ${resp.status}"
}
} catch (e) {
log.error "Harmony Cloud - Something went wrong: $e"
}
}
}
def listHubs() {

View File

@@ -107,8 +107,8 @@ mappings {
path("/locks") {
action: [
GET: "listLocks",
PUT: "updateLock",
POST: "updateLock"
PUT: "updateLocks",
POST: "updateLocks"
]
}
path("/locks/:id") {
@@ -442,31 +442,87 @@ def executePhrase() {
}
private void updateAll(devices) {
def type = params.param1
def command = request.JSON?.command
if (command)
{
command = command.toLowerCase()
devices."$command"()
if (!devices) {
httpError(404, "Devices not found")
}
if (command){
devices.each { device ->
executeCommand(device, type, command)
}
}
}
private void update(devices) {
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
//def command = request.JSON?.command
def command = params.command
if (command)
{
command = command.toLowerCase()
def device = devices.find { it.id == params.id }
if (!device)
{
def type = params.param1
def command = request.JSON?.command
def device = devices?.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
}
else
{
device."$command"()
}
}
if (command) {
executeCommand(device, type, command)
}
}
/**
* Validating the command passed by the user based on capability.
* @return boolean
*/
def validateCommand(device, deviceType, command) {
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
def currentDeviceCapability = getCapabilityName(deviceType)
if (capabilityCommands[currentDeviceCapability]) {
return command in capabilityCommands[currentDeviceCapability] ? true : false
} else {
// Handling other device types here, which don't accept commands
httpError(400, "Bad request.")
}
}
/**
* Need to get the attribute name to do the lookup. Only
* doing it for the device types which accept commands
* @return attribute name of the device type
*/
def getCapabilityName(type) {
switch(type) {
case "switches":
return "Switch"
case "locks":
return "Lock"
default:
return type
}
}
/**
* Constructing the map over here of
* supported commands by device capability
* @return a map of device capability -> supported commands
*/
def getDeviceCapabilityCommands(deviceCapabilities) {
def map = [:]
deviceCapabilities.collect {
map[it.name] = it.commands.collect{ it.name.toString() }
}
return map
}
/**
* Validates and executes the command
* on the device or devices
*/
def executeCommand(device, type, command) {
if (validateCommand(device, type, command)) {
device."$command"()
} else {
httpError(403, "Access denied. This command is not supported by current capability.")
}
}
private show(devices, type) {

View File

@@ -92,22 +92,87 @@ void updateLock() {
private void updateAll(devices) {
def command = request.JSON?.command
if (command) {
devices."$command"()
def type = params.param1
if (!devices) {
httpError(404, "Devices not found")
}
if (command){
devices.each { device ->
executeCommand(device, type, command)
}
}
}
private void update(devices) {
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
def command = request.JSON?.command
if (command) {
def device = devices.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
device."$command"()
}
def type = params.param1
def device = devices?.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
}
if (command) {
executeCommand(device, type, command)
}
}
/**
* Validating the command passed by the user based on capability.
* @return boolean
*/
def validateCommand(device, deviceType, command) {
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
def currentDeviceCapability = getCapabilityName(deviceType)
if (capabilityCommands[currentDeviceCapability]) {
return command in capabilityCommands[currentDeviceCapability] ? true : false
} else {
// Handling other device types here, which don't accept commands
httpError(400, "Bad request.")
}
}
/**
* Need to get the attribute name to do the lookup. Only
* doing it for the device types which accept commands
* @return attribute name of the device type
*/
def getCapabilityName(type) {
switch(type) {
case "switches":
return "Switch"
case "locks":
return "Lock"
default:
return type
}
}
/**
* Constructing the map over here of
* supported commands by device capability
* @return a map of device capability -> supported commands
*/
def getDeviceCapabilityCommands(deviceCapabilities) {
def map = [:]
deviceCapabilities.collect {
map[it.name] = it.commands.collect{ it.name.toString() }
}
return map
}
/**
* Validates and executes the command
* on the device or devices
*/
def executeCommand(device, type, command) {
if (validateCommand(device, type, command)) {
device."$command"()
} else {
httpError(403, "Access denied. This command is not supported by current capability.")
}
}
private show(devices, name) {