Compare commits

...

163 Commits

Author SHA1 Message Date
Vinay Rao
a22f71bc29 Merge pull request #1171 from SmartThingsCommunity/staging
Rolling up staging to prod for deploy
2016-08-30 12:57:12 -07:00
Vinay Rao
8eeb29a8a7 Merge pull request #1170 from SmartThingsCommunity/production
Rolling down prod hotfix to staging
2016-08-30 12:56:16 -07:00
Lars Finander
3d84bca3d7 Merge pull request #1168 from larsfinander/SSVD-2603_removing_hue_bridge_issue_staging
SSVD-2603 Prevent removing of Hue bridge
2016-08-30 12:54:17 -06:00
Lars Finander
46f47128bd Merge pull request #1169 from larsfinander/SSVD-2613_stop_removing_hue_lights_staging
SSVD-2613 UX for Hue when adding lights is confusing
2016-08-30 12:54:08 -06:00
Lars Finander
9c8398b7a0 SSVD-2613 UX for Hue when adding lights is confusing
-Split lights list in SmartApp into already added lights and new lights
-Also fixed WWST-39 Sort list of bulbs by name during discovery
2016-08-29 16:18:07 -06:00
Lars Finander
cdf5d21e8f SSVD-2603 Prevent removing of Hue bridge
-Moved warning messages to top of bridge DTH screen
-User is now presented with a warning message when removing
2016-08-29 15:35:58 -06:00
Vinay Rao
d5b8db99a2 Merge pull request #1160 from workingmonk/bug/battery_health_timeout
CHF-201 changing timeout for health check to 4 hours
2016-08-27 01:07:04 -07:00
Vinay Rao
6ad4f0990c CHF-201 changing timeout for health check to 4 hours for battery powered devices 2016-08-27 01:05:10 -07:00
Vinay Rao
b1c318ef36 Merge pull request #1156 from workingmonk/feature/osram_cct_health
CHF-239 health extension for osram cct bulbs
2016-08-26 13:39:15 -07:00
Vinay Rao
8eb6001f9f CHF-239 extension for osram cct bulbs 2016-08-26 13:20:51 -07:00
Vinay Rao
d90e15ee31 Merge pull request #1151 from larsfinander/PROB-1352_Hue_colors_rendering_incorrectly_production
PROB-1352 Hue colors rendering incorrectly
2016-08-24 13:13:45 -07:00
Lars Finander
cfe25607ac PROB-1352 Hue colors rendering incorrectly
-Disable color conversion algorithm for hue/sat
2016-08-24 13:56:06 -06:00
Vinay Rao
f251042954 Merge pull request #1149 from varzac/dprot-153-null-pointer
Fix event map name in NYCE Motion sensor
2016-08-24 12:10:40 -07:00
Zach Varberg
33df9b1ff1 Fix event map name in NYCE Motion sensor
There was a null pointer as a result of using the wrong map name

