mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 13:21:53 +00:00
Compare commits
792 Commits
MSA-794-2
...
PROD_2016.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f5eb7b85a | ||
|
|
64e4ccc517 | ||
|
|
aa890ae3d5 | ||
|
|
8d701b9fea | ||
|
|
719b24ecd6 | ||
|
|
9d5ab3bfc8 | ||
|
|
aae7f23a22 | ||
|
|
218cc43520 | ||
|
|
5b0ca4b815 | ||
|
|
9ddc020f04 | ||
|
|
aab3b8d7f8 | ||
|
|
a0ccf35eaa | ||
|
|
9fbbaec8f6 | ||
|
|
e4c1824afd | ||
|
|
797a58cb68 | ||
|
|
c428267d63 | ||
|
|
02f30cf425 | ||
|
|
fea802ffce | ||
|
|
6400d26f4a | ||
|
|
5e3aaa3270 | ||
|
|
f5c3997679 | ||
|
|
81cf1179ef | ||
|
|
79d20b0edb | ||
|
|
b6d862fdd4 | ||
|
|
d58084c438 | ||
|
|
dbfaef3e69 | ||
|
|
40ed88e7fd | ||
|
|
1d6e22dc16 | ||
|
|
30993aa218 | ||
|
|
2f8ed277ff | ||
|
|
1d180ac487 | ||
|
|
230541a145 | ||
|
|
8c4f7edc83 | ||
|
|
4f188581df | ||
|
|
71880e2644 | ||
|
|
0b7bb40474 | ||
|
|
8d920ea072 | ||
|
|
e373b6f92e | ||
|
|
43a1ae6371 | ||
|
|
a441b94a33 | ||
|
|
ced03d746d | ||
|
|
5341d0d06f | ||
|
|
2a58d7ff62 | ||
|
|
260917d515 | ||
|
|
c1478d3e96 | ||
|
|
8b9bff15dc | ||
|
|
75c1ede16c | ||
|
|
a7acc384a2 | ||
|
|
c6998e5f1d | ||
|
|
f95e906d6e | ||
|
|
a6c7ab49b6 | ||
|
|
4891e3b947 | ||
|
|
ae91f9bff5 | ||
|
|
bb87ad2cf0 | ||
|
|
5dff03fb69 | ||
|
|
629d768575 | ||
|
|
dd7c6b90d5 | ||
|
|
4523498dab | ||
|
|
e89e45e000 | ||
|
|
78ec280e83 | ||
|
|
1f144d36e4 | ||
|
|
f5bd580c9e | ||
|
|
d5329dbde3 | ||
|
|
48818cfb06 | ||
|
|
079919260b | ||
|
|
570922e2ac | ||
|
|
53ed9b4d2b | ||
|
|
7149a81c85 | ||
|
|
30274f0cd7 | ||
|
|
8869cd3af0 | ||
|
|
fb0caa6446 | ||
|
|
3d05d42cb8 | ||
|
|
3184615e87 | ||
|
|
0f70362e0a | ||
|
|
bc817f8530 | ||
|
|
01b8399893 | ||
|
|
81318bafac | ||
|
|
60c2006bfc | ||
|
|
1e4f1223e7 | ||
|
|
b78bce55b2 | ||
|
|
01593c3973 | ||
|
|
6862785d6c | ||
|
|
763d7411e2 | ||
|
|
c703543f36 | ||
|
|
5db6ecda3e | ||
|
|
43b836f413 | ||
|
|
006b5e7bea | ||
|
|
62c8c19805 | ||
|
|
48e9a4bd6a | ||
|
|
07a4c0decc | ||
|
|
6aa09bb052 | ||
|
|
2f889de11a | ||
|
|
5584020e96 | ||
|
|
4ef2e694c2 | ||
|
|
826993cc45 | ||
|
|
ae3306928b | ||
|
|
8777ec5f6d | ||
|
|
91eb59a10d | ||
|
|
324ac13afb | ||
|
|
878eb66b8b | ||
|
|
8dfc270c2d | ||
|
|
614573a15c | ||
|
|
9c7b0875ba | ||
|
|
7568cbf781 | ||
|
|
1858c280a5 | ||
|
|
6b7d0968f6 | ||
|
|
159d3acf4f | ||
|
|
e8101630a3 | ||
|
|
f5ba78b221 | ||
|
|
19b8a7eeb9 | ||
|
|
1b37d649a5 | ||
|
|
dedb0f8465 | ||
|
|
06acc13575 | ||
|
|
c051d719cc | ||
|
|
1e54b93b0c | ||
|
|
bac37f9ca2 | ||
|
|
f0ecb65c09 | ||
|
|
1c0ddd2571 | ||
|
|
b7e0cbda09 | ||
|
|
f80e094bd9 | ||
|
|
ce9ac624d0 | ||
|
|
f3f5cc42c9 | ||
|
|
313fe8b734 | ||
|
|
0d693386d1 | ||
|
|
d1aee1e874 | ||
|
|
3528e7da51 | ||
|
|
fe2fbc3b97 | ||
|
|
5c2e06c98d | ||
|
|
26df619b4f | ||
|
|
af2ea04442 | ||
|
|
383f72580a | ||
|
|
090a306939 | ||
|
|
d0a16c10b2 | ||
|
|
faa65f204d | ||
|
|
bacd335991 | ||
|
|
740e5e096c | ||
|
|
aac2f9b177 | ||
|
|
048eb77e64 | ||
|
|
dadec937fa | ||
|
|
78aa6691c4 | ||
|
|
315918dc6f | ||
|
|
a22f71bc29 | ||
|
|
8eeb29a8a7 | ||
|
|
3d84bca3d7 | ||
|
|
46f47128bd | ||
|
|
9c8398b7a0 | ||
|
|
cdf5d21e8f | ||
|
|
5e6b4f74e0 | ||
|
|
276f7d3b43 | ||
|
|
ce12ad5013 | ||
|
|
beed783d19 | ||
|
|
7f347638d5 | ||
|
|
1f8ce734e7 | ||
|
|
17bf040c7e | ||
|
|
d5b8db99a2 | ||
|
|
6ad4f0990c | ||
|
|
f1c3f5942b | ||
|
|
212c9c4179 | ||
|
|
4898006e4e | ||
|
|
b1c318ef36 | ||
|
|
8eb6001f9f | ||
|
|
d3eb7f756f | ||
|
|
b95ba37364 | ||
|
|
87f8755faf | ||
|
|
555a9f5ab4 | ||
|
|
0744384dbf | ||
|
|
97e0e9d0f8 | ||
|
|
655e756b1b | ||
|
|
7ce7ad86bd | ||
|
|
d90e15ee31 | ||
|
|
cfe25607ac | ||
|
|
f251042954 | ||
|
|
33df9b1ff1 | ||
|
|
24c64608a9 | ||
|
|
dbc2a1e45c | ||
|
|
92cc8afdf7 | ||
|
|
e545842f7c | ||
|
|
c8bda222cb | ||
|
|
17014dd248 | ||
|
|
555edf623a | ||
|
|
feb6ba0e24 | ||
|
|
9d65150bf7 | ||
|
|
99d6e9f668 | ||
|
|
a35f271a8e | ||
|
|
fd4969981f | ||
|
|
1ec110155d | ||
|
|
79b90d741f | ||
|
|
6009bc52ab | ||
|
|
33a8fe108e | ||
|
|
fadc496e1f | ||
|
|
910694f1d1 | ||
|
|
fa9ebed998 | ||
|
|
0a2f2bffc2 | ||
|
|
8dc36eb8f6 | ||
|
|
d0929ab89e | ||
|
|
df6646103a | ||
|
|
014affe1ea | ||
|
|
53fc948b00 | ||
|
|
f389e925d2 | ||
|
|
ef47ec9393 | ||
|
|
62d800e99a | ||
|
|
a79c9c1ade | ||
|
|
4505ca85b2 | ||
|
|
3c0c050b3a | ||
|
|
2cd915ba77 | ||
|
|
4866ecd204 | ||
|
|
f0071aad6d | ||
|
|
0d4a00ae2b | ||
|
|
90384d0852 | ||
|
|
47183ebbff | ||
|
|
d68f70b3dd | ||
|
|
e1b1479cfc | ||
|
|
16e4954f10 | ||
|
|
a1b375c929 | ||
|
|
929f8e1a44 | ||
|
|
4f97d1a3ef | ||
|
|
255185ee8c | ||
|
|
ad50582e92 | ||
|
|
6de6704502 | ||
|
|
a5bc475cc1 | ||
|
|
4fd5cc5d70 | ||
|
|
236c37290e | ||
|
|
7beb2e3905 | ||
|
|
d17bc1869f | ||
|
|
534118a47a | ||
|
|
c7396349f1 | ||
|
|
089cc1a5dd | ||
|
|
7bfa0304af | ||
|
|
842f39e9ff | ||
|
|
dd308f6d8f | ||
|
|
1a72158a9a | ||
|
|
94ab309335 | ||
|
|
f4571289a5 | ||
|
|
b9b0cc6b37 | ||
|
|
34ad221f5d | ||
|
|
7d8acd5dd6 | ||
|
|
dba8ea7b99 | ||
|
|
49eec58de2 | ||
|
|
f2d635ab44 | ||
|
|
a7cd9e072b | ||
|
|
b70706f150 | ||
|
|
0d0a3f5ebb | ||
|
|
be7fad76fc | ||
|
|
f0e87fa5e9 | ||
|
|
1b385afa5b | ||
|
|
b5843acc38 | ||
|
|
863c49ffd4 | ||
|
|
4e5d1f6ad0 | ||
|
|
2f0d8d814b | ||
|
|
a86eba494f | ||
|
|
2549372bb7 | ||
|
|
38cdde7479 | ||
|
|
853f616cc8 | ||
|
|
2b3a4e1278 | ||
|
|
825e693efd | ||
|
|
686c8f7337 | ||
|
|
11f4e42fe9 | ||
|
|
bc459ae178 | ||
|
|
1392b6e1a5 | ||
|
|
3db96faa00 | ||
|
|
399fbcb676 | ||
|
|
eed1ced71b | ||
|
|
d080833d5c | ||
|
|
e998528e8e | ||
|
|
3472ee329d | ||
|
|
577b127287 | ||
|
|
d85566bb98 | ||
|
|
5f41af35e2 | ||
|
|
e1a5b4dd27 | ||
|
|
a2baa37901 | ||
|
|
922ab45343 | ||
|
|
962774996e | ||
|
|
d79594cbcb | ||
|
|
bf8fe4cad7 | ||
|
|
65752ce378 | ||
|
|
95f08aeb3d | ||
|
|
cd7bc1b262 | ||
|
|
be7f6a76a9 | ||
|
|
1ddd0632c9 | ||
|
|
53d0957383 | ||
|
|
10e5b7e9d7 | ||
|
|
fc38c534f9 | ||
|
|
90e6dc91eb | ||
|
|
2e502024a6 | ||
|
|
b4a4d83ce7 | ||
|
|
ef29820fa1 | ||
|
|
fd47bcb8a8 | ||
|
|
972599b1b5 | ||
|
|
f5d3cca6a0 | ||
|
|
6d3ae11e44 | ||
|
|
dd63e76dfb | ||
|
|
7d6f37d98f | ||
|
|
a015742d65 | ||
|
|
3ee8f86aa3 | ||
|
|
9b285ec93b | ||
|
|
23f66e3caa | ||
|
|
7a44c59581 | ||
|
|
4d343d9bcf | ||
|
|
777f8f7e20 | ||
|
|
de6d84acd2 | ||
|
|
eac48382e8 | ||
|
|
d1a910f11f | ||
|
|
9f09a4b0b2 | ||
|
|
3c47fe7b60 | ||
|
|
45a0822e9b | ||
|
|
8cc87f3858 | ||
|
|
e818695947 | ||
|
|
bbba20288e | ||
|
|
ff9dd3f6e2 | ||
|
|
4ad0a6fd9d | ||
|
|
a94a62d34c | ||
|
|
e861d3c256 | ||
|
|
7adff88d0f | ||
|
|
ad1f1b2dc9 | ||
|
|
8d8b039dda | ||
|
|
c875547942 | ||
|
|
1676a9c381 | ||
|
|
49d293e749 | ||
|
|
5d1b033486 | ||
|
|
31f77513da | ||
|
|
98d7829d1a | ||
|
|
c6f706e47a | ||
|
|
532afd7336 | ||
|
|
ac7f1a0c66 | ||
|
|
cb26f055d7 | ||
|
|
34107f935e | ||
|
|
cc2d19e951 | ||
|
|
031a15ec86 | ||
|
|
fc2db2575d | ||
|
|
fd549631e6 | ||
|
|
417c246d61 | ||
|
|
038d770691 | ||
|
|
ce28ec2039 | ||
|
|
0f1781c02e | ||
|
|
4ce6ee0890 | ||
|
|
f8050a5cd5 | ||
|
|
d44dac448b | ||
|
|
67c20abcba | ||
|
|
d56e132896 | ||
|
|
009ec2539d | ||
|
|
ff0860cbe1 | ||
|
|
ecfb99974b | ||
|
|
aa3a18f421 | ||
|
|
c2f18a91be | ||
|
|
fc6b14b85e | ||
|
|
0c1208928f | ||
|
|
02d9963fab | ||
|
|
f131fb71cf | ||
|
|
22185c5440 | ||
|
|
9c27ed6cb7 | ||
|
|
35edaa19c7 | ||
|
|
dc09201866 | ||
|
|
6afcbf8f70 | ||
|
|
2894d52efa | ||
|
|
a21f9f177c | ||
|
|
02f968b8cb | ||
|
|
32f0385e30 | ||
|
|
b105d9d80e | ||
|
|
9bfad5d6a4 | ||
|
|
85a335d365 | ||
|
|
40e6778e31 | ||
|
|
feba6643a6 | ||
|
|
49c893771d | ||
|
|
d73f4c2ded | ||
|
|
13a056ec8d | ||
|
|
bd0ccd0c21 | ||
|
|
d34508c19d | ||
|
|
a8e118fe83 | ||
|
|
072cc066b6 | ||
|
|
17562c96ae | ||
|
|
f55452d1c6 | ||
|
|
14b5fd41b8 | ||
|
|
0f4d7bd520 | ||
|
|
a95fbf2612 | ||
|
|
0f60ca61cb | ||
|
|
293a73136e | ||
|
|
3b56fb4a2f | ||
|
|
26a0f6f939 | ||
|
|
8de3276ce6 | ||
|
|
f77c5810c6 | ||
|
|
9fc5f14dd7 | ||
|
|
55d7a4a263 | ||
|
|
cca1eccce6 | ||
|
|
56eef9cf22 | ||
|
|
b33d621696 | ||
|
|
45b78eff8d | ||
|
|
a133406b6e | ||
|
|
bd62962ee1 | ||
|
|
f1e54c8a5c | ||
|
|
a5da182bf4 | ||
|
|
a4a48fddd2 | ||
|
|
2bd18859b9 | ||
|
|
32f8d2d944 | ||
|
|
e1de599668 | ||
|
|
0e01cbed06 | ||
|
|
566425c531 | ||
|
|
bf476940e9 | ||
|
|
ab2ba8104d | ||
|
|
a219f37035 | ||
|
|
8821c68e9c | ||
|
|
27c05f4e5b | ||
|
|
7571c1b980 | ||
|
|
bb65c4ce14 | ||
|
|
69ae9973da | ||
|
|
36c0af82fe | ||
|
|
eba1f16ee1 | ||
|
|
f397691fdb | ||
|
|
9a5be2c5db | ||
|
|
39ac9f9a8c | ||
|
|
c353eeae17 | ||
|
|
467c6ff055 | ||
|
|
5beacf0ef2 | ||
|
|
e7448e7908 | ||
|
|
973c16f088 | ||
|
|
77f880af6e | ||
|
|
1c4386a67b | ||
|
|
88dd510e72 | ||
|
|
e278a3b57d | ||
|
|
7786df3262 | ||
|
|
5ac08e5a92 | ||
|
|
b05d956d95 | ||
|
|
6cdb80db1f | ||
|
|
4db99824af | ||
|
|
d9f224fa6e | ||
|
|
72b51d50bc | ||
|
|
b2e245bd85 | ||
|
|
9a9854cf92 | ||
|
|
1e27ff5d4a | ||
|
|
37f1726ee6 | ||
|
|
d17cadc4c7 | ||
|
|
c7e8079ff1 | ||
|
|
481d13a571 | ||
|
|
9d83b850ca | ||
|
|
84de336a1a | ||
|
|
8b465b03b4 | ||
|
|
2f81964479 | ||
|
|
d5ea735df7 | ||
|
|
6428719c79 | ||
|
|
327f8dfb00 | ||
|
|
e150ea4a59 | ||
|
|
cdbcab6dad | ||
|
|
dd99a024c2 | ||
|
|
810f3645d9 | ||
|
|
2dcbcc84fc | ||
|
|
33ef75091b | ||
|
|
1bcad614ec | ||
|
|
3ab83350f3 | ||
|
|
0c75c8806e | ||
|
|
d91fea89df | ||
|
|
d28d27c4ed | ||
|
|
235e3f5507 | ||
|
|
e7c1d88285 | ||
|
|
74c334a0f7 | ||
|
|
6881f469f5 | ||
|
|
be8c84306a | ||
|
|
a6105188ea | ||
|
|
22f218c072 | ||
|
|
7c000dc61a | ||
|
|
8b543d399b | ||
|
|
bb21b9a612 | ||
|
|
e366a2686f | ||
|
|
820405a3ab | ||
|
|
69875becae | ||
|
|
5c90091e36 | ||
|
|
af19cee795 | ||
|
|
55ed08d5e7 | ||
|
|
2dec6f69c8 | ||
|
|
381fcfdd31 | ||
|
|
b23d7ccf2e | ||
|
|
237d6a79e9 | ||
|
|
fd29fe2b2a | ||
|
|
c259af4312 | ||
|
|
82214e29eb | ||
|
|
bf00284c74 | ||
|
|
8de5ed77f4 | ||
|
|
4d31f8dbe8 | ||
|
|
9d378ce9a1 | ||
|
|
8abe4ac29f | ||
|
|
104fa8d616 | ||
|
|
5b5e185ef0 | ||
|
|
3a0c9c1298 | ||
|
|
51fb7fc7a9 | ||
|
|
4a096fc884 | ||
|
|
babc0206df | ||
|
|
d419fb8606 | ||
|
|
70aae0d76c | ||
|
|
1cd5c68e68 | ||
|
|
9e427d4108 | ||
|
|
a8628b7343 | ||
|
|
1736caebfe | ||
|
|
0fa363fa1a | ||
|
|
0c5840087b | ||
|
|
a6ee53641f | ||
|
|
c6818c8c2b | ||
|
|
6ac174c2f3 | ||
|
|
eb8d5ed4c9 | ||
|
|
7b5d618de8 | ||
|
|
c024e09fb8 | ||
|
|
e5841fb3cb | ||
|
|
805b870447 | ||
|
|
fe92f7ad19 | ||
|
|
10245315ee | ||
|
|
0b239d4686 | ||
|
|
9374290d64 | ||
|
|
ffcacb9da5 | ||
|
|
c45129170a | ||
|
|
53406ada8e | ||
|
|
ffd0dd1545 | ||
|
|
5c1236a21a | ||
|
|
0911651f71 | ||
|
|
9cc92b1987 | ||
|
|
1e27dc1824 | ||
|
|
4bf3679942 | ||
|
|
c714720578 | ||
|
|
281fc939ac | ||
|
|
03c2dec425 | ||
|
|
633bef2ac5 | ||
|
|
38d0ca6170 | ||
|
|
836dd608c6 | ||
|
|
43e4db28eb | ||
|
|
df421a51ac | ||
|
|
3affdd21fc | ||
|
|
a8357e7644 | ||
|
|
42865abc55 | ||
|
|
024a6cb698 | ||
|
|
dc1c78391e | ||
|
|
e279172383 | ||
|
|
0deb26810d | ||
|
|
d4fb75cc47 | ||
|
|
8a5f0af0e2 | ||
|
|
2276748a91 | ||
|
|
8d423e7c4b | ||
|
|
0dfbddee38 | ||
|
|
40f88fa436 | ||
|
|
c3c8bafef4 | ||
|
|
1b9f758bd6 | ||
|
|
ef1b04c08a | ||
|
|
9cece36d69 | ||
|
|
be220e02b2 | ||
|
|
131cc7b016 | ||
|
|
dd1e76e95a | ||
|
|
8aff9e78f6 | ||
|
|
6fbef3b297 | ||
|
|
0b5779528c | ||
|
|
63d25528ae | ||
|
|
d8fe639a51 | ||
|
|
92fea16beb | ||
|
|
25ae1306c4 | ||
|
|
321389aee3 | ||
|
|
106f09445b | ||
|
|
410e9f40cc | ||
|
|
6f173981e4 | ||
|
|
a94d8f2378 | ||
|
|
1578c48440 | ||
|
|
2126d85f6e | ||
|
|
9218b40e25 | ||
|
|
7c326ec7c3 | ||
|
|
74598ec943 | ||
|
|
96a82797de | ||
|
|
c8e4eef66b | ||
|
|
5effd158fc | ||
|
|
3a4b4d6345 | ||
|
|
27808c3996 | ||
|
|
bd0d636b8c | ||
|
|
7526d2b445 | ||
|
|
caf761c015 | ||
|
|
62a965d90b | ||
|
|
fb9f1dee47 | ||
|
|
3a433d3865 | ||
|
|
fbb248dc31 | ||
|
|
a53e506538 | ||
|
|
c15a09a077 | ||
|
|
2e4036d694 | ||
|
|
49b6fb02df | ||
|
|
e2ab965e89 | ||
|
|
3824ccb5e1 | ||
|
|
089ab00ab7 | ||
|
|
a7b2e6a6cd | ||
|
|
361eca6b15 | ||
|
|
56d991b8d2 | ||
|
|
20e112f4f8 | ||
|
|
e83d08cf2f | ||
|
|
55905a10da | ||
|
|
546ee007f1 | ||
|
|
21ae20302c | ||
|
|
9880ced851 | ||
|
|
f593f08c5e | ||
|
|
2d7300d9e5 | ||
|
|
f31c3bf5ee | ||
|
|
0799722bdb | ||
|
|
7fdb99524e | ||
|
|
04941dfa21 | ||
|
|
fb99a81704 | ||
|
|
6bda59c340 | ||
|
|
c1422438ac | ||
|
|
8ed23f4c7e | ||
|
|
e7e6ea7d56 | ||
|
|
12896f4095 | ||
|
|
ab4e8a892a | ||
|
|
e076818573 | ||
|
|
cd8bbca5ee | ||
|
|
2d060bddfc | ||
|
|
8f25ff4434 | ||
|
|
4da9730319 | ||
|
|
25db4f5235 | ||
|
|
eae2a9ca08 | ||
|
|
2dd2d7cba4 | ||
|
|
f5708bca8b | ||
|
|
a9da6d130a | ||
|
|
3a2c6f86be | ||
|
|
71d2b89a37 | ||
|
|
b131ba1507 | ||
|
|
f04a9e3f7a | ||
|
|
6a905e4380 | ||
|
|
442f16680d | ||
|
|
1af43681a5 | ||
|
|
b211b298c0 | ||
|
|
1c68099b52 | ||
|
|
cc9321ca9f | ||
|
|
600a9a2ca1 | ||
|
|
515b268374 | ||
|
|
919c9b88ee | ||
|
|
23a76fa72b | ||
|
|
a46f09a84a | ||
|
|
aff8dec3ce | ||
|
|
2438942071 | ||
|
|
0d9bd98cfa | ||
|
|
9e33f190c3 | ||
|
|
c959691536 | ||
|
|
2cb687dca9 | ||
|
|
70ec8a28f8 | ||
|
|
e255316474 | ||
|
|
934449c326 | ||
|
|
9e37a37991 | ||
|
|
050da829d3 | ||
|
|
f616dcbdf6 | ||
|
|
8933510436 | ||
|
|
3130e6ad27 | ||
|
|
0c87601c64 | ||
|
|
d1a5a8631f | ||
|
|
d86dcfd82f | ||
|
|
2184c2b21a | ||
|
|
16126abb2d | ||
|
|
77525c3377 | ||
|
|
9b87d39fe8 | ||
|
|
c17b856e6b | ||
|
|
42b790ef10 | ||
|
|
a4ebe87f4e | ||
|
|
f84e21d83a | ||
|
|
e61be4ff9c | ||
|
|
6123fbeea5 | ||
|
|
85175eb298 | ||
|
|
c75568bcf1 | ||
|
|
a9c078c0cb | ||
|
|
edc98e4840 | ||
|
|
fb2c2cb2a7 | ||
|
|
62aeb0533d | ||
|
|
fb6cbcc35e | ||
|
|
097584944e | ||
|
|
01fae3dcd4 | ||
|
|
6c125fe80f | ||
|
|
a103d437c2 | ||
|
|
ae705deba4 | ||
|
|
69a6fc4f9e | ||
|
|
5728f08770 | ||
|
|
f073df0a57 | ||
|
|
2af0db4e89 | ||
|
|
24bfb7f20f | ||
|
|
d82a387c68 | ||
|
|
9263107f0e | ||
|
|
41b9d71e3d | ||
|
|
e60a9d1925 | ||
|
|
27b7c24536 | ||
|
|
13d9137c9a | ||
|
|
b672a0b810 | ||
|
|
2b6a6a47ce | ||
|
|
7d07b93694 | ||
|
|
512bd3adc4 | ||
|
|
7f707a9dbb | ||
|
|
86e097ba0a | ||
|
|
89ec1f207f | ||
|
|
664af57708 | ||
|
|
d9aa1e378d | ||
|
|
376779598a | ||
|
|
ce26a2941e | ||
|
|
81fb356a21 | ||
|
|
bdd88deb99 | ||
|
|
20d660e236 | ||
|
|
aadbcc2eee | ||
|
|
4a49d4c15d | ||
|
|
0e3bd5aa74 | ||
|
|
bfd68228bc | ||
|
|
a2e8a8303b | ||
|
|
56580634e8 | ||
|
|
a82717744e | ||
|
|
b1096a67af | ||
|
|
ae945d1a15 | ||
|
|
12220da750 | ||
|
|
3f89bbb6d9 | ||
|
|
fe2b36cd30 | ||
|
|
6c5b93da87 | ||
|
|
55c17383e1 | ||
|
|
bd9a1d1dc5 | ||
|
|
b7484ff0b8 | ||
|
|
a6bec43f2a | ||
|
|
6fe60146a4 | ||
|
|
5ec82217ac | ||
|
|
208f0d97df | ||
|
|
e444c8d020 | ||
|
|
3b89368d45 | ||
|
|
12f6039de5 | ||
|
|
65c9da32e7 | ||
|
|
7147770e2d | ||
|
|
9f75bbfbde | ||
|
|
996ac27ba2 | ||
|
|
5eb33eca19 | ||
|
|
ec321ce85d | ||
|
|
7669bec0bc | ||
|
|
a20a58bd48 | ||
|
|
efabd07dea | ||
|
|
16b87c5eda | ||
|
|
05ad96d583 | ||
|
|
1e55f62048 | ||
|
|
12b18eae08 | ||
|
|
0a6bb51974 | ||
|
|
9ab74c3ba6 | ||
|
|
aad82e10ef | ||
|
|
b11b362c60 | ||
|
|
8f08a0819c | ||
|
|
d34d1d3615 | ||
|
|
e1853b8e50 | ||
|
|
968834e33e | ||
|
|
e51a38eb28 | ||
|
|
553b45a3f3 | ||
|
|
d9ab3bca00 | ||
|
|
5085d7f184 | ||
|
|
dc927e0659 | ||
|
|
56cfe9e936 | ||
|
|
991637b1c1 | ||
|
|
3a7abd6169 | ||
|
|
0d214b742e | ||
|
|
1372d4005a | ||
|
|
c20026b376 | ||
|
|
9439efd7b9 | ||
|
|
70b8a042a7 | ||
|
|
451b7a4923 | ||
|
|
22c810e9df | ||
|
|
cc80373b89 | ||
|
|
ea98194abf | ||
|
|
94f11c583f | ||
|
|
4a6e000cee | ||
|
|
c3cf4089f4 | ||
|
|
71f9f19465 | ||
|
|
f8317a6d2c | ||
|
|
7def620f04 | ||
|
|
2edda411e2 | ||
|
|
dd19b6e73f | ||
|
|
ac74c6126d | ||
|
|
80e02416f3 | ||
|
|
177c816348 | ||
|
|
653f0e28ca | ||
|
|
49dc1e96ba | ||
|
|
76fcca90d3 | ||
|
|
c197f4263e | ||
|
|
54f976229e | ||
|
|
3ba148d5d2 | ||
|
|
041733373b | ||
|
|
b67783a235 | ||
|
|
b90e2a1982 | ||
|
|
ae444dfe09 | ||
|
|
df55116ac6 | ||
|
|
7d0b3ef796 | ||
|
|
c3d5f60250 | ||
|
|
f55ea8b2f8 | ||
|
|
968c9cc647 | ||
|
|
3e7ce67ea4 | ||
|
|
479651a330 | ||
|
|
39b00c2a04 | ||
|
|
5a52e69911 | ||
|
|
7f4384cd85 | ||
|
|
42479a94b4 | ||
|
|
57e668f1f2 | ||
|
|
91c01dc643 | ||
|
|
0321a7f071 | ||
|
|
855ed02ffa | ||
|
|
60fd008d4a | ||
|
|
df764d57c3 | ||
|
|
a96bb027c8 | ||
|
|
6b62f88bb7 | ||
|
|
848bbdcf2b | ||
|
|
12288accda | ||
|
|
3533943827 |
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Eclipse files
|
||||
/.settings
|
||||
/.classpath
|
||||
/.project
|
||||
/eclipse/
|
||||
/target-eclipse
|
||||
|
||||
# IntelliJ files
|
||||
*.iws
|
||||
*.iml
|
||||
.idea
|
||||
/out
|
||||
*.ipr
|
||||
|
||||
# Gradle files
|
||||
.gradletasknamecache
|
||||
.gradle/
|
||||
|
||||
# Mac OS files
|
||||
.DS_Store
|
||||
|
||||
# Build files
|
||||
/build
|
||||
133
build.gradle
Normal file
133
build.gradle
Normal file
@@ -0,0 +1,133 @@
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Paths
|
||||
import com.smartthings.deployment.slack.FileUpload
|
||||
import com.smartthings.deployment.slack.Message
|
||||
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'smartthings-executable-deployment'
|
||||
apply plugin: 'smartthings-slack'
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.8"
|
||||
}
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven {
|
||||
credentials {
|
||||
username smartThingsArtifactoryUserName
|
||||
password smartThingsArtifactoryPassword
|
||||
}
|
||||
url "https://artifactory.smartthings.com/libs-release-local"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven {
|
||||
credentials {
|
||||
username smartThingsArtifactoryUserName
|
||||
password smartThingsArtifactoryPassword
|
||||
}
|
||||
url "https://artifactory.smartthings.com/libs-release-local"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
devicetypes {
|
||||
groovy {
|
||||
srcDirs = ['devicetypes']
|
||||
}
|
||||
}
|
||||
smartapps {
|
||||
groovy {
|
||||
srcDirs = ['smartapps']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
devicetypesCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||
devicetypesCompile 'smartthings:appengine-z-wave:0.1.2'
|
||||
devicetypesCompile 'smartthings:appengine-zigbee:0.1.11'
|
||||
smartappsCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||
smartappsCompile 'smartthings:appengine-common:0.1.8'
|
||||
smartappsCompile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1'
|
||||
smartappsCompile 'org.grails:grails-web:2.3.11'
|
||||
smartappsCompile 'org.json:json:20140107'
|
||||
}
|
||||
|
||||
slackSendMessage {
|
||||
String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown'
|
||||
String token = project.hasProperty('slackToken') ? project.property('slackToken') : null
|
||||
String webhookUrl = project.hasProperty('slackWebhookUrl') ? project.property('slackWebhookUrl') : null
|
||||
String channel = project.hasProperty('slackChannel') ? project.property('slackChannel') : null
|
||||
String drinks = 'https://dl.dropboxusercontent.com/s/m1z5mpd3c83lwev/minion_beer.jpeg?dl=0'
|
||||
String wolverine = 'https://dl.dropboxusercontent.com/s/4lbjqzvm2v033u9/minion_wolverine.jpg?dl=0'
|
||||
String beach = 'https://dl.dropboxusercontent.com/s/rqrfgxk53gfng69/minion_beach.png?dl=0'
|
||||
String iconUrl
|
||||
String color
|
||||
String messageText
|
||||
String username
|
||||
switch (branch) {
|
||||
case 'master':
|
||||
username = 'Hickory'
|
||||
iconUrl = wolverine
|
||||
color = '#35D0F2'
|
||||
messageText = 'Began deployment of _SmartThingsPublic[master]_ branch to the _Dev_ environments.'
|
||||
break
|
||||
case 'staging':
|
||||
username = 'Dickory'
|
||||
iconUrl = beach
|
||||
color = '#FFDE20'
|
||||
messageText = 'Began deployment of _SmartThingsPublic[staging]_ branch to the _Staging_ environments.'
|
||||
break
|
||||
case 'production':
|
||||
username = 'Dock'
|
||||
iconUrl = drinks
|
||||
color = '#FF1D23'
|
||||
messageText = 'Began deployment of _SmartThingsPublic[production]_ branch to the _Prod_ environments.'
|
||||
break
|
||||
default:
|
||||
username = 'Hickory'
|
||||
iconUrl = wolverine
|
||||
color = '#35D0F2'
|
||||
messageText = "Began deployment of an _SmartThingsPublic[${branch}]_ branch. Have no idea what's going on."
|
||||
}
|
||||
List<String> archives = []
|
||||
File rootDir = new File("${project.buildDir}/archives")
|
||||
if (rootDir.exists()) {
|
||||
// Create a list of archives which were deployed.
|
||||
java.nio.file.Path rootPath = Paths.get(rootDir.absolutePath)
|
||||
rootDir.eachFileRecurse { File file ->
|
||||
if (file.name.endsWith('.tar.gz')) {
|
||||
java.nio.file.Path archivePath = Paths.get(file.absolutePath)
|
||||
archives.add(rootPath.relativize(archivePath).toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
Date date = new Date()
|
||||
String fileDate = date.format('yyyy-MM-dd_HH-mm-ss', TimeZone.getTimeZone('GMT'))
|
||||
|
||||
// Required Task Arguments.
|
||||
file = new FileUpload(
|
||||
data: archives.join('\n').getBytes(StandardCharsets.UTF_8),
|
||||
filename: "deployment-notes-${fileDate}.txt",
|
||||
title: 'Deployment Notes',
|
||||
channels: channel,
|
||||
token: token,
|
||||
color: color
|
||||
)
|
||||
message = new Message(
|
||||
webhookUrl: webhookUrl,
|
||||
username: username,
|
||||
asUser: true,
|
||||
iconUrl: iconUrl,
|
||||
channel: channel,
|
||||
fallback: 'Deployment Notification',
|
||||
text: messageText
|
||||
)
|
||||
}
|
||||
27
circle.yml
Normal file
27
circle.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
machine:
|
||||
java:
|
||||
version:
|
||||
oraclejdk8
|
||||
|
||||
dependencies:
|
||||
override:
|
||||
- ./gradlew dependencies -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
|
||||
post:
|
||||
- ./gradlew compileSmartappsGroovy compileDevicetypesGroovy -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
|
||||
|
||||
test:
|
||||
override:
|
||||
- echo "We don't have any tests :-("
|
||||
|
||||
deployment:
|
||||
develop:
|
||||
branch: master
|
||||
commands:
|
||||
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Ps3Buckets="$S3_BUCKETS_DEV"
|
||||
- ./gradlew slackSendMessage -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Pbranch="$CIRCLE_BRANCH" -PslackToken="$SLACK_TOKEN" -PslackWebhookUrl="$SLACK_WEBHOOK_URL" -PslackChannel="$SLACK_CHANNEL" --stacktrace
|
||||
|
||||
stage:
|
||||
branch: staging
|
||||
commands:
|
||||
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Ps3Buckets="$S3_BUCKETS_STAGE"
|
||||
- ./gradlew slackSendMessage -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Pbranch="$CIRCLE_BRANCH" -PslackToken="$SLACK_TOKEN" -PslackWebhookUrl="$SLACK_WEBHOOK_URL" -PslackChannel="$SLACK_CHANNEL" --stacktrace
|
||||
107
devicetypes/com-obycode/beaconthing.src/beaconthing.groovy
Normal file
107
devicetypes/com-obycode/beaconthing.src/beaconthing.groovy
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* BeaconThing
|
||||
*
|
||||
* Copyright 2015 obycode
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
metadata {
|
||||
definition (name: "BeaconThing", namespace: "com.obycode", author: "obycode") {
|
||||
capability "Beacon"
|
||||
capability "Presence Sensor"
|
||||
capability "Sensor"
|
||||
|
||||
attribute "inRange", "json_object"
|
||||
attribute "inRangeFriendly", "string"
|
||||
|
||||
command "setPresence", ["string"]
|
||||
command "arrived", ["string"]
|
||||
command "left", ["string"]
|
||||
}
|
||||
|
||||
simulator {
|
||||
status "present": "presence: 1"
|
||||
status "not present": "presence: 0"
|
||||
}
|
||||
|
||||
tiles {
|
||||
standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
|
||||
state("present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0")
|
||||
state("not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff")
|
||||
}
|
||||
valueTile("inRange", "device.inRangeFriendly", inactiveLabel: true, height:1, width:3, decoration: "flat") {
|
||||
state "default", label:'${currentValue}', backgroundColor:"#ffffff"
|
||||
}
|
||||
main "presence"
|
||||
details (["presence","inRange"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
}
|
||||
|
||||
def installed() {
|
||||
sendEvent(name: "presence", value: "not present")
|
||||
def emptyList = []
|
||||
def json = new groovy.json.JsonBuilder(emptyList)
|
||||
sendEvent(name:"inRange", value:json.toString())
|
||||
}
|
||||
|
||||
def setPresence(status) {
|
||||
log.debug "Status is $status"
|
||||
sendEvent(name:"presence", value:status)
|
||||
}
|
||||
|
||||
def arrived(id) {
|
||||
log.debug "$id has arrived"
|
||||
def theList = device.latestValue("inRange")
|
||||
def inRangeList = new JsonSlurper().parseText(theList)
|
||||
if (inRangeList.contains(id)) {
|
||||
return
|
||||
}
|
||||
inRangeList += id
|
||||
def json = new groovy.json.JsonBuilder(inRangeList)
|
||||
log.debug "Now in range: ${json.toString()}"
|
||||
sendEvent(name:"inRange", value:json.toString())
|
||||
|
||||
// Generate human friendly string for tile
|
||||
def friendlyList = "Nearby: " + inRangeList.join(", ")
|
||||
sendEvent(name:"inRangeFriendly", value:friendlyList)
|
||||
|
||||
if (inRangeList.size() == 1) {
|
||||
setPresence("present")
|
||||
}
|
||||
}
|
||||
|
||||
def left(id) {
|
||||
log.debug "$id has left"
|
||||
def theList = device.latestValue("inRange")
|
||||
def inRangeList = new JsonSlurper().parseText(theList)
|
||||
inRangeList -= id
|
||||
def json = new groovy.json.JsonBuilder(inRangeList)
|
||||
log.debug "Now in range: ${json.toString()}"
|
||||
sendEvent(name:"inRange", value:json.toString())
|
||||
|
||||
// Generate human friendly string for tile
|
||||
def friendlyList = "Nearby: " + inRangeList.join(", ")
|
||||
|
||||
if (inRangeList.empty) {
|
||||
setPresence("not present")
|
||||
friendlyList = "No one is nearby"
|
||||
}
|
||||
|
||||
sendEvent(name:"inRangeFriendly", value:friendlyList)
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Netatmo Additional Module", namespace: "dianoga", author: "Brian Steere") {
|
||||
capability "Sensor"
|
||||
capability "Relative Humidity Measurement"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Netatmo Basestation", namespace: "dianoga", author: "Brian Steere") {
|
||||
capability "Sensor"
|
||||
capability "Relative Humidity Measurement"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Netatmo Outdoor Module", namespace: "dianoga", author: "Brian Steere") {
|
||||
capability "Sensor"
|
||||
capability "Relative Humidity Measurement"
|
||||
capability "Temperature Measurement"
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Netatmo Rain", namespace: "dianoga", author: "Brian Steere") {
|
||||
capability "Sensor"
|
||||
|
||||
attribute "rain", "number"
|
||||
attribute "rainSumHour", "number"
|
||||
attribute "rainSumDay", "number"
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* EnerTalk Energy Meter
|
||||
*
|
||||
* Copyright 2015 hyeon seok yang
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "EnerTalk Energy Meter", namespace: "Encored Technologies", author: "hyeon seok yang") {
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles(scale:2) {
|
||||
valueTile("view", "device.view", decoration: "flat") {
|
||||
state "view", label:' ${currentValue} kWh'
|
||||
}
|
||||
valueTile("month", "device.month", width: 6, height : 3, decoration: "flat") {
|
||||
state "month", label:' ${currentValue}'
|
||||
}
|
||||
valueTile("real", "device.real", width: 2, height : 2, decoration: "flat") {
|
||||
state "real", label:' ${currentValue}'
|
||||
}
|
||||
valueTile("tier", "device.tier", width: 2, height : 2, decoration: "flat") {
|
||||
state "tier", label:' ${currentValue}'
|
||||
}
|
||||
valueTile("plan", "device.plan", width: 2, height : 2, decoration: "flat") {
|
||||
state "plan", label:' ${currentValue}'
|
||||
}
|
||||
|
||||
htmlTile(name:"deepLink", action:"linkApp", whitelist:["code.jquery.com",
|
||||
"ajax.googleapis.com",
|
||||
"fonts.googleapis.com",
|
||||
"code.highcharts.com",
|
||||
"enertalk-card.encoredtech.com",
|
||||
"s3-ap-northeast-1.amazonaws.com",
|
||||
"s3.amazonaws.com",
|
||||
"ui-hub.encoredtech.com",
|
||||
"enertalk-auth.encoredtech.com",
|
||||
"api.encoredtech.com",
|
||||
"cdnjs.cloudflare.com",
|
||||
"encoredtech.com",
|
||||
"itunes.apple.com"], width:2, height:2){}
|
||||
|
||||
main (["view"])
|
||||
details (["month", "real", "tier", "plan", "deepLink"])
|
||||
}
|
||||
}
|
||||
|
||||
mappings {
|
||||
|
||||
path("/linkApp") {action: [ GET: "getLinkedApp" ]}
|
||||
}
|
||||
|
||||
def getLinkedApp() {
|
||||
def lang = clientLocale?.language
|
||||
if ("${lang}" == "ko") {
|
||||
lang = "<p style=\'margin-left:15vw; color: #aeaeb0;\'>기기 설정</p>"
|
||||
} else {
|
||||
lang = "<p style=\'margin-left:5vw; color: #aeaeb0;\'>Setup Device</p>"
|
||||
}
|
||||
renderHTML() {
|
||||
head {
|
||||
"""
|
||||
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width, height=device-height">
|
||||
<style>
|
||||
#레이어_1 { margin-left : 17vw; width : 50vw; height : 50vw;}
|
||||
.st0{fill:#B5B6BB;}
|
||||
</style>
|
||||
"""
|
||||
}
|
||||
body {
|
||||
"""
|
||||
<div id="container">
|
||||
<a id="st-deep-link" href="#">
|
||||
<svg version="1.1" id="레이어_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve"><path class="st0" d="M20,0C9,0,0,9,0,20C0,30.5,8,39,18.2,40l3.8-4.8l-3.9-4.8c-4.9-0.9-8.6-5.2-8.6-10.4c0-5.8,4.7-10.5,10.5-10.5
|
||||
S30.5,14.2,30.5,20c0,5.1-3.7,9.4-8.5,10.3l3.7,4.5L21.8,40C32,39.1,40,30.5,40,20C40,9,31,0,20,0z"/></svg>
|
||||
</a>
|
||||
${lang}
|
||||
</div>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
||||
<script>
|
||||
var ua = navigator.userAgent.toLowerCase();
|
||||
var isAndroid = ua.indexOf("android") > -1;
|
||||
if(!isAndroid) {
|
||||
\$("#st-deep-link").attr("href", "https://itunes.apple.com/kr/app/enertalk-for-home/id1024660780?mt=8");
|
||||
} else {
|
||||
\$("#st-deep-link").attr("href", "market://details?id=com.ionicframework.enertalkhome874425");
|
||||
}
|
||||
</script>
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* Fibaro Door/Window Sensor ZW5
|
||||
*
|
||||
* Copyright 2016 Fibar Group S.A.
|
||||
*
|
||||
* 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: "Fibaro Door/Window Sensor ZW5 with Temperature", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
||||
capability "Battery"
|
||||
capability "Contact Sensor"
|
||||
capability "Sensor"
|
||||
capability "Configuration"
|
||||
capability "Tamper Alert"
|
||||
|
||||
capability "Temperature Measurement"
|
||||
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x85, 0x59, 0x22, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x2B, 0x9C, 0x30, 0x31, 0x86", outClusters: ""
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x85, 0x59, 0x22, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x2B, 0x9C, 0x30, 0x31, 0x86, 0x84", outClusters: ""//actual NIF
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"FGK", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
||||
tileAttribute("device.contact", key:"PRIMARY_CONTROL") {
|
||||
attributeState("open", icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
attributeState("closed", icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
}
|
||||
|
||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
||||
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
|
||||
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
|
||||
}
|
||||
}
|
||||
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, , width: 2, height: 2, decoration: "flat") {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
|
||||
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"]
|
||||
]
|
||||
}
|
||||
|
||||
main "FGK"
|
||||
details(["FGK","battery", "temperature"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
def result = []
|
||||
|
||||
if (description.startsWith("Err 106")) {
|
||||
if (state.sec) {
|
||||
result = createEvent(descriptionText:description, displayed:false)
|
||||
} else {
|
||||
result = createEvent(
|
||||
descriptionText: "FGK failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
|
||||
eventType: "ALERT",
|
||||
name: "secureInclusion",
|
||||
value: "failed",
|
||||
displayed: true,
|
||||
)
|
||||
}
|
||||
} else if (description == "updated") {
|
||||
return null
|
||||
} else {
|
||||
def cmd = zwave.parse(description, [0x31: 5, 0x56: 1, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
||||
|
||||
if (cmd) {
|
||||
log.debug "Parsed '${cmd}'"
|
||||
zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//security
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x98: 1])
|
||||
if (encapsulatedCommand) {
|
||||
return zwaveEvent(encapsulatedCommand)
|
||||
} else {
|
||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
||||
createEvent(descriptionText: cmd.toString())
|
||||
}
|
||||
}
|
||||
|
||||
//crc16
|
||||
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
||||
{
|
||||
def versions = [0x31: 5, 0x72: 2, 0x80: 1, 0x86: 1]
|
||||
def version = versions[cmd.commandClass as Integer]
|
||||
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
||||
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
||||
if (!encapsulatedCommand) {
|
||||
log.debug "Could not extract command from $cmd"
|
||||
} else {
|
||||
zwaveEvent(encapsulatedCommand)
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
||||
//it is assumed that default notification events are used
|
||||
//(parameter 20 was not changed before device's re-inclusion)
|
||||
def map = [:]
|
||||
if (cmd.notificationType == 6) {
|
||||
switch (cmd.event) {
|
||||
case 22:
|
||||
map.name = "contact"
|
||||
map.value = "open"
|
||||
map.descriptionText = "${device.displayName}: is open"
|
||||
break
|
||||
|
||||
case 23:
|
||||
map.name = "contact"
|
||||
map.value = "closed"
|
||||
map.descriptionText = "${device.displayName}: is closed"
|
||||
break
|
||||
}
|
||||
} else if (cmd.notificationType == 7) {
|
||||
switch (cmd.event) {
|
||||
case 0:
|
||||
map.name = "tamper"
|
||||
map.value = "inactive"
|
||||
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
|
||||
break
|
||||
|
||||
case 3:
|
||||
map.name = "tamper"
|
||||
map.value = "active"
|
||||
map.descriptionText = "${device.displayName}: tamper alarm activated"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def map = [:]
|
||||
map.name = "battery"
|
||||
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
|
||||
map.unit = "%"
|
||||
map.displayed = true
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
|
||||
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
|
||||
def cmds = []
|
||||
cmds << encap(zwave.batteryV1.batteryGet())
|
||||
cmds << "delay 500"
|
||||
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0))
|
||||
cmds << "delay 1200"
|
||||
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
[event, response(cmds)]
|
||||
}
|
||||
|
||||
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 zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
|
||||
log.debug "deviceIdData: ${cmd.deviceIdData}"
|
||||
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
|
||||
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
|
||||
log.debug "deviceIdType: ${cmd.deviceIdType}"
|
||||
|
||||
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
|
||||
String serialNumber = "h'"
|
||||
|
||||
cmd.deviceIdData.each{ data ->
|
||||
serialNumber += "${String.format("%02X", data)}"
|
||||
}
|
||||
|
||||
updateDataValue("serialNumber", serialNumber)
|
||||
log.debug "${device.displayName} - serial number: ${serialNumber}"
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
||||
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
|
||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
||||
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
|
||||
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
|
||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
||||
def map = [:]
|
||||
if (cmd.sensorType == 1) {
|
||||
// temperature
|
||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
||||
map.unit = getTemperatureScale()
|
||||
map.name = "temperature"
|
||||
map.displayed = true
|
||||
}
|
||||
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
||||
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Executing 'configure'"
|
||||
|
||||
def cmds = []
|
||||
|
||||
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds:21600, nodeid: zwaveHubNodeId)//FGK's default wake up interval
|
||||
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
|
||||
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
||||
cmds += zwave.versionV1.versionGet()
|
||||
cmds += zwave.batteryV1.batteryGet()
|
||||
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0)
|
||||
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId])
|
||||
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
|
||||
|
||||
encapSequence(cmds, 500)
|
||||
}
|
||||
|
||||
private secure(physicalgraph.zwave.Command cmd) {
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
}
|
||||
|
||||
private crc16(physicalgraph.zwave.Command cmd) {
|
||||
//zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
|
||||
"5601${cmd.format()}0000"
|
||||
}
|
||||
|
||||
private encapSequence(commands, delay=200) {
|
||||
delayBetween(commands.collect{ encap(it) }, delay)
|
||||
}
|
||||
|
||||
private encap(physicalgraph.zwave.Command cmd) {
|
||||
def secureClasses = [0x20, 0x2B, 0x30, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
|
||||
|
||||
//todo: check if secure inclusion was successful
|
||||
//if not do not send security-encapsulated command
|
||||
if (secureClasses.find{ it == cmd.commandClassId }) {
|
||||
secure(cmd)
|
||||
} else {
|
||||
crc16(cmd)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* Fibaro Door/Window Sensor ZW5
|
||||
*
|
||||
* Copyright 2016 Fibar Group S.A.
|
||||
*
|
||||
* 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: "Fibaro Door/Window Sensor ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
||||
capability "Battery"
|
||||
capability "Contact Sensor"
|
||||
capability "Sensor"
|
||||
capability "Configuration"
|
||||
capability "Tamper Alert"
|
||||
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x85, 0x59, 0x22, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x2B, 0x9C, 0x30, 0x86, 0x84", outClusters: ""
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"FGK", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
||||
tileAttribute("device.contact", key:"PRIMARY_CONTROL") {
|
||||
attributeState("open", icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
attributeState("closed", icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
}
|
||||
|
||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
||||
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
|
||||
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
|
||||
}
|
||||
}
|
||||
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, , width: 2, height: 2, decoration: "flat") {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main "FGK"
|
||||
details(["FGK","battery"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
def result = []
|
||||
|
||||
if (description.startsWith("Err 106")) {
|
||||
if (state.sec) {
|
||||
result = createEvent(descriptionText:description, displayed:false)
|
||||
} else {
|
||||
result = createEvent(
|
||||
descriptionText: "FGK failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
|
||||
eventType: "ALERT",
|
||||
name: "secureInclusion",
|
||||
value: "failed",
|
||||
displayed: true,
|
||||
)
|
||||
}
|
||||
} else if (description == "updated") {
|
||||
return null
|
||||
} else {
|
||||
def cmd = zwave.parse(description, [0x56: 1, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
||||
|
||||
if (cmd) {
|
||||
log.debug "Parsed '${cmd}'"
|
||||
zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//security
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x98: 1])
|
||||
if (encapsulatedCommand) {
|
||||
return zwaveEvent(encapsulatedCommand)
|
||||
} else {
|
||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
||||
createEvent(descriptionText: cmd.toString())
|
||||
}
|
||||
}
|
||||
|
||||
//crc16
|
||||
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
||||
{
|
||||
def versions = [0x72: 2, 0x80: 1, 0x86: 1]
|
||||
def version = versions[cmd.commandClass as Integer]
|
||||
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
||||
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
||||
if (!encapsulatedCommand) {
|
||||
log.debug "Could not extract command from $cmd"
|
||||
} else {
|
||||
zwaveEvent(encapsulatedCommand)
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
||||
//it is assumed that default notification events are used
|
||||
//(parameter 20 was not changed before device's re-inclusion)
|
||||
def map = [:]
|
||||
if (cmd.notificationType == 6) {
|
||||
switch (cmd.event) {
|
||||
case 22:
|
||||
map.name = "contact"
|
||||
map.value = "open"
|
||||
map.descriptionText = "${device.displayName}: is open"
|
||||
break
|
||||
|
||||
case 23:
|
||||
map.name = "contact"
|
||||
map.value = "closed"
|
||||
map.descriptionText = "${device.displayName}: is closed"
|
||||
break
|
||||
}
|
||||
} else if (cmd.notificationType == 7) {
|
||||
switch (cmd.event) {
|
||||
case 0:
|
||||
map.name = "tamper"
|
||||
map.value = "inactive"
|
||||
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
|
||||
break
|
||||
|
||||
case 3:
|
||||
map.name = "tamper"
|
||||
map.value = "active"
|
||||
map.descriptionText = "${device.displayName}: tamper alarm activated"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def map = [:]
|
||||
map.name = "battery"
|
||||
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
|
||||
map.unit = "%"
|
||||
map.displayed = true
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
|
||||
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
|
||||
def cmds = []
|
||||
cmds << encap(zwave.batteryV1.batteryGet())
|
||||
cmds << "delay 1200"
|
||||
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
[event, response(cmds)]
|
||||
}
|
||||
|
||||
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 zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
|
||||
log.debug "deviceIdData: ${cmd.deviceIdData}"
|
||||
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
|
||||
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
|
||||
log.debug "deviceIdType: ${cmd.deviceIdType}"
|
||||
|
||||
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
|
||||
String serialNumber = "h'"
|
||||
|
||||
cmd.deviceIdData.each{ data ->
|
||||
serialNumber += "${String.format("%02X", data)}"
|
||||
}
|
||||
|
||||
updateDataValue("serialNumber", serialNumber)
|
||||
log.debug "${device.displayName} - serial number: ${serialNumber}"
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
||||
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
|
||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
||||
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
|
||||
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
|
||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
||||
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Executing 'configure'"
|
||||
|
||||
def cmds = []
|
||||
|
||||
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds:21600, nodeid: zwaveHubNodeId)//FGK's default wake up interval
|
||||
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
|
||||
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
||||
cmds += zwave.versionV1.versionGet()
|
||||
cmds += zwave.batteryV1.batteryGet()
|
||||
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId])
|
||||
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
|
||||
|
||||
encapSequence(cmds, 500)
|
||||
}
|
||||
|
||||
private secure(physicalgraph.zwave.Command cmd) {
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
}
|
||||
|
||||
private crc16(physicalgraph.zwave.Command cmd) {
|
||||
//zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
|
||||
"5601${cmd.format()}0000"
|
||||
}
|
||||
|
||||
private encapSequence(commands, delay=200) {
|
||||
delayBetween(commands.collect{ encap(it) }, delay)
|
||||
}
|
||||
|
||||
private encap(physicalgraph.zwave.Command cmd) {
|
||||
def secureClasses = [0x20, 0x2B, 0x30, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
|
||||
|
||||
//todo: check if secure inclusion was successful
|
||||
//if not do not send security-encapsulated command
|
||||
if (secureClasses.find{ it == cmd.commandClassId }) {
|
||||
secure(cmd)
|
||||
} else {
|
||||
crc16(cmd)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
/**
|
||||
* Fibaro Flood Sensor ZW5
|
||||
*
|
||||
* Copyright 2016 Fibar Group S.A.
|
||||
*
|
||||
* 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: "Fibaro Flood Sensor ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Tamper Alert"
|
||||
capability "Temperature Measurement"
|
||||
capability "Water Sensor"
|
||||
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x22, 0x85, 0x59, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x9C, 0x31, 0x86", outClusters: ""
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"FGFS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
||||
tileAttribute("device.water", key:"PRIMARY_CONTROL") {
|
||||
attributeState("dry", icon:"st.alarm.water.dry", backgroundColor:"#79b821")
|
||||
attributeState("wet", icon:"st.alarm.water.wet", backgroundColor:"#ffa81e")
|
||||
}
|
||||
|
||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
||||
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
|
||||
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
|
||||
}
|
||||
}
|
||||
|
||||
valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
|
||||
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", width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main "FGFS"
|
||||
details(["FGFS","battery", "temperature"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
def result = []
|
||||
|
||||
if (description.startsWith("Err 106")) {
|
||||
if (state.sec) {
|
||||
result = createEvent(descriptionText:description, displayed:false)
|
||||
} else {
|
||||
result = createEvent(
|
||||
descriptionText: "FGFS failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
|
||||
eventType: "ALERT",
|
||||
name: "secureInclusion",
|
||||
value: "failed",
|
||||
displayed: true,
|
||||
)
|
||||
}
|
||||
} else if (description == "updated") {
|
||||
return null
|
||||
} else {
|
||||
def cmd = zwave.parse(description, [0x31: 5, 0x56: 1, 0x71: 3, 0x72:2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
||||
|
||||
if (cmd) {
|
||||
log.debug "Parsed '${cmd}'"
|
||||
zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//security
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
||||
if (encapsulatedCommand) {
|
||||
return zwaveEvent(encapsulatedCommand)
|
||||
} else {
|
||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
||||
createEvent(descriptionText: cmd.toString())
|
||||
}
|
||||
}
|
||||
|
||||
//crc16
|
||||
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
||||
{
|
||||
def versions = [0x31: 5, 0x72: 2, 0x80: 1]
|
||||
def version = versions[cmd.commandClass as Integer]
|
||||
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
||||
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
||||
if (!encapsulatedCommand) {
|
||||
log.debug "Could not extract command from $cmd"
|
||||
} else {
|
||||
zwaveEvent(encapsulatedCommand)
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd)
|
||||
{
|
||||
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
|
||||
def cmds = []
|
||||
cmds << encap(zwave.batteryV1.batteryGet())
|
||||
cmds << "delay 500"
|
||||
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0))
|
||||
cmds << "delay 1200"
|
||||
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
[event, response(cmds)]
|
||||
}
|
||||
|
||||
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 zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
|
||||
log.debug "deviceIdData: ${cmd.deviceIdData}"
|
||||
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
|
||||
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
|
||||
log.debug "deviceIdType: ${cmd.deviceIdType}"
|
||||
|
||||
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
|
||||
String serialNumber = "h'"
|
||||
|
||||
cmd.deviceIdData.each{ data ->
|
||||
serialNumber += "${String.format("%02X", data)}"
|
||||
}
|
||||
|
||||
updateDataValue("serialNumber", serialNumber)
|
||||
log.debug "${device.displayName} - serial number: ${serialNumber}"
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
||||
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
|
||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
||||
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
|
||||
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
|
||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def map = [:]
|
||||
map.name = "battery"
|
||||
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
|
||||
map.unit = "%"
|
||||
map.displayed = true
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
||||
def map = [:]
|
||||
if (cmd.notificationType == 5) {
|
||||
switch (cmd.event) {
|
||||
case 2:
|
||||
map.name = "water"
|
||||
map.value = "wet"
|
||||
map.descriptionText = "${device.displayName} is ${map.value}"
|
||||
break
|
||||
|
||||
case 0:
|
||||
map.name = "water"
|
||||
map.value = "dry"
|
||||
map.descriptionText = "${device.displayName} is ${map.value}"
|
||||
break
|
||||
}
|
||||
} else if (cmd.notificationType == 7) {
|
||||
switch (cmd.event) {
|
||||
case 0:
|
||||
map.name = "tamper"
|
||||
map.value = "inactive"
|
||||
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
|
||||
break
|
||||
|
||||
case 3:
|
||||
map.name = "tamper"
|
||||
map.value = "active"
|
||||
map.descriptionText = "${device.displayName}: tamper alarm activated"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
||||
def map = [:]
|
||||
if (cmd.sensorType == 1) {
|
||||
// temperature
|
||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
||||
map.unit = getTemperatureScale()
|
||||
map.name = "temperature"
|
||||
map.displayed = true
|
||||
}
|
||||
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
||||
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Executing 'configure'"
|
||||
|
||||
def cmds = []
|
||||
|
||||
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds:21600, nodeid: zwaveHubNodeId)//FGFS' default wake up interval
|
||||
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
|
||||
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
||||
cmds += zwave.versionV1.versionGet()
|
||||
cmds += zwave.batteryV1.batteryGet()
|
||||
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0)
|
||||
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId])
|
||||
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
|
||||
|
||||
encapSequence(cmds, 500)
|
||||
}
|
||||
|
||||
private secure(physicalgraph.zwave.Command cmd) {
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
}
|
||||
|
||||
private crc16(physicalgraph.zwave.Command cmd) {
|
||||
//zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
|
||||
"5601${cmd.format()}0000"
|
||||
}
|
||||
|
||||
private encapSequence(commands, delay=200) {
|
||||
delayBetween(commands.collect{ encap(it) }, delay)
|
||||
}
|
||||
|
||||
private encap(physicalgraph.zwave.Command cmd) {
|
||||
def secureClasses = [0x20, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
|
||||
|
||||
//todo: check if secure inclusion was successful
|
||||
//if not do not send security-encapsulated command
|
||||
if (secureClasses.find{ it == cmd.commandClassId }) {
|
||||
secure(cmd)
|
||||
} else {
|
||||
crc16(cmd)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* Fibaro Motion Sensor ZW5
|
||||
*
|
||||
* Copyright 2016 Fibar Group S.A.
|
||||
*
|
||||
* 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: "Fibaro Motion Sensor ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Illuminance Measurement"
|
||||
capability "Motion Sensor"
|
||||
capability "Sensor"
|
||||
capability "Tamper Alert"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x20, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x30, 0x9C, 0x98, 0x7A", outClusters: ""
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
||||
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
|
||||
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
|
||||
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
|
||||
}
|
||||
|
||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
||||
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
|
||||
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
|
||||
}
|
||||
}
|
||||
|
||||
valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
|
||||
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("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
|
||||
}
|
||||
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, width: 2, height: 2, decoration: "flat") {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main "FGMS"
|
||||
details(["FGMS","battery","temperature","illuminance"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
def result = []
|
||||
|
||||
if (description.startsWith("Err 106")) {
|
||||
if (state.sec) {
|
||||
result = createEvent(descriptionText:description, displayed:false)
|
||||
} else {
|
||||
result = createEvent(
|
||||
descriptionText: "FGK failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
|
||||
eventType: "ALERT",
|
||||
name: "secureInclusion",
|
||||
value: "failed",
|
||||
displayed: true,
|
||||
)
|
||||
}
|
||||
} else if (description == "updated") {
|
||||
return null
|
||||
} else {
|
||||
def cmd = zwave.parse(description, [0x31: 5, 0x56: 1, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
||||
|
||||
if (cmd) {
|
||||
log.debug "Parsed '${cmd}'"
|
||||
zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//security
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
||||
if (encapsulatedCommand) {
|
||||
return zwaveEvent(encapsulatedCommand)
|
||||
} else {
|
||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
||||
createEvent(descriptionText: cmd.toString())
|
||||
}
|
||||
}
|
||||
|
||||
//crc16
|
||||
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
||||
{
|
||||
def versions = [0x31: 5, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1]
|
||||
def version = versions[cmd.commandClass as Integer]
|
||||
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
||||
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
||||
if (!encapsulatedCommand) {
|
||||
log.debug "Could not extract command from $cmd"
|
||||
} else {
|
||||
zwaveEvent(encapsulatedCommand)
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
||||
def map = [ displayed: true ]
|
||||
switch (cmd.sensorType) {
|
||||
case 1:
|
||||
map.name = "temperature"
|
||||
map.unit = cmd.scale == 1 ? "F" : "C"
|
||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, map.unit, cmd.precision)
|
||||
break
|
||||
case 3:
|
||||
map.name = "illuminance"
|
||||
map.value = cmd.scaledSensorValue.toInteger().toString()
|
||||
map.unit = "lux"
|
||||
break
|
||||
}
|
||||
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
||||
def map = [:]
|
||||
if (cmd.notificationType == 7) {
|
||||
switch (cmd.event) {
|
||||
case 0:
|
||||
if (cmd.eventParameter[0] == 3) {
|
||||
map.name = "tamper"
|
||||
map.value = "inactive"
|
||||
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
|
||||
}
|
||||
if (cmd.eventParameter[0] == 8) {
|
||||
map.name = "motion"
|
||||
map.value = "inactive"
|
||||
map.descriptionText = "${device.displayName}: motion has stopped"
|
||||
}
|
||||
break
|
||||
|
||||
case 3:
|
||||
map.name = "tamper"
|
||||
map.value = "active"
|
||||
map.descriptionText = "${device.displayName}: tamper alarm activated"
|
||||
break
|
||||
|
||||
case 8:
|
||||
map.name = "motion"
|
||||
map.value = "active"
|
||||
map.descriptionText = "${device.displayName}: motion detected"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def map = [:]
|
||||
map.name = "battery"
|
||||
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
|
||||
map.unit = "%"
|
||||
map.displayed = true
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd)
|
||||
{
|
||||
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
|
||||
def cmds = []
|
||||
cmds << encap(zwave.batteryV1.batteryGet())
|
||||
cmds << "delay 500"
|
||||
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0))
|
||||
cmds << "delay 500"
|
||||
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3, scale: 1))
|
||||
cmds << "delay 1200"
|
||||
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
[event, response(cmds)]
|
||||
|
||||
}
|
||||
|
||||
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 zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
|
||||
log.debug "deviceIdData: ${cmd.deviceIdData}"
|
||||
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
|
||||
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
|
||||
log.debug "deviceIdType: ${cmd.deviceIdType}"
|
||||
|
||||
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
|
||||
String serialNumber = "h'"
|
||||
|
||||
cmd.deviceIdData.each{ data ->
|
||||
serialNumber += "${String.format("%02X", data)}"
|
||||
}
|
||||
|
||||
updateDataValue("serialNumber", serialNumber)
|
||||
log.debug "${device.displayName} - serial number: ${serialNumber}"
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
||||
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
|
||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
||||
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
|
||||
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
|
||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
||||
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Executing 'configure'"
|
||||
|
||||
def cmds = []
|
||||
|
||||
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds: 7200, nodeid: zwaveHubNodeId)//FGMS' default wake up interval
|
||||
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
|
||||
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
||||
cmds += zwave.versionV1.versionGet()
|
||||
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId])
|
||||
cmds += zwave.batteryV1.batteryGet()
|
||||
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0)
|
||||
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3, scale: 1)
|
||||
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
|
||||
|
||||
encapSequence(cmds, 500)
|
||||
}
|
||||
|
||||
private secure(physicalgraph.zwave.Command cmd) {
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
}
|
||||
|
||||
private crc16(physicalgraph.zwave.Command cmd) {
|
||||
//zwave.crc16encapV1.crc16Encap().encapsulate(cmd).format()
|
||||
"5601${cmd.format()}0000"
|
||||
}
|
||||
|
||||
private encapSequence(commands, delay=200) {
|
||||
delayBetween(commands.collect{ encap(it) }, delay)
|
||||
}
|
||||
|
||||
private encap(physicalgraph.zwave.Command cmd) {
|
||||
def secureClasses = [0x20, 0x30, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
|
||||
|
||||
//todo: check if secure inclusion was successful
|
||||
//if not do not send security-encapsulated command
|
||||
if (secureClasses.find{ it == cmd.commandClassId }) {
|
||||
secure(cmd)
|
||||
} else {
|
||||
crc16(cmd)
|
||||
}
|
||||
}
|
||||
795
devicetypes/johnrucker/coopboss-h3vx.src/coopboss-h3vx.groovy
Normal file
795
devicetypes/johnrucker/coopboss-h3vx.src/coopboss-h3vx.groovy
Normal file
@@ -0,0 +1,795 @@
|
||||
/**
|
||||
* CoopBoss H3Vx
|
||||
* 02/29/16 Fixed app crash with Android by changing the syntax of default state in tile definition.
|
||||
* Fixed null value errors during join process. Added 3 new commands to refresh data.
|
||||
*
|
||||
* 01/18/16 Masked invalid temperature reporting when TempProbe1 is below 0C
|
||||
* Added setBaseCurrentNE, readBaseCurrentNE, commands as well as baseCurrentNE attribute.
|
||||
*
|
||||
* Copyright 2016 John Rucker
|
||||
*
|
||||
* 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.
|
||||
* Icon location = http://scripts.3dgo.net/smartthings/icons/
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "CoopBoss H3Vx", namespace: "JohnRucker", author: "John.Rucker@Solar-Current.com") {
|
||||
capability "Refresh"
|
||||
capability "Polling"
|
||||
capability "Sensor"
|
||||
capability "Actuator"
|
||||
capability "Configuration"
|
||||
capability "Temperature Measurement"
|
||||
capability "Door Control"
|
||||
capability "Switch"
|
||||
|
||||
command "closeDoor"
|
||||
command "closeDoorHiI"
|
||||
command "openDoor"
|
||||
command "autoCloseOn"
|
||||
command "autoCloseOff"
|
||||
command "autoOpenOn"
|
||||
command "autoOpenOff"
|
||||
command "setCloseLevelTo"
|
||||
command "setOpenLevelTo"
|
||||
command "setSensitivityLevel"
|
||||
command "Aux1On"
|
||||
command "Aux1Off"
|
||||
command "Aux2On"
|
||||
command "Aux2Off"
|
||||
command "updateTemp1"
|
||||
command "updateTemp2"
|
||||
command "updateSun"
|
||||
command "setNewBaseCurrent"
|
||||
command "setNewPhotoCalibration"
|
||||
command "readNewPhotoCalibration"
|
||||
command "readBaseCurrentNE"
|
||||
command "setBaseCurrentNE"
|
||||
command "updateSensitivity"
|
||||
command "updateCloseLightLevel"
|
||||
command "updateOpenLightLevel"
|
||||
|
||||
attribute "doorState","string"
|
||||
attribute "currentLightLevel","number"
|
||||
attribute "closeLightLevel","number"
|
||||
attribute "openLightLevel","number"
|
||||
attribute "autoCloseEnable","string"
|
||||
attribute "autoOpenEnable","string"
|
||||
attribute "TempProb1","number"
|
||||
attribute "TempProb2","number"
|
||||
attribute "dayOrNight","string"
|
||||
attribute "doorSensitivity","number"
|
||||
attribute "doorCurrent","number"
|
||||
attribute "doorVoltage","number"
|
||||
attribute "Aux1","string"
|
||||
attribute "Aux2","string"
|
||||
attribute "coopStatus","string"
|
||||
attribute "baseDoorCurrent","number"
|
||||
attribute "photoCalibration","number"
|
||||
attribute "baseCurrentNE","string"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0101,0402", manufacturer: "Solar-Current", model: "Coop Boss"
|
||||
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
}
|
||||
|
||||
|
||||
preferences {
|
||||
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input "tempOffsetCoop", "number", title: "Coop Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
input "tempOffsetOutside", "number", title: "Outside Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
|
||||
|
||||
// UI tile definitions
|
||||
tiles(scale: 2){
|
||||
multiAttributeTile(name:"doorCtrl", type:"generic", width:6, height:4) {tileAttribute("device.doorState", key: "PRIMARY_CONTROL")
|
||||
{
|
||||
attributeState "unknown", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", nextState:"Sent"
|
||||
attributeState "open", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#0000ff" , nextState:"Sent"
|
||||
attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e"
|
||||
attributeState "closed", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#79b821", nextState:"Sent"
|
||||
attributeState "closing", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e"
|
||||
attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
||||
attributeState "forced close", label: 'forced\rclose', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff8000", nextState:"Sent"
|
||||
attributeState "fault", label: 'FAULT', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
||||
attributeState "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
tileAttribute ("device.coopStatus", key: "SECONDARY_CONTROL") {
|
||||
attributeState "device.coopStatus", label:'${currentValue}'
|
||||
}
|
||||
}
|
||||
|
||||
multiAttributeTile(name:"dtlsDoorCtrl", type:"generic", width:6, height:4) {tileAttribute("device.doorState", key: "PRIMARY_CONTROL")
|
||||
{
|
||||
attributeState "unknown", label: '${name}', action:"openDoor", icon: "st.secondary.tools", nextState:"Sent"
|
||||
attributeState "open", label: '${name}', action:"closeDoor", icon: "st.doors.garage.garage-open", backgroundColor: "#0000ff", nextState:"Sent"
|
||||
attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.doors.garage.garage-opening", backgroundColor: "#ffa81e"
|
||||
attributeState "closed", label: '${name}', action:"openDoor", icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821", nextState:"Sent"
|
||||
attributeState "closing", label: '${name}', action:"openDoor", icon: "st.doors.garage.garage-closing", backgroundColor: "#ffa81e"
|
||||
attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.doors.garage.garage-open", backgroundColor: "#ff0000", nextState:"Sent"
|
||||
attributeState "forced close", label: "forced", action:"openDoor", icon: "st.doors.garage.garage-closed", backgroundColor: "#ff8000", nextState:"Sent"
|
||||
attributeState "fault", label: 'FAULT', action:"openDoor", icon: "st.secondary.tools", backgroundColor: "#ff0000", nextState:"Sent"
|
||||
attributeState "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
tileAttribute ("device.doorState", key: "SECONDARY_CONTROL") {
|
||||
attributeState "unknown", label: 'Door is in unknown state. Push to open.'
|
||||
attributeState "open", label: 'Coop door is open. Push to close.'
|
||||
attributeState "opening", label: 'Caution, door is opening!'
|
||||
attributeState "closed", label: 'Coop door is closed. Push to open.'
|
||||
attributeState "closing", label: 'Caution, door is closing!'
|
||||
attributeState "jammed", label: 'Door open! Push for high-force close'
|
||||
attributeState "forced close", label: "Door is closed. Push to open."
|
||||
attributeState "fault", label: 'Door fault check electrical connection.'
|
||||
attributeState "Sent", label: 'Command sent to CoopBoss...'
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("autoClose", "device.autoCloseEnable", width: 2, height: 2){
|
||||
state "on", label: 'Auto', action:"autoCloseOff", icon: "st.doors.garage.garage-closing", backgroundColor: "#79b821", nextState:"Sent"
|
||||
state "off", label: 'Auto', action:"autoCloseOn", icon: "st.doors.garage.garage-closing", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
|
||||
standardTile("autoOpen", "device.autoOpenEnable", width: 2, height: 2){
|
||||
state "on", label: 'Auto', action:"autoOpenOff", icon: "st.doors.garage.garage-opening", backgroundColor: "#79b821", nextState:"Sent"
|
||||
state "off", label: 'Auto', action:"autoOpenOn", icon: "st.doors.garage.garage-opening", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
|
||||
valueTile("TempProb1", "device.TempProb1", width: 2, height: 2, decoration: "flat"){
|
||||
state "default", label:'Coop\r${currentValue}°', unit:"F", action:"updateTemp1"}
|
||||
|
||||
valueTile("TempProb2", "device.TempProb2", width: 2, height: 2, decoration: "flat"){
|
||||
state "default", label:'Outside\r${currentValue}°', unit:"F", action:"updateTemp2"}
|
||||
|
||||
valueTile("currentLevel", "device.currentLightLevel", width: 2, height: 2, decoration: "flat") {
|
||||
state "default", label:'Sun\r${currentValue}', action:"updateSun"}
|
||||
|
||||
valueTile("dayOrNight", "device.dayOrNight", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "default", label:'${currentValue}.'
|
||||
}
|
||||
|
||||
controlTile("SetClSlider", "device.closeLightLevel", "slider", height: 2, width: 4, inactiveLabel: false, range:"(1..100)") {
|
||||
state "closeLightLevel", action:"setCloseLevelTo", backgroundColor:"#d04e00"
|
||||
}
|
||||
|
||||
valueTile("SetClValue", "device.closeLightLevel", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "default", label:'Close\nSunlight\n${currentValue}', action:'updateCloseLightLevel'
|
||||
}
|
||||
|
||||
controlTile("SetOpSlider", "device.openLightLevel", "slider", height: 2, width: 4, inactiveLabel: false, range:"(1..100)") {
|
||||
state "openLightLevel", action:"setOpenLevelTo", backgroundColor:"#d04e00"
|
||||
}
|
||||
|
||||
valueTile("SetOpValue", "device.openLightLevel", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "default", label:'Open\nSunlight\n${currentValue}', action:'updateOpenLightLevel'
|
||||
}
|
||||
|
||||
controlTile("SetSensitivitySlider", "device.doorSensitivity", "slider", height: 2, width: 4, inactiveLabel: false, range:"(1..100)") {
|
||||
state "openLightLevel", action:"setSensitivityLevel", backgroundColor:"#d04e00"
|
||||
}
|
||||
|
||||
valueTile("SetSensitivityValue", "device.doorSensitivity", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "default", label:'Door\nSensitivity\n${currentValue}', action:'updateSensitivity'
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.refresh", width: 2, height: 2, decoration: "flat", inactiveLabel: false) {
|
||||
state "default", label:'All', action:"refresh.refresh", icon:"st.secondary.refresh-icon"
|
||||
}
|
||||
|
||||
standardTile("aux1", "device.Aux1", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "off", label:'Aux 1', action:"Aux1On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
||||
state "on", label:'Aux 1', action:"Aux1Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
|
||||
standardTile("aux2", "device.Aux2", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "off", label:'Aux 2', action:"Aux2On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
||||
state "on", label:'Aux 2', action:"Aux2Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||
}
|
||||
|
||||
main "doorCtrl"
|
||||
details (["dtlsDoorCtrl", "TempProb1", "TempProb2", "currentLevel", "autoClose", "autoOpen", "dayOrNight",
|
||||
"SetClSlider", "SetClValue", "SetOpSlider", "SetOpValue", "SetSensitivitySlider", "SetSensitivityValue",
|
||||
"aux1", "aux2", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events def parse(String description) {
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('read attr -')) {
|
||||
map = parseReportAttributeMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) {
|
||||
map = parseCustomMessage(description)
|
||||
}
|
||||
log.debug map
|
||||
//return map ? createEvent(map) : null
|
||||
sendEvent(map)
|
||||
callUpdateStatusTxt()
|
||||
}
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
log.debug cluster
|
||||
if (cluster.clusterId == 0x0402) {
|
||||
switch(cluster.sourceEndpoint) {
|
||||
|
||||
case 0x39: // Endpoint 0x39 is the temperature of probe 1
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
resultMap.name = "TempProb1"
|
||||
def celsius = Integer.valueOf(temp,16).shortValue()
|
||||
if (celsius == -32768){ // This number is used to indicate an error in the temperature reading
|
||||
resultMap.value = "---"
|
||||
}else{
|
||||
celsius = celsius / 100 // Temperature value is sent X 100.
|
||||
resultMap.value = celsiusToFahrenheit(celsius)
|
||||
if (tempOffsetOutside) {
|
||||
def offset = tempOffsetOutside as int
|
||||
resultMap.value = resultMap.value + offset
|
||||
}
|
||||
}
|
||||
sendEvent(name: "temperature", value: resultMap.value, displayed: false) // set the temperatureMeasurment capability to temperature
|
||||
break
|
||||
|
||||
case 0x40: // Endpoint 0x40 is the temperature of probe 2
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
resultMap.name = "TempProb2"
|
||||
def celsius = Integer.valueOf(temp,16).shortValue()
|
||||
//resultMap.descriptionText = "Prob2 celsius value = ${celsius}"
|
||||
if (celsius == -32768){ // This number is used to indicate an error in the temperature reading
|
||||
resultMap.value = "---"
|
||||
}else{
|
||||
celsius = celsius / 100 // Temperature value is sent X 100.
|
||||
resultMap.value = celsiusToFahrenheit(celsius)
|
||||
if (tempOffsetCoop) {
|
||||
def offset = tempOffsetCoop as int
|
||||
resultMap.value = resultMap.value + offset
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (cluster.clusterId == 0x0101 && cluster.command == 0x0b) { // This is a default response to a command sent to cluster 0x0101 door control
|
||||
//log.debug "Default Response Data = $cluster.data"
|
||||
switch(cluster.data) {
|
||||
|
||||
case "[10, 0]": // 0x0a turn auto close on command verified
|
||||
resultMap.name = "autoCloseEnable"
|
||||
resultMap.value = "on"
|
||||
break
|
||||
|
||||
case "[11, 0]": // 0x0b turn auto close off command verified
|
||||
resultMap.name = "autoCloseEnable"
|
||||
resultMap.value = "off"
|
||||
break
|
||||
|
||||
case "[12, 0]": // 0x0C turn auto open on command verified
|
||||
resultMap.name = "autoOpenEnable"
|
||||
resultMap.value = "on"
|
||||
break
|
||||
|
||||
case "[13, 0]": // 0x0d turn auto open off command verified
|
||||
resultMap.name = "autoOpenEnable"
|
||||
resultMap.value = "off"
|
||||
break
|
||||
|
||||
|
||||
case "[20, 0]": // 0x14 Aux1 On command verified
|
||||
log.info "verified Aux1 On"
|
||||
sendEvent(name: "switch", value: "on", displayed: false)
|
||||
resultMap.name = "Aux1"
|
||||
resultMap.value = "on"
|
||||
break
|
||||
|
||||
case "[21, 0]": // 0x15 Aux1 Off command verified
|
||||
log.info "verified Aux1 Off"
|
||||
sendEvent(name: "switch", value: "off", displayed: false)
|
||||
resultMap.name = "Aux1"
|
||||
resultMap.value = "off"
|
||||
break
|
||||
|
||||
case "[22, 0]": // 0x16 Aux2 On command verified
|
||||
log.info "verified Aux2 On"
|
||||
resultMap.name = "Aux2"
|
||||
resultMap.value = "on"
|
||||
break
|
||||
|
||||
case "[23, 0]": // 0x17 Aux2 Off command verified
|
||||
log.info "verified Aux2 Off"
|
||||
resultMap.name = "Aux2"
|
||||
resultMap.value = "off"
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def descMap = parseDescriptionAsMap(description)
|
||||
//log.debug "read attr descMap --> $descMap"
|
||||
if (descMap.cluster == "0101" && descMap.attrId == "0003") {
|
||||
resultMap.name = "doorState"
|
||||
if (descMap.value == "00"){
|
||||
resultMap.value = "unknown"
|
||||
sendEvent(name: "door", value: "unknown", displayed: false)
|
||||
}else if(descMap.value == "01"){
|
||||
resultMap.value = "closed"
|
||||
sendEvent(name: "door", value: "closed", displayed: false)
|
||||
}else if(descMap.value == "02"){
|
||||
resultMap.value = "open"
|
||||
sendEvent(name: "door", value: "open", displayed: false)
|
||||
}else if(descMap.value == "03"){
|
||||
resultMap.value = "jammed"
|
||||
}else if(descMap.value == "04"){
|
||||
resultMap.value = "forced close"
|
||||
}else if(descMap.value == "05"){
|
||||
resultMap.value = "forced close"
|
||||
}else if(descMap.value == "06"){
|
||||
resultMap.value = "closing"
|
||||
sendEvent(name: "door", value: "closing", displayed: false)
|
||||
}else if(descMap.value == "07"){
|
||||
resultMap.value = "opening"
|
||||
sendEvent(name: "door", value: "opening", displayed: false)
|
||||
}else if(descMap.value == "08"){
|
||||
resultMap.value = "fault"
|
||||
}else {
|
||||
resultMap.value = "unknown"
|
||||
}
|
||||
resultMap.descriptionText = "Door State Changed to ${resultMap.value}"
|
||||
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0400") {
|
||||
resultMap.name = "currentLightLevel"
|
||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
||||
resultMap.displayed = false
|
||||
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0401") {
|
||||
resultMap.name = "closeLightLevel"
|
||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
||||
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0402") {
|
||||
resultMap.name = "openLightLevel"
|
||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
||||
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0403") {
|
||||
resultMap.name = "autoCloseEnable"
|
||||
if (descMap.value == "01"){resultMap.value = "on"}
|
||||
else{resultMap.value = "off"}
|
||||
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0404") {
|
||||
resultMap.name = "autoOpenEnable"
|
||||
if (descMap.value == "01"){resultMap.value = "on"}
|
||||
else{resultMap.value = "off"}
|
||||
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0405") {
|
||||
resultMap.name = "doorCurrent"
|
||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
||||
resultMap.value = resultMap.value * 0.001
|
||||
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0408") {
|
||||
resultMap.name = "doorSensitivity"
|
||||
resultMap.value = (100 - Integer.parseInt(descMap.value, 16))
|
||||
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0409") {
|
||||
resultMap.name = "baseDoorCurrent"
|
||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
||||
resultMap.value = resultMap.value * 0.001
|
||||
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040a") {
|
||||
resultMap.name = "doorVoltage"
|
||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
||||
resultMap.value = resultMap.value * 0.001
|
||||
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040b") {
|
||||
resultMap.name = "Aux1"
|
||||
if(descMap.value == "01"){
|
||||
resultMap.value = "on"
|
||||
sendEvent(name: "switch", value: "on", displayed: false)
|
||||
}else{
|
||||
resultMap.value = "off"
|
||||
sendEvent(name: "switch", value: "off", displayed: false)
|
||||
}
|
||||
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040c") {
|
||||
resultMap.name = "Aux2"
|
||||
if(descMap.value == "01"){
|
||||
resultMap.value = "on"
|
||||
}else{
|
||||
resultMap.value = "off"
|
||||
}
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040d") {
|
||||
resultMap.name = "photoCalibration"
|
||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040e") {
|
||||
resultMap.name = "baseCurrentNE"
|
||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
//log.info "ParseCustomMessage called with ${description}"
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
resultMap.name = "temperature"
|
||||
def rawT = (description - "temperature: ").trim()
|
||||
resultMap.descriptionText = "Temperature celsius value = ${rawT}"
|
||||
def rawTint = Float.parseFloat(rawT)
|
||||
if (rawTint > 65){
|
||||
resultMap.name = null
|
||||
resultMap.value = null
|
||||
resultMap.descriptionText = "Temperature celsius value = ${rawT} is invalid not updating"
|
||||
log.warn "Invalid temperature value detected! rawT = ${rawT}, description = ${description}"
|
||||
}else if (rawT == -32768){ // This number is used to indicate an error in the temperature reading
|
||||
resultMap.value = "ERR"
|
||||
}else{
|
||||
resultMap.value = celsiusToFahrenheit(rawT.toFloat()) as Float
|
||||
sendEvent(name: "TempProb1", value: resultMap.value, displayed: false) // Workaround for lack of access to endpoint information for Temperature report
|
||||
}
|
||||
}
|
||||
resultMap.displayed = false
|
||||
log.info "Temperature reported = ${resultMap.value}"
|
||||
return resultMap
|
||||
}
|
||||
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
}
|
||||
|
||||
// Added for Temeperature parse
|
||||
def getFahrenheit(value) {
|
||||
def celsius = Integer.parseInt(value, 16)
|
||||
return celsiusToFahrenheit(celsius) as Integer
|
||||
}
|
||||
|
||||
// Private methods
|
||||
def callUpdateStatusTxt(){
|
||||
def cTemp = device.currentState("TempProb1")?.value
|
||||
def cLight = 0
|
||||
def testNull = device.currentState("currentLightLevel")?.value
|
||||
if (testNull != null){
|
||||
cLight = device.currentState("currentLightLevel")?.value as int
|
||||
}
|
||||
updateStatusTxt(cTemp, cLight)
|
||||
}
|
||||
|
||||
def updateStatusTxt(currentTemp, currentLight){
|
||||
//log.info "called updateStatusTxt with ${currentTemp}, ${currentLight}"
|
||||
def cTmp = currentTemp
|
||||
def cLL = 10
|
||||
def oLL = 10
|
||||
|
||||
def testNull = device.currentState("closeLightLevel")?.value
|
||||
if (testNull != null){
|
||||
cLL = device.currentState("closeLightLevel")?.value as int
|
||||
}
|
||||
|
||||
testNull = device.currentState("openLightLevel")?.value
|
||||
if (testNull != null){
|
||||
oLL = device.currentState("openLightLevel")?.value as int
|
||||
}
|
||||
|
||||
def aOpnEn = device.currentState("autoOpenEnable")?.value
|
||||
def aClsEn = device.currentState("autoCloseEnable")?.value
|
||||
|
||||
if (currentLight < cLL){
|
||||
if (aOpnEn == "on"){
|
||||
sendEvent(name: "dayOrNight", value: "Sun must be > ${oLL} to auto open", displayed: false)
|
||||
sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} open at ${oLL}. Coop ${cTmp}°", displayed: false)
|
||||
}else{
|
||||
sendEvent(name: "dayOrNight", value: "Auto Open is turned off.", displayed: false)
|
||||
sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} auto open off. Coop ${cTmp}°", displayed: false)
|
||||
}
|
||||
}else {
|
||||
if (aClsEn == "on"){
|
||||
sendEvent(name: "dayOrNight", value: "Sun must be < ${cLL} to auto close", displayed: false)
|
||||
sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} close at ${cLL}. Coop ${cTmp}°", displayed: false)
|
||||
}else{
|
||||
sendEvent(name: "dayOrNight", value: "Auto Close is turned off.", displayed: false)
|
||||
sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} auto close off. Coop ${cTmp}°", displayed: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commands to device
|
||||
def on() {
|
||||
log.debug "on calling Aux1On"
|
||||
Aux1On()
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "off calling Aux1Off"
|
||||
Aux1Off()
|
||||
}
|
||||
|
||||
def close() {
|
||||
log.debug "close calling closeDoor"
|
||||
closeDoor()
|
||||
}
|
||||
|
||||
def open() {
|
||||
log.debug "open calling openDoor"
|
||||
openDoor()
|
||||
}
|
||||
|
||||
def Aux1On(){
|
||||
log.debug "Sending Aux1 = on command"
|
||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x14 {}"
|
||||
}
|
||||
|
||||
def Aux1Off(){
|
||||
log.debug "Sending Aux1 = off command"
|
||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x15 {}"
|
||||
}
|
||||
|
||||
def Aux2On(){
|
||||
log.debug "Sending Aux2 = on command"
|
||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x16 {}"
|
||||
}
|
||||
|
||||
def Aux2Off(){
|
||||
log.debug "Sending Aux2 = off command"
|
||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x17 {}"
|
||||
}
|
||||
|
||||
def openDoor() {
|
||||
log.debug "Sending Open command"
|
||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x1 {}"
|
||||
}
|
||||
|
||||
def closeDoor() {
|
||||
log.debug "Sending Close command"
|
||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0 {}"
|
||||
}
|
||||
|
||||
def closeDoorHiI() {
|
||||
log.debug "Sending High Current Close command"
|
||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x4 {}"
|
||||
}
|
||||
|
||||
def autoOpenOn() {
|
||||
log.debug "Setting Auto Open On"
|
||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0C {}"
|
||||
}
|
||||
|
||||
def autoOpenOff() {
|
||||
log.debug "Setting Auto Open Off"
|
||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0D {}"
|
||||
}
|
||||
|
||||
def autoCloseOn() {
|
||||
log.debug "Setting Auto Close On"
|
||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0A {}"
|
||||
}
|
||||
|
||||
def autoCloseOff() {
|
||||
log.debug "Setting Auto Close Off"
|
||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0B {}"
|
||||
}
|
||||
|
||||
def setOpenLevelTo(cValue) {
|
||||
def cX = cValue
|
||||
log.debug "Setting Open Light Level to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
||||
|
||||
def cmd = []
|
||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x402 0x23 {${Integer.toHexString(cX)}}"
|
||||
cmd << "delay 150"
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x402" // Read light value
|
||||
cmd
|
||||
}
|
||||
|
||||
def setCloseLevelTo(cValue) {
|
||||
def cX = cValue
|
||||
log.debug "Setting Close Light Level to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
||||
|
||||
def cmd = []
|
||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x401 0x23 {${Integer.toHexString(cX)}}"
|
||||
cmd << "delay 150"
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x401" // Read light value
|
||||
cmd
|
||||
|
||||
}
|
||||
|
||||
def setSensitivityLevel(cValue) {
|
||||
def cX = 100 - cValue
|
||||
log.debug "Setting Door sensitivity level to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
||||
|
||||
def cmd = []
|
||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x408 0x23 {${Integer.toHexString(cX)}}" // Write attribute. 0x23 is a 32 bit integer value.
|
||||
cmd << "delay 150"
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x408" // Read attribute
|
||||
cmd
|
||||
}
|
||||
|
||||
def setNewBaseCurrent(cValue) {
|
||||
def cX = cValue as int
|
||||
log.info "Setting new BaseCurrent to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
||||
|
||||
def cmd = []
|
||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x409 0x23 {${Integer.toHexString(cX)}}" // Write attribute. 0x23 is a 32 bit integer value.
|
||||
cmd << "delay 150"
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x409" // Read attribute
|
||||
cmd
|
||||
}
|
||||
|
||||
def setNewPhotoCalibration(cValue) {
|
||||
def cX = cValue as int
|
||||
log.info "Setting new Photoresister calibration to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
||||
|
||||
def cmd = []
|
||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40D 0x2B {${Integer.toHexString(cX)}}" // Write attribute. 0x2B is a 32 bit signed integer value.
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40D" // Read attribute
|
||||
cmd
|
||||
}
|
||||
|
||||
def readNewPhotoCalibration() {
|
||||
log.info "Requesting current Photoresister calibration "
|
||||
|
||||
def cmd = []
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40D" // Read attribute
|
||||
cmd
|
||||
}
|
||||
|
||||
def readBaseCurrentNE() {
|
||||
log.info "Requesting base current never exceed setting "
|
||||
|
||||
def cmd = []
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40E" // Read attribute
|
||||
cmd
|
||||
}
|
||||
|
||||
def setBaseCurrentNE(cValue) {
|
||||
def cX = cValue as int
|
||||
log.info "Setting new base Current Never Exceed to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
||||
|
||||
def cmd = []
|
||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40E 0x23 {${Integer.toHexString(cX)}}" // Write attribute. 0x23 is a 32 bit unsigned integer value.
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40E" // Read attribute
|
||||
cmd
|
||||
}
|
||||
|
||||
def poll(){
|
||||
log.debug "Polling Device"
|
||||
def cmd = []
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0003" // Read Door State
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0400" // Read Current Light Level
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x39 0x0402 0x0000" // Read probe 1 Temperature
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x40 0x0402 0x0000" // Read probe 2 Temperature
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
def updateTemp1() {
|
||||
log.debug "Sending attribute read request for Temperature Probe1"
|
||||
def cmd = []
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x39 0x0402 0x0000" // Read Current Temperature from Coop Probe 1
|
||||
cmd
|
||||
}
|
||||
|
||||
def updateTemp2() {
|
||||
log.debug "Sending attribute read request for Temperature Probe2"
|
||||
def cmd = []
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x40 0x0402 0x0000" // Read Current Temperature from Coop Probe 2
|
||||
cmd
|
||||
}
|
||||
|
||||
|
||||
def updateSun() {
|
||||
log.debug "Sending attribute read request for Sun Light Level"
|
||||
def cmd = []
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0400" // Read Current Light Level
|
||||
cmd
|
||||
}
|
||||
|
||||
def updateSensitivity() {
|
||||
log.debug "Sending attribute read request for door sensitivity"
|
||||
def cmd = []
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0408" // Read Door sensitivity
|
||||
cmd
|
||||
}
|
||||
|
||||
def updateCloseLightLevel() {
|
||||
log.debug "Sending attribute read close light level"
|
||||
def cmd = []
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0401"
|
||||
cmd
|
||||
}
|
||||
|
||||
def updateOpenLightLevel() {
|
||||
log.debug "Sending attribute read open light level"
|
||||
def cmd = []
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0402"
|
||||
cmd
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "sending refresh command"
|
||||
def cmd = []
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0003" // Read Door State
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0400" // Read Current Light Level
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0401" // Read Door Close Light Level
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0402" // Read Door Open Light Level
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0403" // Read Auto Door Close Settings
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0404" // Read Auto Door Open Settings
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x39 0x0402 0x0000" // Read Current Temperature from Coop Probe 1
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0408" // Object detection sensitivity
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x40 0x0402 0x0000" // Read Current Temperature from Coop Probe 2
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0405" // Current required to close door
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x040B" // Aux1 Status
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x040C" // Aux2 Status
|
||||
cmd << "delay 150"
|
||||
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x409" // Read Base current
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Binding SEP 0x38 DEP 0x01 Cluster 0x0101 Lock cluster to hub"
|
||||
log.debug "Binding SEP 0x39 DEP 0x01 Cluster 0x0402 Temperature cluster to hub"
|
||||
log.debug "Binding SEP 0x40 DEP 0x01 Cluster 0x0402 Temperature cluster to hub"
|
||||
|
||||
def cmd = []
|
||||
cmd << "zdo bind 0x${device.deviceNetworkId} 0x38 0x01 0x0101 {${device.zigbeeId}} {}" // Bind to end point 0x38 and the lock cluster
|
||||
cmd << "delay 150"
|
||||
cmd << "zdo bind 0x${device.deviceNetworkId} 0x39 0x01 0x0402 {${device.zigbeeId}} {}" // Bind to end point 0x39 and the temperature cluster
|
||||
cmd << "delay 150"
|
||||
cmd << "zdo bind 0x${device.deviceNetworkId} 0x40 0x01 0x0402 {${device.zigbeeId}} {}" // Bind to end point 0x40 and the temperature cluster
|
||||
cmd << "delay 1500"
|
||||
|
||||
log.info "Sending ZigBee Configuration Commands to Coop Control"
|
||||
return cmd + refresh()
|
||||
}
|
||||
|
||||
|
||||
@@ -274,6 +274,7 @@ private Map makeTemperatureResult(value) {
|
||||
name: 'temperature',
|
||||
value: "" + value,
|
||||
descriptionText: "${linkText} is ${value}°${temperatureScale}",
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ def setInstallSmartApp(value){
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
|
||||
log.debug description
|
||||
def description_map = parseDescriptionAsMap(description)
|
||||
def event_name = ""
|
||||
def measurement_map = [
|
||||
@@ -129,10 +129,7 @@ def parse(String description) {
|
||||
zigbeedeviceid: device.zigbeeId,
|
||||
created: new Date().time /1000 as int
|
||||
]
|
||||
if (description_map.cluster == "0000"){
|
||||
/* version number, not used */
|
||||
|
||||
} else if (description_map.cluster == "0001"){
|
||||
if (description_map.cluster == "0001"){
|
||||
/* battery voltage in mV (device needs minimium 2.1v to run) */
|
||||
log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}"
|
||||
event_name = "battery_status"
|
||||
@@ -158,6 +155,10 @@ def parse(String description) {
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
if(nameAndValue.length == 2){
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}else{
|
||||
map += []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,627 @@
|
||||
/**
|
||||
* Spruce Controller - Pre Release V2 10/11/2015
|
||||
*
|
||||
* Copyright 2015 Plaid Systems
|
||||
*
|
||||
* Author: NC
|
||||
* Date: 2015-11
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
-----------V3 updates-11-2015------------
|
||||
-Start program button updated to signal schedule check in Scheduler
|
||||
11/17 alarm "0" -> 0 (ln 305)
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Spruce Controller", namespace: "plaidsystems", author: "NCauffman") {
|
||||
capability "Switch"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Actuator"
|
||||
capability "Valve"
|
||||
|
||||
attribute "switch", "string"
|
||||
attribute "switch1", "string"
|
||||
attribute "switch2", "string"
|
||||
attribute "switch8", "string"
|
||||
attribute "switch5", "string"
|
||||
attribute "switch3", "string"
|
||||
attribute "switch4", "string"
|
||||
attribute "switch6", "string"
|
||||
attribute "switch7", "string"
|
||||
attribute "switch9", "string"
|
||||
attribute "switch10", "string"
|
||||
attribute "switch11", "string"
|
||||
attribute "switch12", "string"
|
||||
attribute "switch13", "string"
|
||||
attribute "switch14", "string"
|
||||
attribute "switch15", "string"
|
||||
attribute "switch16", "string"
|
||||
attribute "status", "string"
|
||||
|
||||
command "programOn"
|
||||
command "programOff"
|
||||
command "on"
|
||||
command "off"
|
||||
command "z1on"
|
||||
command "z1off"
|
||||
command "z2on"
|
||||
command "z2off"
|
||||
command "z3on"
|
||||
command "z3off"
|
||||
command "z4on"
|
||||
command "z4off"
|
||||
command "z5on"
|
||||
command "z5off"
|
||||
command "z6on"
|
||||
command "z6off"
|
||||
command "z7on"
|
||||
command "z7off"
|
||||
command "z8on"
|
||||
command "z8off"
|
||||
command "z9on"
|
||||
command "z9off"
|
||||
command "z10on"
|
||||
command "z10off"
|
||||
command "z11on"
|
||||
command "z11off"
|
||||
command "z12on"
|
||||
command "z12off"
|
||||
command "z13on"
|
||||
command "z13off"
|
||||
command "z14on"
|
||||
command "z14off"
|
||||
command "z15on"
|
||||
command "z15off"
|
||||
command "z16on"
|
||||
command "z16off"
|
||||
command "offtime"
|
||||
|
||||
command "refresh"
|
||||
command "rain"
|
||||
command "manual"
|
||||
command "setDisplay"
|
||||
|
||||
command "settingsMap"
|
||||
command "writeTime"
|
||||
command "writeType"
|
||||
command "notify"
|
||||
command "updated"
|
||||
|
||||
fingerprint endpointId: "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18", profileId: "0104", deviceId: "0002", deviceVersion: "00", inClusters: "0000,0003,0004,0005,0006,000F", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZ16-01"
|
||||
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
// status messages
|
||||
|
||||
// reply messages
|
||||
|
||||
}
|
||||
preferences {
|
||||
input description: "Press Configure button after making changes to these preferences", displayDuringSetup: true, type: "paragraph", element: "paragraph", title: ""
|
||||
input "RainEnable", "bool", title: "Rain Sensor Attached?", required: false, displayDuringSetup: true
|
||||
input "ManualTime", "number", title: "Automatic shutoff time when a zone is turned on manually?", required: false, displayDuringSetup: true
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles {
|
||||
|
||||
standardTile("status", "device.status") {
|
||||
state "schedule", label: 'Schedule Set', icon: "http://www.plaidsystems.com/smartthings/st_spruce_leaf_225_t.png"
|
||||
state "finished", label: 'Spruce Finished', icon: "st.Outdoor.outdoor5", backgroundColor: "#46c2e8"
|
||||
state "raintoday", label: 'Rain Today', icon: "st.custom.wuk.nt_chancerain"
|
||||
state "rainy", label: 'Previous Rain', icon: "st.custom.wuk.nt_chancerain"
|
||||
state "raintom", label: 'Rain Tomorrow', icon: "st.custom.wuk.nt_chancerain"
|
||||
state "donewweek", label: 'Spruce Finished', icon: "st.Outdoor.outdoor5", backgroundColor: "#52c435"
|
||||
state "skipping", label: 'Skip Today', icon: "st.Outdoor.outdoor20", backgroundColor: "#36cfe3"
|
||||
state "moisture", label: '', icon: "st.Weather.weather2", backgroundColor: "#36cfe3"
|
||||
state "pause", label: 'PAUSE', icon: "st.contact.contact.open", backgroundColor: "#f2a51f"
|
||||
state "active", label: 'Active', icon: "st.Outdoor.outdoor12", backgroundColor: "#3DC72E"
|
||||
state "season", label: 'Seasonal Adjustment', icon: "st.Outdoor.outdoor17", backgroundColor: "#ffb900"
|
||||
state "disable", label: 'Disabled', icon: "st.secondary.off", backgroundColor: "#888888"
|
||||
state "warning", label: '', icon: "st.categories.damageAndDanger", backgroundColor: "#ffff7f"
|
||||
state "alarm", label: 'Alarm', icon: "st.categories.damageAndDanger", backgroundColor: "#f9240c"
|
||||
}
|
||||
standardTile("switch", "device.switch") {
|
||||
//state "programOff", label: 'Start Program', action: "programOn", icon: "st.sonos.play-icon", backgroundColor: "#a9a9a9"
|
||||
state "off", label: 'Start Program', action: "programOn", icon: "st.sonos.play-icon", backgroundColor: "#a9a9a9"
|
||||
state "programOn", label: 'Initialize Program', action: "programOff", icon: "st.contact.contact.open", backgroundColor: "#f6e10e"
|
||||
state "on", label: 'Program Running', action: "off", icon: "st.Outdoor.outdoor12", backgroundColor: "#3DC72E"
|
||||
}
|
||||
standardTile("rainsensor", "device.rainsensor") {
|
||||
state "rainSensrooff", label: 'Rain Sensor Clear', icon: "st.Weather.weather14", backgroundColor: "#a9a9a9"
|
||||
state "rainSensoron", label: 'Rain Detected', icon: "st.Weather.weather10", backgroundColor: "#f6e10e"
|
||||
}
|
||||
standardTile("switch1", "device.switch1") {
|
||||
state "z1off", label: '1', action: "z1on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z1on", label: '1', action: "z1off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch2", "device.switch2") {
|
||||
state "z2off", label: '2', action: "z2on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z2on", label: '2', action: "z2off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch3", "device.switch3", inactiveLabel: false) {
|
||||
state "z3off", label: '3', action: "z3on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z3on", label: '3', action: "z3off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch4", "device.switch4", inactiveLabel: false) {
|
||||
state "z4off", label: '4', action: "z4on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z4on", label: '4', action: "z4off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch5", "device.switch5", inactiveLabel: false) {
|
||||
state "z5off", label: '5', action: "z5on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z5on", label: '5', action: "z5off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch6", "device.switch6", inactiveLabel: false) {
|
||||
state "z6off", label: '6', action: "z6on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z6on", label: '6', action: "z6off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch7", "device.switch7", inactiveLabel: false) {
|
||||
state "z7off", label: '7', action: "z7on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z7on", label: '7', action: "z7off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch8", "device.switch8", inactiveLabel: false) {
|
||||
state "z8off", label: '8', action: "z8on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z8on", label: '8', action: "z8off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch9", "device.switch9", inactiveLabel: false) {
|
||||
state "z9off", label: '9', action: "z9on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z9on", label: '9', action: "z9off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch10", "device.switch10", inactiveLabel: false) {
|
||||
state "z10off", label: '10', action: "z10on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z10on", label: '10', action: "z10off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch11", "device.switch11", inactiveLabel: false) {
|
||||
state "z11off", label: '11', action: "z11on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z11on", label: '11', action: "z11off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch12", "device.switch12", inactiveLabel: false) {
|
||||
state "z12off", label: '12', action: "z12on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z12on", label: '12', action: "z12off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch13", "device.switch13", inactiveLabel: false) {
|
||||
state "z13off", label: '13', action: "z13on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z13on", label: '13', action: "z13off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch14", "device.switch14", inactiveLabel: false) {
|
||||
state "z14off", label: '14', action: "z14on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z14on", label: '14', action: "z14off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch15", "device.switch15", inactiveLabel: false) {
|
||||
state "z15off", label: '15', action: "z15on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z15on", label: '15', action: "z15off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch16", "device.switch16", inactiveLabel: false) {
|
||||
state "z16off", label: '16', action: "z16on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z16on", label: '16', action: "z16off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", action: "refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
|
||||
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||
}
|
||||
|
||||
main (["status"])
|
||||
details(["status","rainsensor","switch","switch1","switch2","switch3","switch4","switch5","switch6","switch7","switch8","switch9","switch10","switch11","switch12","switch13","switch14","switch15","switch16","refresh","configure"])
|
||||
}
|
||||
}
|
||||
|
||||
def programOn(){
|
||||
sendEvent(name: "switch", value: "programOn", descriptionText: "Program turned on")
|
||||
}
|
||||
|
||||
def programOff(){
|
||||
sendEvent(name: "switch", value: "off", descriptionText: "Program turned off")
|
||||
off()
|
||||
}
|
||||
|
||||
def updated(){
|
||||
log.debug "updated"
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
//log.debug "Parse description $description"
|
||||
def result = null
|
||||
def map = [:]
|
||||
if (description?.startsWith("read attr -")) {
|
||||
def descMap = parseDescriptionAsMap(description)
|
||||
//log.debug "Desc Map: $descMap"
|
||||
//using 000F cluster instead of 0006 (switch) because ST does not differentiate between EPs and processes all as switch
|
||||
if (descMap.cluster == "000F" && descMap.attrId == "0055") {
|
||||
log.debug "Zone"
|
||||
map = getZone(descMap)
|
||||
}
|
||||
else if (descMap.cluster == "0009" && descMap.attrId == "0000") {
|
||||
log.debug "Alarm"
|
||||
map = getAlarm(descMap)
|
||||
}
|
||||
}
|
||||
|
||||
if (map) {
|
||||
result = createEvent(map)
|
||||
}
|
||||
log.debug "Parse returned $map $result"
|
||||
return result
|
||||
}
|
||||
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
}
|
||||
|
||||
def getZone(descMap){
|
||||
def map = [:]
|
||||
|
||||
def EP = Integer.parseInt(descMap.endpoint.trim(), 16)
|
||||
|
||||
String onoff
|
||||
if(descMap.value == "00"){
|
||||
onoff = "off"
|
||||
}
|
||||
else onoff = "on"
|
||||
|
||||
if (EP == 1){
|
||||
map.name = "switch"
|
||||
map.value = onoff
|
||||
map.descriptionText = "${device.displayName} turned sprinkler program $onoff"
|
||||
}
|
||||
|
||||
else if (EP == 18) {
|
||||
map.name = "rainsensor"
|
||||
map.value = "rainSensor" + onoff
|
||||
map.descriptionText = "${device.displayName} rain sensor is $onoff"
|
||||
}
|
||||
else {
|
||||
EP -= 1
|
||||
map.name = "switch" + EP
|
||||
map.value = "z" + EP + onoff
|
||||
map.descriptionText = "${device.displayName} turned Zone $EP $onoff"
|
||||
}
|
||||
|
||||
map.isStateChange = true
|
||||
map.displayed = true
|
||||
return map
|
||||
}
|
||||
|
||||
def getAlarm(descMap){
|
||||
def map = [:]
|
||||
map.name = "status"
|
||||
def alarmID = Integer.parseInt(descMap.value.trim(), 16)
|
||||
log.debug "${alarmID}"
|
||||
if(alarmID <= 0) map.descriptionText = "${device.displayName} has rebooted, no other alarms"
|
||||
else map.descriptionText = "${device.displayName} rebooted, reported error on zone ${alarmID - 1}, please check zone is working correctly"
|
||||
map.value = "alarm"
|
||||
map.isStateChange = true
|
||||
map.displayed = true
|
||||
return map
|
||||
}
|
||||
|
||||
//status notify and change status
|
||||
def notify(value, text){
|
||||
sendEvent(name:"status", value:"$value", descriptionText:"$text", isStateChange: true, display: false)
|
||||
|
||||
}
|
||||
|
||||
//prefrences - rain sensor, manual time
|
||||
def rain() {
|
||||
log.debug "Rain $RainEnable"
|
||||
if (RainEnable) "st wattr 0x${device.deviceNetworkId} 18 0x0F 0x51 0x10 {01}"
|
||||
else "st wattr 0x${device.deviceNetworkId} 18 0x0F 0x51 0x10 {00}"
|
||||
}
|
||||
def manual(){
|
||||
log.debug "Time $ManualTime"
|
||||
def mTime = 10
|
||||
if (ManualTime) mTime = ManualTime
|
||||
def manualTime = hex(mTime)
|
||||
"st wattr 0x${device.deviceNetworkId} 1 6 0x4002 0x21 {00${manualTime}}"
|
||||
|
||||
}
|
||||
|
||||
//write switch time settings map
|
||||
def settingsMap(WriteTimes, attrType){
|
||||
log.debug WriteTimes
|
||||
|
||||
def i = 1
|
||||
def runTime
|
||||
def sendCmds = []
|
||||
while(i <= 17){
|
||||
|
||||
if (WriteTimes."${i}"){
|
||||
runTime = hex(Integer.parseInt(WriteTimes."${i}"))
|
||||
log.debug "${i} : $runTime"
|
||||
|
||||
if (attrType == 4001) sendCmds.push("st wattr 0x${device.deviceNetworkId} ${i} 0x06 0x4001 0x21 {00${runTime}}")
|
||||
else sendCmds.push("st wattr 0x${device.deviceNetworkId} ${i} 0x06 0x4002 0x21 {00${runTime}}")
|
||||
sendCmds.push("delay 500")
|
||||
}
|
||||
i++
|
||||
}
|
||||
return sendCmds
|
||||
}
|
||||
|
||||
//send switch time
|
||||
def writeType(wEP, cycle){
|
||||
log.debug "wt ${wEP} ${cycle}"
|
||||
"st wattr 0x${device.deviceNetworkId} ${wEP} 0x06 0x4001 0x21 {00" + hex(cycle) + "}"
|
||||
}
|
||||
//send switch off time
|
||||
def writeTime(wEP, runTime){
|
||||
"st wattr 0x${device.deviceNetworkId} ${wEP} 0x06 0x4002 0x21 {00" + hex(runTime) + "}"
|
||||
}
|
||||
|
||||
//set reporting and binding
|
||||
def configure() {
|
||||
|
||||
String zigbeeId = swapEndianHex(device.hub.zigbeeId)
|
||||
log.debug "Confuguring Reporting and Bindings ${device.deviceNetworkId} ${device.zigbeeId}"
|
||||
sendEvent(name: 'configuration',value: 100, descriptionText: "Configuration initialized")
|
||||
|
||||
def configCmds = [
|
||||
//program on/off
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x09 {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
//zones 1-8
|
||||
"zdo bind 0x${device.deviceNetworkId} 2 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 3 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 4 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 5 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 6 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 7 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 8 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 9 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
//zones 9-16
|
||||
"zdo bind 0x${device.deviceNetworkId} 10 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 11 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 12 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 13 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 14 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 15 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 16 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 17 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
//rain sensor
|
||||
"zdo bind 0x${device.deviceNetworkId} 18 1 0x0F {${device.zigbeeId}} {}",
|
||||
|
||||
"zcl global send-me-a-report 6 0 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 2", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 3", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 4", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 5", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 6", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 7", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 8", "delay 500",
|
||||
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 9", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 10", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 11", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 12", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 13", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 14", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 15", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 16", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 17", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 18", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x09 0x00 0x21 1 0 {00}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||
]
|
||||
return configCmds + rain() + manual()
|
||||
}
|
||||
|
||||
|
||||
|
||||
private hex(value) {
|
||||
new BigInteger(Math.round(value).toString()).toString(16)
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
|
||||
log.debug "refresh"
|
||||
def refreshCmds = [
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x0F 0x55", "delay 500",
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 2 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 3 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 4 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 5 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 6 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 7 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 8 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 9 0x0F 0x55", "delay 500",
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 10 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 11 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 12 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 13 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 14 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 15 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 16 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 17 0x0F 0x55", "delay 500",
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 18 0x0F 0x51","delay 500",
|
||||
|
||||
]
|
||||
return refreshCmds + rain() + manual()
|
||||
}
|
||||
|
||||
// Commands to device
|
||||
//zones on - 8
|
||||
def on() {
|
||||
//sendEvent(name:"status", value:"active", descriptionText:"Program Running", isStateChange: true, display: false)
|
||||
log.debug "on"
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
||||
}
|
||||
def off() {
|
||||
log.debug "off"
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
||||
}
|
||||
def z1on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 2 6 1 {}"
|
||||
}
|
||||
def z1off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 2 6 0 {}"
|
||||
}
|
||||
def z2on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 3 6 1 {}"
|
||||
}
|
||||
def z2off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 3 6 0 {}"
|
||||
}
|
||||
def z3on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 4 6 1 {}"
|
||||
}
|
||||
def z3off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 4 6 0 {}"
|
||||
}
|
||||
def z4on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 5 6 1 {}"
|
||||
}
|
||||
def z4off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 5 6 0 {}"
|
||||
}
|
||||
def z5on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 6 6 1 {}"
|
||||
}
|
||||
def z5off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 6 6 0 {}"
|
||||
}
|
||||
def z6on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 7 6 1 {}"
|
||||
}
|
||||
def z6off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 7 6 0 {}"
|
||||
}
|
||||
def z7on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 8 6 1 {}"
|
||||
}
|
||||
def z7off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 8 6 0 {}"
|
||||
}
|
||||
def z8on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 9 6 1 {}"
|
||||
}
|
||||
def z8off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 9 6 0 {}"
|
||||
}
|
||||
|
||||
//zones 9 - 16
|
||||
def z9on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 10 6 1 {}"
|
||||
}
|
||||
def z9off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 10 6 0 {}"
|
||||
}
|
||||
def z10on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 11 6 1 {}"
|
||||
}
|
||||
def z10off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 11 6 0 {}"
|
||||
}
|
||||
def z11on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 12 6 1 {}"
|
||||
}
|
||||
def z11off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 12 6 0 {}"
|
||||
}
|
||||
def z12on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 13 6 1 {}"
|
||||
}
|
||||
def z12off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 13 6 0 {}"
|
||||
}
|
||||
def z13on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 14 6 1 {}"
|
||||
}
|
||||
def z13off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 14 6 0 {}"
|
||||
}
|
||||
def z14on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 15 6 1 {}"
|
||||
}
|
||||
def z14off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 15 6 0 {}"
|
||||
}
|
||||
def z15on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 16 6 1 {}"
|
||||
}
|
||||
def z15off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 16 6 0 {}"
|
||||
}
|
||||
def z16on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 17 6 1 {}"
|
||||
}
|
||||
def z16off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 17 6 0 {}"
|
||||
}
|
||||
398
devicetypes/plaidsystems/spruce-sensor.src/spruce-sensor.groovy
Normal file
398
devicetypes/plaidsystems/spruce-sensor.src/spruce-sensor.groovy
Normal file
@@ -0,0 +1,398 @@
|
||||
/**
|
||||
* Spruce Sensor -Pre-release V2 10/8/2015
|
||||
*
|
||||
* Copyright 2014 Plaid Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
-------10/20/2015 Updates--------
|
||||
-Fix/add battery reporting interval to update
|
||||
-remove polling and/or refresh(?)
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "NCauffman") {
|
||||
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Relative Humidity Measurement"
|
||||
capability "Temperature Measurement"
|
||||
capability "Sensor"
|
||||
//capability "Polling"
|
||||
|
||||
attribute "maxHum", "string"
|
||||
attribute "minHum", "string"
|
||||
|
||||
command "resetHumidity"
|
||||
command "refresh"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01"
|
||||
}
|
||||
|
||||
preferences {
|
||||
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph", title: ""
|
||||
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
input "interval", "number", title: "Measurement Interval 1-120 minutes (default: 10 minutes)", description: "Set how often you would like to check soil moisture in minutes", range: "1..120", defaultValue: 10, displayDuringSetup: false
|
||||
input "resetMinMax", "bool", title: "Reset Humidity min and max", required: false, displayDuringSetup: false
|
||||
}
|
||||
|
||||
tiles {
|
||||
valueTile("temperature", "device.temperature", canChangeIcon: false, canChangeBackground: false) {
|
||||
state "temperature", label:'${currentValue}°',
|
||||
backgroundColors:[
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
}
|
||||
valueTile("humidity", "device.humidity", width: 2, height: 2, canChangeIcon: false, canChangeBackground: true) {
|
||||
state "humidity", label:'${currentValue}%', unit:"",
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#635C0C"],
|
||||
[value: 16, color: "#EBEB21"],
|
||||
[value: 22, color: "#C7DE6A"],
|
||||
[value: 42, color: "#9AD290"],
|
||||
[value: 64, color: "#44B621"],
|
||||
[value: 80, color: "#3D79D9"],
|
||||
[value: 96, color: "#0A50C2"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("maxHum", "device.maxHum", canChangeIcon: false, canChangeBackground: false) {
|
||||
state "maxHum", label:'High ${currentValue}%', unit:"",
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#635C0C"],
|
||||
[value: 16, color: "#EBEB21"],
|
||||
[value: 22, color: "#C7DE6A"],
|
||||
[value: 42, color: "#9AD290"],
|
||||
[value: 64, color: "#44B621"],
|
||||
[value: 80, color: "#3D79D9"],
|
||||
[value: 96, color: "#0A50C2"]
|
||||
]
|
||||
}
|
||||
valueTile("minHum", "device.minHum", canChangeIcon: false, canChangeBackground: false) {
|
||||
state "minHum", label:'Low ${currentValue}%', unit:"",
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#635C0C"],
|
||||
[value: 16, color: "#EBEB21"],
|
||||
[value: 22, color: "#C7DE6A"],
|
||||
[value: 42, color: "#9AD290"],
|
||||
[value: 64, color: "#44B621"],
|
||||
[value: 80, color: "#3D79D9"],
|
||||
[value: 96, color: "#0A50C2"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("battery", "device.battery", decoration: "flat", canChangeIcon: false, canChangeBackground: false) {
|
||||
state "battery", label:'${currentValue}% battery'
|
||||
}
|
||||
|
||||
main (["humidity"])
|
||||
details(["humidity","maxHum","minHum","temperature","battery"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "Parse description $description config: ${device.latestValue('configuration')} interval: $interval"
|
||||
|
||||
Map map = [:]
|
||||
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('read attr -')) {
|
||||
map = parseReportAttributeMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) {
|
||||
map = parseCustomMessage(description)
|
||||
}
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
//check in configuration change
|
||||
if (!device.latestValue('configuration')) result = poll()
|
||||
if (device.latestValue('configuration').toInteger() != interval && interval != null) {
|
||||
result = poll()
|
||||
}
|
||||
log.debug "result: $result"
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def linkText = getLinkText(device)
|
||||
//log.debug "Catchall"
|
||||
def descMap = zigbee.parse(description)
|
||||
|
||||
//check humidity configuration is complete
|
||||
if (descMap.command == 0x07 && descMap.clusterId == 0x0405){
|
||||
def configInterval = 10
|
||||
if (interval != null) configInterval = interval
|
||||
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration Successful")
|
||||
//setConfig()
|
||||
log.debug "config complete"
|
||||
//return resultMap = [name: 'configuration', value: configInterval, descriptionText: "Settings configured successfully"]
|
||||
}
|
||||
else if (descMap.command == 0x0001){
|
||||
def hexString = "${hex(descMap.data[5])}" + "${hex(descMap.data[4])}"
|
||||
def intString = Integer.parseInt(hexString, 16)
|
||||
//log.debug "command: $descMap.command clusterid: $descMap.clusterId $hexString $intString"
|
||||
|
||||
if (descMap.clusterId == 0x0402){
|
||||
def value = getTemperature(hexString)
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
else if (descMap.clusterId == 0x0405){
|
||||
def value = Math.round(new BigDecimal(intString / 100)).toString()
|
||||
resultMap = getHumidityResult(value)
|
||||
|
||||
}
|
||||
else return null
|
||||
}
|
||||
else return null
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
def descMap = parseDescriptionAsMap(description)
|
||||
log.debug "Desc Map: $descMap"
|
||||
log.debug "Report Attributes"
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0001" && descMap.attrId == "0000") {
|
||||
resultMap = getBatteryResult(descMap.value)
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
}
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
|
||||
log.debug "parseCustom"
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
else if (description?.startsWith('humidity: ')) {
|
||||
def pct = (description - "humidity: " - "%").trim()
|
||||
if (pct.isNumber()) {
|
||||
def value = Math.round(new BigDecimal(pct)).toString()
|
||||
resultMap = getHumidityResult(value)
|
||||
} else {
|
||||
log.error "invalid humidity: ${pct}"
|
||||
}
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private Map getHumidityResult(value) {
|
||||
def linkText = getLinkText(device)
|
||||
def maxHumValue = 0
|
||||
def minHumValue = 0
|
||||
if (device.currentValue("maxHum") != null) maxHumValue = device.currentValue("maxHum").toInteger()
|
||||
if (device.currentValue("minHum") != null) minHumValue = device.currentValue("minHum").toInteger()
|
||||
log.debug "Humidity max: ${maxHumValue} min: ${minHumValue}"
|
||||
def compare = value.toInteger()
|
||||
|
||||
if (compare > maxHumValue) {
|
||||
sendEvent(name: 'maxHum', value: value, unit: '%', descriptionText: "${linkText} soil moisture high is ${value}%")
|
||||
}
|
||||
else if (((compare < minHumValue) || (minHumValue <= 2)) && (compare != 0)) {
|
||||
sendEvent(name: 'minHum', value: value, unit: '%', descriptionText: "${linkText} soil moisture low is ${value}%")
|
||||
}
|
||||
|
||||
return [
|
||||
name: 'humidity',
|
||||
value: value,
|
||||
unit: '%',
|
||||
descriptionText: "${linkText} soil moisture is ${value}%"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = (Integer.parseInt(value, 16).shortValue()/100)
|
||||
//log.debug "Report Temp $value : $celsius C"
|
||||
if(getTemperatureScale() == "C"){
|
||||
return celsius
|
||||
} else {
|
||||
return celsiusToFahrenheit(celsius) as Integer
|
||||
}
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug "Temperature: $value"
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText = "${linkText} is ${value}°${temperatureScale}"
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
private Map getBatteryResult(value) {
|
||||
log.debug 'Battery'
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [
|
||||
name: 'battery'
|
||||
]
|
||||
|
||||
def min = 2500
|
||||
def percent = ((Integer.parseInt(value, 16) - min) / 5)
|
||||
percent = Math.max(0, Math.min(percent, 100.0))
|
||||
result.value = Math.round(percent)
|
||||
|
||||
def descriptionText
|
||||
if (percent < 10) result.descriptionText = "${linkText} battery is getting low $percent %."
|
||||
else result.descriptionText = "${linkText} battery is ${result.value}%"
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def resetHumidity(){
|
||||
def linkText = getLinkText(device)
|
||||
def minHumValue = 0
|
||||
def maxHumValue = 0
|
||||
sendEvent(name: 'minHum', value: minHumValue, unit: '%', descriptionText: "${linkText} min soil moisture reset to ${minHumValue}%")
|
||||
sendEvent(name: 'maxHum', value: maxHumValue, unit: '%', descriptionText: "${linkText} max soil moisture reset to ${maxHumValue}%")
|
||||
}
|
||||
|
||||
def setConfig(){
|
||||
def configInterval = 100
|
||||
if (interval != null) configInterval = interval
|
||||
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration initialized")
|
||||
}
|
||||
|
||||
//when device preferences are changed
|
||||
def updated(){
|
||||
log.debug "device updated"
|
||||
if (!device.latestValue('configuration')) configure()
|
||||
else{
|
||||
if (resetMinMax == true) resetHumidity()
|
||||
if (device.latestValue('configuration').toInteger() != interval && interval != null){
|
||||
sendEvent(name: 'configuration',value: 0, descriptionText: "Settings changed and will update at next report. Measure interval set to ${interval} mins")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//poll
|
||||
def poll() {
|
||||
log.debug "poll called"
|
||||
List cmds = []
|
||||
if (!device.latestValue('configuration')) cmds += configure()
|
||||
else if (device.latestValue('configuration').toInteger() != interval && interval != null) {
|
||||
cmds += intervalUpdate()
|
||||
}
|
||||
//cmds += refresh()
|
||||
log.debug "commands $cmds"
|
||||
return cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
|
||||
//update intervals
|
||||
def intervalUpdate(){
|
||||
log.debug "intervalUpdate"
|
||||
def minReport = 10
|
||||
def maxReport = 610
|
||||
if (interval != null) {
|
||||
minReport = interval
|
||||
maxReport = interval * 61
|
||||
}
|
||||
[
|
||||
"zcl global send-me-a-report 0x405 0x0000 0x21 $minReport $maxReport {6400}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
"zcl global send-me-a-report 1 0x0000 0x21 0x0C 0 {0500}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
]
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh"
|
||||
[
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x405 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0"
|
||||
]
|
||||
}
|
||||
|
||||
//configure
|
||||
def configure() {
|
||||
//set minReport = measurement in minutes
|
||||
def minReport = 10
|
||||
def maxReport = 610
|
||||
|
||||
//String zigbeeId = swapEndianHex(device.hub.zigbeeId)
|
||||
//log.debug "zigbeeid ${device.zigbeeId} deviceId ${device.deviceNetworkId}"
|
||||
if (!device.zigbeeId) sendEvent(name: 'configuration',value: 0, descriptionText: "Device Zigbee Id not found, remove and attempt to rejoin device")
|
||||
else sendEvent(name: 'configuration',value: 100, descriptionText: "Configuration initialized")
|
||||
//log.debug "Configuring Reporting and Bindings. min: $minReport max: $maxReport "
|
||||
|
||||
[
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x405 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 1000",
|
||||
|
||||
//temperature
|
||||
"zcl global send-me-a-report 0x402 0x0000 0x29 1 0 {3200}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
//min = soil measure interval
|
||||
"zcl global send-me-a-report 0x405 0x0000 0x21 $minReport $maxReport {6400}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
//min = battery measure interval 1 = 1 hour
|
||||
"zcl global send-me-a-report 1 0x0000 0x21 0x0C 0 {0500}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||
] + refresh()
|
||||
}
|
||||
|
||||
private hex(value) {
|
||||
new BigInteger(Math.round(value).toString()).toString(16)
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
@@ -28,6 +28,7 @@ metadata {
|
||||
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,0x5A"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -352,7 +353,7 @@ def configure() {
|
||||
motionSensitivity == "minimum" ? 0 : 64)
|
||||
|
||||
//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
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ metadata {
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
|
||||
command "configureAfterSecure"
|
||||
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A"
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Tone"
|
||||
@@ -32,7 +33,7 @@ metadata {
|
||||
])
|
||||
}
|
||||
section {
|
||||
input "checkInterval", "enum", title: "Presence timeout (minutes)",
|
||||
input "checkInterval", "enum", title: "Presence timeout (minutes)", description: "Tap to set",
|
||||
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
|
||||
}
|
||||
}
|
||||
@@ -76,41 +77,51 @@ def parse(String description) {
|
||||
if (description?.startsWith('read attr -')) {
|
||||
handleReportAttributeMessage(description)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
private handleReportAttributeMessage(String description) {
|
||||
def descMap = zigbee.parseDescriptionAsMap(description)
|
||||
|
||||
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
|
||||
handleBatteryEvent(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
}
|
||||
|
||||
private handleBatteryEvent(rawValue) {
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def eventMap = [
|
||||
name: 'battery',
|
||||
value: '--'
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
if (volts > 0){
|
||||
def minVolts = 2.0
|
||||
def maxVolts = 2.8
|
||||
/**
|
||||
* Create battery event from reported battery voltage.
|
||||
*
|
||||
* @param volts Battery voltage in .1V increments
|
||||
*/
|
||||
private handleBatteryEvent(volts) {
|
||||
def descriptionText
|
||||
if (volts == 0 || volts == 255) {
|
||||
log.debug "Ignoring invalid value for voltage (${volts/10}V)"
|
||||
}
|
||||
else {
|
||||
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
|
||||
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
|
||||
def minVolts = 15
|
||||
def maxVolts = 28
|
||||
|
||||
if (volts < minVolts)
|
||||
volts = minVolts
|
||||
else if (volts > maxVolts)
|
||||
volts = maxVolts
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
|
||||
eventMap.value = Math.round(pct * 100)
|
||||
eventMap.descriptionText = "${linkText} battery was ${eventMap.value}%"
|
||||
def value = batteryMap[volts]
|
||||
if (value != null) {
|
||||
def linkText = getLinkText(device)
|
||||
descriptionText = '{{ linkText }} battery was {{ value }}'
|
||||
def eventMap = [
|
||||
name: 'battery',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
log.debug "Creating battery event for voltage=${volts/10}V: ${linkText} ${eventMap.name} is ${eventMap.value}%"
|
||||
sendEvent(eventMap)
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "Creating battery event: ${eventMap}"
|
||||
sendEvent(eventMap)
|
||||
}
|
||||
|
||||
private handlePresenceEvent(present) {
|
||||
@@ -123,13 +134,19 @@ private handlePresenceEvent(present) {
|
||||
stopTimer()
|
||||
}
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText
|
||||
if ( present )
|
||||
descriptionText = "{{ linkText }} has arrived"
|
||||
else
|
||||
descriptionText = "{{ linkText }} has left"
|
||||
def eventMap = [
|
||||
name: "presence",
|
||||
value: present ? "present" : "not present",
|
||||
linkText: linkText,
|
||||
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
log.debug "Creating presence event: ${eventMap}"
|
||||
log.debug "Creating presence event: ${device.displayName} ${eventMap.name} is ${eventMap.value}"
|
||||
sendEvent(eventMap)
|
||||
}
|
||||
|
||||
@@ -150,4 +167,4 @@ def checkPresenceCallback() {
|
||||
if (timeSinceLastCheckin >= theCheckInterval) {
|
||||
handlePresenceEvent(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# Copyright 2016 SmartThings
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Give your device a name'''.ko=기기 이름 설정
|
||||
'''Set Device Image'''.ko=기기 이미지 설정
|
||||
'''Presence timeout (minutes)'''.ko=알람 유예 시간 설정 (분)
|
||||
'''Tap to set'''.ko=눌러서 설정
|
||||
'''Arrival Sensor'''.ko=도착알림 센서
|
||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
||||
# Events / Notifications
|
||||
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}의 남은 배터리 {{ value }}
|
||||
'''{{ linkText }} has arrived'''.ko={{ linkText }} 귀가
|
||||
'''{{ linkText }} has left'''.ko={{ linkText }} 외출
|
||||
#==============================================================================
|
||||
@@ -87,16 +87,27 @@ def beep() {
|
||||
up to this long from the time you send the message to the time you hear a sound.
|
||||
*/
|
||||
|
||||
// Used source endpoint of 0x02 because we are using smartthings manufacturer specific cluster.
|
||||
[
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
"delay 7000",
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
"delay 7000",
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
"delay 7000",
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
"delay 7000",
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}"
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,9 @@ metadata {
|
||||
|
||||
command "everywhereJoin"
|
||||
command "everywhereLeave"
|
||||
|
||||
command "forceOff"
|
||||
command "forceOn"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,8 +67,10 @@ metadata {
|
||||
}
|
||||
|
||||
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"
|
||||
state "on", label: '${name}', action: "forceOff", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'TURNING OFF', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
|
||||
state "off", label: '${name}', action: "forceOn", 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) {
|
||||
state "station1", label:'${currentValue}', action:"preset1"
|
||||
@@ -138,8 +143,22 @@ metadata {
|
||||
* one place.
|
||||
*
|
||||
*/
|
||||
def off() { onAction("off") }
|
||||
def on() { onAction("on") }
|
||||
def off() {
|
||||
if (device.currentState("switch")?.value == "on") {
|
||||
onAction("off")
|
||||
}
|
||||
}
|
||||
def forceOff() {
|
||||
onAction("off")
|
||||
}
|
||||
def on() {
|
||||
if (device.currentState("switch")?.value == "off") {
|
||||
onAction("on")
|
||||
}
|
||||
}
|
||||
def forceOn() {
|
||||
onAction("on")
|
||||
}
|
||||
def volup() { onAction("volup") }
|
||||
def voldown() { onAction("voldown") }
|
||||
def preset1() { onAction("1") }
|
||||
@@ -238,11 +257,11 @@ def onAction(String user, data=null) {
|
||||
def actions = null
|
||||
switch (user) {
|
||||
case "on":
|
||||
actions = boseSetPowerState(true)
|
||||
boseSetPowerState(true)
|
||||
break
|
||||
case "off":
|
||||
boseSetNowPlaying(null, "STANDBY")
|
||||
actions = boseSetPowerState(false)
|
||||
boseSetPowerState(false)
|
||||
break
|
||||
case "volume":
|
||||
actions = boseSetVolume(data)
|
||||
@@ -747,8 +766,16 @@ def cb_boseSetInput(xml, input) {
|
||||
*/
|
||||
def boseSetPowerState(boolean enable) {
|
||||
log.info "boseSetPowerState(${enable})"
|
||||
queueCallback('nowPlaying', "cb_boseSetPowerState", enable ? "POWERON" : "POWEROFF")
|
||||
return boseRefreshNowPlaying()
|
||||
// Fix to get faster update of power status back from speaker after sending on/off
|
||||
// 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 +814,11 @@ def cb_boseSetPowerState(xml, state) {
|
||||
*/
|
||||
def cb_boseConfirmPowerOn(xml, tries) {
|
||||
def result = []
|
||||
log.warn "boseConfirmPowerOn() attempt #" + tries
|
||||
if (xml.attributes()['source'] == "STANDBY" && tries > 0) {
|
||||
def attempt = tries as Integer
|
||||
log.warn "boseConfirmPowerOn() attempt #$attempt"
|
||||
if (xml.attributes()['source'] == "STANDBY" && attempt > 0) {
|
||||
result << boseRefreshNowPlaying()
|
||||
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", tries-1)
|
||||
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", attempt-1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -105,11 +105,21 @@ def parseDescriptionAsMap(description) {
|
||||
|
||||
// Commands to device
|
||||
def on() {
|
||||
'zcl on-off on'
|
||||
[
|
||||
'zcl on-off on',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 500'
|
||||
]
|
||||
}
|
||||
|
||||
def off() {
|
||||
'zcl on-off off'
|
||||
[
|
||||
'zcl on-off off',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 500'
|
||||
]
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
|
||||
@@ -89,14 +89,17 @@ def parse(String description) {
|
||||
log.debug "TEMP"
|
||||
map.name = "temperature"
|
||||
map.value = getTemperature(descMap.value)
|
||||
map.unit = temperatureScale
|
||||
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
|
||||
log.debug "COOLING SETPOINT"
|
||||
map.name = "coolingSetpoint"
|
||||
map.value = getTemperature(descMap.value)
|
||||
map.unit = temperatureScale
|
||||
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
|
||||
log.debug "HEATING SETPOINT"
|
||||
map.name = "heatingSetpoint"
|
||||
map.value = getTemperature(descMap.value)
|
||||
map.unit = temperatureScale
|
||||
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
|
||||
log.debug "MODE"
|
||||
map.name = "thermostatMode"
|
||||
@@ -169,7 +172,7 @@ def setHeatingSetpoint(degrees) {
|
||||
|
||||
def degreesInteger = Math.round(degrees)
|
||||
log.debug "setHeatingSetpoint({$degreesInteger} ${temperatureScale})"
|
||||
sendEvent("name": "heatingSetpoint", "value": degreesInteger)
|
||||
sendEvent("name": "heatingSetpoint", "value": degreesInteger, "unit": temperatureScale)
|
||||
|
||||
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
||||
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius * 100) + "}"
|
||||
@@ -180,7 +183,7 @@ def setCoolingSetpoint(degrees) {
|
||||
if (degrees != null) {
|
||||
def degreesInteger = Math.round(degrees)
|
||||
log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})"
|
||||
sendEvent("name": "coolingSetpoint", "value": degreesInteger)
|
||||
sendEvent("name": "coolingSetpoint", "value": degreesInteger, "unit": temperatureScale)
|
||||
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
||||
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius * 100) + "}"
|
||||
}
|
||||
|
||||
2
devicetypes/smartthings/cree-bulb.src/.st-ignore
Normal file
2
devicetypes/smartthings/cree-bulb.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
34
devicetypes/smartthings/cree-bulb.src/README.md
Normal file
34
devicetypes/smartthings/cree-bulb.src/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Connected Cree LED Bulb
|
||||
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
* [Connected Cree LED Bulb](https://support.smartthings.com/hc/en-us/articles/204258280-Cree-Connected-LED-Bulb)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Actuator** - represents that a Device has commands
|
||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||
* **Polling** - represents that poll() can be implemented for the device
|
||||
* **Refresh** - _refresh()_ command for status updates
|
||||
* **Switch** - can detect state (possible values: on/off)
|
||||
* **Switch Level** - represents current light level, usually 0-100 in percent
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
## Device Health
|
||||
|
||||
A Category C6 Connected Cree LED Bulb with maxReportTime of 5 mins.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||
Pairing needs to be tried again by placing the device closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||
* [Cree Connected LED Bulb Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/204258280-Cree-Connected-LED-Bulb)
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Cree Bulb
|
||||
*
|
||||
* Copyright 2014 SmartThings
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -13,31 +13,32 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
metadata {
|
||||
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Actuator"
|
||||
capability "Actuator"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Refresh"
|
||||
capability "Switch"
|
||||
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"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
// status messages
|
||||
status "on": "on/off: 1"
|
||||
status "off": "on/off: 0"
|
||||
// simulator metadata
|
||||
simulator {
|
||||
// status messages
|
||||
status "on": "on/off: 1"
|
||||
status "off": "on/off: 0"
|
||||
|
||||
// reply messages
|
||||
reply "zcl on-off on": "on/off: 1"
|
||||
reply "zcl on-off off": "on/off: 0"
|
||||
}
|
||||
// reply messages
|
||||
reply "zcl on-off on": "on/off: 1"
|
||||
reply "zcl on-off off": "on/off: 0"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
// UI tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
@@ -62,18 +63,12 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
def resultMap = zigbee.getEvent(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
sendEvent(resultMap)
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
@@ -87,14 +82,31 @@ def on() {
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
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() {
|
||||
return zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
def healthPoll() {
|
||||
def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it))}
|
||||
}
|
||||
|
||||
def configure() {
|
||||
unschedule()
|
||||
schedule("0 0/5 * * * ? *", "healthPoll")
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||
// minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ metadata {
|
||||
capability "Refresh"
|
||||
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 {
|
||||
@@ -42,6 +44,10 @@ metadata {
|
||||
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) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
@@ -70,11 +76,28 @@ metadata {
|
||||
}
|
||||
|
||||
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 result = null
|
||||
if (description != "updated") {
|
||||
@@ -138,6 +161,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
||||
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])
|
||||
}
|
||||
|
||||
@@ -201,19 +225,19 @@ def refresh() {
|
||||
delayBetween(commands,100)
|
||||
}
|
||||
|
||||
def indicatorWhenOn() {
|
||||
sendEvent(name: "indicatorStatus", value: "when on")
|
||||
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
|
||||
void indicatorWhenOn() {
|
||||
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
def indicatorWhenOff() {
|
||||
sendEvent(name: "indicatorStatus", value: "when off")
|
||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
|
||||
void indicatorWhenOff() {
|
||||
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
def indicatorNever() {
|
||||
sendEvent(name: "indicatorStatus", value: "never")
|
||||
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
|
||||
void indicatorNever() {
|
||||
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
|
||||
}
|
||||
|
||||
def invertSwitch(invert=true) {
|
||||
@@ -223,4 +247,4 @@ def invertSwitch(invert=true) {
|
||||
else {
|
||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/**
|
||||
* Ecobee Sensor
|
||||
*
|
||||
* Copyright 2015 Juan Risso
|
||||
* 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:
|
||||
@@ -12,6 +10,9 @@
|
||||
* 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.
|
||||
*
|
||||
* Ecobee Sensor
|
||||
*
|
||||
* Author: SmartThings
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Ecobee Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
@@ -19,18 +20,22 @@ metadata {
|
||||
capability "Temperature Measurement"
|
||||
capability "Motion Sensor"
|
||||
capability "Refresh"
|
||||
capability "Polling"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles {
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state("temperature", label:'${currentValue}°', unit:"F",
|
||||
backgroundColors:[
|
||||
[value: 31, color: "#153591"],
|
||||
// Celsius
|
||||
[value: 0, color: "#153591"],
|
||||
[value: 7, color: "#1e9cbb"],
|
||||
[value: 15, color: "#90d2a7"],
|
||||
[value: 23, color: "#44b621"],
|
||||
[value: 28, color: "#f1d801"],
|
||||
[value: 35, color: "#d04e00"],
|
||||
[value: 37, color: "#bc2323"],
|
||||
// Fahrenheit
|
||||
[value: 40, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
@@ -42,8 +47,8 @@ metadata {
|
||||
}
|
||||
|
||||
standardTile("motion", "device.motion") {
|
||||
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
|
||||
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
|
||||
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
|
||||
@@ -56,16 +61,12 @@ metadata {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh..."
|
||||
log.debug "refresh called"
|
||||
poll()
|
||||
}
|
||||
|
||||
void poll() {
|
||||
log.debug "Executing 'poll' using parent SmartApp"
|
||||
parent.pollChildren(this)
|
||||
}
|
||||
parent.pollChild()
|
||||
|
||||
//generate custom mobile activity feeds event
|
||||
def generateActivityFeedsEvent(notificationMessage) {
|
||||
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
|
||||
}
|
||||
|
||||
@@ -19,34 +19,48 @@ metadata {
|
||||
definition (name: "Ecobee Thermostat", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Thermostat"
|
||||
capability "Polling"
|
||||
capability "Temperature Measurement"
|
||||
capability "Sensor"
|
||||
capability "Refresh"
|
||||
capability "Refresh"
|
||||
capability "Relative Humidity Measurement"
|
||||
|
||||
command "generateEvent"
|
||||
command "raiseSetpoint"
|
||||
command "lowerSetpoint"
|
||||
command "resumeProgram"
|
||||
command "switchMode"
|
||||
command "generateEvent"
|
||||
command "raiseSetpoint"
|
||||
command "lowerSetpoint"
|
||||
command "resumeProgram"
|
||||
command "switchMode"
|
||||
command "switchFanMode"
|
||||
|
||||
attribute "thermostatSetpoint","number"
|
||||
attribute "thermostatStatus","string"
|
||||
attribute "thermostatSetpoint", "number"
|
||||
attribute "thermostatStatus", "string"
|
||||
attribute "maxHeatingSetpoint", "number"
|
||||
attribute "minHeatingSetpoint", "number"
|
||||
attribute "maxCoolingSetpoint", "number"
|
||||
attribute "minCoolingSetpoint", "number"
|
||||
attribute "deviceTemperatureUnit", "string"
|
||||
}
|
||||
|
||||
simulator { }
|
||||
|
||||
tiles {
|
||||
tiles {
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state("temperature", label:'${currentValue}°', unit:"F",
|
||||
backgroundColors:[
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
backgroundColors:[
|
||||
// Celsius
|
||||
[value: 0, color: "#153591"],
|
||||
[value: 7, color: "#1e9cbb"],
|
||||
[value: 15, color: "#90d2a7"],
|
||||
[value: 23, color: "#44b621"],
|
||||
[value: 28, color: "#f1d801"],
|
||||
[value: 35, color: "#d04e00"],
|
||||
[value: 37, color: "#bc2323"],
|
||||
// Fahrenheit
|
||||
[value: 40, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
)
|
||||
}
|
||||
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
|
||||
@@ -58,10 +72,9 @@ metadata {
|
||||
state "updating", label:"Working", icon: "st.secondary.secondary"
|
||||
}
|
||||
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
|
||||
state "auto", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "on"
|
||||
state "on", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "off"
|
||||
state "off", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "circulate"
|
||||
state "circulate", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "auto"
|
||||
state "auto", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-auto"
|
||||
state "on", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-on"
|
||||
state "updating", label:"Working", icon: "st.secondary.secondary"
|
||||
}
|
||||
standardTile("upButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") {
|
||||
state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up"
|
||||
@@ -91,11 +104,14 @@ metadata {
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("resumeProgram", "device.resumeProgram", inactiveLabel: false, decoration: "flat") {
|
||||
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume Schedule', icon:"st.samsung.da.oven_ic_send"
|
||||
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume', icon:"st.samsung.da.oven_ic_send"
|
||||
state "updating", label:"Working", icon: "st.secondary.secondary"
|
||||
}
|
||||
valueTile("humidity", "device.humidity", decoration: "flat") {
|
||||
state "humidity", label:'${currentValue}%'
|
||||
}
|
||||
main "temperature"
|
||||
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh"])
|
||||
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "fanMode","humidity", "resumeProgram", "refresh"])
|
||||
}
|
||||
|
||||
preferences {
|
||||
@@ -107,8 +123,6 @@ metadata {
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
// TODO: handle '' attribute
|
||||
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
@@ -119,9 +133,7 @@ def refresh() {
|
||||
|
||||
void poll() {
|
||||
log.debug "Executing 'poll' using parent SmartApp"
|
||||
|
||||
def results = parent.pollChild(this)
|
||||
generateEvent(results) //parse received message from parent
|
||||
parent.pollChild()
|
||||
}
|
||||
|
||||
def generateEvent(Map results) {
|
||||
@@ -133,16 +145,27 @@ def generateEvent(Map results) {
|
||||
def isChange = false
|
||||
def isDisplayed = true
|
||||
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
|
||||
handlerName: name]
|
||||
handlerName: name]
|
||||
|
||||
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") {
|
||||
def sendValue = value? convertTemperatureIfNeeded(value.toDouble(), "F", 1): value //API return temperature value in F
|
||||
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
|
||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
||||
isChange = isTemperatureStateChange(device, name, value.toString())
|
||||
isDisplayed = isChange
|
||||
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
|
||||
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
||||
event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed]
|
||||
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
|
||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
||||
event << [value: sendValue, unit: temperatureScale, displayed: false]
|
||||
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
||||
} else if (name=="thermostatFanMode"){
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
||||
} else if (name=="humidity") {
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
|
||||
} else {
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
isDisplayed = isChange
|
||||
@@ -158,13 +181,19 @@ def generateEvent(Map results) {
|
||||
//return descriptionText to be shown on mobile activity feed
|
||||
private getThermostatDescriptionText(name, value, linkText) {
|
||||
if(name == "temperature") {
|
||||
return "$linkText temperature is $value°F"
|
||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
||||
return "$linkText temperature is $sendValue ${location.temperatureScale}"
|
||||
|
||||
} else if(name == "heatingSetpoint") {
|
||||
return "heating setpoint is $value°F"
|
||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
||||
return "heating setpoint is $sendValue ${location.temperatureScale}"
|
||||
|
||||
} else if(name == "coolingSetpoint"){
|
||||
return "cooling setpoint is $value°F"
|
||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
||||
return "cooling setpoint is $sendValue ${location.temperatureScale}"
|
||||
|
||||
} else if (name == "thermostatMode") {
|
||||
return "thermostat mode is ${value}"
|
||||
@@ -172,26 +201,26 @@ private getThermostatDescriptionText(name, value, linkText) {
|
||||
} else if (name == "thermostatFanMode") {
|
||||
return "thermostat fan mode is ${value}"
|
||||
|
||||
} else if (name == "humidity") {
|
||||
return "humidity is ${value} %"
|
||||
} else {
|
||||
return "${name} = ${value}"
|
||||
}
|
||||
}
|
||||
|
||||
void setHeatingSetpoint(setpoint) {
|
||||
setHeatingSetpoint(setpoint.toDouble())
|
||||
}
|
||||
|
||||
void setHeatingSetpoint(Double setpoint) {
|
||||
// def mode = device.currentValue("thermostatMode")
|
||||
log.debug "***heating setpoint $setpoint"
|
||||
def heatingSetpoint = setpoint
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble()
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
||||
|
||||
//enforce limits of heatingSetpoint
|
||||
if (heatingSetpoint > 79) {
|
||||
heatingSetpoint = 79
|
||||
} else if (heatingSetpoint < 45) {
|
||||
heatingSetpoint = 45
|
||||
if (heatingSetpoint > maxHeatingSetpoint) {
|
||||
heatingSetpoint = maxHeatingSetpoint
|
||||
} else if (heatingSetpoint < minHeatingSetpoint) {
|
||||
heatingSetpoint = minHeatingSetpoint
|
||||
}
|
||||
|
||||
//enforce limits of heatingSetpoint vs coolingSetpoint
|
||||
@@ -201,32 +230,34 @@ void setHeatingSetpoint(Double setpoint) {
|
||||
|
||||
log.debug "Sending setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
||||
|
||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||
|
||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) {
|
||||
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
|
||||
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
|
||||
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
|
||||
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
||||
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
|
||||
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
||||
generateSetpointEvent()
|
||||
generateStatusEvent()
|
||||
} else {
|
||||
log.error "Error setHeatingSetpoint(setpoint)" //This error is handled by the connect app
|
||||
log.error "Error setHeatingSetpoint(setpoint)"
|
||||
}
|
||||
}
|
||||
|
||||
void setCoolingSetpoint(setpoint) {
|
||||
setCoolingSetpoint(setpoint.toDouble())
|
||||
}
|
||||
|
||||
void setCoolingSetpoint(Double setpoint) {
|
||||
// def mode = device.currentValue("thermostatMode")
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble()
|
||||
log.debug "***cooling setpoint $setpoint"
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = setpoint
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
||||
|
||||
if (coolingSetpoint > 92) {
|
||||
coolingSetpoint = 92
|
||||
} else if (coolingSetpoint < 65) {
|
||||
coolingSetpoint = 65
|
||||
|
||||
if (coolingSetpoint > maxCoolingSetpoint) {
|
||||
coolingSetpoint = maxCoolingSetpoint
|
||||
} else if (coolingSetpoint < minCoolingSetpoint) {
|
||||
coolingSetpoint = minCoolingSetpoint
|
||||
}
|
||||
|
||||
//enforce limits of heatingSetpoint vs coolingSetpoint
|
||||
@@ -236,15 +267,18 @@ void setCoolingSetpoint(Double setpoint) {
|
||||
|
||||
log.debug "Sending setCoolingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
||||
|
||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||
|
||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) {
|
||||
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
|
||||
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
|
||||
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
|
||||
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
||||
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
|
||||
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
|
||||
generateSetpointEvent()
|
||||
generateStatusEvent()
|
||||
} else {
|
||||
log.error "Error setCoolingSetpoint(setpoint)" //This error is handled by the connect app
|
||||
log.error "Error setCoolingSetpoint(setpoint)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,14 +287,14 @@ void resumeProgram() {
|
||||
log.debug "resumeProgram() is called"
|
||||
sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false)
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
if (parent.resumeProgram(this, deviceId)) {
|
||||
if (parent.resumeProgram(deviceId)) {
|
||||
sendEvent("name":"thermostatStatus", "value":"setpoint is updating", "description":statusText, displayed: false)
|
||||
runIn(5, "poll")
|
||||
log.debug "resumeProgram() is done"
|
||||
sendEvent("name":"resumeProgram", "value":"resume", descriptionText: "resumeProgram is done", displayed: false, isStateChange: true)
|
||||
} else {
|
||||
sendEvent("name":"thermostatStatus", "value":"failed resume click refresh", "description":statusText, displayed: false)
|
||||
log.error "Error resumeProgram() check parent.resumeProgram(this, deviceId)"
|
||||
log.error "Error resumeProgram() check parent.resumeProgram(deviceId)"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -278,7 +312,7 @@ def modes() {
|
||||
}
|
||||
|
||||
def fanModes() {
|
||||
["off", "on", "auto", "circulate"]
|
||||
["on", "auto"]
|
||||
}
|
||||
|
||||
def switchMode() {
|
||||
@@ -307,17 +341,15 @@ def switchFanMode() {
|
||||
def returnCommand
|
||||
|
||||
switch (currentFanMode) {
|
||||
case "fanAuto":
|
||||
returnCommand = switchToFanMode("fanOn")
|
||||
case "on":
|
||||
returnCommand = switchToFanMode("auto")
|
||||
break
|
||||
case "fanOn":
|
||||
returnCommand = switchToFanMode("fanCirculate")
|
||||
break
|
||||
case "fanCirculate":
|
||||
returnCommand = switchToFanMode("fanAuto")
|
||||
case "auto":
|
||||
returnCommand = switchToFanMode("on")
|
||||
break
|
||||
|
||||
}
|
||||
if(!currentFanMode) { returnCommand = switchToFanMode("fanOn") }
|
||||
if(!currentFanMode) { returnCommand = switchToFanMode("auto") }
|
||||
returnCommand
|
||||
}
|
||||
|
||||
@@ -326,25 +358,20 @@ def switchToFanMode(nextMode) {
|
||||
log.debug "switching to fan mode: $nextMode"
|
||||
def returnCommand
|
||||
|
||||
if(nextMode == "fanAuto") {
|
||||
if(!fanModes.contains("fanAuto")) {
|
||||
if(nextMode == "auto") {
|
||||
if(!fanModes.contains("auto")) {
|
||||
returnCommand = fanAuto()
|
||||
} else {
|
||||
returnCommand = switchToFanMode("fanOn")
|
||||
returnCommand = switchToFanMode("on")
|
||||
}
|
||||
} else if(nextMode == "fanOn") {
|
||||
if(!fanModes.contains("fanOn")) {
|
||||
} else if(nextMode == "on") {
|
||||
if(!fanModes.contains("on")) {
|
||||
returnCommand = fanOn()
|
||||
} else {
|
||||
returnCommand = switchToFanMode("fanCirculate")
|
||||
}
|
||||
} else if(nextMode == "fanCirculate") {
|
||||
if(!fanModes.contains("fanCirculate")) {
|
||||
returnCommand = fanCirculate()
|
||||
} else {
|
||||
returnCommand = switchToFanMode("fanAuto")
|
||||
returnCommand = switchToFanMode("auto")
|
||||
}
|
||||
}
|
||||
|
||||
returnCommand
|
||||
}
|
||||
|
||||
@@ -352,15 +379,16 @@ def getDataByName(String name) {
|
||||
state[name] ?: device.getDataValue(name)
|
||||
}
|
||||
|
||||
def setThermostatMode(String value) {
|
||||
log.debug "setThermostatMode({$value})"
|
||||
|
||||
def setThermostatMode(String mode) {
|
||||
log.debug "setThermostatMode($mode)"
|
||||
mode = mode.toLowerCase()
|
||||
switchToMode(mode)
|
||||
}
|
||||
|
||||
def setThermostatFanMode(String value) {
|
||||
|
||||
log.debug "setThermostatFanMode({$value})"
|
||||
|
||||
def setThermostatFanMode(String mode) {
|
||||
log.debug "setThermostatFanMode($mode)"
|
||||
mode = mode.toLowerCase()
|
||||
switchToFanMode(mode)
|
||||
}
|
||||
|
||||
def generateModeEvent(mode) {
|
||||
@@ -368,7 +396,7 @@ def generateModeEvent(mode) {
|
||||
}
|
||||
|
||||
def generateFanModeEvent(fanMode) {
|
||||
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${mode} mode", displayed: true)
|
||||
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${fanMode} mode", displayed: true)
|
||||
}
|
||||
|
||||
def generateOperatingStateEvent(operatingState) {
|
||||
@@ -378,7 +406,7 @@ def generateOperatingStateEvent(operatingState) {
|
||||
def off() {
|
||||
log.debug "off"
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
if (parent.setMode (this,"off", deviceId))
|
||||
if (parent.setMode ("off", deviceId))
|
||||
generateModeEvent("off")
|
||||
else {
|
||||
log.debug "Error setting new mode."
|
||||
@@ -392,7 +420,7 @@ def off() {
|
||||
def heat() {
|
||||
log.debug "heat"
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
if (parent.setMode (this,"heat", deviceId))
|
||||
if (parent.setMode ("heat", deviceId))
|
||||
generateModeEvent("heat")
|
||||
else {
|
||||
log.debug "Error setting new mode."
|
||||
@@ -403,10 +431,14 @@ def heat() {
|
||||
generateStatusEvent()
|
||||
}
|
||||
|
||||
def emergencyHeat() {
|
||||
auxHeatOnly()
|
||||
}
|
||||
|
||||
def auxHeatOnly() {
|
||||
log.debug "auxHeatOnly"
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
if (parent.setMode (this,"auxHeatOnly", deviceId))
|
||||
if (parent.setMode ("auxHeatOnly", deviceId))
|
||||
generateModeEvent("auxHeatOnly")
|
||||
else {
|
||||
log.debug "Error setting new mode."
|
||||
@@ -420,7 +452,7 @@ def auxHeatOnly() {
|
||||
def cool() {
|
||||
log.debug "cool"
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
if (parent.setMode (this,"cool", deviceId))
|
||||
if (parent.setMode ("cool", deviceId))
|
||||
generateModeEvent("cool")
|
||||
else {
|
||||
log.debug "Error setting new mode."
|
||||
@@ -434,7 +466,7 @@ def cool() {
|
||||
def auto() {
|
||||
log.debug "auto"
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
if (parent.setMode (this,"auto", deviceId))
|
||||
if (parent.setMode ("auto", deviceId))
|
||||
generateModeEvent("auto")
|
||||
else {
|
||||
log.debug "Error setting new mode."
|
||||
@@ -447,26 +479,44 @@ def auto() {
|
||||
|
||||
def fanOn() {
|
||||
log.debug "fanOn"
|
||||
// parent.setFanMode (this,"on")
|
||||
String fanMode = "on"
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
|
||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||
|
||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||
|
||||
if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
|
||||
generateFanModeEvent(fanMode)
|
||||
} else {
|
||||
log.debug "Error setting new mode."
|
||||
def currentFanMode = device.currentState("thermostatFanMode")?.value
|
||||
generateFanModeEvent(currentFanMode) // reset the tile back
|
||||
}
|
||||
}
|
||||
|
||||
def fanAuto() {
|
||||
log.debug "fanAuto"
|
||||
// parent.setFanMode (this,"auto")
|
||||
String fanMode = "auto"
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
|
||||
}
|
||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||
|
||||
def fanCirculate() {
|
||||
log.debug "fanCirculate"
|
||||
// parent.setFanMode (this,"circulate")
|
||||
|
||||
}
|
||||
|
||||
def fanOff() {
|
||||
log.debug "fanOff"
|
||||
// parent.setFanMode (this,"off")
|
||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||
|
||||
if (parent.setFanMode(heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
|
||||
generateFanModeEvent(fanMode)
|
||||
} else {
|
||||
log.debug "Error setting new mode."
|
||||
def currentFanMode = device.currentState("thermostatFanMode")?.value
|
||||
generateFanModeEvent(currentFanMode) // reset the tile back
|
||||
}
|
||||
}
|
||||
|
||||
def generateSetpointEvent() {
|
||||
@@ -476,20 +526,42 @@ def generateSetpointEvent() {
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
log.debug "Current Mode = ${mode}"
|
||||
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
log.debug "Heating Setpoint = ${heatingSetpoint}"
|
||||
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
log.debug "Cooling Setpoint = ${coolingSetpoint}"
|
||||
|
||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
||||
|
||||
if(location.temperatureScale == "C")
|
||||
{
|
||||
maxHeatingSetpoint = roundC(maxHeatingSetpoint)
|
||||
maxCoolingSetpoint = roundC(maxCoolingSetpoint)
|
||||
minHeatingSetpoint = roundC(minHeatingSetpoint)
|
||||
minCoolingSetpoint = roundC(minCoolingSetpoint)
|
||||
heatingSetpoint = roundC(heatingSetpoint)
|
||||
coolingSetpoint = roundC(coolingSetpoint)
|
||||
}
|
||||
|
||||
|
||||
sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale)
|
||||
sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale)
|
||||
sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale)
|
||||
sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale)
|
||||
|
||||
|
||||
if (mode == "heat") {
|
||||
|
||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
|
||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
||||
|
||||
}
|
||||
else if (mode == "cool") {
|
||||
|
||||
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString())
|
||||
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale)
|
||||
|
||||
} else if (mode == "auto") {
|
||||
|
||||
@@ -499,9 +571,9 @@ def generateSetpointEvent() {
|
||||
|
||||
sendEvent("name":"thermostatSetpoint", "value":"Off")
|
||||
|
||||
} else if (mode == "emergencyHeat") {
|
||||
} else if (mode == "auxHeatOnly") {
|
||||
|
||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
|
||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
||||
|
||||
}
|
||||
|
||||
@@ -510,29 +582,33 @@ def generateSetpointEvent() {
|
||||
void raiseSetpoint() {
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
def targetvalue
|
||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
||||
|
||||
|
||||
if (mode == "off" || mode == "auto") {
|
||||
log.warn "this mode: $mode does not allow raiseSetpoint"
|
||||
} else {
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger()
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
|
||||
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
|
||||
|
||||
if (device.latestState('thermostatSetpoint')) {
|
||||
targetvalue = device.latestState('thermostatSetpoint').value as Integer
|
||||
targetvalue = device.latestState('thermostatSetpoint').value
|
||||
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
|
||||
} else {
|
||||
targetvalue = 0
|
||||
}
|
||||
targetvalue = targetvalue + 1
|
||||
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
|
||||
|
||||
if (mode == "heat" && targetvalue > 79) {
|
||||
targetvalue = 79
|
||||
} else if (mode == "cool" && targetvalue > 92) {
|
||||
targetvalue = 92
|
||||
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) {
|
||||
targetvalue = maxHeatingSetpoint
|
||||
} else if (mode == "cool" && targetvalue > maxCoolingSetpoint) {
|
||||
targetvalue = maxCoolingSetpoint
|
||||
}
|
||||
|
||||
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
|
||||
sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false)
|
||||
log.info "In mode $mode raiseSetpoint() to $targetvalue"
|
||||
|
||||
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
|
||||
@@ -543,28 +619,32 @@ void raiseSetpoint() {
|
||||
void lowerSetpoint() {
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
def targetvalue
|
||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
||||
|
||||
|
||||
if (mode == "off" || mode == "auto") {
|
||||
log.warn "this mode: $mode does not allow lowerSetpoint"
|
||||
} else {
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger()
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
|
||||
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
|
||||
if (device.latestState('thermostatSetpoint')) {
|
||||
targetvalue = device.latestState('thermostatSetpoint').value as Integer
|
||||
targetvalue = device.latestState('thermostatSetpoint').value
|
||||
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
|
||||
} else {
|
||||
targetvalue = 0
|
||||
}
|
||||
targetvalue = targetvalue - 1
|
||||
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
|
||||
|
||||
if (mode == "heat" && targetvalue.toInteger() < 45) {
|
||||
targetvalue = 45
|
||||
} else if (mode == "cool" && targetvalue.toInteger() < 65) {
|
||||
targetvalue = 65
|
||||
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) {
|
||||
targetvalue = minHeatingSetpoint
|
||||
} else if (mode == "cool" && targetvalue < minCoolingSetpoint) {
|
||||
targetvalue = minCoolingSetpoint
|
||||
}
|
||||
|
||||
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
|
||||
sendEvent("name":"thermostatSetpoint", "value":targetvalue, "unit":location.temperatureScale, displayed: false)
|
||||
log.info "In mode $mode lowerSetpoint() to $targetvalue"
|
||||
|
||||
runIn(3, "alterSetpoint", [data: [value:targetvalue], overwrite: true]) //when user click button this runIn will be overwrite
|
||||
@@ -575,15 +655,15 @@ void lowerSetpoint() {
|
||||
void alterSetpoint(temp) {
|
||||
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
|
||||
def targetHeatingSetpoint
|
||||
def targetCoolingSetpoint
|
||||
|
||||
//step1: check thermostatMode, enforce limits before sending request to cloud
|
||||
if (mode == "heat"){
|
||||
if (mode == "heat" || mode == "auxHeatOnly"){
|
||||
if (temp.value > coolingSetpoint){
|
||||
targetHeatingSetpoint = temp.value
|
||||
targetCoolingSetpoint = temp.value
|
||||
@@ -602,19 +682,22 @@ void alterSetpoint(temp) {
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to ${targetHeatingSetpoint} " +
|
||||
"coolingSetpoint to ${targetCoolingSetpoint} with holdType : ${holdType}"
|
||||
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
|
||||
"coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
|
||||
|
||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||
//step2: call parent.setHold to send http request to 3rd party cloud
|
||||
if (parent.setHold(this, targetHeatingSetpoint, targetCoolingSetpoint, deviceId, sendHoldType)) {
|
||||
sendEvent("name": "thermostatSetpoint", "value": temp.value.toString(), displayed: false)
|
||||
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint)
|
||||
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint)
|
||||
|
||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
|
||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
|
||||
|
||||
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
|
||||
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
|
||||
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint, "unit": location.temperatureScale)
|
||||
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint, "unit": location.temperatureScale)
|
||||
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
|
||||
} else {
|
||||
log.error "Error alterSetpoint()"
|
||||
if (mode == "heat"){
|
||||
if (mode == "heat" || mode == "auxHeatOnly"){
|
||||
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
|
||||
} else if (mode == "cool") {
|
||||
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
|
||||
@@ -626,9 +709,9 @@ void alterSetpoint(temp) {
|
||||
def generateStatusEvent() {
|
||||
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||
def temperature = device.currentValue("temperature").toInteger()
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def temperature = device.currentValue("temperature")
|
||||
|
||||
def statusText
|
||||
|
||||
@@ -643,14 +726,14 @@ def generateStatusEvent() {
|
||||
if (temperature >= heatingSetpoint)
|
||||
statusText = "Right Now: Idle"
|
||||
else
|
||||
statusText = "Heating to ${heatingSetpoint}° F"
|
||||
statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}"
|
||||
|
||||
} else if (mode == "cool") {
|
||||
|
||||
if (temperature <= coolingSetpoint)
|
||||
statusText = "Right Now: Idle"
|
||||
else
|
||||
statusText = "Cooling to ${coolingSetpoint}° F"
|
||||
statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}"
|
||||
|
||||
} else if (mode == "auto") {
|
||||
|
||||
@@ -660,7 +743,7 @@ def generateStatusEvent() {
|
||||
|
||||
statusText = "Right Now: Off"
|
||||
|
||||
} else if (mode == "emergencyHeat") {
|
||||
} else if (mode == "auxHeatOnly") {
|
||||
|
||||
statusText = "Emergency Heat"
|
||||
|
||||
@@ -673,7 +756,18 @@ def generateStatusEvent() {
|
||||
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
|
||||
}
|
||||
|
||||
//generate custom mobile activity feeds event
|
||||
def generateActivityFeedsEvent(notificationMessage) {
|
||||
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
|
||||
}
|
||||
|
||||
def roundC (tempC) {
|
||||
return (Math.round(tempC.toDouble() * 2))/2
|
||||
}
|
||||
|
||||
def convertFtoC (tempF) {
|
||||
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
|
||||
}
|
||||
|
||||
def convertCtoF (tempC) {
|
||||
return (Math.round(tempC * (9/5)) + 32).toInteger()
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ metadata {
|
||||
attribute "tamper", "enum", ["detected", "clear"]
|
||||
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
|
||||
fingerprint mfr:"010F", prod:"0C02", model:"1002"
|
||||
}
|
||||
simulator {
|
||||
//battery
|
||||
|
||||
@@ -682,7 +682,7 @@ def setHeatingSetpoint(degrees) {
|
||||
def temperatureScale = getTemperatureScale()
|
||||
|
||||
def degreesInteger = degrees as Integer
|
||||
sendEvent("name":"heatingSetpoint", "value":degreesInteger)
|
||||
sendEvent("name":"heatingSetpoint", "value":degreesInteger, "unit":temperatureScale)
|
||||
|
||||
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
||||
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius*100) + "}"
|
||||
@@ -691,7 +691,7 @@ def setHeatingSetpoint(degrees) {
|
||||
|
||||
def setCoolingSetpoint(degrees) {
|
||||
def degreesInteger = degrees as Integer
|
||||
sendEvent("name":"coolingSetpoint", "value":degreesInteger)
|
||||
sendEvent("name":"coolingSetpoint", "value":degreesInteger, "unit":temperatureScale)
|
||||
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
||||
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius*100) + "}"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* GE Link Bulb
|
||||
*
|
||||
* Copyright 2014 SmartThings
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -36,155 +36,73 @@
|
||||
* Slider range from 0..100
|
||||
* Change 9: 2015-03-06 (Juan Risso)
|
||||
* Setlevel -> value to integer (to prevent smartapp calling this function from not working).
|
||||
* Change 10: 2016-03-06 (Vinay Rao/Tom Manley)
|
||||
* changed 2/3rds of the file to clean up code and add zigbee library improvements
|
||||
*
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "GE Link Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "GE Link Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Actuator"
|
||||
capability "Actuator"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Sensor"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Switch Level"
|
||||
capability "Polling"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE_Appliances", model: "ZLL Light", deviceJoinName: "GE Link Bulb"
|
||||
}
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE", model: "SoftWhite", deviceJoinName: "GE Link Soft White Bulb"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE", model: "Daylight", deviceJoinName: "GE Link Daylight Bulb"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles {
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState:"turningOff"
|
||||
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', action: "switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action: "switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
|
||||
state "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "level", "levelSliderControl", "refresh"])
|
||||
}
|
||||
|
||||
preferences {
|
||||
|
||||
input("dimRate", "enum", title: "Dim Rate", options: ["Instant", "Normal", "Slow", "Very Slow"], defaultValue: "Normal", required: false, displayDuringSetup: true)
|
||||
input("dimOnOff", "enum", title: "Dim transition for On/Off commands?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: true)
|
||||
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.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "switch"
|
||||
details(["switch", "refresh"])
|
||||
}
|
||||
|
||||
preferences {
|
||||
input("dimRate", "enum", title: "Dim Rate", options: ["Instant", "Normal", "Slow", "Very Slow"], defaultValue: "Normal", required: false, displayDuringSetup: true)
|
||||
input("dimOnOff", "enum", title: "Dim transition for On/Off commands?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: true)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.trace description
|
||||
|
||||
if (description?.startsWith("on/off:")) {
|
||||
log.debug "The bulb was sent a command to do something just now..."
|
||||
if (description[-1] == "1") {
|
||||
def result = createEvent(name: "switch", value: "on")
|
||||
log.debug "On command was sent maybe from manually turning on? : Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
} else if (description[-1] == "0") {
|
||||
def result = createEvent(name: "switch", value: "off")
|
||||
log.debug "Off command was sent : Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
def resultMap = zigbee.getEvent(description)
|
||||
if (resultMap) {
|
||||
if ((resultMap.name == "level" && state.trigger == "setLevel") || resultMap.name != "level") { //doing this to account for weird level reporting bug with GE Link Bulbs
|
||||
sendEvent(resultMap)
|
||||
}
|
||||
}
|
||||
|
||||
def msg = zigbee.parse(description)
|
||||
|
||||
if (description?.startsWith("catchall:")) {
|
||||
// log.trace msg
|
||||
// log.trace "data: $msg.data"
|
||||
|
||||
def x = description[-4..-1]
|
||||
// log.debug x
|
||||
|
||||
switch (x)
|
||||
{
|
||||
|
||||
case "0000":
|
||||
|
||||
def result = createEvent(name: "switch", value: "off")
|
||||
log.debug "${result?.descriptionText}"
|
||||
return result
|
||||
break
|
||||
|
||||
case "1000":
|
||||
|
||||
def result = createEvent(name: "switch", value: "off")
|
||||
log.debug "${result?.descriptionText}"
|
||||
return result
|
||||
break
|
||||
|
||||
case "0100":
|
||||
|
||||
def result = createEvent(name: "switch", value: "on")
|
||||
log.debug "${result?.descriptionText}"
|
||||
return result
|
||||
break
|
||||
|
||||
case "1001":
|
||||
|
||||
def result = createEvent(name: "switch", value: "on")
|
||||
log.debug "${result?.descriptionText}"
|
||||
return result
|
||||
break
|
||||
}
|
||||
else {
|
||||
log.debug "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
|
||||
if (description?.startsWith("read attr")) {
|
||||
|
||||
// log.trace description[27..28]
|
||||
// log.trace description[-2..-1]
|
||||
|
||||
if (description[27..28] == "0A") {
|
||||
|
||||
// log.debug description[-2..-1]
|
||||
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
|
||||
sendEvent( name: "level", value: i )
|
||||
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
|
||||
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
if (description[-2..-1] == "00" && state.trigger == "setLevel") {
|
||||
// log.debug description[-2..-1]
|
||||
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
|
||||
sendEvent( name: "level", value: i )
|
||||
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
|
||||
}
|
||||
|
||||
if (description[-2..-1] == state.lvl) {
|
||||
// log.debug description[-2..-1]
|
||||
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
|
||||
sendEvent( name: "level", value: i )
|
||||
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def poll() {
|
||||
|
||||
[
|
||||
"st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 8 0", "delay 500",
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}"
|
||||
def refreshCmds = [
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
|
||||
]
|
||||
|
||||
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
@@ -270,109 +188,63 @@ def updated() {
|
||||
}
|
||||
|
||||
def on() {
|
||||
state.lvl = "00"
|
||||
state.trigger = "on/off"
|
||||
|
||||
// log.debug "on()"
|
||||
sendEvent(name: "switch", value: "on")
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
||||
zigbee.on()
|
||||
}
|
||||
|
||||
def off() {
|
||||
state.lvl = "00"
|
||||
state.trigger = "on/off"
|
||||
|
||||
// log.debug "off()"
|
||||
sendEvent(name: "switch", value: "off")
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
||||
zigbee.off()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
|
||||
[
|
||||
"st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 8 0", "delay 500",
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}"
|
||||
def refreshCmds = [
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
|
||||
]
|
||||
poll()
|
||||
|
||||
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig()
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
|
||||
def cmds = []
|
||||
value = value as Integer
|
||||
if (value == 0) {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 0 {0000 ${state.rate}}"
|
||||
}
|
||||
else if (device.latestValue("switch") == "off") {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
|
||||
sendEvent(name: "level", value: value)
|
||||
value = (value * 255 / 100)
|
||||
def level = hex(value);
|
||||
|
||||
state.trigger = "setLevel"
|
||||
state.lvl = "${level}"
|
||||
|
||||
def cmd
|
||||
def delayForRefresh = 500
|
||||
if (dimRate && (state?.rate != null)) {
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} ${state.rate}}"
|
||||
def computedRate = convertRateValue(state.rate)
|
||||
cmd = zigbee.setLevel(value, computedRate)
|
||||
delayForRefresh += computedRate * 100 //converting tenth of second to milliseconds
|
||||
}
|
||||
else {
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 1500}"
|
||||
cmd = zigbee.setLevel(value, 20)
|
||||
delayForRefresh += 2000
|
||||
}
|
||||
cmd + ["delay $delayForRefresh"] + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
log.debug cmds
|
||||
cmds
|
||||
int convertRateValue(rate) {
|
||||
int convertedRate = 0
|
||||
switch (rate)
|
||||
{
|
||||
case "0000":
|
||||
convertedRate = 0
|
||||
break
|
||||
|
||||
case "1500":
|
||||
convertedRate = 20 //0015 hex in int is 2.1
|
||||
break
|
||||
|
||||
case "2500":
|
||||
convertedRate = 35 //0025 hex in int is 3.7
|
||||
break
|
||||
|
||||
case "3500":
|
||||
convertedRate = 50 //0035 hex in int is 5.1
|
||||
break
|
||||
}
|
||||
convertedRate
|
||||
}
|
||||
|
||||
def configure() {
|
||||
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
def configCmds = [
|
||||
|
||||
//Switch Reporting
|
||||
"zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1000",
|
||||
|
||||
//Level Control Reporting
|
||||
"zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 8 {${device.zigbeeId}} {}", "delay 500",
|
||||
]
|
||||
return configCmds + refresh() // send refresh cmds as part of config
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
return zigbee.onOffConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
private hex(value, width=2) {
|
||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
||||
while (s.size() < width) {
|
||||
s = "0" + s
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Gentle Wake Up Controller", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Switch"
|
||||
capability "Timed Session"
|
||||
|
||||
attribute "percentComplete", "number"
|
||||
|
||||
command "setPercentComplete", ["number"]
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
|
||||
multiAttributeTile(name: "richTile", type:"generic", width:6, height:4) {
|
||||
tileAttribute("sessionStatus", key: "PRIMARY_CONTROL") {
|
||||
attributeState "cancelled", action: "timed session.start", icon: "http://f.cl.ly/items/322n181j2K3f281r2s0A/playbutton.png", backgroundColor: "#ffffff", nextState: "running"
|
||||
attributeState "stopped", action: "timed session.start", icon: "http://f.cl.ly/items/322n181j2K3f281r2s0A/playbutton.png", backgroundColor: "#ffffff", nextState: "cancelled"
|
||||
attributeState "running", action: "timed session.stop", icon: "http://f.cl.ly/items/0B3y3p2V3X2l3P3y3W09/stopbutton.png", backgroundColor: "#79b821", nextState: "cancelled"
|
||||
}
|
||||
tileAttribute("timeRemaining", key: "SECONDARY_CONTROL") {
|
||||
attributeState "timeRemaining", label:'${currentValue} remaining'
|
||||
}
|
||||
tileAttribute("percentComplete", key: "SLIDER_CONTROL") {
|
||||
attributeState "percentComplete", action: "timed session.setTimeRemaining"
|
||||
}
|
||||
}
|
||||
|
||||
// start/stop
|
||||
standardTile("sessionStatusTile", "sessionStatus", width: 1, height: 1, canChangeIcon: true) {
|
||||
state "cancelled", label: "Stopped", action: "timed session.start", backgroundColor: "#ffffff", icon: "http://f.cl.ly/items/1J1g0H2P0S1G1f2O1s1s/icon.png"
|
||||
state "stopped", label: "Stopped", action: "timed session.start", backgroundColor: "#ffffff", icon: "http://f.cl.ly/items/1J1g0H2P0S1G1f2O1s1s/icon.png"
|
||||
state "running", label: "Running", action: "timed session.stop", backgroundColor: "#79b821", icon: "http://f.cl.ly/items/1J1g0H2P0S1G1f2O1s1s/icon.png"
|
||||
}
|
||||
|
||||
// duration
|
||||
valueTile("timeRemainingTile", "timeRemaining", decoration: "flat", width: 2) {
|
||||
state "timeRemaining", label:'${currentValue} left'
|
||||
}
|
||||
controlTile("percentCompleteTile", "percentComplete", "slider", height: 1, width: 3) {
|
||||
state "percentComplete", action: "timed session.setTimeRemaining"
|
||||
}
|
||||
|
||||
main "sessionStatusTile"
|
||||
details "richTile"
|
||||
// details(["richTile", "sessionStatusTile", "timeRemainingTile", "percentCompleteTile"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
// TODO: handle 'switch' attribute
|
||||
// TODO: handle 'level' attribute
|
||||
// TODO: handle 'sessionStatus' attribute
|
||||
// TODO: handle 'timeRemaining' attribute
|
||||
|
||||
}
|
||||
|
||||
// handle commands
|
||||
def on() {
|
||||
log.debug "Executing 'on'"
|
||||
startDimming()
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "Executing 'off'"
|
||||
stopDimming()
|
||||
}
|
||||
|
||||
def setTimeRemaining(percentComplete) {
|
||||
log.debug "Executing 'setTimeRemaining' to ${percentComplete}% complete"
|
||||
parent.jumpTo(percentComplete)
|
||||
}
|
||||
|
||||
def start() {
|
||||
log.debug "Executing 'start'"
|
||||
startDimming()
|
||||
}
|
||||
|
||||
def stop() {
|
||||
log.debug "Executing 'stop'"
|
||||
stopDimming()
|
||||
}
|
||||
|
||||
def pause() {
|
||||
log.debug "Executing 'pause'"
|
||||
// TODO: handle 'pause' command
|
||||
}
|
||||
|
||||
def cancel() {
|
||||
log.debug "Executing 'cancel'"
|
||||
stopDimming()
|
||||
}
|
||||
|
||||
def startDimming() {
|
||||
log.trace "startDimming"
|
||||
log.debug "parent: ${parent}"
|
||||
parent.start("controller")
|
||||
}
|
||||
|
||||
def stopDimming() {
|
||||
log.trace "stopDimming"
|
||||
log.debug "parent: ${parent}"
|
||||
parent.stop("controller")
|
||||
}
|
||||
|
||||
def controllerEvent(eventData) {
|
||||
log.trace "controllerEvent"
|
||||
sendEvent(eventData)
|
||||
}
|
||||
@@ -80,19 +80,12 @@ def parse(String description) {
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
// log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
|
||||
log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.multiinstancev1.MultiInstanceCmdEncap cmd) {
|
||||
def encapsulated = null
|
||||
if (cmd.respondsTo("encapsulatedCommand")) {
|
||||
encapsulated = cmd.encapsulatedCommand()
|
||||
} else {
|
||||
def hex1 = { n -> String.format("%02X", n) }
|
||||
def sorry = "command: ${hex1(cmd.commandClass)}${hex1(cmd.command)}, payload: " + cmd.parameter.collect{ hex1(it) }.join(" ")
|
||||
encapsulated = zwave.parse(sorry, [0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
|
||||
}
|
||||
def encapsulated = cmd.encapsulatedCommand([0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
|
||||
return encapsulated ? zwaveEvent(encapsulated) : null
|
||||
}
|
||||
|
||||
|
||||
177
devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy
Normal file
177
devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* Hue Bloom
|
||||
*
|
||||
* Philips Hue Type "Color Light"
|
||||
*
|
||||
* Author: SmartThings
|
||||
*/
|
||||
|
||||
// for the UI
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Hue Bloom", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Color Control"
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
|
||||
command "setAdjustedColor"
|
||||
command "reset"
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"setAdjustedColor"
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
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") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["rich-control"])
|
||||
details(["rich-control", "reset", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
void installed() {
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(description) {
|
||||
log.debug "parse() - $description"
|
||||
def results = []
|
||||
|
||||
def map = description
|
||||
if (description instanceof String) {
|
||||
log.debug "Hue Bulb stringToMap - ${map}"
|
||||
map = stringToMap(description)
|
||||
}
|
||||
|
||||
if (map?.name && map?.value) {
|
||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
// handle commands
|
||||
void on() {
|
||||
log.trace parent.on(this)
|
||||
}
|
||||
|
||||
void off() {
|
||||
log.trace parent.off(this)
|
||||
}
|
||||
|
||||
void setLevel(percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
if (verifyPercent(percent)) {
|
||||
log.trace parent.setLevel(this, percent)
|
||||
}
|
||||
}
|
||||
|
||||
void setSaturation(percent) {
|
||||
log.debug "Executing 'setSaturation'"
|
||||
if (verifyPercent(percent)) {
|
||||
log.trace parent.setSaturation(this, percent)
|
||||
}
|
||||
}
|
||||
|
||||
void setHue(percent) {
|
||||
log.debug "Executing 'setHue'"
|
||||
if (verifyPercent(percent)) {
|
||||
log.trace parent.setHue(this, percent)
|
||||
}
|
||||
}
|
||||
|
||||
void setColor(value) {
|
||||
def events = []
|
||||
def validValues = [:]
|
||||
|
||||
if (verifyPercent(value.hue)) {
|
||||
validValues.hue = value.hue
|
||||
}
|
||||
if (verifyPercent(value.saturation)) {
|
||||
validValues.saturation = value.saturation
|
||||
}
|
||||
if (value.hex != null) {
|
||||
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
|
||||
validValues.hex = value.hex
|
||||
} else {
|
||||
log.warn "$value.hex is not a valid color"
|
||||
}
|
||||
}
|
||||
if (verifyPercent(value.level)) {
|
||||
validValues.level = value.level
|
||||
}
|
||||
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
|
||||
validValues.switch = "off"
|
||||
} else {
|
||||
validValues.switch = "on"
|
||||
}
|
||||
if (!validValues.isEmpty()) {
|
||||
log.trace parent.setColor(this, validValues)
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
log.debug "Executing 'reset'"
|
||||
def value = [hue:20, saturation:2]
|
||||
setAdjustedColor(value)
|
||||
}
|
||||
|
||||
void setAdjustedColor(value) {
|
||||
if (value) {
|
||||
log.trace "setAdjustedColor: ${value}"
|
||||
def adjusted = value + [:]
|
||||
// Needed because color picker always sends 100
|
||||
adjusted.level = null
|
||||
setColor(adjusted)
|
||||
} else {
|
||||
log.warn "Invalid color input $value"
|
||||
}
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
}
|
||||
|
||||
|
||||
def verifyPercent(percent) {
|
||||
if (percent == null)
|
||||
return false
|
||||
else if (percent >= 0 && percent <= 100) {
|
||||
return true
|
||||
} else {
|
||||
log.warn "$percent is not 0-100"
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
def ping() {
|
||||
log.debug "${parent.ping(this)}"
|
||||
}
|
||||
@@ -7,8 +7,15 @@
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") {
|
||||
attribute "serialNumber", "string"
|
||||
capability "Health Check"
|
||||
|
||||
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 {
|
||||
@@ -17,28 +24,30 @@ metadata {
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control"){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200"
|
||||
}
|
||||
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
|
||||
attributeState "default", label:'SN: ${currentValue}'
|
||||
}
|
||||
}
|
||||
standardTile("icon", "icon", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
|
||||
state "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#FFFFFF"
|
||||
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
|
||||
attributeState "Offline", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#ffffff"
|
||||
attributeState "Online", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#79b821"
|
||||
}
|
||||
}
|
||||
valueTile("doNotRemove", "v", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
|
||||
state "default", label:'If removed, Hue lights will not work properly'
|
||||
}
|
||||
valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) {
|
||||
state "default", label:'SN: ${currentValue}'
|
||||
valueTile("idNumber", "device.idNumber", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
|
||||
state "default", label:'ID: ${currentValue}'
|
||||
}
|
||||
valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 4, inactiveLabel: false) {
|
||||
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
|
||||
valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
|
||||
state "default", label:'IP: ${currentValue}'
|
||||
}
|
||||
|
||||
main (["icon"])
|
||||
details(["rich-control", "networkAddress"])
|
||||
main (["rich-control"])
|
||||
details(["rich-control", "doNotRemove", "idNumber", "networkAddress"])
|
||||
}
|
||||
}
|
||||
|
||||
void installed() {
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
@@ -59,7 +68,7 @@ def parse(description) {
|
||||
log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value"
|
||||
results << createEvent(name: "${map.name}", value: "${map.value}")
|
||||
} else {
|
||||
log.trace "Parsing description"
|
||||
log.trace "Parsing description"
|
||||
def msg = parseLanMessage(description)
|
||||
if (msg.body) {
|
||||
def contentType = msg.headers["Content-Type"]
|
||||
@@ -67,17 +76,17 @@ def parse(description) {
|
||||
def bulbs = new groovy.json.JsonSlurper().parseText(msg.body)
|
||||
if (bulbs.state) {
|
||||
log.info "Bridge response: $msg.body"
|
||||
} else {
|
||||
// Sending Bulbs List to parent"
|
||||
if (parent.state.inBulbDiscovery)
|
||||
log.info parent.bulbListHandler(device.hub.id, msg.body)
|
||||
}
|
||||
}
|
||||
else if (contentType?.contains("xml")) {
|
||||
} else if (contentType?.contains("xml")) {
|
||||
log.debug "HUE BRIDGE ALREADY PRESENT"
|
||||
parent.hubVerification(device.hub.id, msg.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
def ping() {
|
||||
log.debug "${parent.ping(this)}"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
|
||||
/**
|
||||
* Hue Bulb
|
||||
*
|
||||
* Philips Hue Type "Extended Color Light"
|
||||
*
|
||||
* Author: SmartThings
|
||||
*/
|
||||
|
||||
// for the UI
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
@@ -11,13 +13,15 @@ metadata {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Color Control"
|
||||
capability "Color Temperature"
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
|
||||
command "setAdjustedColor"
|
||||
command "reset"
|
||||
command "refresh"
|
||||
command "reset"
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -25,7 +29,7 @@ metadata {
|
||||
}
|
||||
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
@@ -33,34 +37,49 @@ metadata {
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"setAdjustedColor"
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
|
||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "colorTemperature", label: 'WHITES'
|
||||
}
|
||||
|
||||
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"Reset To White", action:"reset", icon:"st.lights.philips.hue-single"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
|
||||
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "levelSliderControl", "rgbSelector", "refresh", "reset"])
|
||||
main(["rich-control"])
|
||||
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
void installed() {
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(description) {
|
||||
log.debug "parse() - $description"
|
||||
def results = []
|
||||
|
||||
def map = description
|
||||
if (description instanceof String) {
|
||||
log.debug "Hue Bulb stringToMap - ${map}"
|
||||
map = stringToMap(description)
|
||||
}
|
||||
|
||||
if (map?.name && map?.value) {
|
||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
||||
}
|
||||
@@ -68,91 +87,107 @@ def parse(description) {
|
||||
}
|
||||
|
||||
// handle commands
|
||||
def on() {
|
||||
void on() {
|
||||
log.trace parent.on(this)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
|
||||
def off() {
|
||||
void off() {
|
||||
log.trace parent.off(this)
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
|
||||
def 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)
|
||||
}
|
||||
|
||||
def setLevel(percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
parent.setLevel(this, percent)
|
||||
sendEvent(name: "level", value: percent)
|
||||
}
|
||||
|
||||
def setSaturation(percent) {
|
||||
log.debug "Executing 'setSaturation'"
|
||||
parent.setSaturation(this, percent)
|
||||
sendEvent(name: "saturation", value: percent)
|
||||
}
|
||||
|
||||
def setHue(percent) {
|
||||
log.debug "Executing 'setHue'"
|
||||
parent.setHue(this, percent)
|
||||
sendEvent(name: "hue", value: percent)
|
||||
}
|
||||
|
||||
def setColor(value) {
|
||||
log.debug "setColor: ${value}, $this"
|
||||
parent.setColor(this, value)
|
||||
if (value.hue) { sendEvent(name: "hue", value: value.hue)}
|
||||
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation)}
|
||||
if (value.hex) { sendEvent(name: "color", value: value.hex)}
|
||||
if (value.level) { sendEvent(name: "level", value: value.level)}
|
||||
if (value.switch) { sendEvent(name: "switch", value: value.switch)}
|
||||
}
|
||||
|
||||
def reset() {
|
||||
log.debug "Executing 'reset'"
|
||||
def value = [level:100, hex:"#90C638", saturation:56, hue:23]
|
||||
setAdjustedColor(value)
|
||||
parent.poll()
|
||||
}
|
||||
|
||||
def setAdjustedColor(value) {
|
||||
if (value) {
|
||||
log.trace "setAdjustedColor: ${value}"
|
||||
def adjusted = value + [:]
|
||||
adjusted.hue = adjustOutgoingHue(value.hue)
|
||||
// Needed because color picker always sends 100
|
||||
adjusted.level = null
|
||||
setColor(adjusted)
|
||||
void setLevel(percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
if (verifyPercent(percent)) {
|
||||
log.trace parent.setLevel(this, percent)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
void setSaturation(percent) {
|
||||
log.debug "Executing 'setSaturation'"
|
||||
if (verifyPercent(percent)) {
|
||||
log.trace parent.setSaturation(this, percent)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
void setHue(percent) {
|
||||
log.debug "Executing 'setHue'"
|
||||
if (verifyPercent(percent)) {
|
||||
log.trace parent.setHue(this, percent)
|
||||
}
|
||||
}
|
||||
|
||||
void setColor(value) {
|
||||
def events = []
|
||||
def validValues = [:]
|
||||
|
||||
if (verifyPercent(value.hue)) {
|
||||
validValues.hue = value.hue
|
||||
}
|
||||
if (verifyPercent(value.saturation)) {
|
||||
validValues.saturation = value.saturation
|
||||
}
|
||||
if (value.hex != null) {
|
||||
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
|
||||
validValues.hex = value.hex
|
||||
} else {
|
||||
log.warn "$value.hex is not a valid color"
|
||||
}
|
||||
}
|
||||
if (verifyPercent(value.level)) {
|
||||
validValues.level = value.level
|
||||
}
|
||||
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
|
||||
validValues.switch = "off"
|
||||
} else {
|
||||
validValues.switch = "on"
|
||||
}
|
||||
if (!validValues.isEmpty()) {
|
||||
log.trace parent.setColor(this, validValues)
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
log.debug "Executing 'reset'"
|
||||
setColorTemperature(4000)
|
||||
}
|
||||
|
||||
void setAdjustedColor(value) {
|
||||
if (value) {
|
||||
log.trace "setAdjustedColor: ${value}"
|
||||
def adjusted = value + [:]
|
||||
// Needed because color picker always sends 100
|
||||
adjusted.level = null
|
||||
setColor(adjusted)
|
||||
} else {
|
||||
log.warn "Invalid color input $value"
|
||||
}
|
||||
}
|
||||
|
||||
void setColorTemperature(value) {
|
||||
if (value) {
|
||||
log.trace "setColorTemperature: ${value}k"
|
||||
log.trace parent.setColorTemperature(this, value)
|
||||
} else {
|
||||
log.warn "Invalid color temperature $value"
|
||||
}
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent?.manualRefresh()
|
||||
}
|
||||
|
||||
def verifyPercent(percent) {
|
||||
if (percent == null)
|
||||
return false
|
||||
else if (percent >= 0 && percent <= 100) {
|
||||
return true
|
||||
} else {
|
||||
log.warn "$percent is not 0-100"
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
def ping() {
|
||||
log.trace "${parent.ping(this)}"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/**
|
||||
* Hue Lux Bulb
|
||||
*
|
||||
* Philips Hue Type "Dimmable Light"
|
||||
*
|
||||
* Author: SmartThings
|
||||
*/
|
||||
// for the UI
|
||||
@@ -12,14 +14,15 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
command "refresh"
|
||||
capability "Health Check"
|
||||
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
@@ -31,29 +34,23 @@ metadata {
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||
attributeState "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
|
||||
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["rich-control"])
|
||||
details(["rich-control", "refresh"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void installed() {
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
@@ -74,23 +71,28 @@ def parse(description) {
|
||||
}
|
||||
|
||||
// handle commands
|
||||
def on() {
|
||||
parent.on(this)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
void on() {
|
||||
log.trace parent.on(this)
|
||||
}
|
||||
|
||||
def off() {
|
||||
parent.off(this)
|
||||
sendEvent(name: "switch", value: "off")
|
||||
void off() {
|
||||
log.trace parent.off(this)
|
||||
}
|
||||
|
||||
def setLevel(percent) {
|
||||
void setLevel(percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
parent.setLevel(this, percent)
|
||||
sendEvent(name: "level", value: percent)
|
||||
if (percent != null && percent >= 0 && percent <= 100) {
|
||||
parent.setLevel(this, percent)
|
||||
} else {
|
||||
log.warn "$percent is not 0-100"
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
void refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
}
|
||||
|
||||
def ping() {
|
||||
log.debug "${parent.ping(this)}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Hue White Ambiance Bulb
|
||||
*
|
||||
* Philips Hue Type "Color Temperature Light"
|
||||
*
|
||||
* Author: SmartThings
|
||||
*/
|
||||
|
||||
// for the UI
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Hue White Ambiance Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Color Temperature"
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Health Check"
|
||||
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
}
|
||||
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2200..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
|
||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "colorTemperature", label: 'WHITES'
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["rich-control"])
|
||||
details(["rich-control", "colorTempSliderControl", "colorTemp", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
void installed() {
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(description) {
|
||||
log.debug "parse() - $description"
|
||||
def results = []
|
||||
|
||||
def map = description
|
||||
if (description instanceof String) {
|
||||
log.debug "Hue Ambience Bulb stringToMap - ${map}"
|
||||
map = stringToMap(description)
|
||||
}
|
||||
|
||||
if (map?.name && map?.value) {
|
||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
// handle commands
|
||||
void on() {
|
||||
log.trace parent.on(this)
|
||||
}
|
||||
|
||||
void off() {
|
||||
log.trace parent.off(this)
|
||||
}
|
||||
|
||||
void setLevel(percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
if (percent != null && percent >= 0 && percent <= 100) {
|
||||
log.trace parent.setLevel(this, percent)
|
||||
} else {
|
||||
log.warn "$percent is not 0-100"
|
||||
}
|
||||
}
|
||||
|
||||
void setColorTemperature(value) {
|
||||
if (value) {
|
||||
log.trace "setColorTemperature: ${value}k"
|
||||
log.trace parent.setColorTemperature(this, value)
|
||||
} else {
|
||||
log.warn "Invalid color temperature"
|
||||
}
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
}
|
||||
|
||||
def ping() {
|
||||
log.debug "${parent.ping(this)}"
|
||||
}
|
||||
@@ -84,6 +84,7 @@ def setHue(percentage) {
|
||||
log.error("Bad setHue result: [${resp.status}] ${resp.data}")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def setSaturation(percentage) {
|
||||
@@ -97,6 +98,7 @@ def setSaturation(percentage) {
|
||||
log.error("Bad setSaturation result: [${resp.status}] ${resp.data}")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def setColor(Map color) {
|
||||
@@ -122,13 +124,15 @@ def setColor(Map color) {
|
||||
parent.logErrors(logObject:log) {
|
||||
def resp = parent.apiPUT("/lights/${selector()}/state", [color: attrs.join(" "), power: "on"])
|
||||
if (resp.status < 300) {
|
||||
sendEvent(name: "color", value: color.hex)
|
||||
if (color.hex)
|
||||
sendEvent(name: "color", value: color.hex)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
events.each { sendEvent(it) }
|
||||
} else {
|
||||
log.error("Bad setColor result: [${resp.status}] ${resp.data}")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def setLevel(percentage) {
|
||||
@@ -150,6 +154,7 @@ def setLevel(percentage) {
|
||||
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def setColorTemperature(kelvin) {
|
||||
@@ -165,6 +170,7 @@ def setColorTemperature(kelvin) {
|
||||
}
|
||||
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def on() {
|
||||
@@ -174,6 +180,7 @@ def on() {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def off() {
|
||||
@@ -183,6 +190,7 @@ def off() {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def poll() {
|
||||
|
||||
@@ -84,6 +84,7 @@ def setLevel(percentage) {
|
||||
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def setColorTemperature(kelvin) {
|
||||
@@ -99,6 +100,7 @@ def setColorTemperature(kelvin) {
|
||||
log.error("Bad setColorTemperature result: [${resp.status}] ${resp.data}")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def on() {
|
||||
@@ -108,6 +110,7 @@ def on() {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def off() {
|
||||
@@ -117,6 +120,7 @@ def off() {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def poll() {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Copyright 2016 SmartThings
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Give your device a name'''.ko=기기 이름 설정
|
||||
'''Set Device Image'''.ko=기기 이미지 설정
|
||||
# Events / Notifications
|
||||
'''{{ linkText }} has left'''.ko={{ linkText }} 외출
|
||||
'''{{ linkText }} has arrived'''.ko={{ linkText }} 귀가
|
||||
'''present'''.ko=집안
|
||||
'''not present'''.ko=외출
|
||||
@@ -1,16 +1,19 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
/*
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
* 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.
|
||||
*
|
||||
* 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: "Mobile Presence", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Presence Sensor"
|
||||
@@ -41,6 +44,7 @@ def parse(String description) {
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
|
||||
def results = [
|
||||
translatable: true,
|
||||
name: name,
|
||||
value: value,
|
||||
unit: null,
|
||||
@@ -72,8 +76,8 @@ private String parseValue(String description) {
|
||||
|
||||
private parseDescriptionText(String linkText, String value, String description) {
|
||||
switch(value) {
|
||||
case "present": return "$linkText has arrived"
|
||||
case "not present": return "$linkText has left"
|
||||
case "present": return "{{ linkText }} has arrived"
|
||||
case "not present": return "{{ linkText }} has left"
|
||||
default: return value
|
||||
}
|
||||
}
|
||||
@@ -84,4 +88,4 @@ private getState(String value) {
|
||||
case "not present": return "left"
|
||||
default: return value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
metadata {
|
||||
definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
@@ -143,51 +144,14 @@ private Map parseReportAttributeMessage(String description) {
|
||||
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0030': // Closed/No Motion/Dry
|
||||
log.debug 'no motion'
|
||||
resultMap.name = 'motion'
|
||||
resultMap.value = 'inactive'
|
||||
break
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
Map resultMap = [:]
|
||||
|
||||
case '0x0032': // Open/Motion/Wet
|
||||
log.debug 'motion'
|
||||
resultMap.name = 'motion'
|
||||
resultMap.value = 'active'
|
||||
break
|
||||
resultMap.name = 'motion'
|
||||
resultMap.value = zs.isAlarm2Set() ? 'active' : 'inactive'
|
||||
log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion')
|
||||
|
||||
case '0x0032': // Tamper Alarm
|
||||
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
|
||||
return resultMap
|
||||
}
|
||||
|
||||
def refresh()
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
|
||||
metadata {
|
||||
definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") {
|
||||
capability "Battery"
|
||||
@@ -24,6 +27,7 @@ metadata {
|
||||
command "enrollResponse"
|
||||
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3010", deviceJoinName: "NYCE Door Hinge Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
|
||||
@@ -218,40 +222,33 @@ private Map parseReportAttributeMessage(String description) {
|
||||
}
|
||||
|
||||
private List parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(" ")
|
||||
String msgCode = parsedMsg[2]
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
log.debug "parseIasMessage: $description"
|
||||
|
||||
List resultListMap = []
|
||||
Map resultMap_battery = [:]
|
||||
Map resultMap_battery_state = [:]
|
||||
Map resultMap_sensor = [:]
|
||||
|
||||
// Relevant bit field definitions from ZigBee spec
|
||||
def BATTERY_BIT = ( 1 << 3 )
|
||||
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}"
|
||||
resultMap_sensor.name = "contact"
|
||||
resultMap_sensor.value = zs.isAlarm1Set() ? "open" : "closed"
|
||||
|
||||
// Check each relevant bit, create map for it, and add to list
|
||||
log.debug "parseIasMessage: Battery Status ${zoneStatus & BATTERY_BIT}"
|
||||
log.debug "parseIasMessage: Trouble Status ${zoneStatus & TROUBLE_BIT}"
|
||||
log.debug "parseIasMessage: Sensor Status ${zoneStatus & SENSOR_BIT}"
|
||||
log.debug "parseIasMessage: Battery Status ${zs.battery}"
|
||||
log.debug "parseIasMessage: Trouble Status ${zs.trouble}"
|
||||
log.debug "parseIasMessage: Sensor Status ${zs.alarm1}"
|
||||
|
||||
/* 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
|
||||
resultMap_battery_state.name = "battery_state"
|
||||
if (zoneStatus & TROUBLE_BIT) {
|
||||
if (zs.isTroubleSet()) {
|
||||
resultMap_battery_state.value = "failed"
|
||||
|
||||
resultMap_battery.name = "battery"
|
||||
resultMap_battery.value = 0
|
||||
}
|
||||
else {
|
||||
if (zoneStatus & BATTERY_BIT) {
|
||||
if (zs.isBatterySet()) {
|
||||
resultMap_battery_state.value = "low"
|
||||
|
||||
// to generate low battery notification by the platform
|
||||
@@ -269,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
|
||||
resultListMap << resultMap_sensor
|
||||
|
||||
@@ -91,7 +91,7 @@ def parse(String description) {
|
||||
|
||||
if (descMap.cluster == "0300") {
|
||||
if(descMap.attrId == "0000"){ //Hue Attribute
|
||||
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360)
|
||||
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
|
||||
log.debug "Hue value returned is $hueValue"
|
||||
sendEvent(name: "hue", value: hueValue, displayed:false)
|
||||
}
|
||||
@@ -203,7 +203,7 @@ def setLevel(value) {
|
||||
|
||||
//input Hue Integer values; returns color name for saturation 100%
|
||||
private getColorName(hueValue){
|
||||
if(hueValue>360 || hueValue<0)
|
||||
if(hueValue>100 || hueValue<0)
|
||||
return
|
||||
|
||||
hueValue = Math.round(hueValue / 100 * 360)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/*
|
||||
/*
|
||||
Osram Flex RGBW Light Strip
|
||||
|
||||
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
|
||||
that issue by using state variables
|
||||
*/
|
||||
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
|
||||
|
||||
metadata {
|
||||
definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
|
||||
capability "Color Temperature"
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
@@ -17,14 +18,14 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Color Control"
|
||||
|
||||
|
||||
attribute "colorName", "string"
|
||||
|
||||
command "setAdjustedColor"
|
||||
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW"
|
||||
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW"
|
||||
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW"
|
||||
|
||||
}
|
||||
|
||||
@@ -48,7 +49,7 @@ metadata {
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
@@ -117,7 +118,7 @@ def parse(String description) {
|
||||
}
|
||||
}
|
||||
else if(descMap.attrId == "0000"){ //Hue Attribute
|
||||
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360)
|
||||
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
|
||||
log.debug "Hue value returned is $hueValue"
|
||||
sendEvent(name: "hue", value: hueValue, displayed:false)
|
||||
}
|
||||
@@ -273,7 +274,7 @@ private getGenericName(value){
|
||||
|
||||
//input Hue Integer values; returns color name for saturation 100%
|
||||
private getColorName(hueValue){
|
||||
if(hueValue>360 || hueValue<0)
|
||||
if(hueValue>100 || hueValue<0)
|
||||
return
|
||||
|
||||
hueValue = Math.round(hueValue / 100 * 360)
|
||||
@@ -448,7 +449,7 @@ def setColor(value){
|
||||
def level = hex(value.level * 255 / 100)
|
||||
cmd << zigbeeSetLevel(level)
|
||||
}
|
||||
|
||||
|
||||
if (value.switch == "off") {
|
||||
cmd << "delay 150"
|
||||
cmd << off()
|
||||
|
||||
@@ -19,11 +19,6 @@ metadata {
|
||||
capability "Sensor"
|
||||
|
||||
attribute "colorName", "string"
|
||||
|
||||
// indicates that device keeps track of heartbeat (in state.heartbeat)
|
||||
attribute "heartbeat", "string"
|
||||
|
||||
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -75,9 +70,6 @@ metadata {
|
||||
def parse(String description) {
|
||||
//log.trace description
|
||||
|
||||
// save heartbeat (i.e. last time we got a message from device)
|
||||
state.heartbeat = Calendar.getInstance().getTimeInMillis()
|
||||
|
||||
if (description?.startsWith("catchall:")) {
|
||||
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
|
||||
{
|
||||
@@ -132,7 +124,6 @@ def off() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
sendEvent(name: "heartbeat", value: "alive", displayed:false)
|
||||
[
|
||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
* Samsung TV
|
||||
*
|
||||
* Author: SmartThings (juano23@gmail.com)
|
||||
* Date: 2015-01-08
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Samsung Smart TV", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "switch"
|
||||
|
||||
command "mute"
|
||||
command "source"
|
||||
command "menu"
|
||||
command "tools"
|
||||
command "HDMI"
|
||||
command "Sleep"
|
||||
command "Up"
|
||||
command "Down"
|
||||
command "Left"
|
||||
command "Right"
|
||||
command "chup"
|
||||
command "chdown"
|
||||
command "prech"
|
||||
command "volup"
|
||||
command "voldown"
|
||||
command "Enter"
|
||||
command "Return"
|
||||
command "Exit"
|
||||
command "Info"
|
||||
command "Size"
|
||||
}
|
||||
|
||||
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
||||
state "default", label:'TV', action:"switch.off", icon:"st.Electronics.electronics15", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("power", "device.switch", width: 1, height: 1, canChangeIcon: false) {
|
||||
state "default", label:'', action:"switch.off", decoration: "flat", icon:"st.thermostat.heating-cooling-off", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("mute", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Mute', action:"mute", icon:"st.custom.sonos.muted", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("source", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Source', action:"source", icon:"st.Electronics.electronics15"
|
||||
}
|
||||
standardTile("tools", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Tools', action:"tools", icon:"st.secondary.tools"
|
||||
}
|
||||
standardTile("menu", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Menu', action:"menu", icon:"st.vents.vent"
|
||||
}
|
||||
standardTile("HDMI", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Source', action:"HDMI", icon:"st.Electronics.electronics15"
|
||||
}
|
||||
standardTile("Sleep", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Sleep', action:"Sleep", icon:"st.Bedroom.bedroom10"
|
||||
}
|
||||
standardTile("Up", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Up', action:"Up", icon:"st.thermostat.thermostat-up"
|
||||
}
|
||||
standardTile("Down", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Down', action:"Down", icon:"st.thermostat.thermostat-down"
|
||||
}
|
||||
standardTile("Left", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Left', action:"Left", icon:"st.thermostat.thermostat-left"
|
||||
}
|
||||
standardTile("Right", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Right', action:"Right", icon:"st.thermostat.thermostat-right"
|
||||
}
|
||||
standardTile("chup", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'CH Up', action:"chup", icon:"st.thermostat.thermostat-up"
|
||||
}
|
||||
standardTile("chdown", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'CH Down', action:"chdown", icon:"st.thermostat.thermostat-down"
|
||||
}
|
||||
standardTile("prech", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Pre CH', action:"prech", icon:"st.secondary.refresh-icon"
|
||||
}
|
||||
standardTile("volup", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Vol Up', action:"volup", icon:"st.thermostat.thermostat-up"
|
||||
}
|
||||
standardTile("voldown", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Vol Down', action:"voldown", icon:"st.thermostat.thermostat-down"
|
||||
}
|
||||
standardTile("Enter", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Enter', action:"Enter", icon:"st.illuminance.illuminance.dark"
|
||||
}
|
||||
standardTile("Return", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Return', action:"Return", icon:"st.secondary.refresh-icon"
|
||||
}
|
||||
standardTile("Exit", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Exit', action:"Exit", icon:"st.locks.lock.unlocked"
|
||||
}
|
||||
standardTile("Info", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Info', action:"Info", icon:"st.motion.acceleration.active"
|
||||
}
|
||||
standardTile("Size", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Picture Size', action:"Size", icon:"st.contact.contact.open"
|
||||
}
|
||||
main "switch"
|
||||
details (["power","HDMI","Sleep","chup","prech","volup","chdown","mute","voldown", "menu", "Up", "tools", "Left", "Enter", "Right", "Return", "Down", "Exit", "Info","Size"])
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
return null
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "Turning TV OFF"
|
||||
parent.tvAction("POWEROFF",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Power Off", displayed: true)
|
||||
}
|
||||
|
||||
def mute() {
|
||||
log.trace "MUTE pressed"
|
||||
parent.tvAction("MUTE",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Mute", displayed: true)
|
||||
}
|
||||
|
||||
def source() {
|
||||
log.debug "SOURCE pressed"
|
||||
parent.tvAction("SOURCE",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Source", displayed: true)
|
||||
}
|
||||
|
||||
def menu() {
|
||||
log.debug "MENU pressed"
|
||||
parent.tvAction("MENU",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def tools() {
|
||||
log.debug "TOOLS pressed"
|
||||
parent.tvAction("TOOLS",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Tools", displayed: true)
|
||||
}
|
||||
|
||||
def HDMI() {
|
||||
log.debug "HDMI pressed"
|
||||
parent.tvAction("HDMI",device.deviceNetworkId)
|
||||
sendEvent(name:"Command sent", value: "Source", displayed: true)
|
||||
}
|
||||
|
||||
def Sleep() {
|
||||
log.debug "SLEEP pressed"
|
||||
parent.tvAction("SLEEP",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Sleep", displayed: true)
|
||||
}
|
||||
|
||||
def Up() {
|
||||
log.debug "UP pressed"
|
||||
parent.tvAction("UP",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def Down() {
|
||||
log.debug "DOWN pressed"
|
||||
parent.tvAction("DOWN",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def Left() {
|
||||
log.debug "LEFT pressed"
|
||||
parent.tvAction("LEFT",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def Right() {
|
||||
log.debug "RIGHT pressed"
|
||||
parent.tvAction("RIGHT",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def chup() {
|
||||
log.debug "CHUP pressed"
|
||||
parent.tvAction("CHUP",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Channel Up", displayed: true)
|
||||
}
|
||||
|
||||
def chdown() {
|
||||
log.debug "CHDOWN pressed"
|
||||
parent.tvAction("CHDOWN",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Channel Down", displayed: true)
|
||||
}
|
||||
|
||||
def prech() {
|
||||
log.debug "PRECH pressed"
|
||||
parent.tvAction("PRECH",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Prev Channel", displayed: true)
|
||||
}
|
||||
|
||||
def Exit() {
|
||||
log.debug "EXIT pressed"
|
||||
parent.tvAction("EXIT",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def volup() {
|
||||
log.debug "VOLUP pressed"
|
||||
parent.tvAction("VOLUP",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Volume Up", displayed: true)
|
||||
}
|
||||
|
||||
def voldown() {
|
||||
log.debug "VOLDOWN pressed"
|
||||
parent.tvAction("VOLDOWN",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Volume Down", displayed: true)
|
||||
}
|
||||
|
||||
def Enter() {
|
||||
log.debug "ENTER pressed"
|
||||
parent.tvAction("ENTER",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def Return() {
|
||||
log.debug "RETURN pressed"
|
||||
parent.tvAction("RETURN",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def Info() {
|
||||
log.debug "INFO pressed"
|
||||
parent.tvAction("INFO",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Info", displayed: true)
|
||||
}
|
||||
|
||||
def Size() {
|
||||
log.debug "PICTURE_SIZE pressed"
|
||||
parent.tvAction("PICTURE_SIZE",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Picture Size", displayed: true)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0006", outClusters: "0019"
|
||||
fingerprint profileId: "0104", inClusters: "0006, 0004, 0003, 0000, 0005", outClusters: "0019", manufacturer: "Compacta International, Ltd", model: "ZBMPlug15", deviceJoinName: "SmartPower Outlet V1"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -47,9 +47,21 @@ def parse(String description) {
|
||||
|
||||
// Commands to device
|
||||
def on() {
|
||||
'zcl on-off on'
|
||||
[
|
||||
'zcl on-off on',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 500'
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
def off() {
|
||||
'zcl on-off off'
|
||||
[
|
||||
'zcl on-off off',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 500'
|
||||
]
|
||||
}
|
||||
|
||||
2
devicetypes/smartthings/smartpower-outlet.src/.st-ignore
Normal file
2
devicetypes/smartthings/smartpower-outlet.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
38
devicetypes/smartthings/smartpower-outlet.src/README.md
Normal file
38
devicetypes/smartthings/smartpower-outlet.src/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# SmartPower Outlet
|
||||
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
* [Samsung SmartPower Outlet](https://shop.smartthings.com/#!/products/smartpower-outlet)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||
* **Actuator** - represents that a Device has commands
|
||||
* **Switch** - can detect state (possible values: on/off)
|
||||
* **Refresh** - _refresh()_ command for status updates
|
||||
* **Power Meter** - detects power meter for device in either w or kw.
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
* **Sensor** - detects sensor events
|
||||
|
||||
## Device Health
|
||||
|
||||
A Category C1 smart power outlet with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||
Pairing needs to be tried again by placing the device closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following links
|
||||
for the different models:
|
||||
* [SmartPower Outlet Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/201084854-SmartPower-Outlet)
|
||||
* [Samsung SmartThings Outlet Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957620)
|
||||
@@ -0,0 +1,26 @@
|
||||
# Copyright 2016 SmartThings
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Give your device a name'''.ko=기기 이름 설정
|
||||
'''Outlet'''.ko= 스마트 플러그
|
||||
# Events descriptionText
|
||||
'''{{ device.displayName }} is On'''.ko={{ device.displayName }} 켜짐
|
||||
'''{{ device.displayName }} is Off'''.ko={{ device.displayName }} 꺼짐
|
||||
'''{{ device.displayName }} power is {{ value }} Watts'''.ko={{ device.displayName }} 전력은 {{ value }}와트입니다.
|
||||
'''On'''.ko= 켜짐
|
||||
'''Off'''.ko=꺼짐
|
||||
'''Turning On'''.ko=켜는 중
|
||||
'''Turning Off'''.ko=끄는 중
|
||||
#==============================================================================
|
||||
@@ -1,32 +1,29 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
/*
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
* 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.
|
||||
*
|
||||
* SmartPower Outlet (CentraLite)
|
||||
*
|
||||
* Author: SmartThings
|
||||
* Date: 2015-08-23
|
||||
* 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 {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings", category: "C1") {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Power Meter"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
// indicates that device keeps track of heartbeat (in state.heartbeat)
|
||||
attribute "heartbeat", "string"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet"
|
||||
@@ -58,10 +55,10 @@ metadata {
|
||||
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"
|
||||
attributeState "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
||||
attributeState "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
||||
attributeState "turningOn", label: 'Turning On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
||||
attributeState "turningOff", label: 'Turning Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
||||
}
|
||||
tileAttribute ("power", key: "SECONDARY_CONTROL") {
|
||||
attributeState "power", label:'${currentValue} W'
|
||||
@@ -81,9 +78,6 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
// save heartbeat (i.e. last time we got a message from device)
|
||||
state.heartbeat = Calendar.getInstance().getTimeInMillis()
|
||||
|
||||
def finalResult = zigbee.getKnownDescription(description)
|
||||
|
||||
//TODO: Remove this after getKnownDescription can parse it automatically
|
||||
@@ -91,22 +85,22 @@ def parse(String description) {
|
||||
finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description))
|
||||
|
||||
if (finalResult) {
|
||||
log.info finalResult
|
||||
log.info "final result = $finalResult"
|
||||
if (finalResult.type == "update") {
|
||||
log.info "$device updates: ${finalResult.value}"
|
||||
}
|
||||
else if (finalResult.type == "power") {
|
||||
def powerValue = (finalResult.value as Integer)/10
|
||||
sendEvent(name: "power", value: powerValue)
|
||||
sendEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true )
|
||||
/*
|
||||
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
|
||||
|
||||
power level is an integer. The exact power level with correct units needs to be handled in the device type
|
||||
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
|
||||
*/
|
||||
}
|
||||
else {
|
||||
sendEvent(name: finalResult.type, value: finalResult.value)
|
||||
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
|
||||
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -122,14 +116,22 @@ def off() {
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
return zigbee.onOffRefresh()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
sendEvent(name: "heartbeat", value: "alive", displayed:false)
|
||||
zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
|
||||
zigbee.onOffRefresh() + zigbee.electricMeasurementPowerRefresh()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
zigbee.onOffConfig() + powerConfig() + refresh()
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
zigbee.onOffConfig(0, 300) + powerConfig() + refresh()
|
||||
}
|
||||
|
||||
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -0,0 +1,44 @@
|
||||
# Smartsense Moisture Sensor
|
||||
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
* [Samsung SmartThings Moisture Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-water-leak-sensor)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Battery](#battery-specification)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||
* **Battery** - defines device uses a battery
|
||||
* **Refresh** - _refresh()_ command for status updates
|
||||
* **Temperature Measurement** - defines device measures current temperature
|
||||
* **Water Sensor** - can detect presence of water (dry or wet)
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
## Device Health
|
||||
|
||||
A Category C2 moisture sensor with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Battery Specification
|
||||
|
||||
One CR2 3V battery required.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the different sensors from SmartThings can be found in the following links
|
||||
for the different models:
|
||||
* [SmartSense Moisture Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202847044-SmartSense-Moisture-Sensor)
|
||||
* [Samsung SmartThings Water Leak Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957630)
|
||||
Other troubleshooting tips are listed as follows:
|
||||
* [Troubleshooting: Samsung SmartThings Water Leak Sensor won’t pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)
|
||||
@@ -0,0 +1,35 @@
|
||||
# Copyright 2016 SmartThings
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Dry'''.ko=건조
|
||||
'''Wet'''.ko=누수
|
||||
'''dry'''.ko=건조
|
||||
'''wet'''.ko=누수
|
||||
'''battery'''.ko=배터리
|
||||
'''Temperature Offset'''.ko=온도 직접 설정
|
||||
'''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'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
||||
'''Degrees'''.ko=온도
|
||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||
'''Give your device a name'''.ko=기기 이름 설정
|
||||
'''Water Leak Sensor'''.ko=누수감지 센서
|
||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
||||
# Events descriptionText
|
||||
'''{{ device.displayName }} is dry'''.ko={{ device.displayName }}가 건조
|
||||
'''{{ device.displayName }} is wet'''.ko={{ device.displayName }}누수
|
||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}에서 {{ value }}°C 감지
|
||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
||||
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
|
||||
#==============================================================================
|
||||
@@ -1,27 +1,32 @@
|
||||
/**
|
||||
* SmartSense Moisture Sensor
|
||||
/*
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Copyright 2014 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
* 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.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
capability "Water Sensor"
|
||||
|
||||
command "enrollResponse"
|
||||
capability "Health Check"
|
||||
capability "Sensor"
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
|
||||
@@ -29,9 +34,9 @@ metadata {
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "moisturev4", deviceJoinName: "Water Leak Sensor"
|
||||
}
|
||||
|
||||
|
||||
simulator {
|
||||
|
||||
|
||||
}
|
||||
|
||||
preferences {
|
||||
@@ -43,11 +48,11 @@ metadata {
|
||||
])
|
||||
}
|
||||
section {
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"water", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.water", key: "PRIMARY_CONTROL") {
|
||||
@@ -78,7 +83,7 @@ metadata {
|
||||
details(["water", "temperature", "battery", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
@@ -92,29 +97,29 @@ def parse(String description) {
|
||||
else if (description?.startsWith('temperature: ')) {
|
||||
map = parseCustomMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
return result
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
|
||||
case 0x0402:
|
||||
// temp is last 2 data values. reverse to swap endian
|
||||
@@ -129,22 +134,22 @@ private Map parseCatchAllMessage(String description) {
|
||||
}
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
log.debug "Desc Map: $descMap"
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
@@ -153,10 +158,10 @@ private Map parseReportAttributeMessage(String description) {
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
@@ -167,72 +172,66 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getMoistureResult('dry')
|
||||
break
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
|
||||
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
|
||||
return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return celsius
|
||||
return Math.round(celsius)
|
||||
} else {
|
||||
return celsiusToFahrenheit(celsius) as Integer
|
||||
return Math.round(celsiusToFahrenheit(celsius))
|
||||
}
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug 'Battery'
|
||||
log.debug "Battery rawValue = ${rawValue}"
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [
|
||||
name: 'battery'
|
||||
]
|
||||
|
||||
|
||||
def result = [
|
||||
name: 'battery',
|
||||
value: '--',
|
||||
translatable: true
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
||||
}
|
||||
else {
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
volts = rawValue // For the batteryMap to work the key needs to be an int
|
||||
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
|
||||
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
|
||||
def minVolts = 15
|
||||
def maxVolts = 28
|
||||
|
||||
if (volts < minVolts)
|
||||
volts = minVolts
|
||||
else if (volts > maxVolts)
|
||||
volts = maxVolts
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
result.value = pct
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
if (roundedPct <= 0)
|
||||
roundedPct = 1
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -240,34 +239,52 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug 'TEMP'
|
||||
def linkText = getLinkText(device)
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||
def descriptionText
|
||||
if ( temperatureScale == 'C' )
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°C'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
descriptionText: descriptionText,
|
||||
translatable: true,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
private Map getMoistureResult(value) {
|
||||
log.debug 'water'
|
||||
String descriptionText = "${device.displayName} is ${value}"
|
||||
log.debug "water"
|
||||
def descriptionText
|
||||
if ( value == "wet" )
|
||||
descriptionText = '{{ device.displayName }} is wet'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} is dry'
|
||||
return [
|
||||
name: 'water',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Temperature and Battery"
|
||||
def refreshCmds = [
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
|
||||
]
|
||||
|
||||
@@ -275,34 +292,32 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
def configCmds = [
|
||||
def enrollCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||
]
|
||||
return configCmds + refresh() // send refresh cmds as part of config
|
||||
|
||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def enrollResponse() {
|
||||
log.debug "Sending enroll response"
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
[
|
||||
[
|
||||
//Resending the CIE in case the enroll request is sent before CIE is written
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
//Enroll Response
|
||||
"raw 0x500 {01 23 00 00 00}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
private getEndpointId() {
|
||||
@@ -314,19 +329,19 @@ private hex(value) {
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ metadata {
|
||||
capability "Water Sensor"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
fingerprint deviceId: "0x2001", inClusters: "0x30,0x9C,0x9D,0x85,0x80,0x72,0x31,0x84,0x86"
|
||||
fingerprint deviceId: "0x2101", inClusters: "0x71,0x70,0x85,0x80,0x72,0x31,0x84,0x86"
|
||||
@@ -39,17 +40,29 @@ metadata {
|
||||
attributeState "wet", label: "Wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
|
||||
}
|
||||
}
|
||||
standardTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
standardTile("temperatureState", "device.temperature", width: 2, height: 2) {
|
||||
state "normal", icon:"st.alarm.temperature.normal", backgroundColor:"#ffffff"
|
||||
state "freezing", icon:"st.alarm.temperature.freeze", backgroundColor:"#53a7c0"
|
||||
state "overheated", icon:"st.alarm.temperature.overheat", backgroundColor:"#F80000"
|
||||
}
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
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", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main (["water", "temperature"])
|
||||
details(["water", "temperature", "battery"])
|
||||
main (["water", "temperatureState"])
|
||||
details(["water", "temperatureState", "temperature", "battery"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +128,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
|
||||
map.descriptionText = "${device.displayName} is ${map.value}"
|
||||
}
|
||||
if(cmd.zwaveAlarmType == physicalgraph.zwave.commands.alarmv2.AlarmReport.ZWAVE_ALARM_TYPE_HEAT) {
|
||||
map.name = "temperature"
|
||||
map.name = "temperatureState"
|
||||
if(cmd.zwaveAlarmEvent == 1) { map.value = "overheated"}
|
||||
if(cmd.zwaveAlarmEvent == 2) { map.value = "overheated"}
|
||||
if(cmd.zwaveAlarmEvent == 3) { map.value = "changing temperature rapidly"}
|
||||
@@ -129,17 +142,30 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
|
||||
map
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd)
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd)
|
||||
{
|
||||
def map = [:]
|
||||
map.name = "water"
|
||||
map.value = cmd.value ? "wet" : "dry"
|
||||
map.descriptionText = "${device.displayName} is ${map.value}"
|
||||
if(cmd.sensorType == 1) {
|
||||
map.name = "temperature"
|
||||
if(cmd.scale == 0) {
|
||||
map.value = getTemperature(cmd.scaledSensorValue)
|
||||
} else {
|
||||
map.value = cmd.scaledSensorValue
|
||||
}
|
||||
map.unit = location.temperatureScale
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
if(location.temperatureScale == "C"){
|
||||
return value
|
||||
} else {
|
||||
return Math.round(celsiusToFahrenheit(value))
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd)
|
||||
{
|
||||
log.debug "COMMAND CLASS: $cmd"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -0,0 +1,45 @@
|
||||
# Smartsense Motion Sensor
|
||||
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
* [Samsung SmartThings Motion Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-motion-sensor)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Battery](#battery-specification)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||
* **Motion Sensor** - can detect motion
|
||||
* **Battery** - defines device uses a battery
|
||||
* **Refresh** - _refresh()_ command for status updates
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
## Device Health
|
||||
|
||||
A Category C2 motion sensor with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Battery Specification
|
||||
|
||||
One CR2477 (for Samsung SmartThings Motion Sensor) / CR123A (SmartSense Motion Sensor) 3V battery is required.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the different motion sensors from SmartThings can be found in the following links
|
||||
for the different models:
|
||||
* [SmartSense Motion Sensor (original model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903280-SmartSense-Motion-Sensor-original-model-)
|
||||
* [SmartSense Motion Sensor (2014 model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/203077520-SmartSense-Motion-Sensor-2014-model-)
|
||||
* [Samsung SmartThings Motion Sensor (2015 model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957580-Samsung-SmartThings-Motion-Sensor-2015-model-)
|
||||
Other troubleshooting tips are listed as follows:
|
||||
* [Troubleshooting: Samsung SmartThings Motion Sensor is stuck showing "Motion Detected" or "No Motion"](https://support.smartthings.com/hc/en-us/articles/200961130-Troubleshooting-Samsung-SmartThings-Motion-Sensor-is-stuck-showing-Motion-Detected-or-No-Motion-)
|
||||
* [Troubleshooting: Samsung SmartThings Motion Sensor won’t pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)
|
||||
@@ -0,0 +1,33 @@
|
||||
# Copyright 2016 SmartThings
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''battery'''.ko=배터리
|
||||
'''Temperature Offset'''.ko=온도 직접 설정
|
||||
'''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'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
||||
'''Degrees'''.ko=온도
|
||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||
'''Give your device a name'''.ko=기기 이름 설정
|
||||
'''Motion Sensor'''.ko=모션 센서
|
||||
'''motion'''.ko= 동작 감지
|
||||
'''no motion'''.ko=동작 없음
|
||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
||||
# Events descriptionText
|
||||
'''{{ device.displayName }} detected motion'''.ko={{ device.displayName }}에서 움직임이 감지되었습니다.
|
||||
'''{{ device.displayName }} motion has stopped'''.ko={{ device.displayName }}에서 움직임이 중단되었습니다.
|
||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}에서 {{ value }}°C 감지
|
||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
||||
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
|
||||
#==============================================================================
|
||||
@@ -1,35 +1,40 @@
|
||||
/**
|
||||
* SmartSense Motion/Temp Sensor
|
||||
/*
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Copyright 2014 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
* 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.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
capability "Motion Sensor"
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Temperature Measurement"
|
||||
capability "Temperature Measurement"
|
||||
capability "Refresh"
|
||||
|
||||
command "enrollResponse"
|
||||
capability "Health Check"
|
||||
capability "Sensor"
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305-S"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325-S", deviceJoinName: "Motion Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325-S", deviceJoinName: "Motion Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326-L", deviceJoinName: "Iris Motion Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -46,7 +51,7 @@ metadata {
|
||||
])
|
||||
}
|
||||
section {
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input 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
|
||||
}
|
||||
}
|
||||
@@ -85,7 +90,7 @@ metadata {
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
@@ -96,55 +101,55 @@ def parse(String description) {
|
||||
else if (description?.startsWith('temperature: ')) {
|
||||
map = parseCustomMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
return result
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
|
||||
case 0x0402:
|
||||
// temp is last 2 data values. reverse to swap endian
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
break
|
||||
case 0x0402:
|
||||
// temp is last 2 data values. reverse to swap endian
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
break
|
||||
|
||||
case 0x0406:
|
||||
log.debug 'motion'
|
||||
resultMap.name = 'motion'
|
||||
break
|
||||
}
|
||||
}
|
||||
log.debug 'motion'
|
||||
resultMap.name = 'motion'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
@@ -153,7 +158,7 @@ private Map parseReportAttributeMessage(String description) {
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
log.debug "Desc Map: $descMap"
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
@@ -162,14 +167,14 @@ private Map parseReportAttributeMessage(String description) {
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
|
||||
def value = descMap.value.endsWith("01") ? "active" : "inactive"
|
||||
resultMap = getMotionResult(value)
|
||||
}
|
||||
|
||||
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
|
||||
def value = descMap.value.endsWith("01") ? "active" : "inactive"
|
||||
resultMap = getMotionResult(value)
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
@@ -180,80 +185,67 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getMotionResult('inactive')
|
||||
break
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
|
||||
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
|
||||
// Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion
|
||||
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return celsius
|
||||
return Math.round(celsius)
|
||||
} else {
|
||||
return celsiusToFahrenheit(celsius) as Integer
|
||||
return Math.round(celsiusToFahrenheit(celsius))
|
||||
}
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug 'Battery'
|
||||
log.debug "Battery rawValue = ${rawValue}"
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
log.debug rawValue
|
||||
|
||||
def result = [
|
||||
name: 'battery',
|
||||
value: '--'
|
||||
value: '--',
|
||||
translatable: true
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
|
||||
if (rawValue == 0) {}
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
||||
}
|
||||
else if (volts > 0){
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
else {
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
volts = rawValue // For the batteryMap to work the key needs to be an int
|
||||
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
|
||||
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
|
||||
def minVolts = 15
|
||||
def maxVolts = 28
|
||||
|
||||
if (volts < minVolts)
|
||||
volts = minVolts
|
||||
else if (volts > maxVolts)
|
||||
volts = maxVolts
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
result.value = pct
|
||||
def value = pct
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
if (roundedPct <= 0)
|
||||
roundedPct = 1
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,31 +254,44 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug 'TEMP'
|
||||
def linkText = getLinkText(device)
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||
def descriptionText
|
||||
if ( temperatureScale == 'C' )
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°C'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
descriptionText: descriptionText,
|
||||
translatable: true,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
private Map getMotionResult(value) {
|
||||
log.debug 'motion'
|
||||
String linkText = getLinkText(device)
|
||||
String descriptionText = value == 'active' ? "${linkText} detected motion" : "${linkText} motion has stopped"
|
||||
String descriptionText = value == 'active' ? "{{ device.displayName }} detected motion" : "{{ device.displayName }} motion has stopped"
|
||||
return [
|
||||
name: 'motion',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh called"
|
||||
def refreshCmds = [
|
||||
@@ -298,22 +303,19 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
def configCmds = [
|
||||
def enrollCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
return configCmds + refresh() // send refresh cmds as part of config
|
||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def enrollResponse() {
|
||||
@@ -338,19 +340,19 @@ private hex(value) {
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
//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 {
|
||||
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Motion Sensor"
|
||||
@@ -22,13 +25,9 @@ metadata {
|
||||
capability "Temperature Measurement"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305-S"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -75,7 +74,7 @@ metadata {
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
@@ -89,10 +88,10 @@ def parse(String description) {
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
@@ -130,7 +129,7 @@ private Map parseCatchAllMessage(String description) {
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
@@ -143,7 +142,7 @@ private Map parseReportAttributeMessage(String description) {
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
log.debug "Desc Map: $descMap"
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
@@ -155,11 +154,11 @@ private Map parseReportAttributeMessage(String description) {
|
||||
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
|
||||
def value = descMap.value.endsWith("01") ? "active" : "inactive"
|
||||
resultMap = getMotionResult(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
@@ -170,44 +169,8 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
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
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
@@ -233,7 +196,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
|
||||
if (rawValue == 0) {}
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
@@ -242,7 +205,10 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
if (roundedPct <= 0)
|
||||
roundedPct = 1
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
@@ -262,7 +228,8 @@ private Map getTemperatureResult(value) {
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
descriptionText: descriptionText,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -0,0 +1,45 @@
|
||||
# Smartsense Multi Sensor
|
||||
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
* [Samsung SmartThings Multi Sensor](https://shop.smartthings.com/#!/products/smartsense-multi)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Battery](#battery-specification)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Three Axis** - monitors the state of a single axis
|
||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||
* **Battery** - defines device uses a battery
|
||||
* **Sensor** - detects sensor events
|
||||
* **Contact Sensor** - can detect contact (possible values: open,closed)
|
||||
* **Acceleration Sensor** - allows for acceleration detection.
|
||||
* **Refresh** - _refresh()_ command for status updates
|
||||
* **Temperature Measurement** - defines device measures current temperature
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
## Device Health
|
||||
|
||||
A Category C2 multi sensor with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Battery Specification
|
||||
|
||||
One CR2450 (for Samsung SmartThings Multipurpose Sensor) battery / Two AAAA (for SmartSense Multi Sensor) batteries required.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
||||
Other troubleshooting tips are listed as follows:
|
||||
* [Troubleshooting: Samsung SmartThings Multipurpose Sensor is stuck on "open" or "closed"](https://support.smartthings.com/hc/en-us/articles/200955940-Troubleshooting-Samsung-SmartThings-Multipurpose-Sensor-is-stuck-on-open-or-closed-)
|
||||
* [Troubleshooting: Temperature reading for the Samsung SmartThings Multipurpose Sensor is off](https://support.smartthings.com/hc/en-us/articles/200756845-Troubleshooting-Temperature-reading-for-the-Samsung-SmartThings-Multipurpose-Sensor-is-off)
|
||||
* [Troubleshooting: Samsung SmartThings Multipurpose Sensor won’t pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)
|
||||
@@ -0,0 +1,41 @@
|
||||
# Copyright 2016 SmartThings
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Yes'''.ko=예
|
||||
'''No'''.ko=아니요
|
||||
'''battery'''.ko=배터리
|
||||
'''Temperature Offset'''.ko=온도 직접 설정
|
||||
'''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'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
||||
'''Degrees'''.ko=온도
|
||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||
'''Do you want to use this sensor on a garage door?'''.ko=차고 문의 센서 사용 설정하기
|
||||
'''Tap to set'''.ko=눌러서 설정
|
||||
'''Give your device a name'''.ko=기기 이름 설정
|
||||
'''Multipurpose Sensor'''.ko=문 및 창 센서
|
||||
# Events descriptionText
|
||||
'''{{ device.displayName }} was opened'''.ko={{ device.displayName }}에서 열림이 감지되었습니다.
|
||||
'''{{ device.displayName }} was closed'''.ko={{ device.displayName }}에서 닫힘이 감지되었습니다.
|
||||
'''{{ device.displayName }} was active'''.ko={{ device.displayName }} 활성화
|
||||
'''{{ device.displayName }} was inactive'''.ko={{ device.displayName }} 비활성화
|
||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}에서 {{ value }}°C 감지
|
||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
|
||||
'''Updating device to garage sensor'''.ko=기기-차고 센서 업데이트 중
|
||||
'''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중
|
||||
'''Inactive'''.ko=비활성 상태
|
||||
'''Active'''.ko=활성 상태
|
||||
'''Open'''.ko= 열림이 감지될 때
|
||||
'''Closed'''.ko=닫힘
|
||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
||||
@@ -1,41 +1,43 @@
|
||||
/**
|
||||
* SmartSense Multi
|
||||
/*
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
* 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.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Three Axis"
|
||||
metadata {
|
||||
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
|
||||
capability "Three Axis"
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Contact Sensor"
|
||||
capability "Acceleration Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
capability "Health Check"
|
||||
|
||||
command "enrollResponse"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor"
|
||||
|
||||
attribute "status", "string"
|
||||
}
|
||||
}
|
||||
|
||||
simulator {
|
||||
simulator {
|
||||
status "open": "zone report :: type: 19 value: 0031"
|
||||
status "closed": "zone report :: type: 19 value: 0030"
|
||||
|
||||
@@ -52,7 +54,7 @@
|
||||
status "x,y,z: 0,1000,0": "x: 0, y: 1000, z: 0"
|
||||
status "x,y,z: 0,0,1000": "x: 0, y: 0, z: 1000"
|
||||
}
|
||||
preferences {
|
||||
preferences {
|
||||
section {
|
||||
image(name: 'educationalcontent', multiple: true, images: [
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi1.jpg",
|
||||
@@ -62,30 +64,30 @@
|
||||
])
|
||||
}
|
||||
section {
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
section {
|
||||
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
|
||||
input 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
|
||||
}
|
||||
}
|
||||
section {
|
||||
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", description: "Tap to set", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
|
||||
}
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.status", 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"
|
||||
attributeState "open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||
attributeState "closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
||||
attributeState "garage-open", label:'Open', icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e"
|
||||
attributeState "garage-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821"
|
||||
}
|
||||
}
|
||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
state("open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
}
|
||||
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
|
||||
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
||||
state("inactive", label:'${name}', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
||||
state("active", label:'Active', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
||||
state("inactive", label:'Inactive', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
||||
}
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state("temperature", label:'${currentValue}°',
|
||||
@@ -100,19 +102,16 @@
|
||||
]
|
||||
)
|
||||
}
|
||||
valueTile("3axis", "device.threeAxis", decoration: "flat", wordWrap: false, width: 2, height: 2) {
|
||||
state("threeAxis", label:'${currentValue}', unit:"", backgroundColor:"#ffffff")
|
||||
}
|
||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
|
||||
main(["status", "acceleration", "temperature"])
|
||||
details(["status", "acceleration", "temperature", "3axis", "battery", "refresh"])
|
||||
details(["status", "acceleration", "temperature", "battery", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,61 +120,61 @@ def parse(String description) {
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('temperature: ')) {
|
||||
else if (description?.startsWith('temperature: ')) {
|
||||
map = parseCustomMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
def result = map ? createEvent(map) : null
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
else if (description?.startsWith('read attr -')) {
|
||||
result = parseReportAttributeMessage(description).each { createEvent(it) }
|
||||
}
|
||||
else if (description?.startsWith('read attr -')) {
|
||||
result = parseReportAttributeMessage(description).each { createEvent(it) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
log.debug cluster
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
log.debug cluster
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
|
||||
case 0xFC02:
|
||||
log.debug 'ACCELERATION'
|
||||
break
|
||||
case 0xFC02:
|
||||
log.debug 'ACCELERATION'
|
||||
break
|
||||
|
||||
case 0x0402:
|
||||
log.debug 'TEMP'
|
||||
// temp is last 2 data values. reverse to swap endian
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
break
|
||||
}
|
||||
}
|
||||
case 0x0402:
|
||||
log.debug 'TEMP'
|
||||
// temp is last 2 data values. reverse to swap endian
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
|
||||
private List parseReportAttributeMessage(String description) {
|
||||
@@ -202,7 +201,7 @@ private List parseReportAttributeMessage(String description) {
|
||||
result << parseAxis(threeAxisAttributes)
|
||||
descMap.value = descMap.value[-2..-1]
|
||||
}
|
||||
result << getAccelerationResult(descMap.value)
|
||||
result << getAccelerationResult(descMap.value)
|
||||
}
|
||||
else if (descMap.cluster == "FC02" && descMap.attrId == "0012" && descMap.value.size() == 24) {
|
||||
// The size is checked to ensure the attribute report contains X, Y and Z values
|
||||
@@ -226,48 +225,14 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
def updated() {
|
||||
@@ -276,19 +241,19 @@ def updated() {
|
||||
if (garageSensor == "Yes") {
|
||||
def descriptionText = "Updating device to garage sensor"
|
||||
if (device.latestValue("status") == "open") {
|
||||
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText)
|
||||
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
else if (device.latestValue("status") == "closed") {
|
||||
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText)
|
||||
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
else {
|
||||
def descriptionText = "Updating device to open/close sensor"
|
||||
if (device.latestValue("status") == "garage-open") {
|
||||
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText)
|
||||
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
else if (device.latestValue("status") == "garage-closed") {
|
||||
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText)
|
||||
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,169 +261,162 @@ def updated() {
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
return celsius
|
||||
return Math.round(celsius)
|
||||
} else {
|
||||
return celsiusToFahrenheit(celsius) as Integer
|
||||
return Math.round(celsiusToFahrenheit(celsius))
|
||||
}
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug "Battery"
|
||||
log.debug rawValue
|
||||
def linkText = getLinkText(device)
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug "Battery rawValue = ${rawValue}"
|
||||
|
||||
def result = [
|
||||
def result = [
|
||||
name: 'battery',
|
||||
value: '--'
|
||||
]
|
||||
value: '--',
|
||||
translatable: true
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
|
||||
if (rawValue == 255) {}
|
||||
else {
|
||||
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
def volts = rawValue / 10
|
||||
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}}
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
volts = rawValue // For the batteryMap to work the key needs to be an int
|
||||
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
|
||||
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
|
||||
def minVolts = 15
|
||||
def maxVolts = 28
|
||||
|
||||
return result
|
||||
if (volts < minVolts)
|
||||
volts = minVolts
|
||||
else if (volts > maxVolts)
|
||||
volts = maxVolts
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
result.value = pct
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
if (roundedPct <= 0)
|
||||
roundedPct = 1
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug "Temperature"
|
||||
def linkText = getLinkText(device)
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||
return [
|
||||
return result
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug "Temperature"
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C':
|
||||
'{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
descriptionText: descriptionText,
|
||||
translatable: true,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
private Map getContactResult(value) {
|
||||
log.debug "Contact: ${device.displayName} value = ${value}"
|
||||
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
|
||||
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true)
|
||||
sendEvent(name: 'status', value: value, descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
|
||||
private getAccelerationResult(numValue) {
|
||||
log.debug "Acceleration"
|
||||
def name = "acceleration"
|
||||
def value
|
||||
def descriptionText
|
||||
|
||||
if ( numValue.endsWith("1") ) {
|
||||
value = "active"
|
||||
descriptionText = '{{ device.displayName }} was active'
|
||||
} else {
|
||||
value = "inactive"
|
||||
descriptionText = '{{ device.displayName }} was inactive'
|
||||
}
|
||||
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
return [
|
||||
name: name,
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
isStateChange: isStateChange,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Values "
|
||||
|
||||
def refreshCmds = []
|
||||
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
||||
/* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276)
|
||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||
as of now.
|
||||
*/
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode])
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
|
||||
} else {
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
|
||||
}
|
||||
|
||||
private Map getContactResult(value) {
|
||||
log.debug "Contact"
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
|
||||
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
|
||||
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
|
||||
}
|
||||
//Common refresh commands
|
||||
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
|
||||
zigbee.readAttribute(0x0001, 0x0020) +
|
||||
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
|
||||
|
||||
private getAccelerationResult(numValue) {
|
||||
log.debug "Acceleration"
|
||||
def name = "acceleration"
|
||||
def value = numValue.endsWith("1") ? "active" : "inactive"
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = "$linkText was $value"
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
[
|
||||
name: name,
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
isStateChange: isStateChange
|
||||
]
|
||||
}
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Values "
|
||||
|
||||
def refreshCmds = []
|
||||
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
|
||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
||||
refreshCmds = refreshCmds + [
|
||||
def configure() {
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||
|
||||
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
|
||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||
as of now.
|
||||
*/
|
||||
log.debug "Configuring Reporting"
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
|
||||
]
|
||||
|
||||
|
||||
} else {
|
||||
refreshCmds = refreshCmds + [
|
||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||
def configCmds = enrollResponse() +
|
||||
zigbee.batteryConfig() +
|
||||
zigbee.temperatureConfig(30, 300) +
|
||||
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
|
||||
|
||||
/* 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
|
||||
refreshCmds = refreshCmds + [
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global read 0xFC02 0x0010",
|
||||
"send 0x${device.deviceNetworkId} 1 1","delay 400"
|
||||
]
|
||||
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting"
|
||||
|
||||
def configCmds = [
|
||||
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
|
||||
]
|
||||
|
||||
return configCmds + refresh()
|
||||
return configCmds + refresh()
|
||||
}
|
||||
|
||||
private getEndpointId() {
|
||||
@@ -473,17 +431,12 @@ def enrollResponse() {
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
//Enroll Response
|
||||
"raw 0x500 {01 23 00 00 00}",
|
||||
"raw 0x500 {01 23 00 00 00}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
||||
]
|
||||
}
|
||||
|
||||
private Map parseAxis(String description) {
|
||||
def hexToSignedInt = { hexVal ->
|
||||
def unsignedVal = hexToInt(hexVal)
|
||||
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
|
||||
}
|
||||
|
||||
def z = hexToSignedInt(description[0..3])
|
||||
def y = hexToSignedInt(description[10..13])
|
||||
def x = hexToSignedInt(description[20..23])
|
||||
@@ -510,6 +463,11 @@ private Map parseAxis(String description) {
|
||||
getXyzResult(xyzResults, description)
|
||||
}
|
||||
|
||||
private hexToSignedInt(hexVal) {
|
||||
def unsignedVal = hexToInt(hexVal)
|
||||
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
|
||||
}
|
||||
|
||||
def garageEvent(zValue) {
|
||||
def absValue = zValue.abs()
|
||||
def contactValue = null
|
||||
@@ -523,10 +481,9 @@ def garageEvent(zValue) {
|
||||
garageValue = 'garage-open'
|
||||
}
|
||||
if (contactValue != null){
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = "${linkText} was ${contactValue == 'open' ? 'opened' : 'closed'}"
|
||||
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false)
|
||||
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText)
|
||||
def descriptionText = contactValue == 'open' ? '{{ device.displayName }} was opened' :'{{ device.displayName }} was closed'
|
||||
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false, translatable: true)
|
||||
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,5 +539,3 @@ private byte[] reverseArray(byte[] array) {
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ metadata {
|
||||
capability "Battery"
|
||||
|
||||
fingerprint profileId: "FC01", deviceId: "0139"
|
||||
|
||||
attribute "status", "string"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -72,15 +74,12 @@ metadata {
|
||||
]
|
||||
)
|
||||
}
|
||||
valueTile("3axis", "device.threeAxis", decoration: "flat", wordWrap: false, width: 2, height: 2) {
|
||||
state("threeAxis", label:'${currentValue}', unit:"", backgroundColor:"#ffffff")
|
||||
}
|
||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main(["contact", "acceleration", "temperature"])
|
||||
details(["contact", "acceleration", "temperature", "3axis", "battery"])
|
||||
details(["contact", "acceleration", "temperature", "battery"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +101,7 @@ def parse(String description) {
|
||||
|
||||
}
|
||||
|
||||
private Map parseSingleMessage(description) {
|
||||
private List parseSingleMessage(description) {
|
||||
|
||||
def name = parseName(description)
|
||||
def value = parseValue(description)
|
||||
@@ -111,8 +110,9 @@ private Map parseSingleMessage(description) {
|
||||
def handlerName = value == 'open' ? 'opened' : value
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
|
||||
def results = [
|
||||
name: name,
|
||||
def results = []
|
||||
results << createEvent(
|
||||
name: "contact",
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
@@ -120,8 +120,18 @@ private Map parseSingleMessage(description) {
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed: displayed(description, isStateChange)
|
||||
]
|
||||
log.debug "Parse results for $device: $results"
|
||||
)
|
||||
|
||||
results << createEvent(
|
||||
name: "status",
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed: displayed(description, isStateChange)
|
||||
)
|
||||
|
||||
results
|
||||
}
|
||||
@@ -193,7 +203,7 @@ private List parseContactMessage(String description) {
|
||||
parts.each { part ->
|
||||
part = part.trim()
|
||||
if (part.startsWith('contactState:')) {
|
||||
results << getContactResult(part, description)
|
||||
results.addAll(getContactResult(part, description))
|
||||
}
|
||||
else if (part.startsWith('accelerationState:')) {
|
||||
results << getAccelerationResult(part, description)
|
||||
@@ -272,7 +282,7 @@ private List parseRssiLqiMessage(String description) {
|
||||
results
|
||||
}
|
||||
|
||||
private getContactResult(part, description) {
|
||||
private List getContactResult(part, description) {
|
||||
def name = "contact"
|
||||
def value = part.endsWith("1") ? "open" : "closed"
|
||||
def handlerName = value == 'open' ? 'opened' : value
|
||||
@@ -280,19 +290,33 @@ private getContactResult(part, description) {
|
||||
def descriptionText = "$linkText was $handlerName"
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
|
||||
[
|
||||
name: name,
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed: displayed(description, isStateChange)
|
||||
]
|
||||
def results = []
|
||||
results << createEvent(
|
||||
name: "contact",
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed:false
|
||||
)
|
||||
|
||||
results << createEvent(
|
||||
name: "status",
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed: displayed(description, isStateChange)
|
||||
)
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
private getAccelerationResult(part, description) {
|
||||
private Map getAccelerationResult(part, description) {
|
||||
def name = "acceleration"
|
||||
def value = part.endsWith("1") ? "active" : "inactive"
|
||||
def linkText = getLinkText(device)
|
||||
@@ -311,7 +335,7 @@ private getAccelerationResult(part, description) {
|
||||
]
|
||||
}
|
||||
|
||||
private getTempResult(part, description) {
|
||||
private Map getTempResult(part, description) {
|
||||
def name = "temperature"
|
||||
def temperatureScale = getTemperatureScale()
|
||||
def value = zigbee.parseSmartThingsTemperatureValue(part, "temp: ", temperatureScale)
|
||||
@@ -336,7 +360,7 @@ private getTempResult(part, description) {
|
||||
]
|
||||
}
|
||||
|
||||
private getXyzResult(results, description) {
|
||||
private Map getXyzResult(results, description) {
|
||||
def name = "threeAxis"
|
||||
def value = "${results.x},${results.y},${results.z}"
|
||||
def linkText = getLinkText(device)
|
||||
@@ -355,7 +379,7 @@ private getXyzResult(results, description) {
|
||||
]
|
||||
}
|
||||
|
||||
private getBatteryResult(part, description) {
|
||||
private Map getBatteryResult(part, description) {
|
||||
def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null
|
||||
def name = "battery"
|
||||
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
|
||||
@@ -376,7 +400,7 @@ private getBatteryResult(part, description) {
|
||||
]
|
||||
}
|
||||
|
||||
private getRssiResult(part, description, lastHop=false) {
|
||||
private Map getRssiResult(part, description, lastHop=false) {
|
||||
def name = lastHop ? "lastHopRssi" : "rssi"
|
||||
def valueString = part.split(":")[1].trim()
|
||||
def value = (Integer.parseInt(valueString) - 128).toString()
|
||||
@@ -407,7 +431,7 @@ private getRssiResult(part, description, lastHop=false) {
|
||||
* Note: To make the signal strength indicator more accurate, we could combine
|
||||
* LQI with RSSI.
|
||||
*/
|
||||
private getLqiResult(part, description, lastHop=false) {
|
||||
private Map getLqiResult(part, description, lastHop=false) {
|
||||
def name = lastHop ? "lastHopLqi" : "lqi"
|
||||
def valueString = part.split(":")[1].trim()
|
||||
def percentageOf = 255
|
||||
@@ -452,6 +476,7 @@ private Boolean isOrientationMessage(String description) {
|
||||
description ==~ /x:.*y:.*z:.*rssi:.*lqi:.*/
|
||||
}
|
||||
|
||||
//Note: Not using this method anymore
|
||||
private String parseName(String description) {
|
||||
if (isSupportedDescription(description)) {
|
||||
return "contact"
|
||||
|
||||
@@ -13,18 +13,21 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
//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 {
|
||||
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Contact Sensor"
|
||||
capability "Acceleration Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
command "enrollResponse"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
|
||||
capability "Health Check"
|
||||
capability "Sensor"
|
||||
|
||||
command "enrollResponse"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -170,40 +173,9 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
|
||||
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
|
||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
@@ -225,14 +197,18 @@ def getTemperature(value) {
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (volts > 3.5) {
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
if (roundedPct <= 0)
|
||||
roundedPct = 1
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
@@ -249,9 +225,10 @@ def getTemperature(value) {
|
||||
}
|
||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
@@ -298,6 +275,7 @@ def getTemperature(value) {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -0,0 +1,41 @@
|
||||
# Smartsense Open/Closed Sensor
|
||||
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
* [Samsung SmartThings Open/Closed Sensor](https://shop.smartthings.com/#!/packs/smartsense-open-closed-sensor/)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Battery](#battery-specification)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||
* **Battery** - defines device uses a battery
|
||||
* **Contact Sensor** - can detect contact (possible values: open,closed)
|
||||
* **Refresh** - _refresh()_ command for status updates
|
||||
* **Temperature Measurement** - defines device measures current temperature
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
* **Sensor** - detects sensor events
|
||||
|
||||
## Device Health
|
||||
|
||||
A Category C2 open/closed sensor with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Battery Specification
|
||||
|
||||
One CR2 3V battery required.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
|
||||
* [SmartSense Open/Closed Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202836844-SmartSense-Open-Closed-Sensor)
|
||||
@@ -13,31 +13,35 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Battery"
|
||||
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Contact Sensor"
|
||||
capability "Contact Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
|
||||
capability "Health Check"
|
||||
capability "Sensor"
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
|
||||
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor"
|
||||
}
|
||||
|
||||
|
||||
simulator {
|
||||
|
||||
|
||||
}
|
||||
|
||||
preferences {
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
||||
@@ -70,10 +74,10 @@ metadata {
|
||||
details(["contact","temperature","battery","refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
@@ -87,10 +91,10 @@ def parse(String description) {
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
@@ -98,7 +102,7 @@ def parse(String description) {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
@@ -124,7 +128,7 @@ private Map parseCatchAllMessage(String description) {
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
@@ -134,14 +138,14 @@ private boolean shouldProcessMessage(cluster) {
|
||||
private int getHumidity(value) {
|
||||
return Math.round(Double.parseDouble(value))
|
||||
}
|
||||
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
log.debug "Desc Map: $descMap"
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
@@ -150,10 +154,10 @@ private Map parseReportAttributeMessage(String description) {
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
@@ -164,42 +168,10 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
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
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
||||
}
|
||||
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
@@ -212,21 +184,25 @@ def getTemperature(value) {
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug 'Battery'
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
|
||||
def result = [
|
||||
name: 'battery'
|
||||
]
|
||||
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (volts > 3.5) {
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
if (roundedPct <= 0)
|
||||
roundedPct = 1
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
@@ -245,7 +221,8 @@ private Map getTemperatureResult(value) {
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
descriptionText: descriptionText,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
@@ -260,6 +237,13 @@ private Map getContactResult(value) {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Temperature and Battery"
|
||||
def refreshCmds = [
|
||||
@@ -271,22 +255,19 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
def configCmds = [
|
||||
def enrollCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||
]
|
||||
return configCmds + refresh() // send refresh cmds as part of config
|
||||
|
||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def enrollResponse() {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -0,0 +1,41 @@
|
||||
# SmartSense Temp/Humidity Sensor
|
||||
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
* [Samsung SmartSense Temp/Humidity Sensor](https://shop.smartthings.com/#!/products/smartsense-temp-humidity-sensor)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Battery](#battery-specification)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||
* **Battery** - defines device uses a battery
|
||||
* **Relative Humidity Measurement** - defines device measures relative humidity
|
||||
* **Refresh** - _refresh()_ command for status updates
|
||||
* **Temperature Measurement** - defines device measures current temperature
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
* **Sensor** - detects sensor events
|
||||
|
||||
## Device Health
|
||||
|
||||
A Category C2 SmartSense Temp/Humidity Sensor with maxReportTime of 5 mins.
|
||||
Check-in interval is double the value of maxReportTime.
|
||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
||||
Check-in interval = 12 mins
|
||||
|
||||
## Battery Specification
|
||||
|
||||
One CR2 battery is required.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
||||
Pairing needs to be tried by placing the sensor closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
|
||||
* [Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/203040294)
|
||||
@@ -14,12 +14,14 @@
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
capability "Relative Humidity Measurement"
|
||||
capability "Health Check"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003"
|
||||
}
|
||||
@@ -196,14 +198,18 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (volts > 3.5) {
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
if (roundedPct <= 0)
|
||||
roundedPct = 1
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
@@ -222,7 +228,8 @@ private Map getTemperatureResult(value) {
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
descriptionText: descriptionText,
|
||||
unit: temperatureScale
|
||||
]
|
||||
}
|
||||
|
||||
@@ -235,6 +242,13 @@ private Map getHumidityResult(value) {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
}
|
||||
|
||||
def refresh()
|
||||
{
|
||||
log.debug "refresh temperature, humidity, and battery"
|
||||
@@ -250,22 +264,19 @@ def refresh()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
|
||||
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
def configCmds = [
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
def humidityConfigCmds = [
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||
]
|
||||
return configCmds + refresh() // send refresh cmds as part of config
|
||||
|
||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||
return humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
private hex(value) {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
metadata {
|
||||
definition (name: "Simulated Alarm", namespace: "smartthings/testing", author: "SmartThings") {
|
||||
capability "Alarm"
|
||||
capability "Sensor"
|
||||
capability "Actuator"
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
metadata {
|
||||
definition (name: "Simulated Color Control", namespace: "smartthings/testing", author: "SmartThings") {
|
||||
capability "Color Control"
|
||||
capability "Sensor"
|
||||
capability "Actuator"
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
@@ -15,6 +15,7 @@ metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Simulated Contact Sensor", namespace: "smartthings/testing", author: "bob") {
|
||||
capability "Contact Sensor"
|
||||
capability "Sensor"
|
||||
|
||||
command "open"
|
||||
command "close"
|
||||
|
||||
@@ -15,6 +15,8 @@ metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Simulated Lock", namespace: "smartthings/testing", author: "bob") {
|
||||
capability "Lock"
|
||||
capability "Sensor"
|
||||
capability "Actuator"
|
||||
}
|
||||
|
||||
// Simulated lock
|
||||
|
||||
@@ -15,6 +15,7 @@ metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Simulated Motion Sensor", namespace: "smartthings/testing", author: "bob") {
|
||||
capability "Motion Sensor"
|
||||
capability "Sensor"
|
||||
|
||||
command "active"
|
||||
command "inactive"
|
||||
|
||||
@@ -15,6 +15,7 @@ metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Simulated Presence Sensor", namespace: "smartthings/testing", author: "bob") {
|
||||
capability "Presence Sensor"
|
||||
capability "Sensor"
|
||||
|
||||
command "arrived"
|
||||
command "departed"
|
||||
|
||||
@@ -16,6 +16,8 @@ metadata {
|
||||
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
|
||||
capability "Switch"
|
||||
capability "Relay Switch"
|
||||
capability "Sensor"
|
||||
capability "Actuator"
|
||||
|
||||
command "onPhysical"
|
||||
command "offPhysical"
|
||||
|
||||
@@ -16,6 +16,7 @@ metadata {
|
||||
definition (name: "Simulated Temperature Sensor", namespace: "smartthings/testing", author: "SmartThings") {
|
||||
capability "Temperature Measurement"
|
||||
capability "Switch Level"
|
||||
capability "Sensor"
|
||||
|
||||
command "up"
|
||||
command "down"
|
||||
|
||||
@@ -16,6 +16,8 @@ metadata {
|
||||
definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") {
|
||||
capability "Thermostat"
|
||||
capability "Relative Humidity Measurement"
|
||||
capability "Sensor"
|
||||
capability "Actuator"
|
||||
|
||||
command "tempUp"
|
||||
command "tempDown"
|
||||
@@ -32,14 +34,15 @@ metadata {
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||
attributeState("default", action: "setTemperature")
|
||||
attributeState("VALUE_UP", action: "tempUp")
|
||||
attributeState("VALUE_DOWN", action: "tempDown")
|
||||
}
|
||||
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
||||
attributeState("default", label:'${currentValue}%', unit:"%")
|
||||
}
|
||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
||||
attributeState("idle", backgroundColor:"#44b621")
|
||||
attributeState("heating", backgroundColor:"#ffa81e")
|
||||
attributeState("heating", backgroundColor:"#ea5462")
|
||||
attributeState("cooling", backgroundColor:"#269bd2")
|
||||
}
|
||||
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
|
||||
|
||||
@@ -15,6 +15,7 @@ metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Simulated Water Sensor", namespace: "smartthings/testing", author: "SmartThings") {
|
||||
capability "Water Sensor"
|
||||
capability "Sensor"
|
||||
|
||||
command "wet"
|
||||
command "dry"
|
||||
|
||||
42
devicetypes/smartthings/tile-ux/README.md
Normal file
42
devicetypes/smartthings/tile-ux/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Device Tiles Examples and Reference
|
||||
|
||||
This package contains examples of Device tiles, organized by tile type.
|
||||
|
||||
## Purpose
|
||||
|
||||
Each Device Handler shows example usages of a specific tile, and is meant to represent the variety of permutations that a tile can be configured.
|
||||
|
||||
The various tiles can be used by QA to test tiles on all supported mobile devices, and by developers as a reference implementation.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Self-publish the Device Handlers in this package.
|
||||
2. Self-publish the Device Tile Controller SmartApp. The SmartApp can be found [here](https://github.com/SmartThingsCommunity/SmartThingsPublic/blob/master/smartapps/smartthings/tile-ux/device-tile-controller.src/device-tile-controller.groovy).
|
||||
3. Install the SmartApp from the Marketplace, under "My Apps".
|
||||
4. Select the simulated devices you want to install and press "Done".
|
||||
|
||||
The simulated devices can then be found in the "Things" view of "My Home" in the mobile app.
|
||||
You may wish to create a new room for these simulated devices for easy access.
|
||||
|
||||
## Usage
|
||||
|
||||
Each simulated device can be interacted with like other devices.
|
||||
You can use the mobile app to interact with the tiles to see how they look and behave.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you get an error when installing the simulated devices using the controller SmartApp, ensure that you have published all the Device Handlers for yourself.
|
||||
Also check live logging to see if there is a specific tile that is causing installation issues.
|
||||
|
||||
## FAQ
|
||||
|
||||
*Question: A tile isn't behaving as expected. What should I do?*
|
||||
|
||||
QA should create a JIRA ticket for any issues or inconsistencies of tiles across devices.
|
||||
|
||||
Developers may file a support ticket, and reference the specific tile and issue observed.
|
||||
|
||||
*Question: I'd like to contribute an example tile usage that would be helpful for testing and reference purposes. Can I do that?*
|
||||
|
||||
We recommend that you open an issue in the SmartThingsPublic repository describing the example tile and usage.
|
||||
That way we can discuss with you the proposed change, and then if appropriate you can create a PR associated to the issue.
|
||||
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings, Inc.
|
||||
*
|
||||
* 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: "carouselDeviceTile",
|
||||
namespace: "smartthings/tile-ux",
|
||||
author: "SmartThings") {
|
||||
|
||||
capability "Thermostat"
|
||||
capability "Relative Humidity Measurement"
|
||||
|
||||
command "tempUp"
|
||||
command "tempDown"
|
||||
command "heatUp"
|
||||
command "heatDown"
|
||||
command "coolUp"
|
||||
command "coolDown"
|
||||
command "setTemperature", ["number"]
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) {
|
||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||
attributeState("default", action: "setTemperature")
|
||||
}
|
||||
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
||||
attributeState("default", label:'${currentValue}%', unit:"%")
|
||||
}
|
||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
||||
attributeState("idle", backgroundColor:"#44b621")
|
||||
attributeState("heating", backgroundColor:"#ffa81e")
|
||||
attributeState("cooling", backgroundColor:"#269bd2")
|
||||
}
|
||||
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
|
||||
attributeState("off", label:'${name}')
|
||||
attributeState("heat", label:'${name}')
|
||||
attributeState("cool", label:'${name}')
|
||||
attributeState("auto", label:'${name}')
|
||||
}
|
||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
}
|
||||
|
||||
main("thermostatMulti")
|
||||
details([
|
||||
"thermostatMulti"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
sendEvent(name: "temperature", value: 72, unit: "F")
|
||||
sendEvent(name: "heatingSetpoint", value: 70, unit: "F")
|
||||
sendEvent(name: "thermostatSetpoint", value: 70, unit: "F")
|
||||
sendEvent(name: "coolingSetpoint", value: 76, unit: "F")
|
||||
sendEvent(name: "thermostatMode", value: "off")
|
||||
sendEvent(name: "thermostatFanMode", value: "fanAuto")
|
||||
sendEvent(name: "thermostatOperatingState", value: "idle")
|
||||
sendEvent(name: "humidity", value: 53, unit: "%")
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
}
|
||||
|
||||
def evaluate(temp, heatingSetpoint, coolingSetpoint) {
|
||||
log.debug "evaluate($temp, $heatingSetpoint, $coolingSetpoint"
|
||||
def threshold = 1.0
|
||||
def current = device.currentValue("thermostatOperatingState")
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
|
||||
def heating = false
|
||||
def cooling = false
|
||||
def idle = false
|
||||
if (mode in ["heat","emergency heat","auto"]) {
|
||||
if (heatingSetpoint - temp >= threshold) {
|
||||
heating = true
|
||||
sendEvent(name: "thermostatOperatingState", value: "heating")
|
||||
}
|
||||
else if (temp - heatingSetpoint >= threshold) {
|
||||
idle = true
|
||||
}
|
||||
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
|
||||
}
|
||||
if (mode in ["cool","auto"]) {
|
||||
if (temp - coolingSetpoint >= threshold) {
|
||||
cooling = true
|
||||
sendEvent(name: "thermostatOperatingState", value: "cooling")
|
||||
}
|
||||
else if (coolingSetpoint - temp >= threshold && !heating) {
|
||||
idle = true
|
||||
}
|
||||
sendEvent(name: "thermostatSetpoint", value: coolingSetpoint)
|
||||
}
|
||||
else {
|
||||
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
|
||||
}
|
||||
if (idle && !heating && !cooling) {
|
||||
sendEvent(name: "thermostatOperatingState", value: "idle")
|
||||
}
|
||||
}
|
||||
|
||||
def setHeatingSetpoint(Double degreesF) {
|
||||
log.debug "setHeatingSetpoint($degreesF)"
|
||||
sendEvent(name: "heatingSetpoint", value: degreesF)
|
||||
evaluate(device.currentValue("temperature"), degreesF, device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def setCoolingSetpoint(Double degreesF) {
|
||||
log.debug "setCoolingSetpoint($degreesF)"
|
||||
sendEvent(name: "coolingSetpoint", value: degreesF)
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), degreesF)
|
||||
}
|
||||
|
||||
def setThermostatMode(String value) {
|
||||
sendEvent(name: "thermostatMode", value: value)
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def setThermostatFanMode(String value) {
|
||||
sendEvent(name: "thermostatFanMode", value: value)
|
||||
}
|
||||
|
||||
def off() {
|
||||
sendEvent(name: "thermostatMode", value: "off")
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def heat() {
|
||||
sendEvent(name: "thermostatMode", value: "heat")
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def auto() {
|
||||
sendEvent(name: "thermostatMode", value: "auto")
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def emergencyHeat() {
|
||||
sendEvent(name: "thermostatMode", value: "emergency heat")
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def cool() {
|
||||
sendEvent(name: "thermostatMode", value: "cool")
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def fanOn() {
|
||||
sendEvent(name: "thermostatFanMode", value: "fanOn")
|
||||
}
|
||||
|
||||
def fanAuto() {
|
||||
sendEvent(name: "thermostatFanMode", value: "fanAuto")
|
||||
}
|
||||
|
||||
def fanCirculate() {
|
||||
sendEvent(name: "thermostatFanMode", value: "fanCirculate")
|
||||
}
|
||||
|
||||
def tempUp() {
|
||||
def ts = device.currentState("temperature")
|
||||
def value = ts ? ts.integerValue + 1 : 72
|
||||
sendEvent(name:"temperature", value: value)
|
||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def tempDown() {
|
||||
def ts = device.currentState("temperature")
|
||||
def value = ts ? ts.integerValue - 1 : 72
|
||||
sendEvent(name:"temperature", value: value)
|
||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def setTemperature(value) {
|
||||
def ts = device.currentState("temperature")
|
||||
sendEvent(name:"temperature", value: value)
|
||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def heatUp() {
|
||||
def ts = device.currentState("heatingSetpoint")
|
||||
def value = ts ? ts.integerValue + 1 : 68
|
||||
sendEvent(name:"heatingSetpoint", value: value)
|
||||
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def heatDown() {
|
||||
def ts = device.currentState("heatingSetpoint")
|
||||
def value = ts ? ts.integerValue - 1 : 68
|
||||
sendEvent(name:"heatingSetpoint", value: value)
|
||||
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
|
||||
def coolUp() {
|
||||
def ts = device.currentState("coolingSetpoint")
|
||||
def value = ts ? ts.integerValue + 1 : 76
|
||||
sendEvent(name:"coolingSetpoint", value: value)
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
|
||||
}
|
||||
|
||||
def coolDown() {
|
||||
def ts = device.currentState("coolingSetpoint")
|
||||
def value = ts ? ts.integerValue - 1 : 76
|
||||
sendEvent(name:"coolingSetpoint", value: value)
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings, Inc.
|
||||
*
|
||||
* 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: "colorWheelDeviceTile",
|
||||
namespace: "smartthings/tile-ux",
|
||||
author: "SmartThings") {
|
||||
|
||||
capability "Color Control"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
valueTile("currentColor", "device.color") {
|
||||
state "color", label: '${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
controlTile("rgbSelector", "device.color", "color", height: 6, width: 6, inactiveLabel: false) {
|
||||
state "color", action: "color control.setColor"
|
||||
}
|
||||
|
||||
main("currentColor")
|
||||
details([
|
||||
"rgbSelector"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
}
|
||||
|
||||
def setColor(value) {
|
||||
log.debug "setting color: $value"
|
||||
if (value.hex) { sendEvent(name: "color", value: value.hex) }
|
||||
if (value.hue) { sendEvent(name: "hue", value: value.hue) }
|
||||
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation) }
|
||||
}
|
||||
|
||||
def setSaturation(percent) {
|
||||
log.debug "Executing 'setSaturation'"
|
||||
sendEvent(name: "saturation", value: percent)
|
||||
}
|
||||
|
||||
def setHue(percent) {
|
||||
log.debug "Executing 'setHue'"
|
||||
sendEvent(name: "hue", value: percent)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings, Inc.
|
||||
*
|
||||
* 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: "presenceDeviceTile",
|
||||
namespace: "smartthings/tile-ux",
|
||||
author: "SmartThings") {
|
||||
|
||||
capability "Presence Sensor"
|
||||
|
||||
command "arrived"
|
||||
command "departed"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
// You only get a presence tile view when the size is 3x3 otherwise it's a value tile
|
||||
standardTile("presence", "device.presence", width: 3, height: 3, canChangeBackground: true) {
|
||||
state("present", labelIcon:"st.presence.tile.mobile-present", backgroundColor:"#53a7c0")
|
||||
state("not present", labelIcon:"st.presence.tile.mobile-not-present", backgroundColor:"#ebeef2")
|
||||
}
|
||||
|
||||
standardTile("notPresentBtn", "device.fake", width: 3, height: 3, decoration: "flat") {
|
||||
state("not present", label:'not present', backgroundColor:"#ffffff", action:"departed")
|
||||
}
|
||||
|
||||
standardTile("presentBtn", "device.fake", width: 3, height: 3, decoration: "flat") {
|
||||
state("present", label:'present', backgroundColor:"#53a7c0", action:"arrived")
|
||||
}
|
||||
|
||||
main("presence")
|
||||
details([
|
||||
"presence", "presenceControl", "notPresentBtn", "presentBtn"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
sendEvent(name: "presence", value: "present")
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
}
|
||||
|
||||
def arrived() {
|
||||
log.trace "Executing 'arrived'"
|
||||
sendEvent(name: "presence", value: "present")
|
||||
}
|
||||
|
||||
def departed() {
|
||||
log.trace "Executing 'arrived'"
|
||||
sendEvent(name: "presence", value: "not present")
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings, Inc.
|
||||
*
|
||||
* 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: "sliderDeviceTile",
|
||||
namespace: "smartthings/tile-ux",
|
||||
author: "SmartThings") {
|
||||
|
||||
capability "Switch Level"
|
||||
command "setRangedLevel", ["number"]
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
controlTile("tinySlider", "device.level", "slider", height: 2, width: 2, inactiveLabel: false) {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
|
||||
controlTile("mediumSlider", "device.level", "slider", height: 2, width: 4, inactiveLabel: false) {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
|
||||
controlTile("largeSlider", "device.level", "slider", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
|
||||
controlTile("rangeSlider", "device.rangedLevel", "slider", height: 2, width: 4, range: "(20..80)") {
|
||||
state "level", action:"setRangedLevel"
|
||||
}
|
||||
|
||||
valueTile("rangeValue", "device.rangedLevel", height: 2, width: 2) {
|
||||
state "range", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
controlTile("rangeSliderConstrained", "device.rangedLevel", "slider", height: 2, width: 4, range: "(40..60)") {
|
||||
state "level", action:"setRangedLevel"
|
||||
}
|
||||
|
||||
main("rangeValue")
|
||||
details([
|
||||
"tinySlider", "mediumSlider",
|
||||
"largeSlider",
|
||||
"rangeSlider", "rangeValue",
|
||||
"rangeSliderConstrained"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
sendEvent(name: "level", value: 63)
|
||||
sendEvent(name: "rangedLevel", value: 47)
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
log.debug "setting level to $value"
|
||||
sendEvent(name:"level", value:value)
|
||||
}
|
||||
|
||||
def setRangedLevel(value) {
|
||||
log.debug "setting ranged level to $value"
|
||||
sendEvent(name:"rangedLevel", value:value)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings, Inc.
|
||||
*
|
||||
* 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: "standardDeviceTile",
|
||||
namespace: "smartthings/tile-ux",
|
||||
author: "SmartThings") {
|
||||
|
||||
capability "Switch"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
// standard tile with actions
|
||||
standardTile("actionRings", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "off", label: '${currentValue}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
||||
state "on", label: '${currentValue}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
|
||||
}
|
||||
|
||||
// standard flat tile with actions
|
||||
standardTile("actionFlat", "device.switch", width: 2, height: 2, canChangeIcon: true, decoration: "flat") {
|
||||
state "off", label: '${currentValue}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
||||
state "on", label: '${currentValue}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
|
||||
}
|
||||
|
||||
// standard flat tile without actions
|
||||
standardTile("noActionFlat", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "off", label: '${currentValue}',icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
||||
state "on", label: '${currentValue}', icon: "st.switches.switch.on", backgroundColor: "#79b821"
|
||||
}
|
||||
|
||||
// standard flat tile with only a label
|
||||
standardTile("flatLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
|
||||
state "label", label: 'On Action', action: "switch.on", backgroundColor: "#ffffff", defaultState: true
|
||||
}
|
||||
|
||||
// standard flat tile with icon and label
|
||||
standardTile("flatIconLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
|
||||
state "iconLabel", label: 'Off Action', action: "switch.off", icon:"st.switches.switch.off", backgroundColor: "#ffffff", defaultState: true
|
||||
}
|
||||
|
||||
// standard flat tile with only icon (Refreh text is IN the icon file)
|
||||
standardTile("flatIcon", "device.switch", width: 2, height: 2, decoration: "flat") {
|
||||
state "icon", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true
|
||||
}
|
||||
|
||||
// standard with defaultState = true
|
||||
standardTile("flatDefaultState", "null", width: 2, height: 2, decoration: "flat") {
|
||||
state "off", label: 'Fail!', icon: "st.switches.switch.off"
|
||||
state "on", label: 'Pass!', icon: "st.switches.switch.on", defaultState: true
|
||||
}
|
||||
|
||||
// standard with implicit defaultState based on order (0 index is selected)
|
||||
standardTile("flatImplicitDefaultState1", "null", width: 2, height: 2, decoration: "flat") {
|
||||
state "on", label: 'Pass!', icon: "st.switches.switch.on"
|
||||
state "off", label: 'Fail!', icon: "st.switches.switch.off"
|
||||
}
|
||||
|
||||
// standard with implicit defaultState based on state.name == default
|
||||
standardTile("flatImplicitDefaultState2", "null", width: 2, height: 2, decoration: "flat") {
|
||||
state "off", label: 'Fail!', icon: "st.switches.switch.off"
|
||||
state "default", label: 'Pass!', icon: "st.switches.switch.on"
|
||||
}
|
||||
|
||||
// utility tiles to fill the spaces
|
||||
standardTile("empty2x2", "null", width: 2, height: 2, decoration: "flat") {
|
||||
state "emptySmall", label:'', defaultState: true
|
||||
}
|
||||
standardTile("empty4x2", "null", width: 4, height: 2, decoration: "flat") {
|
||||
state "emptyBigger", label:'', defaultState: true
|
||||
}
|
||||
|
||||
// multi-line text (explicit newlines)
|
||||
standardTile("multiLine", "device.multiLine", width: 2, height: 2) {
|
||||
state "multiLine", label: '${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
standardTile("multiLineWithIcon", "device.multiLine", width: 2, height: 2) {
|
||||
state "multiLineIcon", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true
|
||||
}
|
||||
|
||||
main("actionRings")
|
||||
details([
|
||||
"actionRings", "actionFlat", "noActionFlat",
|
||||
|
||||
"flatLabel", "flatIconLabel", "flatIcon",
|
||||
|
||||
"flatDefaultState", "flatImplicitDefaultState1", "flatImplicitDefaultState2",
|
||||
|
||||
"multiLine", "multiLineWithIcon"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
sendEvent(name: "multiLine", value: "Line 1\nLine 2\nLine 3")
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
}
|
||||
|
||||
def on() {
|
||||
log.debug "on()"
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "off()"
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings, Inc.
|
||||
*
|
||||
* 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: "valueDeviceTile",
|
||||
namespace: "smartthings/tile-ux",
|
||||
author: "SmartThings") {
|
||||
|
||||
capability "Sensor"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
valueTile("text", "device.text", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("longText", "device.longText", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("integer", "device.integer", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("integerFloat", "device.integerFloat", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("pi", "device.pi", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("floatAsText", "device.floatAsText", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("bgColor", "device.integer", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', backgroundColor: "#e86d13", defaultState: true
|
||||
}
|
||||
|
||||
valueTile("bgColorRange", "device.integer", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [
|
||||
[value: 10, color: "#ff0000"],
|
||||
[value: 90, color: "#0000ff"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("bgColorRangeSingleItem", "device.integer", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [
|
||||
[value: 10, color: "#333333"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("bgColorRangeConflict", "device.integer", width: 2, height: 2) {
|
||||
state "valWithConflict", label:'${currentValue}', defaultState: true, backgroundColors: [
|
||||
[value: 10, color: "#990000"],
|
||||
[value: 10, color: "#000099"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("noValue", "device.nada", width: 4, height: 2) {
|
||||
state "noval", label:'${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("multiLine", "device.multiLine", width: 3, height: 2) {
|
||||
state "val", label: '${currentValue}', defaultState: true
|
||||
}
|
||||
|
||||
valueTile("multiLineWithIcon", "device.multiLine", width: 3, height: 2) {
|
||||
state "val", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true
|
||||
}
|
||||
|
||||
main("text")
|
||||
details([
|
||||
"text", "longText", "integer",
|
||||
"integerFloat", "pi", "floatAsText",
|
||||
"bgColor", "bgColorRange", "bgColorRangeSingleItem",
|
||||
"bgColorRangeConflict", "noValue",
|
||||
"multiLine", "multiLineWithIcon"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
sendEvent(name: "text", value: "Test")
|
||||
sendEvent(name: "longText", value: "The Longer The Text, The Better The Test")
|
||||
sendEvent(name: "integer", value: 47)
|
||||
sendEvent(name: "integerFloat", value: 47.0)
|
||||
sendEvent(name: "pi", value: 3.14159)
|
||||
sendEvent(name: "floatAsText", value: "3.14159")
|
||||
sendEvent(name: "multiLine", value: "Line 1\nLine 2\nLine 3")
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings, Inc.
|
||||
*
|
||||
* 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: "genericDeviceTile", namespace: "smartthings/tile-ux", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
|
||||
command "levelUp"
|
||||
command "levelDown"
|
||||
command "randomizeLevel"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"basicTile", type:"generic", width:6, height:4) {
|
||||
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"
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"sliderTile", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute("device.level", key: "SECONDARY_CONTROL") {
|
||||
attributeState "level", icon: 'st.Weather.weather1', action:"randomizeLevel", defaultState: true
|
||||
}
|
||||
tileAttribute("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", defaultState: true
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"valueTile", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.level", key: "PRIMARY_CONTROL") {
|
||||
attributeState "level", label:'${currentValue}', defaultState: true, backgroundColors:[
|
||||
[value: 0, color: "#ff0000"],
|
||||
[value: 20, color: "#ffff00"],
|
||||
[value: 40, color: "#00ff00"],
|
||||
[value: 60, color: "#00ffff"],
|
||||
[value: 80, color: "#0000ff"],
|
||||
[value: 100, color: "#ff00ff"]
|
||||
]
|
||||
}
|
||||
tileAttribute("device.switch", key: "SECONDARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'…', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'…', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute("device.level", key: "VALUE_CONTROL") {
|
||||
attributeState "VALUE_UP", action: "levelUp"
|
||||
attributeState "VALUE_DOWN", action: "levelDown"
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"lengthyTile", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") {
|
||||
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", defaultState: true
|
||||
}
|
||||
tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", defaultState: true
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"multilineTile", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
|
||||
attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true
|
||||
}
|
||||
tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"lengthyTileWithIcon", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") {
|
||||
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
|
||||
}
|
||||
tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"multilineTileWithIcon", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
|
||||
attributeState "multilineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
|
||||
}
|
||||
tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "multilineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
|
||||
}
|
||||
}
|
||||
|
||||
main(["basicTile"])
|
||||
details(["basicTile", "sliderTile", "valueTile", "lengthyTile", "multilineTile", "lengthyTileWithIcon", "multilineTileWithIcon"])
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
sendEvent(name: "lengthyText", value: "The value of this tile is long and should wrap to two lines")
|
||||
sendEvent(name: "multilineText", value: "Line 1 YES\nLine 2 YES\nLine 3 NO")
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
// This is a simulated device. No incoming data to parse.
|
||||
}
|
||||
|
||||
def on() {
|
||||
log.debug "turningOn"
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "turningOff"
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
|
||||
def setLevel(percent) {
|
||||
log.debug "setLevel: ${percent}, this"
|
||||
sendEvent(name: "level", value: percent)
|
||||
}
|
||||
|
||||
def randomizeLevel() {
|
||||
def level = Math.round(Math.random() * 100)
|
||||
setLevel(level)
|
||||
}
|
||||
|
||||
def levelUp() {
|
||||
def level = device.latestValue("level") as Integer ?: 0
|
||||
if (level < 100) {
|
||||
level = level + 1
|
||||
}
|
||||
setLevel(level)
|
||||
}
|
||||
|
||||
def levelDown() {
|
||||
def level = device.latestValue("level") as Integer ?: 0
|
||||
if (level > 0) {
|
||||
level = level - 1
|
||||
}
|
||||
setLevel(level)
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings, Inc.
|
||||
*
|
||||
* 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: "lightingDeviceTile",
|
||||
namespace: "smartthings/tile-ux",
|
||||
author: "SmartThings") {
|
||||
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Color Control"
|
||||
capability "Power Meter"
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
command "setAdjustedColor"
|
||||
command "reset"
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.power", key: "SECONDARY_CONTROL") {
|
||||
attributeState "power", label:'Power level: ${currentValue}W', icon: "st.Appliances.appliances17"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"setAdjustedColor"
|
||||
}
|
||||
}
|
||||
|
||||
multiAttributeTile(name:"switchNoPower", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"setAdjustedColor"
|
||||
}
|
||||
}
|
||||
|
||||
multiAttributeTile(name:"switchNoSlider", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.power", key: "SECONDARY_CONTROL") {
|
||||
attributeState "power", label:'The power level is currently: ${currentValue}W', icon: "st.Appliances.appliances17"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"setAdjustedColor"
|
||||
}
|
||||
}
|
||||
|
||||
multiAttributeTile(name:"switchNoSliderOrColor", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.power", key: "SECONDARY_CONTROL") {
|
||||
attributeState "power", label:'The light is currently consuming this amount of power: ${currentValue}W', icon: "st.Appliances.appliances17"
|
||||
}
|
||||
}
|
||||
|
||||
valueTile("color", "device.color", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "color", label: '${currentValue}'
|
||||
}
|
||||
|
||||
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "reset", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single", defaultState: true
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "refresh", label:"", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "switchNoPower", "switchNoSlider", "switchNoSliderOrColor", "color", "refresh", "reset"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(description) {
|
||||
log.debug "parse() - $description"
|
||||
def results = []
|
||||
def map = description
|
||||
if (description instanceof String) {
|
||||
log.debug "Hue Bulb stringToMap - ${map}"
|
||||
map = stringToMap(description)
|
||||
}
|
||||
if (map?.name && map?.value) {
|
||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
// handle commands
|
||||
def on() {
|
||||
//log.trace parent.on(this)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
|
||||
def off() {
|
||||
//log.trace parent.off(this)
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
|
||||
def 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)
|
||||
}
|
||||
|
||||
def setLevel(percent) {
|
||||
log.debug "setLevel: ${percent}, this"
|
||||
sendEvent(name: "level", value: percent)
|
||||
def power = Math.round(percent / 1.175) * 0.1
|
||||
sendEvent(name: "power", value: power)
|
||||
}
|
||||
|
||||
def setSaturation(percent) {
|
||||
log.debug "setSaturation: ${percent}, $this"
|
||||
sendEvent(name: "saturation", value: percent)
|
||||
}
|
||||
|
||||
def setHue(percent) {
|
||||
log.debug "setHue: ${percent}, $this"
|
||||
sendEvent(name: "hue", value: percent)
|
||||
}
|
||||
|
||||
def setColor(value) {
|
||||
log.debug "setColor: ${value}, $this"
|
||||
if (value.hue) { sendEvent(name: "hue", value: value.hue)}
|
||||
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation)}
|
||||
if (value.hex) { sendEvent(name: "color", value: value.hex)}
|
||||
if (value.level) { sendEvent(name: "level", value: value.level)}
|
||||
if (value.switch) { sendEvent(name: "switch", value: value.switch)}
|
||||
}
|
||||
|
||||
def reset() {
|
||||
log.debug "Executing 'reset'"
|
||||
setAdjustedColor([level:100, hex:"#90C638", saturation:56, hue:23])
|
||||
}
|
||||
|
||||
def setAdjustedColor(value) {
|
||||
if (value) {
|
||||
log.trace "setAdjustedColor: ${value}"
|
||||
def adjusted = value + [:]
|
||||
adjusted.hue = adjustOutgoingHue(value.hue)
|
||||
// Needed because color picker always sends 100
|
||||
adjusted.level = null
|
||||
setColor(adjusted)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings, Inc.
|
||||
*
|
||||
* 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: "mediaPlayerDeviceTile",
|
||||
namespace: "smartthings/tile-ux",
|
||||
author: "SmartThings") {
|
||||
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Music Player"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name: "mediaMulti", type:"mediaPlayer", width:6, height:4) {
|
||||
tileAttribute("device.status", key: "PRIMARY_CONTROL") {
|
||||
attributeState("paused", label:"Paused",)
|
||||
attributeState("playing", label:"Playing")
|
||||
attributeState("stopped", label:"Stopped")
|
||||
}
|
||||
tileAttribute("device.status", key: "MEDIA_STATUS") {
|
||||
attributeState("paused", label:"Paused", action:"music Player.play", nextState: "playing")
|
||||
attributeState("playing", label:"Playing", action:"music Player.pause", nextState: "paused")
|
||||
attributeState("stopped", label:"Stopped", action:"music Player.play", nextState: "playing")
|
||||
}
|
||||
tileAttribute("device.status", key: "PREVIOUS_TRACK") {
|
||||
attributeState("status", action:"music Player.previousTrack", defaultState: true)
|
||||
}
|
||||
tileAttribute("device.status", key: "NEXT_TRACK") {
|
||||
attributeState("status", action:"music Player.nextTrack", defaultState: true)
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState("level", action:"music Player.setLevel")
|
||||
}
|
||||
tileAttribute ("device.mute", key: "MEDIA_MUTED") {
|
||||
attributeState("unmuted", action:"music Player.mute", nextState: "muted")
|
||||
attributeState("muted", action:"music Player.unmute", nextState: "unmuted")
|
||||
}
|
||||
tileAttribute("device.trackDescription", key: "MARQUEE") {
|
||||
attributeState("trackDescription", label:"${currentValue}", defaultState: true)
|
||||
}
|
||||
}
|
||||
|
||||
main "mediaMulti"
|
||||
details(["mediaMulti"])
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
state.tracks = [
|
||||
"Gangnam Style (강남스타일)\nPSY\nPsy 6 (Six Rules), Part 1",
|
||||
"Careless Whisper\nWham!\nMake It Big",
|
||||
"Never Gonna Give You Up\nRick Astley\nWhenever You Need Somebody",
|
||||
"Shake It Off\nTaylor Swift\n1989",
|
||||
"Ironic\nAlanis Morissette\nJagged Little Pill",
|
||||
"Hotline Bling\nDrake\nHotline Bling - Single"
|
||||
]
|
||||
state.currentTrack = 0
|
||||
|
||||
sendEvent(name: "level", value: 72)
|
||||
sendEvent(name: "mute", value: "unmuted")
|
||||
sendEvent(name: "status", value: "stopped")
|
||||
}
|
||||
|
||||
def parse(description) {
|
||||
// No parsing will happen with this simulated device.
|
||||
}
|
||||
|
||||
def play() {
|
||||
sendEvent(name: "status", value: "playing")
|
||||
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
|
||||
}
|
||||
|
||||
def pause() {
|
||||
sendEvent(name: "status", value: "paused")
|
||||
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
|
||||
}
|
||||
|
||||
def stop() {
|
||||
sendEvent(name: "status", value: "stopped")
|
||||
}
|
||||
|
||||
def previousTrack() {
|
||||
state.currentTrack = state.currentTrack - 1
|
||||
if (state.currentTrack < 0)
|
||||
state.currentTrack = state.tracks.size()-1
|
||||
|
||||
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
|
||||
}
|
||||
|
||||
def nextTrack() {
|
||||
state.currentTrack = state.currentTrack + 1
|
||||
if (state.currentTrack == state.tracks.size())
|
||||
state.currentTrack = 0
|
||||
|
||||
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
|
||||
}
|
||||
|
||||
def mute() {
|
||||
sendEvent(name: "mute", value: "muted")
|
||||
}
|
||||
|
||||
def unmute() {
|
||||
sendEvent(name: "mute", value: "unmuted")
|
||||
}
|
||||
|
||||
def setLevel(level) {
|
||||
sendEvent(name: "level", value: level)
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings, Inc.
|
||||
*
|
||||
* 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: "thermostatDeviceTile",
|
||||
namespace: "smartthings/tile-ux",
|
||||
author: "SmartThings") {
|
||||
|
||||
capability "Thermostat"
|
||||
capability "Relative Humidity Measurement"
|
||||
|
||||
command "tempUp"
|
||||
command "tempDown"
|
||||
command "heatUp"
|
||||
command "heatDown"
|
||||
command "coolUp"
|
||||
command "coolDown"
|
||||
command "setTemperature", ["number"]
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"thermostatFull", type:"thermostat", width:6, height:4) {
|
||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
}
|
||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||
attributeState("VALUE_UP", action: "tempUp")
|
||||
attributeState("VALUE_DOWN", action: "tempDown")
|
||||
}
|
||||
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
||||
attributeState("humidity", label:'${currentValue}%', unit:"%", defaultState: true)
|
||||
}
|
||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
||||
attributeState("idle", backgroundColor:"#44b621")
|
||||
attributeState("heating", backgroundColor:"#ffa81e")
|
||||
attributeState("cooling", backgroundColor:"#269bd2")
|
||||
}
|
||||
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
|
||||
attributeState("off", label:'${name}')
|
||||
attributeState("heat", label:'${name}')
|
||||
attributeState("cool", label:'${name}')
|
||||
attributeState("auto", label:'${name}')
|
||||
}
|
||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
||||
attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
}
|
||||
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"thermostatNoHumidity", type:"thermostat", width:6, height:4) {
|
||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
attributeState("temp", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||
attributeState("VALUE_UP", action: "tempUp")
|
||||
attributeState("VALUE_DOWN", action: "tempDown")
|
||||
}
|
||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
||||
attributeState("idle", backgroundColor:"#44b621")
|
||||
attributeState("heating", backgroundColor:"#ffa81e")
|
||||
attributeState("cooling", backgroundColor:"#269bd2")
|
||||
}
|
||||
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
|
||||
attributeState("off", label:'${name}')
|
||||
attributeState("heat", label:'${name}')
|
||||
attributeState("cool", label:'${name}')
|
||||
attributeState("auto", label:'${name}')
|
||||
}
|
||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"thermostatBasic", type:"thermostat", width:6, height:4) {
|
||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true,
|
||||
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"]
|
||||
])
|
||||
}
|
||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||
attributeState("VALUE_UP", action: "tempUp")
|
||||
attributeState("VALUE_DOWN", action: "tempDown")
|
||||
}
|
||||
}
|
||||
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state("temperature", label:'${currentValue}', unit:"dF",
|
||||
backgroundColors:[
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
)
|
||||
}
|
||||
standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "tempDown", label:'down', action:"tempDown", defaultState: true
|
||||
}
|
||||
standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "tempUp", label:'up', action:"tempUp", defaultState: true
|
||||
}
|
||||
|
||||
valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "heatDown", label:'down', action:"heatDown", defaultState: true
|
||||
}
|
||||
standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "heatUp", label:'up', action:"heatUp", defaultState: true
|
||||
}
|
||||
|
||||
valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "coolDown", label:'down', action:"coolDown", defaultState: true
|
||||
}
|
||||
standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "coolUp", label:'up', action:"coolUp", defaultState: true
|
||||
}
|
||||
|
||||
standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "off", label:'${name}', action:"thermostat.heat", backgroundColor:"#ffffff"
|
||||
state "heat", label:'${name}', action:"thermostat.cool", backgroundColor:"#ffa81e"
|
||||
state "cool", label:'${name}', action:"thermostat.auto", backgroundColor:"#269bd2"
|
||||
state "auto", label:'${name}', action:"thermostat.off", backgroundColor:"#79b821"
|
||||
}
|
||||
standardTile("fanMode", "device.thermostatFanMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "fanAuto", label:'${name}', action:"thermostat.fanOn", backgroundColor:"#ffffff"
|
||||
state "fanOn", label:'${name}', action:"thermostat.fanCirculate", backgroundColor:"#ffffff"
|
||||
state "fanCirculate", label:'${name}', action:"thermostat.fanAuto", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("operatingState", "device.thermostatOperatingState", width: 2, height: 2) {
|
||||
state "idle", label:'${name}', backgroundColor:"#ffffff"
|
||||
state "heating", label:'${name}', backgroundColor:"#ffa81e"
|
||||
state "cooling", label:'${name}', backgroundColor:"#269bd2"
|
||||
}
|
||||
|
||||
|
||||
main("thermostatFull")
|
||||
details([
|
||||
"thermostatFull", "thermostatNoHumidity", "thermostatBasic",
|
||||
"temperature","tempDown","tempUp",
|
||||
"mode", "fanMode", "operatingState",
|
||||
"heatingSetpoint", "heatDown", "heatUp",
|
||||
"coolingSetpoint", "coolDown", "coolUp"
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
sendEvent(name: "temperature", value: 72, unit: "F")
|
||||
sendEvent(name: "heatingSetpoint", value: 70, unit: "F")
|
||||
sendEvent(name: "thermostatSetpoint", value: 70, unit: "F")
|
||||
sendEvent(name: "coolingSetpoint", value: 76, unit: "F")
|
||||
sendEvent(name: "thermostatMode", value: "off")
|
||||
sendEvent(name: "thermostatFanMode", value: "fanAuto")
|
||||
sendEvent(name: "thermostatOperatingState", value: "idle")
|
||||
sendEvent(name: "humidity", value: 53, unit: "%")
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
}
|
||||
|
||||
def evaluate(temp, heatingSetpoint, coolingSetpoint) {
|
||||
log.debug "evaluate($temp, $heatingSetpoint, $coolingSetpoint"
|
||||
def threshold = 1.0
|
||||
def current = device.currentValue("thermostatOperatingState")
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
|
||||
def heating = false
|
||||
def cooling = false
|
||||
def idle = false
|
||||
if (mode in ["heat","emergency heat","auto"]) {
|
||||
if (heatingSetpoint - temp >= threshold) {
|
||||
heating = true
|
||||
sendEvent(name: "thermostatOperatingState", value: "heating")
|
||||
}
|
||||
else if (temp - heatingSetpoint >= threshold) {
|
||||
idle = true
|
||||
}
|
||||
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
|
||||
}
|
||||
if (mode in ["cool","auto"]) {
|
||||
if (temp - coolingSetpoint >= threshold) {
|
||||
cooling = true
|
||||
sendEvent(name: "thermostatOperatingState", value: "cooling")
|
||||
}
|
||||
else if (coolingSetpoint - temp >= threshold && !heating) {
|
||||
idle = true
|
||||
}
|
||||
sendEvent(name: "thermostatSetpoint", value: coolingSetpoint)
|
||||
}
|
||||
else {
|
||||
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
|
||||
}
|
||||
|
||||
if (mode == "off") {
|
||||
idle = true
|
||||
}
|
||||
|
||||
if (idle && !heating && !cooling) {
|
||||
sendEvent(name: "thermostatOperatingState", value: "idle")
|
||||
}
|
||||
}
|
||||
|
||||
def setHeatingSetpoint(Double degreesF) {
|
||||
log.debug "setHeatingSetpoint($degreesF)"
|
||||
sendEvent(name: "heatingSetpoint", value: degreesF)
|
||||
evaluate(device.currentValue("temperature"), degreesF, device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def setCoolingSetpoint(Double degreesF) {
|
||||
log.debug "setCoolingSetpoint($degreesF)"
|
||||
sendEvent(name: "coolingSetpoint", value: degreesF)
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), degreesF)
|
||||
}
|
||||
|
||||
def setThermostatMode(String value) {
|
||||
sendEvent(name: "thermostatMode", value: value)
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def setThermostatFanMode(String value) {
|
||||
sendEvent(name: "thermostatFanMode", value: value)
|
||||
}
|
||||
|
||||
def off() {
|
||||
sendEvent(name: "thermostatMode", value: "off")
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def heat() {
|
||||
sendEvent(name: "thermostatMode", value: "heat")
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def auto() {
|
||||
sendEvent(name: "thermostatMode", value: "auto")
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def emergencyHeat() {
|
||||
sendEvent(name: "thermostatMode", value: "emergency heat")
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def cool() {
|
||||
sendEvent(name: "thermostatMode", value: "cool")
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def fanOn() {
|
||||
sendEvent(name: "thermostatFanMode", value: "fanOn")
|
||||
}
|
||||
|
||||
def fanAuto() {
|
||||
sendEvent(name: "thermostatFanMode", value: "fanAuto")
|
||||
}
|
||||
|
||||
def fanCirculate() {
|
||||
sendEvent(name: "thermostatFanMode", value: "fanCirculate")
|
||||
}
|
||||
|
||||
def poll() {
|
||||
null
|
||||
}
|
||||
|
||||
def tempUp() {
|
||||
def ts = device.currentState("temperature")
|
||||
def value = ts ? ts.integerValue + 1 : 72
|
||||
sendEvent(name:"temperature", value: value)
|
||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def tempDown() {
|
||||
def ts = device.currentState("temperature")
|
||||
def value = ts ? ts.integerValue - 1 : 72
|
||||
sendEvent(name:"temperature", value: value)
|
||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def setTemperature(value) {
|
||||
def ts = device.currentState("temperature")
|
||||
sendEvent(name:"temperature", value: value)
|
||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def heatUp() {
|
||||
def ts = device.currentState("heatingSetpoint")
|
||||
def value = ts ? ts.integerValue + 1 : 68
|
||||
sendEvent(name:"heatingSetpoint", value: value)
|
||||
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
def heatDown() {
|
||||
def ts = device.currentState("heatingSetpoint")
|
||||
def value = ts ? ts.integerValue - 1 : 68
|
||||
sendEvent(name:"heatingSetpoint", value: value)
|
||||
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
|
||||
}
|
||||
|
||||
|
||||
def coolUp() {
|
||||
def ts = device.currentState("coolingSetpoint")
|
||||
def value = ts ? ts.integerValue + 1 : 76
|
||||
sendEvent(name:"coolingSetpoint", value: value)
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
|
||||
}
|
||||
|
||||
def coolDown() {
|
||||
def ts = device.currentState("coolingSetpoint")
|
||||
def value = ts ? ts.integerValue - 1 : 76
|
||||
sendEvent(name:"coolingSetpoint", value: value)
|
||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user