This resolves: https://smartthings.atlassian.net/browse/DPROT-153
2016-08-24 14:04:19 -05:00
Vinay Rao
c8bda222cb Merge pull request #1146 from SmartThingsCommunity/master
Rolling up master for next week's deploy
2016-08-23 15:12:15 -07:00
Vinay Rao
17014dd248 Merge branch 'staging'
# Conflicts:
#	devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy
#	devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy
#	devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy
2016-08-23 14:50:39 -07:00
Vinay Rao
555edf623a Merge pull request #1144 from SmartThingsCommunity/revert-1141-revert-dprot-2
DPROT-2 Reverting again to put the change back " Revert "Update DTHs to use ZigBee library ZoneStatus""
2016-08-23 14:05:48 -07:00
Vinay Rao
feb6ba0e24 Revert " Revert DPROT-2: "Update DTHs to use ZigBee library ZoneStatus"" 2016-08-23 14:04:47 -07:00
Vinay Rao
9d65150bf7 Merge pull request #1143 from SmartThingsCommunity/staging
Rolling up changes to production for deploy
2016-08-23 14:04:20 -07:00
Vinay Rao
99d6e9f668 Merge pull request #1142 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-08-23 12:23:38 -07:00
Vinay Rao
a35f271a8e Merge pull request #1138 from workingmonk/bug/SSVD-2532
SSVD-2532 setting up the generic name at install
2016-08-23 11:34:46 -07:00
Vinay Rao
fd4969981f Merge pull request #1141 from varzac/revert-dprot-2
Revert DPROT-2: "Update DTHs to use ZigBee library ZoneStatus"
2016-08-23 11:27:23 -07:00
Jack Chi
1ec110155d Merge pull request #1137 from jackchi/healthcheck-osram
[CHF-239] Health Check Osram RGB / Dimmer Devices
2016-08-23 10:03:04 -07:00
Zach Varberg
79b90d741f Revert "Update DTHs to use ZigBee library ZoneStatus"
This reverts commit 7bfa0304af.
2016-08-23 08:45:47 -05:00
Vinay Rao
6009bc52ab SSVD-2532 setting up the generic name at install 2016-08-23 02:59:06 -07:00
Lars Finander
33a8fe108e Merge pull request #1133 from larsfinander/DVCSMP-1727_Philips_Hue_Update_status_response_staging
DVCSMP-1727 Philips Hue: Update status on response
2016-08-22 16:09:37 -06:00
jackchi
fadc496e1f [CHF-239] Health Check Osram RGB / Dimmer Devices 2016-08-22 15:06:57 -07:00
Vinay Rao
910694f1d1 Merge pull request #1136 from jackchi/revert-pr1424
Revert PR-1424
2016-08-22 14:08:58 -07:00
jackchi
fa9ebed998 Revert PR-1424 2016-08-22 13:58:11 -07:00
Jack Chi
0a2f2bffc2 Merge pull request #1131 from jackchi/healthcheck-core
[CHF-201] [CHF-206] Health Check Core Devices
2016-08-22 11:14:12 -07:00
jackchi
8dc36eb8f6 [CHF-201] [CHF-206] Health Check Core Devices 2016-08-22 11:05:24 -07:00
Jason Botello
d0929ab89e Merge pull request #1017 from SmartThingsCommunity/MSA-1375-1
MSA-1375: Reminders using notifications and light
2016-08-19 17:08:29 -07:00
Lars Finander
df6646103a DVCSMP-1727 Philips Hue: Update status on response
-Only send events when feedback is recevied from bridge
-Cleaned up color conversions
-DVCSMP-1728 Philips Hue: Handle new LAN polling
-DVCSMP-1763 Philips Hue: Bad rounding can cause level to be 0, light on
-DVCSMP-1678 Philips Hue: Bridge detail page should display the status.
-DVCSMP-1669 Phillips Hue: The on/off button flickering between states
-DEVC-450 Hue Connect app is throwing errors in the IDE
-Changed manual refresh to check bridge and run poll()
-Updated some strings
2016-08-19 14:58:44 -06:00
Vinay Rao
014affe1ea Merge pull request #1130 from workingmonk/cccTile
SSVD-2465 changing the kelvin tile to name tiles for OSRAM
2016-08-18 16:17:48 -07:00
Vinay Rao
53fc948b00 SSVD-2465 changing the kelvin tile to name tiles 2016-08-18 15:04:48 -07:00
Vinay Rao
f389e925d2 Merge pull request #1129 from workingmonk/vdfixosram
SSVD-2463 hotfix for level jumping issues
2016-08-17 21:59:52 -07:00
Vinay Rao
ef47ec9393 hotfix for level jumping issues 2016-08-17 18:21:54 -07:00
Vinay Rao
62d800e99a Merge pull request #1128 from larsfinander/SSVD-2465_production
SSVD-2465 Philips Hue No description for slider
2016-08-17 15:08:18 -07:00
Lars Finander
a79c9c1ade SVD-2465 Philips Hue No description for slider
-Changed Kelvin tile to Whites (same as Philips Hue app)
2016-08-17 15:56:31 -06:00
Vinay Rao
4505ca85b2 Merge pull request #1126 from SmartThingsCommunity/master
Rolling up master to staging
2016-08-16 16:11:09 -07:00
Vinay Rao
3c0c050b3a Merge pull request #1125 from SmartThingsCommunity/staging
Rolling up staging to production for deployment
2016-08-16 14:45:48 -07:00
Rohan Desai
2cd915ba77 Merge pull request #1124 from moorage/master
DVCSMP-1959: remove logging of phone number in barkley been fed
2016-08-16 13:58:24 -07:00
Matthew Moore
4866ecd204 DVCSMP-1959: remove logging of phone number in barkley been fed 2016-08-16 13:52:53 -07:00
dsainteclaire
f0071aad6d Merge pull request #1123 from dsainteclaire/DVCSMP-1959-fix-smartapps-logging-personal-information
DVCSMP-1959 fixed issue with logging personal information and unused subscription from SmartApps
2016-08-16 12:35:50 -07:00
David Sainte-Claire
0d4a00ae2b fixed wrong subscription based on code review comment 2016-08-16 11:46:47 -07:00
David Sainte-Claire
90384d0852 DVCSMP-1959 fixed issue with logging personal information and unused subscription from SmartApps 2016-08-16 11:35:04 -07:00
Jack Chi
47183ebbff Merge pull request #1116 from jackchi/healthcheck-outlet
[CHF-204] Implementation of PING in SmartPower Outlet
2016-08-15 14:32:13 -07:00
jackchi
d68f70b3dd [CHF-204] Implementation of PING in SmartPower Outlet 2016-08-12 14:33:42 -07:00
Vinay Rao
e1b1479cfc Merge pull request #1101 from CosmicPuppy/CosmicPuppy-SensorActuator-Patches
Added Capability "Sensor" and/or "Actuator" per DTH standards.
2016-08-11 12:12:34 -07:00
Zach Varberg
16e4954f10 Merge pull request #1087 from varzac/update-zonestatus
Update DTHs to use ZigBee library ZoneStatus
2016-08-10 10:04:59 -05:00
Vinay Rao
a1b375c929 Merge pull request #1111 from SmartThingsCommunity/master
Rolling up master to staging
2016-08-09 14:55:52 -07:00
Vinay Rao
929f8e1a44 Merge pull request #1110 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-08-09 14:54:50 -07:00
Vinay Rao
4f97d1a3ef Merge pull request #1109 from SmartThingsCommunity/staging
Rolling up staging to production for deploy
2016-08-09 14:30:15 -07:00
Vinay Rao
255185ee8c Merge pull request #1108 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-08-09 14:29:35 -07:00
SmartThings, Inc
ad50582e92 Merge pull request #1013 from SmartThingsCommunity/MSA-1372-2
Merged publication request 'Gidjit - Smart Launcher OAuth Permission Capability'
2016-08-09 14:02:26 -05:00
Vinay Rao
6de6704502 Merge pull request #694 from workingmonk/feature/zigbee_valve
[DEVC-21] zigbee valve DTH for HA 1.2 Waxman Valve
2016-08-08 20:05:45 -07:00
Vinay Rao
a5bc475cc1 zigbee valve DTH for HA 1.2 Waxman Valve 2016-08-08 20:04:23 -07:00
Vinay Rao
4fd5cc5d70 Merge pull request #1106 from workingmonk/leviton_switch
DEVC-365 adding leviton switch to zigbee switch post certification
2016-08-08 12:38:39 -07:00
Vinay Rao
236c37290e adding leviton switch to zigbee switch post certification 2016-08-08 12:36:22 -07:00
CosmicPuppy
7beb2e3905 Added Capability "Sensor" and/or "Actuator" per http://docs.smartthings.com/en/latest/device-type-developers-guide/overview.html?highlight=sensor%20actuator#actuator-and-sensor.
There are some integrations out there using the "Actuator" and "Sensor" Capabilities and this doesn't show up for them (e.g., SmartTiles V6).
2016-08-05 01:09:32 -07:00
Vinay Rao
d17bc1869f Merge pull request #1098 from marstorp/bose-power-8-4
SSVD-1605 and SSVD-1897 Fix for update of switch tile
2016-08-04 16:57:31 -07:00
marstorp
534118a47a Fix for SSVD-1605 and SSVD-1897, update of switch tile when speaker is turned on/off 2016-08-04 16:47:13 -07:00
Vinay Rao
c7396349f1 Merge pull request #851 from workingmonk/feature/new_button_dth
DVCSMP-1742 DVCSMP-1513 Adding support for osram and iris ZigBee buttons
2016-08-04 15:55:47 -07:00
Vinay Rao
089cc1a5dd adding support for osram and iris buttons
commenting out iris fingerprints until cert is complete
2016-08-04 15:43:22 -07:00
Zach Varberg
7bfa0304af Update DTHs to use ZigBee library ZoneStatus
This makes use of a new class exposed from the ZigBee library to
automatically parse the ZoneStatus attribute bit map and expose
the individual values in a simple way.  This fixes a lot of
unhandled cases in the previous copy-pasted switch statements,
as well as allowing for a simpler interface with less copy/pasted
code.
2016-08-03 15:53:30 -05:00
Vinay Rao
842f39e9ff Merge pull request #1091 from juano2310/staging
SSVD-1823 - Name change to correct filename and filepath for new zwave device
2016-08-03 13:53:25 -07:00
juano2310
dd308f6d8f SSVD-1823 - Name change 2016-08-02 18:53:28 -04:00
Vinay Rao
1a72158a9a Merge pull request #1089 from SmartThingsCommunity/master
Rolling up master to staging for next week's deploy
2016-08-02 14:38:46 -07:00
Vinay Rao
94ab309335 Merge pull request #1088 from SmartThingsCommunity/staging
Rolling up staging to production
2016-08-02 13:54:57 -07:00
Luke Ness
f4571289a5 Merge pull request #1086 from gausnes/PRP-205
[PRP-205] Fixing dimmer name to reflect other Z-Wave devices
2016-08-02 11:32:14 -07:00
Luke Ness
b9b0cc6b37 [PRP-205] Changed name of ZWave Dimmer to reflect other DTH naming conventions 2016-08-02 12:48:18 -05:00
Juan Pablo Risso
34ad221f5d No parenthesis (#1085)
* SSVD-1823 - Z-wave Switch Generic

Remove Indicator

Update Name

deviceJoinName

* SSVD-1823 - Drop parenthesis
2016-08-02 12:04:34 -04:00
Juan Pablo Risso
7d8acd5dd6 SSVD-1823 - Z-wave Dimmer Switch (Generic) (#1084)
- deviceJoinName
- Wall
- sendHubCommand and preferences
- remove 014F
2016-07-30 17:42:01 -04:00
Juan Pablo Risso
dba8ea7b99 SSVD-1823 - Z-wave Switch Generic (#1083)
Remove Indicator

Update Name

deviceJoinName
2016-07-30 17:14:02 -04:00
Juan Pablo Risso
49eec58de2 SSVD-1823 - Indicator Light Preference page (#1080)
required = false

indentation

Bob's update
2016-07-30 14:08:39 -04:00
Vinay Rao
f2d635ab44 Merge pull request #1079 from SmartThingsCommunity/master
Rolling up master to staging
2016-07-26 14:09:07 -07:00
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
Donald C. Kirker
b70706f150 Merge pull request #1077 from SmartThingsCommunity/aeon-multi-6
Aeon multi 6
2016-07-26 09:31:58 -07:00
Donald C. Kirker
0d0a3f5ebb Protect ternary operations in configure
Protect ternary operations against order of operations, resulting in incorrect math and math against null values.
2016-07-26 06:32:23 -07:00
Donald C. Kirker
be7fad76fc Add updated Aeon Multisensor 6 fingerprint 2016-07-26 05:30:31 -07:00
Steve Vlaminck
f0e87fa5e9 Merge pull request #1070 from vlaminck/DVCSMP-1904
Fix: Gentle Wake Up requires contact input for Contact Book users [DVCSMP-1904]
2016-07-22 08:23:51 -05:00
vlaminck
1b385afa5b Fix: Gentle Wake Up requires contact input for Contact Book users 2016-07-22 08:16:42 -05: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
Jim Mangione
1ddd0632c9 Modifying 'Reminders using notifications and light' 2016-06-29 20:44:59 -05:00
Jim Mangione
53d0957383 Modifying 'Reminders using notifications and light' 2016-06-29 20:03:27 -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
Jim Mangione
b4a4d83ce7 MSA-1375: These apps use different sensors to detect if a draw or container has been opened or moved. If no activity is detected without 60 minutes of a set reminder time, then an in-app reminder is generated. If no activity after 10 additional minutes, an LED light is turned on and set to RED until activity is recorded.
Additionally, the Temp-Motion app sends an in-app notification if the temperature of the container exceeds a particular threshold.
2016-06-27 20:31:56 -05:00
Matthew Page
ef29820fa1 MSA-1372: This is an updated version of the previous, however a user can now set the preferences and permissions from an OAuth page. 2016-06-24 10:31:30 -05: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
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
Vinay Rao
c6f706e47a Merge pull request #960 from SmartThingsCommunity/staging
Rolling down Staging to master
2016-06-02 14:30:49 -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
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
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
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
57 changed files with 3330 additions and 1218 deletions

View File

@@ -28,6 +28,7 @@ metadata {
attribute "powerSupply", "enum", ["USB Cable", "Battery"] attribute "powerSupply", "enum", ["USB Cable", "Battery"]
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A" fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A,0x5A"
} }
simulator { simulator {
@@ -352,7 +353,7 @@ def configure() {
motionSensitivity == "minimum" ? 0 : 64) motionSensitivity == "minimum" ? 0 : 64)
//5. report every x minutes (threshold reports don't work on battery power, default 8 mins) //5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval] ?: 8*60) //association group 1 request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval] ?: (8*60)) //association group 1
request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 6*60*60) //association group 2 request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 6*60*60) //association group 2

View File

@@ -21,6 +21,8 @@ metadata {
capability "Sensor" capability "Sensor"
capability "Battery" capability "Battery"
command "configureAfterSecure"
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A" fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A"
} }

View File

@@ -64,8 +64,10 @@ metadata {
} }
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) { standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff" state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff"
state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821" state "turningOff", label:'TURNING OFF', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff", nextState:"turningOn"
state "turningOn", label:'TURNING ON', icon:"st.Electronics.electronics16", backgroundColor:"#79b821"
} }
valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) { valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) {
state "station1", label:'${currentValue}', action:"preset1" state "station1", label:'${currentValue}', action:"preset1"
@@ -747,8 +749,16 @@ def cb_boseSetInput(xml, input) {
*/ */
def boseSetPowerState(boolean enable) { def boseSetPowerState(boolean enable) {
log.info "boseSetPowerState(${enable})" log.info "boseSetPowerState(${enable})"
queueCallback('nowPlaying', "cb_boseSetPowerState", enable ? "POWERON" : "POWEROFF") // Fix to get faster update of power status back from speaker after sending on/off
return boseRefreshNowPlaying() // Instead of queuing the command to be sent after the refresh send it directly via sendHubCommand
// Note: This is a temporary hack that should be replaced by a re-design of the
// DTH to use sendHubCommand for all commands
sendHubCommand(bosePOST("/key", "<key state=\"press\" sender=\"Gabbo\">POWER</key>"))
sendHubCommand(bosePOST("/key", "<key state=\"release\" sender=\"Gabbo\">POWER</key>"))
sendHubCommand(boseGET("/now_playing"))
if (enable) {
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", 5)
}
} }
/** /**
@@ -787,10 +797,11 @@ def cb_boseSetPowerState(xml, state) {
*/ */
def cb_boseConfirmPowerOn(xml, tries) { def cb_boseConfirmPowerOn(xml, tries) {
def result = [] def result = []
log.warn "boseConfirmPowerOn() attempt #" + tries def attempt = tries as Integer
if (xml.attributes()['source'] == "STANDBY" && tries > 0) { log.warn "boseConfirmPowerOn() attempt #$attempt"
if (xml.attributes()['source'] == "STANDBY" && attempt > 0) {
result << boseRefreshNowPlaying() result << boseRefreshNowPlaying()
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", tries-1) queueCallback('nowPlaying', "cb_boseConfirmPowerOn", attempt-1)
} }
return result return result
} }

View File

@@ -23,6 +23,7 @@ metadata {
capability "Refresh" capability "Refresh"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
capability "Health Check"
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019" fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
} }
@@ -66,6 +67,12 @@ def parse(String description) {
def resultMap = zigbee.getEvent(description) def resultMap = zigbee.getEvent(description)
if (resultMap) { if (resultMap) {
sendEvent(resultMap) sendEvent(resultMap)
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
} }
else { else {
log.debug "DID NOT PARSE MESSAGE for description : $description" log.debug "DID NOT PARSE MESSAGE for description : $description"
@@ -85,6 +92,21 @@ def setLevel(value) {
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.levelRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
} }
@@ -95,5 +117,6 @@ def poll() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -21,7 +21,9 @@ metadata {
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
fingerprint inClusters: "0x26" fingerprint mfr:"0063", prod:"4457", deviceJoinName: "Z-Wave Wall Dimmer"
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "Z-Wave Wall Dimmer"
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "Z-Wave Plug-In Dimmer"
} }
simulator { simulator {
@@ -42,6 +44,10 @@ metadata {
reply "200163,delay 5000,2602": "command: 2603, payload: 63" reply "200163,delay 5000,2602": "command: 2603, payload: 63"
} }
preferences {
input "ledIndicator", "enum", title: "LED Indicator", description: "Turn LED indicator... ", required: false, options:["on": "When On", "off": "When Off", "never": "Never"], defaultValue: "off"
}
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
@@ -70,11 +76,28 @@ metadata {
} }
main(["switch"]) main(["switch"])
details(["switch", "level", "indicator", "refresh"]) details(["switch", "level", "refresh"])
} }
} }
def updated(){
switch (ledIndicator) {
case "on":
indicatorWhenOn()
break
case "off":
indicatorWhenOff()
break
case "never":
indicatorNever()
break
default:
indicatorWhenOn()
break
}
}
def parse(String description) { def parse(String description) {
def result = null def result = null
if (description != "updated") { if (description != "updated") {
@@ -202,19 +225,19 @@ def refresh() {
delayBetween(commands,100) delayBetween(commands,100)
} }
def indicatorWhenOn() { void indicatorWhenOn() {
sendEvent(name: "indicatorStatus", value: "when on") sendEvent(name: "indicatorStatus", value: "when on", display: false)
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format() sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
} }
def indicatorWhenOff() { void indicatorWhenOff() {
sendEvent(name: "indicatorStatus", value: "when off") sendEvent(name: "indicatorStatus", value: "when off", display: false)
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format() sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
} }
def indicatorNever() { void indicatorNever() {
sendEvent(name: "indicatorStatus", value: "never") sendEvent(name: "indicatorStatus", value: "never", display: false)
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format() sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
} }
def invertSwitch(invert=true) { def invertSwitch(invert=true) {
@@ -224,4 +247,4 @@ def invertSwitch(invert=true) {
else { else {
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format() zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
} }
} }

View File

@@ -43,7 +43,7 @@ metadata {
} }
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single" state "default", label:"Reset To White", action:"reset", icon:"st.lights.philips.hue-single"
} }
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
@@ -51,7 +51,7 @@ metadata {
} }
main(["rich-control"]) main(["rich-control"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"]) details(["rich-control", "reset", "refresh"])
} }
} }
@@ -75,118 +75,78 @@ def parse(description) {
// handle commands // handle commands
void on() { void on() {
log.trace parent.on(this) log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
} }
void off() { void off() {
log.trace parent.off(this) log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
}
else {
level = 25
}
setLevel(level)
} }
void setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
if (verifyPercent(percent)) { if (verifyPercent(percent)) {
parent.setLevel(this, percent) log.trace parent.setLevel(this, percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
sendEvent(name: "switch", value: "on")
} }
} }
void setSaturation(percent) { void setSaturation(percent) {
log.debug "Executing 'setSaturation'" log.debug "Executing 'setSaturation'"
if (verifyPercent(percent)) { if (verifyPercent(percent)) {
parent.setSaturation(this, percent) log.trace parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent, displayed: false)
} }
} }
void setHue(percent) { void setHue(percent) {
log.debug "Executing 'setHue'" log.debug "Executing 'setHue'"
if (verifyPercent(percent)) { if (verifyPercent(percent)) {
parent.setHue(this, percent) log.trace parent.setHue(this, percent)
sendEvent(name: "hue", value: percent, displayed: false)
} }
} }
void setColor(value) { void setColor(value) {
log.debug "setColor: ${value}, $this"
def events = [] def events = []
def validValues = [:] def validValues = [:]
if (verifyPercent(value.hue)) { if (verifyPercent(value.hue)) {
events << createEvent(name: "hue", value: value.hue, displayed: false)
validValues.hue = value.hue validValues.hue = value.hue
} }
if (verifyPercent(value.saturation)) { if (verifyPercent(value.saturation)) {
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
validValues.saturation = value.saturation validValues.saturation = value.saturation
} }
if (value.hex != null) { if (value.hex != null) {
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) { if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
events << createEvent(name: "color", value: value.hex)
validValues.hex = value.hex validValues.hex = value.hex
} else { } else {
log.warn "$value.hex is not a valid color" log.warn "$value.hex is not a valid color"
} }
} }
if (verifyPercent(value.level)) { if (verifyPercent(value.level)) {
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
validValues.level = value.level validValues.level = value.level
} }
if (value.switch == "off" || (value.level != null && value.level <= 0)) { if (value.switch == "off" || (value.level != null && value.level <= 0)) {
events << createEvent(name: "switch", value: "off")
validValues.switch = "off" validValues.switch = "off"
} else { } else {
events << createEvent(name: "switch", value: "on")
validValues.switch = "on" validValues.switch = "on"
} }
if (!events.isEmpty()) { if (!validValues.isEmpty()) {
parent.setColor(this, validValues) log.trace parent.setColor(this, validValues)
}
events.each {
sendEvent(it)
} }
} }
void reset() { void reset() {
log.debug "Executing 'reset'" log.debug "Executing 'reset'"
def value = [level:100, saturation:56, hue:23] def value = [hue:20, saturation:2]
setAdjustedColor(value) setAdjustedColor(value)
parent.poll()
} }
void setAdjustedColor(value) { void setAdjustedColor(value) {
if (value) { if (value) {
log.trace "setAdjustedColor: ${value}" log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:] def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100 // Needed because color picker always sends 100
adjusted.level = null adjusted.level = null
setColor(adjusted) setColor(adjusted)
} else { } else {
log.warn "Invalid color input" log.warn "Invalid color input $value"
}
}
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"
} }
} }
@@ -195,22 +155,6 @@ void refresh() {
parent.manualRefresh() parent.manualRefresh()
} }
def adjustOutgoingHue(percent) {
def adjusted = percent
if (percent > 31) {
if (percent < 63.0) {
adjusted = percent + (7 * (percent -30 ) / 32)
}
else if (percent < 73.0) {
adjusted = 69 + (5 * (percent - 62) / 10)
}
else {
adjusted = percent + (2 * (100 - percent) / 28)
}
}
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}
def verifyPercent(percent) { def verifyPercent(percent) {
if (percent == null) if (percent == null)

View File

@@ -7,8 +7,13 @@
metadata { metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") { definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") {
attribute "serialNumber", "string"
attribute "networkAddress", "string" attribute "networkAddress", "string"
// Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network
// Possible values "Online" or "Offline"
attribute "status", "string"
// Id is the number on the back of the hub, Hue uses last six digits of Mac address
// This is also used in the Hue application as ID
attribute "idNumber", "string"
} }
simulator { simulator {
@@ -17,22 +22,23 @@ metadata {
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"rich-control"){ multiAttributeTile(name:"rich-control"){
tileAttribute ("", key: "PRIMARY_CONTROL") { tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200" attributeState "Offline", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#ffffff"
attributeState "Online", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#79b821"
} }
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
attributeState "default", label:'SN: ${currentValue}'
} }
} valueTile("doNotRemove", "v", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) { state "default", label:'If removed, Hue lights will not work properly'
state "default", label:'SN: ${currentValue}'
} }
valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 4, inactiveLabel: false) { valueTile("idNumber", "device.idNumber", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false state "default", label:'ID: ${currentValue}'
}
valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
state "default", label:'IP: ${currentValue}'
} }
main (["rich-control"]) main (["rich-control"])
details(["rich-control", "networkAddress"]) details(["rich-control", "doNotRemove", "idNumber", "networkAddress"])
} }
} }

View File

@@ -43,16 +43,16 @@ metadata {
} }
} }
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature" state "colorTemperature", action:"color temperature.setColorTemperature"
} }
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K' state "colorTemperature", label: 'WHITES'
} }
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single" state "default", label:"Reset To White", action:"reset", icon:"st.lights.philips.hue-single"
} }
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
@@ -84,118 +84,86 @@ def parse(description) {
// handle commands // handle commands
void on() { void on() {
log.trace parent.on(this) log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
} }
void off() { void off() {
log.trace parent.off(this) log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
}
else {
level = 25
}
setLevel(level)
} }
void setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
if (verifyPercent(percent)) { if (verifyPercent(percent)) {
parent.setLevel(this, percent) log.trace parent.setLevel(this, percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
sendEvent(name: "switch", value: "on")
} }
} }
void setSaturation(percent) { void setSaturation(percent) {
log.debug "Executing 'setSaturation'" log.debug "Executing 'setSaturation'"
if (verifyPercent(percent)) { if (verifyPercent(percent)) {
parent.setSaturation(this, percent) log.trace parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent, displayed: false)
} }
} }
void setHue(percent) { void setHue(percent) {
log.debug "Executing 'setHue'" log.debug "Executing 'setHue'"
if (verifyPercent(percent)) { if (verifyPercent(percent)) {
parent.setHue(this, percent) log.trace parent.setHue(this, percent)
sendEvent(name: "hue", value: percent, displayed: false)
} }
} }
void setColor(value) { void setColor(value) {
log.debug "setColor: ${value}, $this"
def events = [] def events = []
def validValues = [:] def validValues = [:]
if (verifyPercent(value.hue)) { if (verifyPercent(value.hue)) {
events << createEvent(name: "hue", value: value.hue, displayed: false)
validValues.hue = value.hue validValues.hue = value.hue
} }
if (verifyPercent(value.saturation)) { if (verifyPercent(value.saturation)) {
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
validValues.saturation = value.saturation validValues.saturation = value.saturation
} }
if (value.hex != null) { if (value.hex != null) {
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) { if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
events << createEvent(name: "color", value: value.hex)
validValues.hex = value.hex validValues.hex = value.hex
} else { } else {
log.warn "$value.hex is not a valid color" log.warn "$value.hex is not a valid color"
} }
} }
if (verifyPercent(value.level)) { if (verifyPercent(value.level)) {
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
validValues.level = value.level validValues.level = value.level
} }
if (value.switch == "off" || (value.level != null && value.level <= 0)) { if (value.switch == "off" || (value.level != null && value.level <= 0)) {
events << createEvent(name: "switch", value: "off")
validValues.switch = "off" validValues.switch = "off"
} else { } else {
events << createEvent(name: "switch", value: "on")
validValues.switch = "on" validValues.switch = "on"
} }
if (!events.isEmpty()) { if (!validValues.isEmpty()) {
parent.setColor(this, validValues) log.trace parent.setColor(this, validValues)
}
events.each {
sendEvent(it)
} }
} }
void reset() { void reset() {
log.debug "Executing 'reset'" log.debug "Executing 'reset'"
def value = [level:100, saturation:56, hue:23] setColorTemperature(4000)
setAdjustedColor(value)
parent.poll()
} }
void setAdjustedColor(value) { void setAdjustedColor(value) {
if (value) { if (value) {
log.trace "setAdjustedColor: ${value}" log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:] def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100 // Needed because color picker always sends 100
adjusted.level = null adjusted.level = null
setColor(adjusted) setColor(adjusted)
} else { } else {
log.warn "Invalid color input" log.warn "Invalid color input $value"
} }
} }
void setColorTemperature(value) { void setColorTemperature(value) {
if (value) { if (value) {
log.trace "setColorTemperature: ${value}k" log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value) log.trace parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else { } else {
log.warn "Invalid color temperature" log.warn "Invalid color temperature $value"
} }
} }
@@ -204,23 +172,6 @@ void refresh() {
parent.manualRefresh() parent.manualRefresh()
} }
def adjustOutgoingHue(percent) {
def adjusted = percent
if (percent > 31) {
if (percent < 63.0) {
adjusted = percent + (7 * (percent -30 ) / 32)
}
else if (percent < 73.0) {
adjusted = 69 + (5 * (percent - 62) / 10)
}
else {
adjusted = percent + (2 * (100 - percent) / 28)
}
}
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}
def verifyPercent(percent) { def verifyPercent(percent) {
if (percent == null) if (percent == null)
return false return false

View File

@@ -68,20 +68,16 @@ def parse(description) {
// handle commands // handle commands
void on() { void on() {
log.trace parent.on(this) log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
} }
void off() { void off() {
log.trace parent.off(this) log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
} }
void setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
if (percent != null && percent >= 0 && percent <= 100) { if (percent != null && percent >= 0 && percent <= 100) {
parent.setLevel(this, percent) parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
sendEvent(name: "switch", value: "on")
} else { } else {
log.warn "$percent is not 0-100" log.warn "$percent is not 0-100"
} }

View File

@@ -36,12 +36,12 @@ metadata {
} }
} }
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2200..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature" state "colorTemperature", action:"color temperature.setColorTemperature"
} }
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K' state "colorTemperature", label: 'WHITES'
} }
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
@@ -73,20 +73,16 @@ def parse(description) {
// handle commands // handle commands
void on() { void on() {
log.trace parent.on(this) log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
} }
void off() { void off() {
log.trace parent.off(this) log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
} }
void setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
if (percent != null && percent >= 0 && percent <= 100) { if (percent != null && percent >= 0 && percent <= 100) {
parent.setLevel(this, percent) log.trace parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
sendEvent(name: "switch", value: "on")
} else { } else {
log.warn "$percent is not 0-100" log.warn "$percent is not 0-100"
} }
@@ -95,9 +91,7 @@ void setLevel(percent) {
void setColorTemperature(value) { void setColorTemperature(value) {
if (value) { if (value) {
log.trace "setColorTemperature: ${value}k" log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value) log.trace parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else { } else {
log.warn "Invalid color temperature" log.warn "Invalid color temperature"
} }
@@ -107,4 +101,3 @@ void refresh() {
log.debug "Executing 'refresh'" log.debug "Executing 'refresh'"
parent.manualRefresh() parent.manualRefresh()
} }

View File

@@ -13,6 +13,7 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -143,51 +144,14 @@ private Map parseReportAttributeMessage(String description) {
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ') ZoneStatus zs = zigbee.parseZoneStatus(description)
String msgCode = parsedMsg[2] Map resultMap = [:]
Map resultMap = [:]
switch(msgCode) {
case '0x0030': // Closed/No Motion/Dry
log.debug 'no motion'
resultMap.name = 'motion'
resultMap.value = 'inactive'
break
case '0x0032': // Open/Motion/Wet resultMap.name = 'motion'
log.debug 'motion' resultMap.value = zs.isAlarm2Set() ? 'active' : 'inactive'
resultMap.name = 'motion' log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion')
resultMap.value = 'active'
break
case '0x0032': // Tamper Alarm return resultMap
log.debug 'motion with tamper alarm'
resultMap.name = 'motion'
resultMap.value = 'active'
break
case '0x0033': // Battery Alarm
break
case '0x0034': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap.name = 'motion'
resultMap.value = 'inactive'
break
case '0x0035': // Restore Report
break
case '0x0036': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap.name = 'motion'
resultMap.value = 'active'
break
case '0x0038': // Test Mode
break
}
return resultMap
} }
def refresh() def refresh()

View File

@@ -13,7 +13,10 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") { definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") {
capability "Battery" capability "Battery"
@@ -219,40 +222,33 @@ private Map parseReportAttributeMessage(String description) {
} }
private List parseIasMessage(String description) { private List parseIasMessage(String description) {
List parsedMsg = description.split(" ") ZoneStatus zs = zigbee.parseZoneStatus(description)
String msgCode = parsedMsg[2] log.debug "parseIasMessage: $description"
List resultListMap = [] List resultListMap = []
Map resultMap_battery = [:] Map resultMap_battery = [:]
Map resultMap_battery_state = [:] Map resultMap_battery_state = [:]
Map resultMap_sensor = [:] Map resultMap_sensor = [:]
// Relevant bit field definitions from ZigBee spec resultMap_sensor.name = "contact"
def BATTERY_BIT = ( 1 << 3 ) resultMap_sensor.value = zs.isAlarm1Set() ? "open" : "closed"
def TROUBLE_BIT = ( 1 << 6 )
def SENSOR_BIT = ( 1 << 0 ) // it's ALARM1 bit from the ZCL spec
// Convert hex string to integer
def zoneStatus = Integer.parseInt(msgCode[-4..-1],16)
log.debug "parseIasMessage: zoneStatus: ${zoneStatus}"
// Check each relevant bit, create map for it, and add to list // Check each relevant bit, create map for it, and add to list
log.debug "parseIasMessage: Battery Status ${zoneStatus & BATTERY_BIT}" log.debug "parseIasMessage: Battery Status ${zs.battery}"
log.debug "parseIasMessage: Trouble Status ${zoneStatus & TROUBLE_BIT}" log.debug "parseIasMessage: Trouble Status ${zs.trouble}"
log.debug "parseIasMessage: Sensor Status ${zoneStatus & SENSOR_BIT}" log.debug "parseIasMessage: Sensor Status ${zs.alarm1}"
/* Comment out this path to check the battery state to avoid overwriting the /* Comment out this path to check the battery state to avoid overwriting the
battery value (Change log #2), but keep these conditions for later use battery value (Change log #2), but keep these conditions for later use
resultMap_battery_state.name = "battery_state" resultMap_battery_state.name = "battery_state"
if (zoneStatus & TROUBLE_BIT) { if (zs.isTroubleSet()) {
resultMap_battery_state.value = "failed" resultMap_battery_state.value = "failed"
resultMap_battery.name = "battery" resultMap_battery.name = "battery"
resultMap_battery.value = 0 resultMap_battery.value = 0
} }
else { else {
if (zoneStatus & BATTERY_BIT) { if (zs.isBatterySet()) {
resultMap_battery_state.value = "low" resultMap_battery_state.value = "low"
// to generate low battery notification by the platform // to generate low battery notification by the platform
@@ -270,9 +266,6 @@ private List parseIasMessage(String description) {
} }
*/ */
resultMap_sensor.name = "contact"
resultMap_sensor.value = (zoneStatus & SENSOR_BIT) ? "open" : "closed"
resultListMap << resultMap_battery_state resultListMap << resultMap_battery_state
resultListMap << resultMap_battery resultListMap << resultMap_battery
resultListMap << resultMap_sensor resultListMap << resultMap_sensor

View File

@@ -101,6 +101,12 @@ def parse(String description) {
else { else {
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off' def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true) sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
} }
} }
else { else {
@@ -116,13 +122,27 @@ def off() {
def on() { def on() {
zigbee.on() zigbee.on()
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B") zigbee.onOffRefresh() + zigbee.electricMeasurementPowerRefresh()
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 1200, displayed: false) sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + powerConfig() + refresh() zigbee.onOffConfig() + powerConfig() + refresh()
} }

View File

@@ -13,6 +13,8 @@
* License for the specific language governing permissions and limitations * License for the specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -22,6 +24,7 @@ metadata {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Water Sensor" capability "Water Sensor"
capability "Health Check" capability "Health Check"
capability "Sensor"
command "enrollResponse" command "enrollResponse"
@@ -98,6 +101,13 @@ def parse(String description) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
@@ -169,42 +179,9 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ') ZoneStatus zs = zigbee.parseZoneStatus(description)
String msgCode = parsedMsg[2]
Map resultMap = [:] return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry')
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMoistureResult('dry')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMoistureResult('wet')
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
log.debug 'dry with tamper alarm'
resultMap = getMoistureResult('dry')
break
case '0x0025': // Restore Report
log.debug 'water with tamper alarm'
resultMap = getMoistureResult('wet')
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
} }
def getTemperature(value) { def getTemperature(value) {
@@ -255,7 +232,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) 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 }}%" result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
} }
@@ -300,6 +278,21 @@ private Map getMoistureResult(value) {
] ]
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
log.debug "Refreshing Temperature and Battery" log.debug "Refreshing Temperature and Battery"
def refreshCmds = [ def refreshCmds = [
@@ -311,7 +304,7 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false) sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -13,6 +13,8 @@
* License for the specific language governing permissions and limitations * License for the specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -22,6 +24,7 @@ metadata {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Refresh" capability "Refresh"
capability "Health Check" capability "Health Check"
capability "Sensor"
command "enrollResponse" command "enrollResponse"
@@ -102,6 +105,13 @@ def parse(String description) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
@@ -182,44 +192,10 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ') ZoneStatus zs = zigbee.parseZoneStatus(description)
String msgCode = parsedMsg[2]
Map resultMap = [:] // Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion
switch(msgCode) { return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
case '0x0020': // Closed/No Motion/Dry
resultMap = getMotionResult('inactive')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMotionResult('active')
break
case '0x0022': // Tamper Alarm
log.debug 'motion with tamper alarm'
resultMap = getMotionResult('active')
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap = getMotionResult('inactive')
break
case '0x0025': // Restore Report
break
case '0x0026': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap = getMotionResult('active')
break
case '0x0028': // Test Mode
break
}
return resultMap
} }
def getTemperature(value) { def getTemperature(value) {
@@ -271,7 +247,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) 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 }}%" result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
} }
@@ -312,6 +289,21 @@ private Map getMotionResult(value) {
] ]
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
log.debug "refresh called" log.debug "refresh called"
def refreshCmds = [ def refreshCmds = [
@@ -323,7 +315,7 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false) sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -15,6 +15,7 @@
*/ */
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH //DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -24,7 +25,7 @@ metadata {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
command "enrollResponse" command "enrollResponse"
} }
@@ -73,7 +74,7 @@ metadata {
def parse(String description) { def parse(String description) {
log.debug "description: $description" log.debug "description: $description"
Map map = [:] Map map = [:]
if (description?.startsWith('catchall:')) { if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description) map = parseCatchAllMessage(description)
@@ -87,10 +88,10 @@ def parse(String description) {
else if (description?.startsWith('zone status')) { else if (description?.startsWith('zone status')) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
log.debug "Parse returned $map" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) { if (description?.startsWith('enroll request')) {
List cmds = enrollResponse() List cmds = enrollResponse()
log.debug "enroll response: ${cmds}" log.debug "enroll response: ${cmds}"
@@ -128,7 +129,7 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) { private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through // 0x0B is default response indicating message got through
// 0x07 is bind message // 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 || boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B || cluster.command == 0x0B ||
cluster.command == 0x07 || cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e) (cluster.data.size() > 0 && cluster.data.first() == 0x3e)
@@ -141,7 +142,7 @@ private Map parseReportAttributeMessage(String description) {
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
} }
log.debug "Desc Map: $descMap" log.debug "Desc Map: $descMap"
Map resultMap = [:] Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") { if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value) def value = getTemperature(descMap.value)
@@ -153,11 +154,11 @@ private Map parseReportAttributeMessage(String description) {
else if (descMap.cluster == "0406" && descMap.attrId == "0000") { else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
def value = descMap.value.endsWith("01") ? "active" : "inactive" def value = descMap.value.endsWith("01") ? "active" : "inactive"
resultMap = getMotionResult(value) resultMap = getMotionResult(value)
} }
return resultMap return resultMap
} }
private Map parseCustomMessage(String description) { private Map parseCustomMessage(String description) {
Map resultMap = [:] Map resultMap = [:]
if (description?.startsWith('temperature: ')) { if (description?.startsWith('temperature: ')) {
@@ -168,44 +169,8 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ') ZoneStatus zs = zigbee.parseZoneStatus(description)
String msgCode = parsedMsg[2] return zs.isAlarm1Set() ? getMotionResult('active') : getMotionResult('inactive')
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getMotionResult('inactive')
break
case '0x0021': // Open/Motion/Wet
resultMap = getMotionResult('active')
break
case '0x0022': // Tamper Alarm
log.debug 'motion with tamper alarm'
resultMap = getMotionResult('active')
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
log.debug 'no motion with tamper alarm'
resultMap = getMotionResult('inactive')
break
case '0x0025': // Restore Report
break
case '0x0026': // Trouble/Failure
log.debug 'motion with failure alarm'
resultMap = getMotionResult('active')
break
case '0x0028': // Test Mode
break
}
return resultMap
} }
def getTemperature(value) { def getTemperature(value) {
@@ -240,7 +205,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) 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}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }

View File

@@ -13,6 +13,7 @@
* License for the specific language governing permissions and limitations * License for the specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -126,6 +127,13 @@ def parse(String description) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) { if (description?.startsWith('enroll request')) {
@@ -224,47 +232,13 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ') ZoneStatus zs = zigbee.parseZoneStatus(description)
String msgCode = parsedMsg[2]
Map resultMap = [:] Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
if (garageSensor != "Yes"){ if (garageSensor != "Yes"){
resultMap = getContactResult('closed') resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
} }
break
case '0x0021': // Open/Motion/Wet
if (garageSensor != "Yes"){
resultMap = getContactResult('open')
}
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
if (garageSensor != "Yes"){
resultMap = getContactResult('closed')
}
break
case '0x0025': // Restore Report
if (garageSensor != "Yes"){
resultMap = getContactResult('open')
}
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap return resultMap
} }
@@ -338,7 +312,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) 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 }}%" result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
} }
@@ -396,6 +371,21 @@ private getAccelerationResult(numValue) {
] ]
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
log.debug "Refreshing Values " log.debug "Refreshing Values "
@@ -403,78 +393,37 @@ def refresh() {
if (device.getDataValue("manufacturer") == "SmartThings") { if (device.getDataValue("manufacturer") == "SmartThings") {
log.debug "Refreshing Values for manufacturer: SmartThings " log.debug "Refreshing Values for manufacturer: SmartThings "
refreshCmds = refreshCmds + [ /* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276)
/* 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.
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
Separating these out in a separate if-else because I do not want to touch Centralite part as of now.
as of now. */
*/ refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode])
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
} else { } else {
refreshCmds = refreshCmds + [ refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
/* 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",
]
} }
//Common refresh commands //Common refresh commands
refreshCmds = refreshCmds + [ refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200", zigbee.readAttribute(0x0001, 0x0020) +
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200", zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
return refreshCmds + enrollResponse() return refreshCmds + enrollResponse()
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false) sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting" log.debug "Configuring Reporting"
def configCmds = [ def configCmds = enrollResponse() +
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", zigbee.batteryConfig() +
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", zigbee.temperatureConfig() +
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200", zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
"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"
]
return configCmds + refresh() return configCmds + refresh()
} }

View File

@@ -14,6 +14,7 @@
* *
*/ */
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH //DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
@@ -24,6 +25,7 @@
capability "Refresh" capability "Refresh"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Health Check" capability "Health Check"
capability "Sensor"
command "enrollResponse" command "enrollResponse"
} }
@@ -171,40 +173,9 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ') ZoneStatus zs = zigbee.parseZoneStatus(description)
String msgCode = parsedMsg[2]
Map resultMap = [:] return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getContactResult('closed')
break
case '0x0021': // Open/Motion/Wet
resultMap = getContactResult('open')
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
resultMap = getContactResult('closed')
break
case '0x0025': // Restore Report
resultMap = getContactResult('open')
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
} }
def getTemperature(value) { def getTemperature(value) {
@@ -234,7 +205,8 @@ def getTemperature(value) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) 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}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }

View File

@@ -13,32 +13,35 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { 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 "Battery"
capability "Configuration" capability "Configuration"
capability "Contact Sensor" capability "Contact Sensor"
capability "Refresh" capability "Refresh"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Health Check"
capability "Sensor"
command "enrollResponse" command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor" fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor"
} }
simulator { simulator {
} }
preferences { 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 title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){ multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") { tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
@@ -71,10 +74,10 @@ metadata {
details(["contact","temperature","battery","refresh"]) details(["contact","temperature","battery","refresh"])
} }
} }
def parse(String description) { def parse(String description) {
log.debug "description: $description" log.debug "description: $description"
Map map = [:] Map map = [:]
if (description?.startsWith('catchall:')) { if (description?.startsWith('catchall:')) {
map = parseCatchAllMessage(description) map = parseCatchAllMessage(description)
@@ -88,10 +91,17 @@ def parse(String description) {
else if (description?.startsWith('zone status')) { else if (description?.startsWith('zone status')) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) { if (description?.startsWith('enroll request')) {
List cmds = enrollResponse() List cmds = enrollResponse()
log.debug "enroll response: ${cmds}" log.debug "enroll response: ${cmds}"
@@ -99,7 +109,7 @@ def parse(String description) {
} }
return result return result
} }
private Map parseCatchAllMessage(String description) { private Map parseCatchAllMessage(String description) {
Map resultMap = [:] Map resultMap = [:]
def cluster = zigbee.parse(description) def cluster = zigbee.parse(description)
@@ -125,7 +135,7 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) { private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through // 0x0B is default response indicating message got through
// 0x07 is bind message // 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 || boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B || cluster.command == 0x0B ||
cluster.command == 0x07 || cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e) (cluster.data.size() > 0 && cluster.data.first() == 0x3e)
@@ -135,14 +145,14 @@ private boolean shouldProcessMessage(cluster) {
private int getHumidity(value) { private int getHumidity(value) {
return Math.round(Double.parseDouble(value)) return Math.round(Double.parseDouble(value))
} }
private Map parseReportAttributeMessage(String description) { private Map parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param -> Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":") def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
} }
log.debug "Desc Map: $descMap" log.debug "Desc Map: $descMap"
Map resultMap = [:] Map resultMap = [:]
if (descMap.cluster == "0402" && descMap.attrId == "0000") { if (descMap.cluster == "0402" && descMap.attrId == "0000") {
def value = getTemperature(descMap.value) def value = getTemperature(descMap.value)
@@ -151,10 +161,10 @@ private Map parseReportAttributeMessage(String description) {
else if (descMap.cluster == "0001" && descMap.attrId == "0020") { else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16)) resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
} }
return resultMap return resultMap
} }
private Map parseCustomMessage(String description) { private Map parseCustomMessage(String description) {
Map resultMap = [:] Map resultMap = [:]
if (description?.startsWith('temperature: ')) { if (description?.startsWith('temperature: ')) {
@@ -165,42 +175,10 @@ private Map parseCustomMessage(String description) {
} }
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
List parsedMsg = description.split(' ') ZoneStatus zs = zigbee.parseZoneStatus(description)
String msgCode = parsedMsg[2] return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
Map resultMap = [:]
switch(msgCode) {
case '0x0020': // Closed/No Motion/Dry
resultMap = getContactResult('closed')
break
case '0x0021': // Open/Motion/Wet
resultMap = getContactResult('open')
break
case '0x0022': // Tamper Alarm
break
case '0x0023': // Battery Alarm
break
case '0x0024': // Supervision Report
resultMap = getContactResult('closed')
break
case '0x0025': // Restore Report
resultMap = getContactResult('open')
break
case '0x0026': // Trouble/Failure
break
case '0x0028': // Test Mode
break
}
return resultMap
} }
def getTemperature(value) { def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100 def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){ if(getTemperatureScale() == "C"){
@@ -213,11 +191,11 @@ def getTemperature(value) {
private Map getBatteryResult(rawValue) { private Map getBatteryResult(rawValue) {
log.debug 'Battery' log.debug 'Battery'
def linkText = getLinkText(device) def linkText = getLinkText(device)
def result = [ def result = [
name: 'battery' name: 'battery'
] ]
def volts = rawValue / 10 def volts = rawValue / 10
def descriptionText def descriptionText
if (rawValue == 0 || rawValue == 255) {} if (rawValue == 0 || rawValue == 255) {}
@@ -228,7 +206,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) 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}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
@@ -262,6 +241,21 @@ private Map getContactResult(value) {
] ]
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
log.debug "Refreshing Temperature and Battery" log.debug "Refreshing Temperature and Battery"
def refreshCmds = [ def refreshCmds = [
@@ -273,6 +267,7 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -21,6 +21,7 @@ metadata {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Health Check" capability "Health Check"
capability "Sensor"
fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003" fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003"
} }
@@ -82,6 +83,13 @@ def parse(String description) {
map = parseCustomMessage(description) map = parseCustomMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map" log.debug "Parse returned $map"
return map ? createEvent(map) : null return map ? createEvent(map) : null
} }
@@ -205,7 +213,8 @@ private Map getBatteryResult(rawValue) {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) 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}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
@@ -237,6 +246,20 @@ private Map getHumidityResult(value) {
] ]
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() def refresh()
{ {
log.debug "refresh temperature, humidity, and battery" log.debug "refresh temperature, humidity, and battery"
@@ -252,7 +275,7 @@ def refresh()
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false) sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"])
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
def configCmds = [ def configCmds = [

View File

@@ -16,6 +16,8 @@
metadata { metadata {
definition (name: "Simulated Alarm", namespace: "smartthings/testing", author: "SmartThings") { definition (name: "Simulated Alarm", namespace: "smartthings/testing", author: "SmartThings") {
capability "Alarm" capability "Alarm"
capability "Sensor"
capability "Actuator"
} }
simulator { simulator {

View File

@@ -1,6 +1,8 @@
metadata { metadata {
definition (name: "Simulated Color Control", namespace: "smartthings/testing", author: "SmartThings") { definition (name: "Simulated Color Control", namespace: "smartthings/testing", author: "SmartThings") {
capability "Color Control" capability "Color Control"
capability "Sensor"
capability "Actuator"
} }
simulator { simulator {

View File

@@ -15,6 +15,7 @@ metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Simulated Contact Sensor", namespace: "smartthings/testing", author: "bob") { definition (name: "Simulated Contact Sensor", namespace: "smartthings/testing", author: "bob") {
capability "Contact Sensor" capability "Contact Sensor"
capability "Sensor"
command "open" command "open"
command "close" command "close"

View File

@@ -15,6 +15,8 @@ metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Simulated Lock", namespace: "smartthings/testing", author: "bob") { definition (name: "Simulated Lock", namespace: "smartthings/testing", author: "bob") {
capability "Lock" capability "Lock"
capability "Sensor"
capability "Actuator"
} }
// Simulated lock // Simulated lock

View File

@@ -15,6 +15,7 @@ metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Simulated Motion Sensor", namespace: "smartthings/testing", author: "bob") { definition (name: "Simulated Motion Sensor", namespace: "smartthings/testing", author: "bob") {
capability "Motion Sensor" capability "Motion Sensor"
capability "Sensor"
command "active" command "active"
command "inactive" command "inactive"

View File

@@ -15,6 +15,7 @@ metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Simulated Presence Sensor", namespace: "smartthings/testing", author: "bob") { definition (name: "Simulated Presence Sensor", namespace: "smartthings/testing", author: "bob") {
capability "Presence Sensor" capability "Presence Sensor"
capability "Sensor"
command "arrived" command "arrived"
command "departed" command "departed"

View File

@@ -16,6 +16,8 @@ metadata {
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") { definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
capability "Switch" capability "Switch"
capability "Relay Switch" capability "Relay Switch"
capability "Sensor"
capability "Actuator"
command "onPhysical" command "onPhysical"
command "offPhysical" command "offPhysical"

View File

@@ -16,6 +16,7 @@ metadata {
definition (name: "Simulated Temperature Sensor", namespace: "smartthings/testing", author: "SmartThings") { definition (name: "Simulated Temperature Sensor", namespace: "smartthings/testing", author: "SmartThings") {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Switch Level" capability "Switch Level"
capability "Sensor"
command "up" command "up"
command "down" command "down"

View File

@@ -16,6 +16,8 @@ metadata {
definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") { definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") {
capability "Thermostat" capability "Thermostat"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
capability "Sensor"
capability "Actuator"
command "tempUp" command "tempUp"
command "tempDown" command "tempDown"

View File

@@ -15,6 +15,7 @@ metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Simulated Water Sensor", namespace: "smartthings/testing", author: "SmartThings") { definition (name: "Simulated Water Sensor", namespace: "smartthings/testing", author: "SmartThings") {
capability "Water Sensor" capability "Water Sensor"
capability "Sensor"
command "wet" command "wet"
command "dry" command "dry"

View File

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

View File

@@ -1,8 +1,7 @@
/** /**
* Iris Smart Fob * ZigBee Button
* *
* Copyright 2015 Mitch Pond * Copyright 2015 Mitch Pond
* Presence code adapted from SmartThings Arrival Sensor HA device type
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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: * in compliance with the License. You may obtain a copy of the License at:
@@ -14,181 +13,235 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
metadata { metadata {
definition (name: "ZigBee Button", namespace: "smartthings", author: "Mitch Pond") { definition (name: "ZigBee Button", namespace: "smartthings", author: "Mitch Pond") {
capability "Battery" capability "Actuator"
capability "Button" capability "Battery"
capability "Button"
capability "Configuration" capability "Configuration"
capability "Presence Sensor" capability "Refresh"
capability "Sensor" capability "Sensor"
//fingerprint endpointId: "01", profileId: "0104", inClusters: "0000,0001,0003,0007,0020,0B05", outClusters: "0003,0006,0019", model:"3450-L", manufacturer: "CentraLite" command "enrollResponse"
}
fingerprint inClusters: "0000, 0001, 0003, 0020, 0402, 0B05", outClusters: "0003, 0006, 0008, 0019", manufacturer: "OSRAM", model: "LIGHTIFY Dimming Switch", deviceJoinName: "OSRAM LIGHTIFY Dimming Switch"
preferences{ //fingerprint inClusters: "0000, 0001, 0003, 0020, 0500", outClusters: "0003,0019", manufacturer: "CentraLite", model: "3455-L", deviceJoinName: "Iris Care Pendant"
input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"", //fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0402, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model: "3460-L", deviceJoinName: "Iris Smart Button"
defaultValue: 3, displayDuringSetup: false) //fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L", deviceJoinName: "Iris KeyFob"
input "checkInterval", "enum", title: "Presence timeout (minutes)",
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
input "logging", "bool", title: "Enable debug logging",
defaultValue: false, displayDuringSetup: false
} }
tiles(scale: 2) { simulator {}
standardTile("presence", "device.presence", width: 4, height: 4, canChangeBackground: true) {
state "present", label: "Present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff"
}
standardTile("button", "device.button", decoration: "flat", width: 2, height: 2) {
state "default", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
}
valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
main (["presence"]) preferences {
details(["presence","button","battery"]) section {
} input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"", defaultValue: 1, displayDuringSetup: false)
}
}
tiles {
standardTile("button", "device.button", width: 2, height: 2) {
state "default", label: "", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
state "button 1 pushed", label: "pushed #1", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#79b821"
}
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"
}
main (["button"])
details(["button", "battery", "refresh"])
}
} }
def parse(String description) { def parse(String description) {
def descMap = zigbee.parseDescriptionAsMap(description) log.debug "description is $description"
logIt descMap def event = zigbee.getEvent(description)
state.lastCheckin = now() if (event) {
logIt "lastCheckin = ${state.lastCheckin}" sendEvent(event)
handlePresenceEvent(true) }
else {
def results = [] if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
if (description?.startsWith('catchall:')) def descMap = zigbee.parseDescriptionAsMap(description)
results = parseCatchAllMessage(descMap) if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
else if (description?.startsWith('read attr -')) event = getBatteryResult(zigbee.convertHexToInt(descMap.value))
results = parseReportAttributeMessage(descMap) }
else logIt(descMap, "trace") else if (descMap.clusterInt == 0x0006 || descMap.clusterInt == 0x0008) {
event = parseNonIasButtonMessage(descMap)
return results; }
}
else if (description?.startsWith('zone status')) {
event = parseIasButtonMessage(description)
}
log.debug "Parse returned $event"
def result = event ? createEvent(event) : []
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
}
}
private Map parseIasButtonMessage(String description) {
int zoneInt = Integer.parseInt((description - "zone status 0x"), 16)
if (zoneInt & 0x02) {
resultMap = getButtonResult('press')
} else {
resultMap = getButtonResult('release')
}
return resultMap
}
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def volts = rawValue / 10
if (volts > 3.0 || volts == 0 || rawValue == 0xFF) {
return [:]
}
else {
def result = [
name: 'battery'
]
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
def linkText = getLinkText(device)
result.descriptionText = "${linkText} battery was ${result.value}%"
return result
}
}
private Map parseNonIasButtonMessage(Map descMap){
def buttonState = ""
def buttonNumber = 0
if (((device.getDataValue("model") == "3460-L") || (device.getDataValue("model") == "3450-L"))
&&(descMap.clusterInt == 0x0006)) {
if (descMap.command == "01") {
getButtonResult("press")
}
else if (descMap.command == "00") {
getButtonResult("release")
}
}
else if (descMap.clusterInt == 0x0006) {
buttonState = "pushed"
if (descMap.command == "01") {
buttonNumber = 1
}
else if (descMap.command == "00") {
buttonNumber = 2
}
if (buttonNumber !=0) {
def descriptionText = "$device.displayName button $buttonNumber was $buttonState"
return createEvent(name: "button", value: buttonState, data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true)
}
else {
return [:]
}
}
else if (descMap.clusterInt == 0x0008) {
if (descMap.command == "05") {
state.buttonNumber = 1
getButtonResult("press", 1)
}
else if (descMap.command == "01") {
state.buttonNumber = 2
getButtonResult("press", 2)
}
else if (descMap.command == "03") {
getButtonResult("release", state.buttonNumber)
}
}
}
def refresh() {
log.debug "Refreshing Battery"
return zigbee.readAttribute(0x0001, 0x20) +
zigbee.enrollResponse()
}
def configure() {
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def cmds = []
if (device.getDataValue("model") == "3450-L") {
cmds << [
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 300",
"zdo bind 0x${device.deviceNetworkId} 2 1 6 {${device.zigbeeId}} {}", "delay 300",
"zdo bind 0x${device.deviceNetworkId} 3 1 6 {${device.zigbeeId}} {}", "delay 300",
"zdo bind 0x${device.deviceNetworkId} 4 1 6 {${device.zigbeeId}} {}", "delay 300"
]
}
return zigbee.onOffConfig() +
zigbee.levelConfig() +
zigbee.configureReporting(0x0001, 0x20, 0x20, 30, 21600, 0x01) +
zigbee.enrollResponse() +
zigbee.readAttribute(0x0001, 0x20) +
cmds
}
private Map getButtonResult(buttonState, buttonNumber = 1) {
if (buttonState == 'release') {
log.debug "Button was value : $buttonState"
def timeDiff = now() - state.pressTime
log.info "timeDiff: $timeDiff"
def holdPreference = holdTime ?: 1
log.info "holdp1 : $holdPreference"
holdPreference = (holdPreference as int) * 1000
log.info "holdp2 : $holdPreference"
if (timeDiff > 10000) { //timeDiff>10sec check for refresh sending release value causing actions to be executed
return [:]
}
else {
if (timeDiff < holdPreference) {
buttonState = "pushed"
}
else {
buttonState = "held"
}
def descriptionText = "$device.displayName button $buttonNumber was $buttonState"
return createEvent(name: "button", value: buttonState, data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true)
}
}
else if (buttonState == 'press') {
log.debug "Button was value : $buttonState"
state.pressTime = now()
log.info "presstime: ${state.pressTime}"
return [:]
}
}
def installed() {
initialize()
} }
def updated() { def updated() {
startTimer() initialize()
configure()
} }
def configure(){ def initialize() {
logIt "Configuring Smart Fob..." if ((device.getDataValue("manufacturer") == "OSRAM") && (device.getDataValue("model") == "LIGHTIFY Dimming Switch")) {
[ sendEvent(name: "numberOfButtons", value: 2)
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 2 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 3 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 4 1 6 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 200"
] +
zigbee.configureReporting(0x0001,0x0020,0x20,20,20,0x01)
}
def parseCatchAllMessage(descMap) {
if (descMap?.clusterId == "0006" && descMap?.command == "01") //button pressed
handleButtonPress(descMap.sourceEndpoint as int)
else if (descMap?.clusterId == "0006" && descMap?.command == "00") //button released
handleButtonRelease(descMap.sourceEndpoint as int)
else logIt("Parse: Unhandled message: ${descMap}","trace")
}
def parseReportAttributeMessage(descMap) {
if (descMap?.cluster == "0001" && descMap?.attrId == "0020") createBatteryEvent(getBatteryLevel(descMap.value))
else logIt descMap
}
private createBatteryEvent(percent) {
logIt "Battery level at " + percent
return createEvent([name: "battery", value: percent])
}
//this method determines if a press should count as a push or a hold and returns the relevant event type
private handleButtonRelease(button) {
logIt "lastPress state variable: ${state.lastPress}"
def sequenceError = {logIt("Uh oh...missed a message? Dropping this event.", "error"); state.lastPress = null; return []}
if (!state.lastPress) return sequenceError()
else if (state.lastPress.button != button) return sequenceError()
def currentTime = now()
def startOfPress = state.lastPress?.time
def timeDif = currentTime - startOfPress
def holdTimeMillisec = (settings.holdTime?:3).toInteger() * 1000
state.lastPress = null //we're done with this. clear it to make error conditions easier to catch
if (timeDif < 0)
//likely a message sequence issue or dropped packet. Drop this press and wait for another.
return sequenceError()
else if (timeDif < holdTimeMillisec)
return createButtonEvent(button,"pushed")
else
return createButtonEvent(button,"held")
}
private handleButtonPress(button) {
state.lastPress = [button: button, time: now()]
}
private createButtonEvent(button,action) {
logIt "Button ${button} ${action}"
return createEvent([
name: "button",
value: action,
data:[buttonNumber: button],
descriptionText: "${device.displayName} button ${button} was ${action}",
isStateChange: true,
displayed: true])
}
private getBatteryLevel(rawValue) {
def intValue = Integer.parseInt(rawValue,16)
def min = 2.1
def max = 3.0
def vBatt = intValue / 10
return ((vBatt - min) / (max - min) * 100) as int
}
private handlePresenceEvent(present) {
def wasPresent = device.currentState("presence")?.value == "present"
if (!wasPresent && present) {
logIt "Sensor is present"
startTimer()
} else if (!present) {
logIt "Sensor is not present"
stopTimer()
} }
def linkText = getLinkText(device) else if ((device.getDataValue("manufacturer") == "CentraLite") &&
def eventMap = [ ((device.getDataValue("model") == "3455-L") || (device.getDataValue("model") == "3460-L"))) {
name: "presence", sendEvent(name: "numberOfButtons", value: 1)
value: present ? "present" : "not present",
linkText: linkText,
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
]
logIt "Creating presence event: ${eventMap}"
sendEvent(eventMap)
}
private startTimer() {
logIt "Scheduling periodic timer"
schedule("0 * * * * ?", checkPresenceCallback)
}
private stopTimer() {
logIt "Stopping periodic timer"
unschedule()
}
def checkPresenceCallback() {
def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000
def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60
logIt "Sensor checked in ${timeSinceLastCheckin} seconds ago"
if (timeSinceLastCheckin >= theCheckInterval) {
handlePresenceEvent(false)
} }
else if ((device.getDataValue("manufacturer") == "CentraLite") && (device.getDataValue("model") == "3450-L")) {
sendEvent(name: "numberOfButtons", value: 4)
}
else {
//default. can be changed
sendEvent(name: "numberOfButtons", value: 4)
}
} }
// ****** Utility functions ******
private logIt(str, logLevel = 'debug') {if (settings.logging) log."$logLevel"(str) }

View File

@@ -19,6 +19,7 @@ metadata {
capability "Refresh" capability "Refresh"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
@@ -53,7 +54,16 @@ def parse(String description) {
def event = zigbee.getEvent(description) def event = zigbee.getEvent(description)
if (event) { if (event) {
sendEvent(event) // Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {}
else {
sendEvent(event)
}
} }
else { else {
log.warn "DID NOT PARSE MESSAGE for description : $description" log.warn "DID NOT PARSE MESSAGE for description : $description"
@@ -72,6 +82,20 @@ def on() {
def setLevel(value) { def setLevel(value) {
zigbee.setLevel(value) zigbee.setLevel(value)
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
@@ -79,5 +103,7 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -27,6 +27,10 @@ metadata {
capability "Refresh" capability "Refresh"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
capability "Health Check"
attribute "colorName", "string"
command "setGenericName"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
@@ -54,15 +58,15 @@ metadata {
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature" state "colorTemperature", action:"color temperature.setColorTemperature"
} }
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K' state "colorName", label: '${currentValue}'
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main(["switch"]) main(["switch"])
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"]) details(["switch", "colorTempSliderControl", "colorName", "refresh"])
} }
} }
@@ -78,10 +82,22 @@ private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
def parse(String description) { def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def finalResult = zigbee.getEvent(description) def event = zigbee.getEvent(description)
if (finalResult) { if (event) {
log.debug finalResult log.debug event
sendEvent(finalResult) // Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {}
else {
if (event.name=="colorTemperature") {
setGenericName(event.value)
}
sendEvent(event)
}
} }
else { else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description) def zigbeeMap = zigbee.parseDescriptionAsMap(description)
@@ -110,20 +126,54 @@ def on() {
def off() { def off() {
zigbee.off() zigbee.off()
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) zigbee.onOffRefresh() + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
} }
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
} }
def setColorTemperature(value) { def setColorTemperature(value) {
setGenericName(value)
zigbee.setColorTemperature(value) zigbee.setColorTemperature(value)
} }
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
def setGenericName(value){
if (value != null) {
def genericName = "White"
if (value < 3300) {
genericName = "Soft White"
} else if (value < 4150) {
genericName = "Moonlight"
} else if (value <= 5000) {
genericName = "Cool White"
} else if (value >= 5000) {
genericName = "Daylight"
}
sendEvent(name: "colorName", value: genericName)
}
}
def setLevel(value) { def setLevel(value) {
zigbee.setLevel(value) zigbee.setLevel(value)
} }

View File

@@ -20,6 +20,7 @@ metadata {
capability "Switch" capability "Switch"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "0003, 0006, 0019, 0406", manufacturer: "Leviton", model: "ZSS-10", deviceJoinName: "Leviton Switch"
} }
// simulator metadata // simulator metadata

View File

@@ -1,5 +1,5 @@
/** /**
* Copyright 2015 SmartThings * Copyright 2016 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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: * in compliance with the License. You may obtain a copy of the License at:
@@ -11,100 +11,133 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
/*
* Capabilities
* - Battery
* - Configuration
* - Refresh
* - Switch
* - Valve
*/
metadata { metadata {
definition (name: "Zigbee Valve", namespace: "smartthings", author: "SmartThings") { definition (name: "ZigBee Valve", namespace: "smartthings", author: "SmartThings") {
capability "Battery" capability "Actuator"
capability "Configuration" capability "Battery"
capability "Refresh" capability "Configuration"
capability "Switch" capability "Power Source"
capability "Valve" capability "Refresh"
capability "Valve"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0020,0006,0B02", outClusters: "0003" fingerprint profileId: "0104", inClusters: "0000, 0001, 0003, 0006, 0020, 0B02, FC02", outClusters: "0019", manufacturer: "WAXMAN", model: "leakSMART Water Valve v2.10", deviceJoinName: "leakSMART Valve"
} fingerprint profileId: "0104", inClusters: "0000, 0001, 0003, 0004, 0005, 0006, 0008, 000F, 0020, 0B02", outClusters: "0003, 0019", manufacturer: "WAXMAN", model: "House Water Valve - MDL-TBD", deviceJoinName: "Waxman House Water Valve"
}
// simulator metadata // simulator metadata
simulator { simulator {
// status messages // status messages
status "on": "on/off: 1" status "on": "on/off: 1"
status "off": "on/off: 0" status "off": "on/off: 0"
// reply messages // reply messages
reply "zcl on-off on": "on/off: 1" reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0" reply "zcl on-off off": "on/off: 0"
} }
// UI tile definitions tiles(scale: 2) {
tiles { multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
state "off", label: 'closed', action: "switch.on", icon: "st.Outdoor.outdoor16", backgroundColor: "#e86d13" attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#53a7c0", nextState:"closing"
state "on", label: 'open', action: "switch.off", icon: "st.Outdoor.outdoor16", backgroundColor: "#53a7c0" attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#e86d13", nextState:"opening"
} attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#53a7c0", nextState:"closing"
main "switch" attributeState "closing", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#e86d13", nextState:"opening"
details(["switch"]) }
} tileAttribute ("powerSource", key: "SECONDARY_CONTROL") {
attributeState "powerSource", label:'Power Source: ${currentValue}'
}
}
valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:2) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["valve"])
details(["valve", "battery", "refresh"])
}
} }
private getCLUSTER_BASIC() { 0x0000 }
private getBASIC_ATTR_POWER_SOURCE() { 0x0007 }
private getCLUSTER_POWER() { 0x0001 }
private getPOWER_ATTR_BATTERY_PERCENTAGE_REMAINING() { 0x0021 }
private getTYPE_U8() { 0x20 }
private getTYPE_ENUM8() { 0x30 }
// Parse incoming device messages to generate events // Parse incoming device messages to generate events
def parse(String description) { def parse(String description) {
log.info description log.debug "description is $description"
if (description?.startsWith("catchall:")) { def event = zigbee.getEvent(description)
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null if (event) {
def result = createEvent(name: name, value: value) if(event.name == "switch") {
def msg = zigbee.parse(description) event.name = "contact" //0006 cluster in valve is tied to contact
log.debug "Parse returned ${result?.descriptionText}" if(event.value == "on") {
return result event.value = "open"
log.trace msg }
log.trace "data: $msg.data" else if(event.value == "off") {
} event.value = "closed"
else { }
def name = description?.startsWith("on/off: ") ? "switch" : null }
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null sendEvent(event)
def result = createEvent(name: name, value: value) }
log.debug "Parse returned ${result?.descriptionText}" else {
return result def descMap = zigbee.parseDescriptionAsMap(description)
} if (descMap.clusterInt == CLUSTER_BASIC && descMap.attrInt == BASIC_ATTR_POWER_SOURCE){
} def value = descMap.value
if (value == "01" || value == "02") {
// Commands to device sendEvent(name: "powerSource", value: "Mains")
def on() { }
log.debug "on()" else if (value == "03") {
sendEvent(name: "switch", value: "on") sendEvent(name: "powerSource", value: "Battery")
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}" }
} else if (value == "04") {
sendEvent(name: "powerSource", value: "DC")
def off() { }
log.debug "off()" else {
sendEvent(name: "switch", value: "off") sendEvent(name: "powerSource", value: "Unknown")
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}" }
}
else if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
event.name = "battery"
event.value = Math.round(Integer.parseInt(descMap.value, 16) / 2)
sendEvent(event)
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug descMap
}
}
} }
def open() { def open() {
log.debug "on()" zigbee.on()
sendEvent(name: "switch", value: "on")
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
} }
def close() { def close() {
log.debug "off()" zigbee.off()
sendEvent(name: "switch", value: "off")
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
} }
def refresh() { def refresh() {
log.debug "sending refresh command" log.debug "refresh called"
"st rattr 0x${device.deviceNetworkId} 1 6 0" zigbee.onOffRefresh() +
zigbee.readAttribute(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE) +
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) +
zigbee.onOffConfig() +
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, TYPE_U8, 600, 21600, 1) +
zigbee.configureReporting(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE, TYPE_ENUM8, 5, 21600, 1)
} }
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings."
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}" zigbee.onOffConfig() +
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, TYPE_U8, 600, 21600, 1) +
zigbee.configureReporting(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE, TYPE_ENUM8, 5, 21600, 1) +
zigbee.onOffRefresh() +
zigbee.readAttribute(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE) +
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
} }

View File

@@ -22,6 +22,7 @@ metadata {
capability "Actuator" capability "Actuator"
capability "Color Temperature" capability "Color Temperature"
capability "Configuration" capability "Configuration"
capability "Health Check"
capability "Refresh" capability "Refresh"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
@@ -49,9 +50,6 @@ metadata {
tileAttribute ("device.level", key: "SLIDER_CONTROL") { tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel" attributeState "level", action:"switch level.setLevel"
} }
tileAttribute ("colorName", key: "SECONDARY_CONTROL") {
attributeState "colorName", label:'${currentValue}'
}
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
@@ -61,12 +59,12 @@ metadata {
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature" state "colorTemperature", action:"color temperature.setColorTemperature"
} }
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K' state "colorName", label: '${currentValue}'
} }
main(["switch"]) main(["switch"])
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"]) details(["switch", "colorTempSliderControl", "colorName", "refresh"])
} }
} }
@@ -75,7 +73,19 @@ def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def event = zigbee.getEvent(description) def event = zigbee.getEvent(description)
if (event) { if (event) {
sendEvent(event) // Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {}
else {
if (event.name=="colorTemperature") {
setGenericName(event.value)
}
sendEvent(event)
}
} }
else { else {
log.warn "DID NOT PARSE MESSAGE for description : $description" log.warn "DID NOT PARSE MESSAGE for description : $description"
@@ -95,12 +105,29 @@ def setLevel(value) {
zigbee.setLevel(value) zigbee.setLevel(value)
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
} }
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
} }

View File

@@ -0,0 +1,205 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Z-Wave Dimmer Switch Generic", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Switch"
capability "Polling"
capability "Refresh"
capability "Sensor"
fingerprint inClusters: "0x26", deviceJoinName: "Z-Wave Dimmer"
}
simulator {
status "on": "command: 2003, payload: FF"
status "off": "command: 2003, payload: 00"
status "09%": "command: 2003, payload: 09"
status "10%": "command: 2003, payload: 0A"
status "33%": "command: 2003, payload: 21"
status "66%": "command: 2003, payload: 42"
status "99%": "command: 2003, payload: 63"
// reply messages
reply "2001FF,delay 5000,2602": "command: 2603, payload: FF"
reply "200100,delay 5000,2602": "command: 2603, payload: 00"
reply "200119,delay 5000,2602": "command: 2603, payload: 19"
reply "200132,delay 5000,2602": "command: 2603, payload: 32"
reply "20014B,delay 5000,2602": "command: 2603, payload: 4B"
reply "200163,delay 5000,2602": "command: 2603, payload: 63"
}
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
}
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "level", label:'${currentValue} %', unit:"%", backgroundColor:"#ffffff"
}
main(["switch"])
details(["switch", "level", "refresh"])
}
}
def parse(String description) {
def result = null
if (description != "updated") {
log.debug "parse() >> zwave.parse($description)"
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
if (cmd) {
result = zwaveEvent(cmd)
}
}
if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) {
result = [result, response(zwave.basicV1.basicGet())]
log.debug "Was hailed: requesting state update"
} else {
log.debug "Parse returned ${result?.descriptionText}"
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
dimmerEvents(cmd)
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
dimmerEvents(cmd)
}
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd) {
dimmerEvents(cmd)
}
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd) {
dimmerEvents(cmd)
}
private dimmerEvents(physicalgraph.zwave.Command cmd) {
def value = (cmd.value ? "on" : "off")
def result = [createEvent(name: "switch", value: value)]
if (cmd.value && cmd.value <= 100) {
result << createEvent(name: "level", value: cmd.value, unit: "%")
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
log.debug "ConfigurationReport $cmd"
def value = "when off"
if (cmd.configurationValue[0] == 1) {value = "when on"}
if (cmd.configurationValue[0] == 2) {value = "never"}
createEvent([name: "indicatorStatus", value: value])
}
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
createEvent([name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false])
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
log.debug "manufacturerId: ${cmd.manufacturerId}"
log.debug "manufacturerName: ${cmd.manufacturerName}"
log.debug "productId: ${cmd.productId}"
log.debug "productTypeId: ${cmd.productTypeId}"
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
updateDataValue("MSR", msr)
updateDataValue("manufacturer", cmd.manufacturerName)
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
}
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd) {
[createEvent(name:"switch", value:"on"), response(zwave.switchMultilevelV1.switchMultilevelGet().format())]
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
[:]
}
def on() {
delayBetween([
zwave.basicV1.basicSet(value: 0xFF).format(),
zwave.switchMultilevelV1.switchMultilevelGet().format()
],5000)
}
def off() {
delayBetween([
zwave.basicV1.basicSet(value: 0x00).format(),
zwave.switchMultilevelV1.switchMultilevelGet().format()
],5000)
}
def setLevel(value) {
log.debug "setLevel >> value: $value"
def valueaux = value as Integer
def level = Math.max(Math.min(valueaux, 99), 0)
if (level > 0) {
sendEvent(name: "switch", value: "on")
} else {
sendEvent(name: "switch", value: "off")
}
sendEvent(name: "level", value: level, unit: "%")
delayBetween ([zwave.basicV1.basicSet(value: level).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
}
def setLevel(value, duration) {
log.debug "setLevel >> value: $value, duration: $duration"
def valueaux = value as Integer
def level = Math.max(Math.min(valueaux, 99), 0)
def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60)
def getStatusDelay = duration < 128 ? (duration*1000)+2000 : (Math.round(duration / 60)*60*1000)+2000
delayBetween ([zwave.switchMultilevelV2.switchMultilevelSet(value: level, dimmingDuration: dimmingDuration).format(),
zwave.switchMultilevelV1.switchMultilevelGet().format()], getStatusDelay)
}
def poll() {
zwave.switchMultilevelV1.switchMultilevelGet().format()
}
def refresh() {
log.debug "refresh() is called"
def commands = []
commands << zwave.switchMultilevelV1.switchMultilevelGet().format()
if (getDataValue("MSR") == null) {
commands << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
}
delayBetween(commands,100)
}
def invertSwitch(invert=true) {
if (invert) {
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format()
}
else {
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
}
}

View File

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

View File

@@ -21,6 +21,13 @@ metadata {
capability "Motion Sensor" capability "Motion Sensor"
capability "Sensor" capability "Sensor"
capability "Battery" 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 { 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) { if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
result << response(zwave.batteryV1.batteryGet()) result << response(zwave.batteryV1.batteryGet())
result << response("delay 1200") } else {
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
} }
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
result 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

@@ -0,0 +1,143 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Z-Wave Switch Generic", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Switch"
capability "Polling"
capability "Refresh"
capability "Sensor"
fingerprint inClusters: "0x25", deviceJoinName: "Z-Wave Switch"
}
// simulator metadata
simulator {
status "on": "command: 2003, payload: FF"
status "off": "command: 2003, payload: 00"
// reply messages
reply "2001FF,delay 100,2502": "command: 2503, payload: FF"
reply "200100,delay 100,2502": "command: 2503, payload: 00"
}
// tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
}
}
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
details(["switch","refresh"])
}
}
def parse(String description) {
def result = null
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
if (cmd) {
result = createEvent(zwaveEvent(cmd))
}
if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) {
result = [result, response(zwave.basicV1.basicGet())]
log.debug "Was hailed: requesting state update"
} else {
log.debug "Parse returned ${result?.descriptionText}"
}
return result
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
[name: "switch", value: cmd.value ? "on" : "off", type: "digital"]
}
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
def value = "when off"
if (cmd.configurationValue[0] == 1) {value = "when on"}
if (cmd.configurationValue[0] == 2) {value = "never"}
[name: "indicatorStatus", value: value, display: false]
}
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
[name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false]
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
log.debug "manufacturerId: ${cmd.manufacturerId}"
log.debug "manufacturerName: ${cmd.manufacturerName}"
log.debug "productId: ${cmd.productId}"
log.debug "productTypeId: ${cmd.productTypeId}"
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
updateDataValue("MSR", msr)
updateDataValue("manufacturer", cmd.manufacturerName)
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
[:]
}
def on() {
delayBetween([
zwave.basicV1.basicSet(value: 0xFF).format(),
zwave.switchBinaryV1.switchBinaryGet().format()
])
}
def off() {
delayBetween([
zwave.basicV1.basicSet(value: 0x00).format(),
zwave.switchBinaryV1.switchBinaryGet().format()
])
}
def poll() {
delayBetween([
zwave.switchBinaryV1.switchBinaryGet().format(),
zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
])
}
def refresh() {
delayBetween([
zwave.switchBinaryV1.switchBinaryGet().format(),
zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
])
}
def invertSwitch(invert=true) {
if (invert) {
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format()
}
else {
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
}
}

View File

@@ -15,12 +15,15 @@ metadata {
definition (name: "Z-Wave Switch", namespace: "smartthings", author: "SmartThings") { definition (name: "Z-Wave Switch", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Actuator"
capability "Indicator" capability "Indicator"
capability "Switch" capability "Switch"
capability "Polling" capability "Polling"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
fingerprint inClusters: "0x25" fingerprint mfr:"0063", prod:"4952", deviceJoinName: "Z-Wave Wall Switch"
fingerprint mfr:"0063", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
fingerprint mfr:"0063", prod:"5052", deviceJoinName: "Z-Wave Plug-In Switch"
fingerprint mfr:"0113", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
} }
// simulator metadata // simulator metadata
@@ -33,6 +36,10 @@ metadata {
reply "200100,delay 100,2502": "command: 2503, payload: 00" reply "200100,delay 100,2502": "command: 2503, payload: 00"
} }
preferences {
input "ledIndicator", "enum", title: "LED Indicator", description: "Turn LED indicator... ", required: false, options:["on": "When On", "off": "When Off", "never": "Never"], defaultValue: "off"
}
// tile definitions // tile definitions
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
@@ -52,10 +59,27 @@ metadata {
} }
main "switch" main "switch"
details(["switch","refresh","indicator"]) details(["switch","refresh"])
} }
} }
def updated(){
switch (ledIndicator) {
case "on":
indicatorWhenOn()
break
case "off":
indicatorWhenOff()
break
case "never":
indicatorNever()
break
default:
indicatorWhenOn()
break
}
}
def parse(String description) { def parse(String description) {
def result = null def result = null
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1]) def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
@@ -139,19 +163,19 @@ def refresh() {
]) ])
} }
def indicatorWhenOn() { void indicatorWhenOn() {
sendEvent(name: "indicatorStatus", value: "when on", display: false) sendEvent(name: "indicatorStatus", value: "when on", display: false)
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format() sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
} }
def indicatorWhenOff() { void indicatorWhenOff() {
sendEvent(name: "indicatorStatus", value: "when off", display: false) sendEvent(name: "indicatorStatus", value: "when off", display: false)
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format() sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
} }
def indicatorNever() { void indicatorNever() {
sendEvent(name: "indicatorStatus", value: "never", display: false) sendEvent(name: "indicatorStatus", value: "never", display: false)
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format() sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
} }
def invertSwitch(invert=true) { def invertSwitch(invert=true) {

View File

@@ -24,17 +24,24 @@ definition(
iconX3Url: "http://www.gidjit.com/appicon@3x.png", iconX3Url: "http://www.gidjit.com/appicon@3x.png",
oauth: [displayName: "Gidjit", displayLink: "www.gidjit.com"]) oauth: [displayName: "Gidjit", displayLink: "www.gidjit.com"])
preferences(oauthPage: "deviceAuthorization") {
// deviceAuthorization page is simply the devices to authorize
page(name: "deviceAuthorization", title: "Device Authorization", nextPage: "instructionPage",
install: false, uninstall: true) {
section ("Allow Gidjit to have access, thereby allowing you to quickly control and monitor your following devices. Privacy Policy can be found at http://priv.gidjit.com/privacy.html") {
input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false
input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false
input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade
}
}
preferences { page(name: "instructionPage", title: "Device Discovery", install: true) {
section ("Allow Gidjit to have access, there by allowing you to quickly control and monitor the following devices") { section() {
input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false paragraph "Now the process is complete return to the Devices section of the Detected Screen. From there and you can add actions to each of your device panels, including launching SmartThings routines."
input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false }
input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade }
//input "bulbs", "capability.colorControl", title: "Control your lights", multiple: true, required: false //windowShade
}
} }
mappings { mappings {
path("/structureinfo") { path("/structureinfo") {
action: [ action: [

View File

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

View File

@@ -78,7 +78,7 @@ def humidityHandler(evt) {
log.debug "Notification already sent within the last ${deltaMinutes} minutes" log.debug "Notification already sent within the last ${deltaMinutes} minutes"
} else { } else {
log.debug "Humidity Rose Above ${tooHumid}: sending SMS to $phone1 and activating ${mySwitch}" log.debug "Humidity Rose Above ${tooHumid}: sending SMS and activating ${mySwitch}"
send("${humiditySensor1.label} sensed high humidity level of ${evt.value}") send("${humiditySensor1.label} sensed high humidity level of ${evt.value}")
switch1?.on() switch1?.on()
} }
@@ -91,7 +91,7 @@ def humidityHandler(evt) {
log.debug "Notification already sent within the last ${deltaMinutes} minutes" log.debug "Notification already sent within the last ${deltaMinutes} minutes"
} else { } else {
log.debug "Humidity Fell Below ${notHumidEnough}: sending SMS to $phone1 and activating ${mySwitch}" log.debug "Humidity Fell Below ${notHumidEnough}: sending SMS and activating ${mySwitch}"
send("${humiditySensor1.label} sensed high humidity level of ${evt.value}") send("${humiditySensor1.label} sensed high humidity level of ${evt.value}")
switch1?.off() switch1?.off()
} }

View File

@@ -25,15 +25,11 @@ preferences {
def installed() { def installed() {
subscribe(contact1, "contact", contactHandler) subscribe(contact1, "contact", contactHandler)
subscribe(switch1, "switch.on", switchOnHandler)
subscribe(switch1, "switch.off", switchOffHandler)
} }
def updated() { def updated() {
unsubscribe() unsubscribe()
subscribe(contact1, "contact", contactHandler) subscribe(contact1, "contact", contactHandler)
subscribe(switch1, "switch.on", switchOnHandler)
subscribe(switch1, "switch.off", switchOffHandler)
} }
def contactHandler(evt) { def contactHandler(evt) {
@@ -46,4 +42,4 @@ def contactHandler(evt) {
if (evt.value == "closed") { if (evt.value == "closed") {
if(state.wasOn)switch1.on() if(state.wasOn)switch1.on()
} }
} }

View File

@@ -0,0 +1,188 @@
/**
* Medicine Management - Contact Sensor
*
* Copyright 2016 Jim Mangione
*
* 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.
*
* Logic:
* --- Send notification at the medicine reminder time IF draw wasn't alread opened in past 60 minutes
* --- If draw still isn't open 10 minutes AFTER reminder time, LED will turn RED.
* --- ----- Once draw IS open, LED will return back to it's original color
*
*/
import groovy.time.TimeCategory
definition(
name: "Medicine Management - Contact Sensor",
namespace: "MangioneImagery",
author: "Jim Mangione",
description: "This supports devices with capabilities of ContactSensor and ColorControl (LED). It sends an in-app and ambient light notification if you forget to open the drawer or cabinet where meds are stored. A reminder will be set to a single time per day. If the draw or cabinet isn't opened within 60 minutes of that reminder, an in-app message will be sent. If the draw or cabinet still isn't opened after an additional 10 minutes, then an LED light turns red until the draw or cabinet is opened",
category: "Health & Wellness",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
preferences {
section("My Medicine Draw/Cabinet"){
input "deviceContactSensor", "capability.contactSensor", title: "Opened Sensor"
}
section("Remind me to take my medicine at"){
input "reminderTime", "time", title: "Time"
}
// NOTE: Use REAL device - virtual device causes compilation errors
section("My LED Light"){
input "deviceLight", "capability.colorControl", title: "Smart light"
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// will stop LED notification incase it was set by med reminder
subscribe(deviceContactSensor, "contact", contactHandler)
// how many minutes to look in the past from the reminder time, for an open draw
state.minutesToCheckOpenDraw = 60
// is true when LED notification is set after exceeding 10 minutes past reminder time
state.ledNotificationTriggered = false
// Set a timer to run once a day to notify if draw wasn't opened yet
schedule(reminderTime, checkOpenDrawInPast)
}
// Should turn off any LED notification on OPEN state
def contactHandler(evt){
if (evt.value == "open") {
// if LED notification triggered, reset it.
log.debug "Cabinet opened"
if (state.ledNotificationTriggered) {
resetLEDNotification()
}
}
}
// If the draw was NOT opened within 60 minutes of the timer send notification out.
def checkOpenDrawInPast(){
log.debug "Checking past 60 minutes of activity from $reminderTime"
// check activity of sensor for past 60 minutes for any OPENED status
def cabinetOpened = isOpened(state.minutesToCheckOpenDraw)
log.debug "Cabinet found opened: $cabinetOpened"
// if it's opened, then do nothing and assume they took their meds
if (!cabinetOpened) {
sendNotification("Hi, please remember to take your meds in the cabinet")
// if no open activity, send out notification and set new reminder
def reminderTimePlus10 = new Date(now() + (10 * 60000))
// needs to be scheduled if draw wasn't already opened
runOnce(reminderTimePlus10, checkOpenDrawAfterReminder)
}
}
// If the draw was NOT opened after 10 minutes past reminder, use LED notification
def checkOpenDrawAfterReminder(){
log.debug "Checking additional 10 minutes of activity from $reminderTime"
// check activity of sensor for past 10 minutes for any OPENED status
def cabinetOpened = isOpened(10)
log.debug "Cabinet found opened: $cabinetOpened"
// if no open activity, blink lights
if (!cabinetOpened) {
log.debug "Set LED to Notification color"
setLEDNotification()
}
}
// Helper function for sending out an app notification
def sendNotification(msg){
log.debug "Message Sent: $msg"
sendPush(msg)
}
// Check if the sensor has been opened since the minutes entered
// Return true if opened found, else false.
def isOpened(minutes){
// query last X minutes of activity log
def previousDateTime = new Date(now() - (minutes * 60000))
// capture all events recorded
def evts = deviceContactSensor.eventsSince(previousDateTime)
def cabinetOpened = false
if (evts.size() > 0) {
evts.each{
if(it.value == "open") {
cabinetOpened = true
}
}
}
return cabinetOpened
}
// Saves current color and sets the light to RED
def setLEDNotification(){
state.ledNotificationTriggered = true
// turn light back off when reset is called if it was originally off
state.ledState = deviceLight.currentValue("switch")
// set light to RED and store original color until stopped
state.origColor = deviceLight.currentValue("hue")
deviceLight.on()
deviceLight.setHue(100)
log.debug "LED set to RED. Original color stored: $state.origColor"
}
// Sets the color back to the original saved color
def resetLEDNotification(){
state.ledNotificationTriggered = false
// return color to original
log.debug "Reset LED color to: $state.origColor"
if (state.origColor != null) {
deviceLight.setHue(state.origColor)
}
// if the light was turned on just for the notification, turn it back off now
if (state.ledState == "off") {
deviceLight.off()
}
}

View File

@@ -0,0 +1,189 @@
/**
* Medicine Management - Temp-Motion
*
* Copyright 2016 Jim Mangione
*
* 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.
*
* Logic:
* --- If temp > threshold set, send notification
* --- Send in-app notification at the medicine reminder time if no motion is detected in past 60 minutes
* --- If motion still isn't detected 10 minutes AFTER reminder time, LED will turn RED
* --- ----- Once motion is detected, LED will turn back to it's original color
*/
import groovy.time.TimeCategory
definition(
name: "Medicine Management - Temp-Motion",
namespace: "MangioneImagery",
author: "Jim Mangione",
description: "This only supports devices with capabilities TemperatureMeasurement, AccelerationSensor and ColorControl (LED). Supports two use cases. First, will notifies via in-app if the fridge where meds are stored exceeds a temperature threshold set in degrees. Secondly, sends an in-app and ambient light notification if you forget to take your meds by sensing movement of the medicine box in the fridge. A reminder will be set to a single time per day. If the box isn't moved within 60 minutes of that reminder, an in-app message will be sent. If the box still isn't moved after an additional 10 minutes, then an LED light turns red until the box is moved",
category: "Health & Wellness",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
preferences {
section("My Medicine in the Refrigerator"){
input "deviceAccelerationSensor", "capability.accelerationSensor", required: true, multiple: false, title: "Movement"
input "deviceTemperatureMeasurement", "capability.temperatureMeasurement", required: true, multiple: false, title: "Temperature"
}
section("Temperature Threshold"){
input "tempThreshold", "number", title: "Temperature Threshold"
}
section("Remind me to take my medicine at"){
input "reminderTime", "time", title: "Time"
}
// NOTE: Use REAL device - virtual device causes compilation errors
section("My LED Light"){
input "deviceLight", "capability.colorControl", title: "Smart light"
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// will notify when temp exceeds max
subscribe(deviceTemperatureMeasurement, "temperature", tempHandler)
// will stop LED notification incase it was set by med reminder
subscribe(deviceAccelerationSensor, "acceleration.active", motionHandler)
// how many minutes to look in the past from the reminder time
state.minutesToCheckPriorToReminder = 60
// Set a timer to run once a day to notify if draw wasn't opened yet
schedule(reminderTime, checkMotionInPast)
}
// If temp > 39 then send an app notification out.
def tempHandler(evt){
if (evt.doubleValue > tempThreshold) {
log.debug "Fridge temp of $evt.value exceeded threshold"
sendNotification("WARNING: Fridge temp is $evt.value with threshold of $tempThreshold")
}
}
// Should turn off any LED notification once motion detected
def motionHandler(evt){
// always call out to stop any possible LED notification
log.debug "Medication moved. Send stop LED notification"
resetLEDNotification()
}
// If no motion detected within 60 minutes of the timer send notification out.
def checkMotionInPast(){
log.debug "Checking past 60 minutes of activity from $reminderTime"
// check activity of sensor for past 60 minutes for any OPENED status
def movement = isMoved(state.minutesToCheckPriorToReminder)
log.debug "Motion found: $movement"
// if there was movement, then do nothing and assume they took their meds
if (!movement) {
sendNotification("Hi, please remember to take your meds in the fridge")
// if no movement, send out notification and set new reminder
def reminderTimePlus10 = new Date(now() + (10 * 60000))
// needs to be scheduled if draw wasn't already opened
runOnce(reminderTimePlus10, checkMotionAfterReminder)
}
}
// If still no movement after 10 minutes past reminder, use LED notification
def checkMotionAfterReminder(){
log.debug "Checking additional 10 minutes of activity from $reminderTime"
// check activity of sensor for past 10 minutes for any OPENED status
def movement = isMoved(10)
log.debug "Motion found: $movement"
// if no open activity, blink lights
if (!movement) {
log.debug "Notify LED API"
setLEDNotification()
}
}
// Helper function for sending out an app notification
def sendNotification(msg){
log.debug "Message Sent: $msg"
sendPush(msg)
}
// Check if the accelerometer has been activated since the minutes entered
// Return true if active, else false.
def isMoved(minutes){
// query last X minutes of activity log
def previousDateTime = new Date(now() - (minutes * 60000))
// capture all events recorded
def evts = deviceAccelerationSensor.eventsSince(previousDateTime)
def motion = false
if (evts.size() > 0) {
evts.each{
if(it.value == "active") {
motion = true
}
}
}
return motion
}
// Saves current color and sets the light to RED
def setLEDNotification(){
// turn light back off when reset is called if it was originally off
state.ledState = deviceLight.currentValue("switch")
// set light to RED and store original color until stopped
state.origColor = deviceLight.currentValue("hue")
deviceLight.on()
deviceLight.setHue(100)
log.debug "LED set to RED. Original color stored: $state.origColor"
}
// Sets the color back to the original saved color
def resetLEDNotification(){
// return color to original
log.debug "Reset LED color to: $state.origColor"
deviceLight.setHue(state.origColor)
// if the light was turned on just for the notification, turn it back off now
if (state.ledState == "off") {
deviceLight.off()
}
}

View File

@@ -77,7 +77,7 @@ def humidityHandler(evt) {
} else { } else {
if (state.lastStatus != "off") { if (state.lastStatus != "off") {
log.debug "Humidity Rose Above $humidityHigh1: sending SMS to $phone1 and deactivating $mySwitch" log.debug "Humidity Rose Above $humidityHigh1: sending SMS and deactivating $mySwitch"
send("${humiditySensor1.label} sensed high humidity level of ${evt.value}, turning off ${switch1.label}") send("${humiditySensor1.label} sensed high humidity level of ${evt.value}, turning off ${switch1.label}")
switch1?.off() switch1?.off()
state.lastStatus = "off" state.lastStatus = "off"
@@ -99,7 +99,7 @@ def humidityHandler(evt) {
} else { } else {
if (state.lastStatus != "on") { if (state.lastStatus != "on") {
log.debug "Humidity Dropped Below $humidityLow1: sending SMS to $phone1 and activating $mySwitch" log.debug "Humidity Dropped Below $humidityLow1: sending SMS and activating $mySwitch"
send("${humiditySensor1.label} sensed low humidity level of ${evt.value}, turning on ${switch1.label}") send("${humiditySensor1.label} sensed low humidity level of ${evt.value}, turning on ${switch1.label}")
switch1?.on() switch1?.on()
state.lastStatus = "on" state.lastStatus = "on"
@@ -125,4 +125,4 @@ private send(msg) {
} }
log.debug msg log.debug msg
} }

View File

@@ -39,6 +39,7 @@ preferences {
page(name: "completionPage") page(name: "completionPage")
page(name: "numbersPage") page(name: "numbersPage")
page(name: "controllerExplanationPage") page(name: "controllerExplanationPage")
page(name: "unsupportedDevicesPage")
} }
def rootPage() { def rootPage() {
@@ -47,6 +48,9 @@ def rootPage() {
section("What to dim") { section("What to dim") {
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true) input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
if (dimmers) { 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") 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() { def controllerExplanationPage() {
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") { dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
@@ -208,7 +237,7 @@ def completionPage() {
} }
section("Notifications") { section("Notifications") {
input("recipients", "contact", title: "Send notifications to") { input("recipients", "contact", title: "Send notifications to", required: false) {
input(name: "completionPhoneNumber", type: "phone", title: "Text This Number", description: "Phone number", required: false) input(name: "completionPhoneNumber", type: "phone", title: "Text This Number", description: "Phone number", required: false)
input(name: "completionPush", type: "bool", title: "Send A Push Notification", description: "Phone number", required: false) input(name: "completionPush", type: "bool", title: "Send A Push Notification", description: "Phone number", required: false)
} }
@@ -528,14 +557,16 @@ def updateDimmers(percentComplete) {
} else { } else {
def shouldChangeColors = (colorize && colorize != "false") def shouldChangeColors = (colorize && colorize != "false")
def canChangeColors = hasSetColorCommand(dimmer)
log.debug "Setting ${deviceLabel(dimmer)} to ${nextLevel}" if (shouldChangeColors && hasSetColorCommand(dimmer)) {
def hue = getHue(dimmer, nextLevel)
if (shouldChangeColors && canChangeColors) { log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel} and hue to ${hue}"
dimmer.setColor([hue: getHue(dimmer, nextLevel), saturation: 100, level: nextLevel]) dimmer.setColor([hue: hue, saturation: 100, level: nextLevel])
} else { } else if (hasSetLevelCommand(dimmer)) {
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel}"
dimmer.setLevel(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 now = new Date().getTime()
def timeElapsed = now - atomicState.start def timeElapsed = now - atomicState.start
def totalRunTime = totalRunTimeMillis() def totalRunTime = totalRunTimeMillis() ?: 1
def percentComplete = timeElapsed / totalRunTime * 100 def percentComplete = timeElapsed / totalRunTime * 100
log.debug "percentComplete: ${percentComplete}" log.debug "percentComplete: ${percentComplete}"
@@ -817,24 +848,21 @@ private getRedHue(level) {
if (level >= 96) return 17 if (level >= 96) return 17
} }
private dimmersContainUnsupportedDevices() {
def found = dimmers.find { hasSetLevelCommand(it) == false }
return found != null
}
private hasSetLevelCommand(device) { private hasSetLevelCommand(device) {
def isDimmer = false return hasCommand(device, "setLevel")
device.supportedCommands.each {
if (it.name.contains("setLevel")) {
isDimmer = true
}
}
return isDimmer
} }
private hasSetColorCommand(device) { private hasSetColorCommand(device) {
def hasColor = false return hasCommand(device, "setColor")
device.supportedCommands.each { }
if (it.name.contains("setColor")) {
hasColor = true private hasCommand(device, String command) {
} return (device.supportedCommands.find { it.name == command } != null)
}
return hasColor
} }
private dimmersWithSetColorCommand() { private dimmersWithSetColorCommand() {
@@ -1073,4 +1101,4 @@ def hasStartLevel() {
def hasEndLevel() { def hasEndLevel() {
return (endLevel != null && endLevel != "") return (endLevel != null && endLevel != "")
} }

View File

@@ -68,7 +68,7 @@ def scheduleCheck()
sendNotificationToContacts("No one has fed the dog", recipients) sendNotificationToContacts("No one has fed the dog", recipients)
} }
else { else {
log.debug "Feeder was not opened since $midnight, texting $phone1" log.debug "Feeder was not opened since $midnight, texting one phone number"
sendSms(phone1, "No one has fed the dog") sendSms(phone1, "No one has fed the dog")
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -69,10 +69,10 @@ def temperatureHandler(evt) {
def alreadySentSms = recentEvents.count { it.doubleValue <= tooCold } > 1 def alreadySentSms = recentEvents.count { it.doubleValue <= tooCold } > 1
if (alreadySentSms) { if (alreadySentSms) {
log.debug "SMS already sent to $phone1 within the last $deltaMinutes minutes" log.debug "SMS already sent within the last $deltaMinutes minutes"
// TODO: Send "Temperature back to normal" SMS, turn switch off // TODO: Send "Temperature back to normal" SMS, turn switch off
} else { } else {
log.debug "Temperature dropped below $tooCold: sending SMS to $phone1 and activating $mySwitch" log.debug "Temperature dropped below $tooCold: sending SMS and activating $mySwitch"
def tempScale = location.temperatureScale ?: "F" def tempScale = location.temperatureScale ?: "F"
send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:tempScale}") send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
switch1?.on() switch1?.on()

View File

@@ -658,29 +658,73 @@ def updateDevice() {
def data = request.JSON def data = request.JSON
def command = data.command def command = data.command
def arguments = data.arguments def arguments = data.arguments
log.debug "updateDevice, params: ${params}, request: ${data}" log.debug "updateDevice, params: ${params}, request: ${data}"
if (!command) { if (!command) {
render status: 400, data: '{"msg": "command is required"}' render status: 400, data: '{"msg": "command is required"}'
} else { } else {
def device = allDevices.find { it.id == params.id } def device = allDevices.find { it.id == params.id }
if (device) { if (device) {
if (device.hasCommand("$command")) { if (validateCommand(device, command)) {
if (arguments) { if (arguments) {
device."$command"(*arguments) device."$command"(*arguments)
} else { } else {
device."$command"() device."$command"()
} }
render status: 204, data: "{}" render status: 204, data: "{}"
} else { } else {
render status: 404, data: '{"msg": "Command not supported by this Device"}' render status: 403, data: '{"msg": "Access denied. This command is not supported by current capability."}'
} }
} else { } else {
render status: 404, data: '{"msg": "Device not found"}' 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() { def listSubscriptions() {
log.debug "listSubscriptions()" log.debug "listSubscriptions()"
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect { app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
@@ -779,18 +823,33 @@ def deviceHandler(evt) {
} }
def sendToHarmony(evt, String callbackUrl) { def sendToHarmony(evt, String callbackUrl) {
def callback = new URI(callbackUrl) def callback = new URI(callbackUrl)
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host if (callback.port != -1) {
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
sendHubCommand(new physicalgraph.device.HubAction( def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
method: "POST", sendHubCommand(new physicalgraph.device.HubAction(
path: path, method: "POST",
headers: [ path: path,
"Host": host, headers: [
"Content-Type": "application/json" "Host": host,
], "Content-Type": "application/json"
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]] ],
)) 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() { def listHubs() {

View File

@@ -92,22 +92,87 @@ void updateLock() {
private void updateAll(devices) { private void updateAll(devices) {
def command = request.JSON?.command def command = request.JSON?.command
if (command) { def type = params.param1
devices."$command"() if (!devices) {
httpError(404, "Devices not found")
}
if (command){
devices.each { device ->
executeCommand(device, type, command)
}
} }
} }
private void update(devices) { private void update(devices) {
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id" log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
def command = request.JSON?.command def command = request.JSON?.command
if (command) { def type = params.param1
def device = devices.find { it.id == params.id } def device = devices?.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found") if (!device) {
} else { httpError(404, "Device not found")
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, name) { private show(devices, name) {