283 Commits

Author SHA1 Message Date
Nick Farina
7a561f8754 Move slackin deployment to mitigate NodeJS security issue 2017-07-14 15:06:21 -07:00
Khaos Tian
d9d58855cd 0.4.22 2017-07-02 14:46:27 -07:00
Khaos Tian
3acded3ea2 Bump hap-nodejs version.
This version addressed connectivity problem with node 8.0
2017-07-02 14:46:23 -07:00
Khaos Tian
8a35d75cb5 0.4.21 2017-07-01 22:02:04 -06:00
Khaos Tian
c70cb8be07 Bump hap-nodejs version 2017-07-01 22:01:56 -06:00
Olivier Guerriat
7406f6c9f3 Added "limitations" to the README (#1338) 2017-06-10 12:09:09 -07:00
Brent Unkrich
cbae68afdd updated formatting (#1284)
took out extra spaces and fixed capitalization.
2017-05-11 19:23:08 -07:00
Khaos Tian
495143e1a1 Update hap-nodejs version 2017-05-01 23:30:05 -07:00
Khaos Tian
7585cf3f83 0.4.19
Bump hap-nodejs version to add support for programmable switch label.
2017-04-16 17:35:44 -07:00
Khaos Tian
d521755a49 0.4.18
Add support for updated programmable service profile.
2017-04-16 13:32:00 -07:00
Khaos Tian
6f4ce80aea Bump hap-nodejs version. 2017-04-07 22:06:17 -07:00
Stephan Esch
efda0fac11 Added serverVersion to API; bumped API version to 2.2 (#1064)
* Added serverVersion to API; bumped API version to 2.1.1

* Fixed version number to 2.2
2017-01-13 09:48:56 -08:00
Khaos Tian
600760884d Address a metadata issue that blocks camera support. 2016-12-13 15:50:19 +08:00
Khaos Tian
3a4830ee57 Update hap-nodejs to support new services in iOS 10.2 2016-12-13 11:05:30 +08:00
Khaos Tian
582e00a6ef 0.4.12 2016-12-10 14:23:53 -08:00
Khaos Tian
debba05d2f Merge pull request #967 from andig/patch-1
Upgrade hap-nodejs
2016-12-02 01:20:34 -08:00
andig
027a693c0d Update package.json 2016-12-02 10:19:58 +01:00
andig
b5e1fc52a8 Upgrade hap-nodejs 2016-12-02 10:18:49 +01:00
Khaos Tian
f17fe59590 Fix an warning in nodejs v7 2016-11-29 18:42:17 -08:00
Khaos Tian
69e3ed5ee4 Merge pull request #491 from straccio/master
Added pluginPath based on npm
2016-11-28 13:21:27 -08:00
Khaos Tian
3354842e81 Merge pull request #917 from crzcrz/master
Use chalk instead of hardcoded ANSI codes
2016-11-28 13:17:35 -08:00
straccio
e5e2a400ec Merge remote-tracking branch 'nfarina/master'
# Conflicts:
#	lib/plugin.js
2016-11-28 13:36:00 +01:00
Khaos Tian
4884087041 0.4.10 2016-11-27 15:24:43 -08:00
Khaos Tian
e1867b2bc0 Improve error handling. 2016-11-27 15:24:27 -08:00
Khaos Tian
4d0f9d86f6 Merge pull request #941 from djMax/master
Allow externally specified config
2016-11-21 12:04:57 -08:00
Max Metral
ecda18029f Allow externally specified config 2016-11-21 14:58:39 -05:00
straccio
7acac442a8 Merge remote-tracking branch 'nfarina/master' 2016-11-21 09:12:23 +01:00
Khaos Tian
efc570e5a9 Update example plugin
Add comment for service name.
2016-11-18 00:33:36 -08:00
straccio
7955049337 Merge remote-tracking branch 'nfarina/master' 2016-11-14 11:24:01 +01:00
Stanislav Kljuhhin
8c6cb53dcb Use chalk instead of hardcoded ANSI codes
Improves the readability of the source code and prevents the raw ANSI codes from appearing in the log, if ran as a systemd service.
2016-11-13 16:29:10 +01:00
Khaos Tian
b6cfe3ba7c 0.4.9 2016-11-09 18:11:57 -08:00
Khaos Tian
f836d4a42c Bump hap-nodejs version to fix #791 2016-11-09 18:11:41 -08:00
straccio
f893322887 Merge remote-tracking branch 'nfarina/master' 2016-11-04 10:05:11 +01:00
Khaos Tian
63ab1025e9 Update hap-nodejs to allow homebridge work with nodejs v4.x 2016-11-03 12:20:46 -07:00
straccio
9a25a363d4 Merge remote-tracking branch 'nfarina/master' 2016-11-03 17:05:53 +01:00
Khaos Tian
dc43d0b7c4 Update hap-nodejs
This version requires node v5.10.0 or later.
2016-11-01 18:33:59 -07:00
straccio
1513e5398f Merge remote-tracking branch 'nfarina/master' 2016-10-11 15:52:58 +02:00
Khaos Tian
7c3543ba61 Merge pull request #839 from rxseger/collision-name
Fix camera name in collision error message
2016-10-09 17:14:29 -07:00
rxseger
5adb5f3282 Fix camera name in collision error message 2016-10-09 23:36:53 +00:00
straccio
ffe343c65f Merge remote-tracking branch 'nfarina/master' 2016-10-03 09:24:28 +02:00
Khaos Tian
fedd341970 0.4.6 2016-10-01 16:48:05 -07:00
Khaos Tian
c7c9aa0150 Bump hap-nodejs version. 2016-10-01 16:48:01 -07:00
straccio
a2baa93801 Merge remote-tracking branch 'nfarina/master' 2016-09-29 09:00:45 +02:00
Khaos Tian
e3e08414f6 0.4.5 2016-09-28 17:11:01 -07:00
Khaos Tian
ea9df45d2d Add back the ability to designate the port homebridge is running. 2016-09-28 17:10:52 -07:00
Khaos Tian
7425f8beca 0.4.4 2016-09-28 12:40:35 -07:00
straccio
8192fc2672 Merge remote-tracking branch 'nfarina/master' 2016-09-28 08:19:30 +02:00
Khaos Tian
dbb7b89cf9 0.4.3 2016-09-27 17:01:08 -07:00
Khaos Tian
4f000485db Bump hap-nodejs again... 2016-09-27 17:01:00 -07:00
Khaos Tian
c0884f484e 0.4.2 2016-09-27 12:36:29 -07:00
Khaos Tian
363c997058 Bump hap-nodejs version 2016-09-27 12:36:23 -07:00
straccio
332385d605 Merge remote-tracking branch 'nfarina/master' 2016-09-27 08:38:18 +02:00
Khaos Tian
0ccd80c8e7 0.4.1 2016-09-26 20:32:06 -07:00
Khaos Tian
ef8fe5ced2 Update hap-nodejs version to fix side load problem 2016-09-26 20:31:55 -07:00
Khaos Tian
4a531ede5c Merge pull request #805 from nfarina/ip-cam
IP Camera support for homebridge
2016-09-26 14:34:35 -07:00
Khaos Tian
ff35ece65c 0.4.0 2016-09-26 14:22:22 -07:00
Khaos Tian
66ea6e640d Minor Tweak 2016-09-26 14:15:12 -07:00
Khaos Tian
ecd06d7239 Bump api version 2016-09-26 13:19:50 -07:00
Khaos Tian
c89ff97ac5 init design 2016-09-26 13:01:29 -07:00
Khaos Tian
ceec4c74fd Merge pull request #803 from nfarina/use-hap-0.4
Use hap-nodejs 0.4.0
2016-09-26 12:02:32 -07:00
Jon Maddox
925c1630c4 bump hap-nodejs to 0.4.0 2016-09-26 10:46:21 -04:00
straccio
41c53f8f10 Merge remote-tracking branch 'nfarina/master' 2016-09-20 09:17:15 +02:00
Khaos Tian
4eabc4ad52 Merge pull request #593 from PatchworkBoy/patch-1
Ref #582
2016-09-18 21:37:49 -07:00
Khaos Tian
c0859a29d3 Merge pull request #764 from alessiodionisi/new-bridge-configs
New bridge configs
2016-09-18 21:37:18 -07:00
Khaos Tian
c15707e875 Merge pull request #778 from nrobinson2000/patch-1
Some changes to the README
2016-09-18 21:36:42 -07:00
Nathan Robinson
8c476b45a0 Update README.md 2016-09-18 21:13:38 -04:00
Nick Farina
f49229d73c Update README to reflect the new iOS 10 Home app. 2016-09-15 21:05:39 -07:00
Khaos Tian
fbccc031f4 Merge pull request #770 from kcjonson/patch-1
Update README.md
2016-09-15 14:57:11 -07:00
Kevin Jonson
d70fa741d8 Update README.md 2016-09-15 14:07:09 -07:00
Alessio Dionisi
4740bf1fc5 New bridge configs
"bridge": {
    "manufacturer": "Manufacturer here",
    "serialNumber": "Serial number here",
    "model": "Model here"
}
2016-09-13 13:54:37 +02:00
Khaos Tian
da57b29972 Merge pull request #758 from cflurin/master
Warning config.json not found
2016-09-06 08:28:36 -07:00
cflurin
5944365bc6 Warning config.json not found 2016-09-06 14:49:53 +02:00
Khaos Tian
a8908fd9b8 Merge pull request #714 from EdJoPaTo/platformLog
optimized logging of the SamplePlatform example
2016-07-30 18:41:01 -07:00
Khaos Tian
8ef7e62094 Merge pull request #715 from EdJoPaTo/multiAccessories
fixed SamplePlatform crashes
2016-07-30 18:40:35 -07:00
Edgar To
15c8eaaf29 use of the own log method instead of the general console.log(); added accessory.displayName to the log output if possible/ useful 2016-07-24 22:43:21 +02:00
Edgar To
e6648375c7 different add times will create different UUIDs -> no crash with the second add call 2016-07-24 22:38:05 +02:00
straccio
4251b15291 Merge remote-tracking branch 'nfarina/master' 2016-05-31 08:52:59 +02:00
Khaos Tian
a52bc9e437 Merge pull request #642 from alexbain/patch-1
Add note about --unsafe-perm
2016-05-22 18:35:12 -07:00
Alex Bain
b78c081cd4 Add note about --unsafe-perm
I received the following error when trying to install homebridge on a RaspberryPi. Assuming I'm not the first or the last to see this error, I thought adding a note to README about the --unsafe-perm flag would aid others.
2016-05-22 18:01:32 -07:00
straccio
3f2cd08383 Merge remote-tracking branch 'nfarina/master' 2016-05-02 16:44:42 +02:00
Khaos Tian
87050a2267 update hap-nodejs dependency for node v6 2016-04-29 10:23:15 -07:00
straccio
c8cb0731ff Merge remote-tracking branch 'nfarina/master' 2016-04-11 09:45:54 +02:00
Khaos Tian
35dfaabc69 update hap-nodejs dependency 2016-04-08 23:51:03 -07:00
Khaos Tian
77ce39e157 0.3.3 2016-04-08 23:50:34 -07:00
Khaos Tian
0af8a43dc9 Expose platform accessory category to hap-nodejs
Fix an error in plugin config look up
2016-04-08 23:50:18 -07:00
Marci
f203a2ac6f Ref #582
Ref#582 Add ‘--unsafe-perm’ flag to npm install command to get round all the .gyp warns etc.
2016-03-31 15:50:50 +01:00
straccio
39af2ebbef Merge remote-tracking branch 'nfarina/master'
# Conflicts:
#	lib/logger.js
2016-03-21 08:43:36 +01:00
Khaos Tian
620c8473b8 #572 2016-03-20 22:12:47 -07:00
Khaos Tian
b2f476f833 0.3.2 2016-03-20 21:53:01 -07:00
Khaos Tian
c6d2f889fc Update hap-nodejs version to solve #575 2016-03-20 21:52:56 -07:00
straccio
f73783787d Merge remote-tracking branch 'nfarina/master' 2016-03-11 08:18:56 +01:00
Khaos Tian
2ea2052769 0.3.1 2016-03-10 20:19:47 -08:00
Khaos Tian
64e8c83d9c Update hap-nodejs 2016-03-10 20:19:42 -08:00
straccio
b94c3caa3b Changed color for logging timestamp to white. 2016-03-08 15:59:44 +01:00
straccio
1a710badef Merge remote-tracking branch 'nfarina/master' 2016-03-08 08:45:04 +01:00
straccio
73fdec5928 Revert to upstream/master, no need to skip cached accessories 2016-03-08 08:44:52 +01:00
Nick Farina
13333999f3 Update README.md 2016-03-07 14:43:16 -08:00
Nick Farina
87c48d7267 Update README.md 2016-03-07 14:42:28 -08:00
Nick Farina
9b42fafdaf Add README example for plugin development 2016-03-07 11:49:13 -08:00
straccio
911f088df9 A way to skip cached accessories from being loaded in HAP 2016-03-07 17:32:18 +01:00
straccio
6fade3c3cc Merge remote-tracking branch 'nfarina/master' 2016-03-07 17:28:41 +01:00
Khaos Tian
842ec105be Merge pull request #554 from Danimal4326/master
Prepend date/time to logger messages
2016-03-03 10:29:29 -08:00
Danimal4326
df8508a38f Prepend date/time to logger messages 2016-03-03 12:24:54 -06:00
straccio
191c75c281 Merge remote-tracking branch 'upstream/master' 2016-03-02 08:58:03 +01:00
straccio
1fb58be2b9 ignore idea 2016-03-02 08:47:15 +01:00
Khaos Tian
9d7c1de9dd Update readme to reflect new API system 2016-03-01 18:41:29 -08:00
Khaos Tian
195255bf0d 0.3.0 2016-03-01 18:34:15 -08:00
Khaos Tian
6b182fc4e7 Merge pull request #497 from nfarina/plugin-2
Plugin 2.0
2016-03-01 18:33:27 -08:00
Khaos Tian
c7b2500518 Prepare for merge 2016-03-01 18:28:41 -08:00
Khaos Tian
1f1030766a Update sample plugin 2016-03-01 18:26:40 -08:00
Khaos Tian
8cb22efb83 Add a example to use "identify" event 2016-03-01 00:25:05 -08:00
straccio
ca66cc3499 Merge branch 'master' of https://github.com/straccio/homebridge 2016-02-29 09:06:32 +01:00
straccio
6ae2a19d37 missed ";" 2016-02-29 09:05:42 +01:00
straccio
ffe4232c3b Added pluginPath based on npm 2016-02-29 09:05:42 +01:00
Khaos Tian
f6df85695d Forward identity only if plugin cares about event 2016-02-28 18:19:46 -08:00
Khaos Tian
32e776203f Forward "identify" event 2016-02-28 18:17:46 -08:00
Khaos Tian
c3c2f8815d Merge branch 'master' into plugin-2 2016-02-22 16:52:02 -08:00
Khaos Tian
6500912f54 0.2.19 2016-02-22 16:50:52 -08:00
Khaos Tian
2e2c8eb207 Update hap-nodejs dependency for iOS 9.3 2016-02-22 16:50:33 -08:00
straccio
fa9561d98a missed ";" 2016-02-22 10:01:25 +01:00
straccio
16a29f302d Merge remote-tracking branch 'upstream/master' 2016-02-22 08:53:35 +01:00
Khaos Tian
012005ddc7 Save cached accessories to persist storage when shutting down homebridge. 2016-02-18 14:50:55 -08:00
Khaos Tian
27ffd6e944 Merge branch 'master' into plugin-2 2016-02-18 14:49:05 -08:00
Khaos Tian
0b28387cb1 Merge pull request #532 from torarnv/handle-shutdown-signals
Handle SIGINT and SIGTERM to enable clean shutdown of Homebridge
2016-02-18 14:47:41 -08:00
Khaos Tian
815ea7abea Track setup session termination 2016-02-17 17:45:47 -08:00
Tor Arne Vestbø
cf80e4f2da Handle SIGINT and SIGTERM to enable clean shutdown of Homebridge
For now we terminate the process, but in the future we may tell the
server to stop, which may possibly include some teardown logic.

Handling these signals also make it easier to put Homebridge inside
a docker container, as docker uses SIGTERM to tell a container process
to stop, and passes SIGINT when attached to the container and receiving
a Ctrl+C.
2016-02-18 00:36:56 +01:00
Khaos Tian
40266af8b2 Add the ability to remove services/characteristics 2016-02-17 13:18:25 -08:00
Nick Farina
57beabf0b4 Add comment 2016-02-17 10:43:54 -08:00
Khaos Tian
d3c77a4cda Merge branch 'master' into plugin-2 2016-02-17 10:41:32 -08:00
Khaos Tian
287aa6d034 0.2.18 2016-02-17 10:38:08 -08:00
Khaos Tian
1da98bb19f Add the insecure launching option to homebridge cli. 2016-02-17 10:37:11 -08:00
Nick Farina
aea485c33e Version bump; publish v0.2.17 2016-02-16 13:00:22 -08:00
Khaos Tian
386cf34827 Update hap-nodejs to 0.2.3 so the reachability for existing accessories will be correct. 2016-02-15 19:23:38 -08:00
Khaos Tian
8e360491cf Update hap-nodejs to 0.2.3 2016-02-15 19:22:43 -08:00
Khaos Tian
e546440575 Update hap-nodejs to 0.2.2 2016-02-13 22:44:22 -08:00
Khaos Tian
902fdded65 Address the problem that callback get invoked with wrong signature 😅 2016-02-13 22:21:54 -08:00
Khaos Tian
8de375a4b0 Fix the issue with transaction ID
Update hap-nodejs to fix #497-183825263
2016-02-13 21:57:11 -08:00
Khaos Tian
d04c20ca2f Merge pull request #522 from leoneleone/patch-1
Update hap-nodejs to v0.2.0
2016-02-12 09:52:13 -08:00
leoneleone
c53f890a8c Update hap-nodejs to v0.2.0 2016-02-12 14:58:35 +00:00
Khaos Tian
c02e212b4c bump hap-nodejs version 2016-02-08 14:51:34 -08:00
Khaos Tian
7436be9b44 Add example to update reachability 2016-02-08 14:50:50 -08:00
Khaos Tian
2ad7932fbc Merge pull request #515 from snowdd1/patch-1
Update platformAccessory.js
2016-02-08 14:49:13 -08:00
Raoul
7dd8e12791 Update platformAccessory.js
Some more copy-and-paste errors in the same line.
Was wondering why my platform was failing, but never looked to that simple lines of code :-(
2016-02-08 23:06:32 +01:00
Raoul
c93b0b0df1 Update platformAccessory.js
Small type, big result.
2016-02-08 22:18:54 +01:00
Khaos Tian
b49fd2d6a5 Merge pull request #513 from snowdd1/patch-1
new getServiceByUUIDAndSubtype(UUID, subtype) function
2016-02-07 15:47:46 -08:00
Raoul
9c8812da70 new getServiceByUUIDAndSubtype(UUID, subtype) function
Some platforms may have accessories that contain more than one service of a given type, such as multiple lightbulbs.
2016-02-08 00:01:44 +01:00
Nick Farina
9e6bf028ba Fix license field for Node 2016-02-04 13:34:53 -08:00
Khaos Tian
aebd152ff9 Reverse .gitignore 2016-01-30 21:55:37 -08:00
Khaos Tian
5b9c5192fe add SamplePlatform 2016-01-30 21:55:12 -08:00
Khaos Tian
e1334c5196 Init Plugin 2.0 2016-01-30 18:36:55 -08:00
straccio
40fc7acbed Added pluginPath based on npm 2016-01-27 07:40:26 +01:00
Nick Farina
1a356a1783 Add a fun gif illustrating Why Homebridge 2016-01-23 17:11:25 -08:00
Nick Farina
da8d5fd267 Version bump to v0.2.16 2016-01-22 07:11:30 -08:00
Nick Farina
798275774a Merge pull request #482 from nfarina/update-hap-nodejs
Update hap-nodejs to 0.1.1
2016-01-22 07:10:52 -08:00
Khaos Tian
74e1cf257a Update hap-nodejs to 0.1.1
Addressed #478
2016-01-21 21:20:07 -08:00
Nick Farina
f4aa57314b Merge pull request #467 from rudders/master
Updated README.md to explain Startup Errors.
2016-01-12 20:04:50 -08:00
Adrian Rudman
66505457c7 Updated README.md to explain Startup Errors.
This issue is frequently discussed so let's see if people read the documentation...
2016-01-13 14:30:43 +11:00
Nick Farina
ae24e28ac4 Version bump to v0.2.15
Fix link to searching NPM for plugins—the old "correct" method of
searching for specific keywords doesn't work when the results span
multiple pages (see https://github.com/npm/newww/issues/1630).

This way requires more clicking but actually allows you to discover all
known plugins.
2016-01-07 12:37:24 -08:00
Nick Farina
c8c84c08f2 Bump HAP-NodeJS to v0.0.7; Version bump to v0.2.14 2016-01-06 13:51:47 -08:00
Nick Farina
2213997acc Add note about editing config.json 2015-12-28 16:11:53 -08:00
Nick Farina
3616d61fed Version bump for npm 2015-12-28 09:33:47 -08:00
Nick Farina
e52aff802b Merge pull request #442 from n8henrie/master
Add test for valid username
2015-12-28 09:33:08 -08:00
Nathan Henrie
15bf22805c Add test for valid username
Like a MAC address, a valid username can only contain 6 pairs of
hexadecimal characters, and for homebridge purposes must be
colon-separated. This adds a simple regex test for a proper username,
since I just spent nearly 24h trying to debug an issue that all came
down to not realizing that a MAC address could only have letters A-F
(see https://github.com/nfarina/homebridge/issues/385).
2015-12-27 10:29:21 -07:00
Nick Farina
f5b790ff35 Just make the sudo explicit 2015-11-30 14:01:40 -08:00
Nick Farina
08f8ae53c5 Link to HomeKitTypes 2015-11-28 12:16:40 -08:00
Nick Farina
2f0c60984a Remove notice at top
many plugins have now been extracted!
2015-11-28 08:54:56 -08:00
Nick Farina
507835986b Merge pull request #403 from victorklijmeij/patch-1
correct errors
2015-11-21 11:33:09 -08:00
victorklijmeij
9567c41a58 correct errors
Json was found invalid on jsonlint.com
2015-11-21 20:22:16 +01:00
Nick Farina
ec839b2dbc Tweaks to plugin load error detection 2015-11-11 15:16:29 -08:00
Nick Farina
2d73e70670 Merge pull request #387 from knalli/patch-1
Provide and output proper existing error message
2015-11-11 15:15:35 -08:00
Nick Farina
65b74253df Fix command-line examples 2015-11-09 11:29:22 -08:00
Nick Farina
78b8c265e1 Include repo and bugs in example plugin 2015-11-09 10:59:49 -08:00
Jan Philipp
affe3fde63 Provide and output proper existing error message
If the found plugin / package does not fulfill some specs (i.e. does not contain the keyword `homebridge-plugin`), it will be rejected with an error. Without that change nobody will be notified WHY the plugin has been rejected/skipped.
2015-11-08 17:31:02 +01:00
Nick Farina
e3a5c5a96a More helpful plugin load error logging; Publish v0.2.11 2015-11-05 10:48:32 -08:00
Nick Farina
b3dbe1165c New "Common Issues" section 2015-11-05 06:35:51 -08:00
Nick Farina
0b9e931fe1 Tweak README text 2015-11-05 06:20:33 -08:00
Nick Farina
50efd29b6d Fix (remove) relative links in README; publish v0.2.10 2015-11-04 16:22:49 -08:00
Nick Farina
6d2cb11eae Publish v0.2.9 2015-11-04 15:59:49 -08:00
Nick Farina
7bfc5ee84d Expose User object to Plugin API
Closes #379
Changes #378
2015-11-04 15:59:38 -08:00
Nick Farina
8f1a043121 Publish v0.2.8 2015-11-04 15:52:08 -08:00
Nick Farina
a5de3f820f Don't search the same plugin path twice
Fix part of #379
2015-11-04 15:50:23 -08:00
Nick Farina
8e795f48cf Add note about legacy plugins; publish v0.2.7 2015-11-04 10:29:14 -08:00
Nick Farina
1c4bf23b60 Publish v0.2.6 2015-11-04 10:22:47 -08:00
Nick Farina
988c6ef6d0 Basic info on writing plugins 2015-11-04 10:22:36 -08:00
Nick Farina
52aa6bba2c Publish v0.2.5 2015-11-04 09:48:05 -08:00
Nick Farina
f2ad339a13 CLI setting for custom homebridge "home" path
Fixes #378
2015-11-04 09:47:48 -08:00
Nick Farina
9883eb7189 Add Slack info to README 2015-11-04 09:37:20 -08:00
Nick Farina
974eef3ea8 HAP-NodeJS -> v0.0.3, publish v0.2.3 2015-11-04 06:18:57 -08:00
Nick Farina
752a5b7854 Publish v0.2.2 2015-11-04 04:35:44 -08:00
Nick Farina
15357a5fe8 Fix old-style accessory JSON
Closes #375
2015-11-04 04:35:22 -08:00
Nick Farina
e3178e5fa4 Fix -D flag
Fixes #376
2015-11-04 04:31:52 -08:00
Nick Farina
2470f26451 Better handling of custom plugin paths 2015-11-03 21:14:25 -08:00
Nick Farina
52d51c0fcf README tweaks 2015-11-03 15:31:26 -08:00
Nick Farina
3e7d2aedcc Merge pull request #293 from nfarina/plugin-system
Plugin system
2015-11-03 15:22:53 -08:00
Nick Farina
54cff454dd Merge branch 'master' into plugin-system 2015-11-03 15:12:06 -08:00
Nick Farina
46c4b6f589 Trim down config-sample.json 2015-11-03 15:09:56 -08:00
Nick Farina
abc6ccfae7 Require plugin name during registration 2015-11-03 15:02:52 -08:00
Nick Farina
eda8e91d0f Merge pull request #303 from PJCzx/master
Semi working HTTP garage door implementation
2015-11-03 10:26:28 -08:00
Nick Farina
269a8cf43a Merge pull request #320 from fcarucci/master
LiftMaster: return garage door state
2015-11-03 10:25:41 -08:00
Nick Farina
2df1ebed21 Merge pull request #357 from mifi/master
added serialport library as a dependency
2015-11-03 09:34:18 -08:00
Nick Farina
36ed2ab0ad Merge pull request #366 from chkr1011/SupportForHttpBody
Support for http body
2015-11-03 09:30:14 -08:00
Nick Farina
193810691b Merge pull request #370 from ilcato/patch-2
Virtual device optimization
2015-11-03 08:50:30 -08:00
ilcato
7db78a50b5 Virtual device optimization
Managed Fibaro Virtual Devices push buttons with a single HomeKit Bridged Accessory with multiple Switches
2015-11-03 17:26:04 +01:00
Nick Farina
eabf45526d Guard against multiple WeMo callbacks
Closes #296
2015-11-03 06:37:53 -08:00
Nick Farina
52d3a9a96e First pass at README update. 2015-11-02 15:30:29 -08:00
Christian
a682e1dee5 Updated the example configuration. 2015-11-03 00:05:38 +01:00
Christian
5440694243 Renamed duplicate body variable. 2015-11-03 00:04:46 +01:00
Christian
6011ad3d2e The HTTP accessory now supports a body value. 2015-11-02 23:55:15 +01:00
Nick Farina
15cc98217b Move bugged peerDependencies -> engines 2015-11-02 12:04:18 -08:00
Nick Farina
108602a6b0 Merge pull request #361 from kemathy/master
Http accessory improvement
2015-11-02 11:55:42 -08:00
Nick Farina
98d8158d04 Merge pull request #360 from KraigM/hotfix/PhilipsHueUnreachable
Fixed unreachable PhilipsHue bulbs to show up as off.
2015-11-02 11:55:02 -08:00
Nick Farina
a62137477a Merge pull request #359 from KraigM/hotfix/NestHumidity
Fixed humidity on Nest
2015-11-02 11:54:41 -08:00
Nick Farina
386eac8094 Merge pull request #355 from ilcato/patch-1
Added Fibaro Virtual devices management
2015-11-02 11:52:59 -08:00
Nick Farina
9dcebb7320 Merge pull request #353 from thkl/master
HomeMatic Platform - new Devices and Bugfixing
2015-11-02 11:46:01 -08:00
Kevin Mathy
91044f0d92 Http.js shim modification corrections 2015-11-01 22:00:17 +01:00
Kraig McConaghy
82c62f78ed Fixed unreachable PhilipsHue bulbs to show up as off. If you have a manual switch hooked up to a hue bulb, turning it off the switch makes it unreachable but doesn't mark it as off. 2015-11-01 12:42:03 -06:00
Kraig McConaghy
bc1c879412 Fixed humidity and added change handling of settings (like Celsius/Fahrenheit) 2015-11-01 10:16:54 -06:00
Thomas Kluge
c1cc8be8fa new Devices : Smokedetector 2015-11-01 12:57:29 +01:00
Thomas Kluge
bccdbd1755 Merge remote-tracking branch 'homebridge/master' 2015-11-01 12:54:28 +01:00
Mikael Finstad
9f42689256 added serialport library as a dependency 2015-11-01 02:29:11 +01:00
ilcato
73b61fd730 Added Fibaro Virtual devices management 2015-10-31 20:02:29 +01:00
Nick Farina
90feacf53f Merge pull request #352 from planetk/master
Netatmo: new API and added noise and atmospheric pressure
2015-10-31 10:56:41 -07:00
Nick Farina
127741a737 Merge pull request #351 from pedroserano/patch-1
Fix for Issue 343
2015-10-31 10:56:00 -07:00
Thomas Kluge
5c92157009 removed setup 2015-10-31 16:28:51 +01:00
bwilliot
70f2dc6230 Update PhilipsHue.js
fix issue
2015-10-31 16:03:53 +01:00
Thomas König
1dfa190b18 Add HomeMatic Platform sample 2015-10-31 16:03:53 +01:00
Thomas König
7623bf4204 cleanup formatting 2015-10-31 16:03:53 +01:00
Thomas König
6b36ec9dc4 cleanup formatting 2015-10-31 16:03:53 +01:00
Thomas König
b277b76c39 reintegrated Homematic Platform fork from https://github.com/thkl/homebridge/tree/xmlrpc 2015-10-31 16:03:53 +01:00
stipus
3b19613528 SecuritySystem,Window, WindowCovering, obstruction 2015-10-31 16:03:53 +01:00
Stefan Kuper
48bec8a1bc new API and added noise and atmospheric pressure 2015-10-31 14:56:24 +01:00
pedroserano
cfed5d7a66 Fix for Issue 343
Fix for Issue 343 as described here: https://github.com/nfarina/homebridge/issues/343
2015-10-31 09:22:12 -04:00
Nick Farina
75417fb322 Merge pull request #350 from bwilliot/patch-1
Update PhilipsHue.js
2015-10-31 06:18:20 -07:00
Thomas Kluge
04f48ecbab fixed thermostats 2015-10-31 12:02:06 +01:00
bwilliot
ec015e58a3 Update PhilipsHue.js
fix issue
2015-10-31 11:30:01 +01:00
Kevin Mathy
bee6d03d80 New fields for Http Accessory 2015-10-30 23:02:46 +01:00
Kevin Mathy
255a064c08 Http Accessory improvements : accessory type (switch or light), and ligtht brightness handling toggle 2015-10-30 22:55:53 +01:00
Thomas Kluge
727809e9b4 removed some logs
changed to autodetection if hm channel is supported yet
2015-10-30 18:30:07 +01:00
Thomas Kluge
ea1f75abb0 setup.sh complete installation on a pi2 2015-10-30 18:13:31 +01:00
Thomas Kluge
ead491fb4c switch back from xmlrpc branch to master 2015-10-30 16:50:31 +01:00
Nick Farina
7362b48df7 Merge pull request #346 from tkoenig/master
Homematic Platform
2015-10-29 13:36:33 -07:00
Thomas König
5f82fa6fb6 Add HomeMatic Platform sample 2015-10-29 21:04:32 +01:00
Thomas König
fd49b96d78 cleanup formatting 2015-10-29 20:33:07 +01:00
Thomas König
cbc34897f9 cleanup formatting 2015-10-29 19:15:35 +01:00
Thomas König
98c61bc72a reintegrated Homematic Platform fork from https://github.com/thkl/homebridge/tree/xmlrpc 2015-10-29 19:06:47 +01:00
Nick Farina
fe4c63fd75 Merge pull request #342 from stipus/master
Added SecuritySystem,Window/Covering,obstruction
2015-10-29 04:55:57 -07:00
stipus
2e6a487f80 SecuritySystem,Window, WindowCovering, obstruction 2015-10-29 12:16:05 +01:00
stipus
5cf54d7ff6 Merge pull request #2 from nfarina/master
Update fork from nfarina/homebridge/master
2015-10-29 12:12:10 +01:00
Nick Farina
94c5bd6de0 Merge pull request #337 from KraigM/hotfix/PhilipsHue
Fixed issue on Philips Hue where initial state was not being loaded
2015-10-27 21:56:32 -07:00
Kraig McConaghy
e944d3ed2a Fixed issue on Philips Hue where initial state was not being loaded (quick fix). 2015-10-27 23:55:06 -05:00
Nick Farina
3d6ddfc520 Merge pull request #336 from KraigM/feature/Nest
Enhancements to Nest Platform including support for range temperatures
2015-10-27 21:23:42 -07:00
Kraig McConaghy
528c83bd1a Brought Nest feature up to date with master branch 2015-10-27 23:14:25 -05:00
Kraig McConaghy
9740a50520 Several fixes. Full simulated temperature range support. 2015-10-27 23:07:34 -05:00
Nick Farina
af53950080 Merge pull request #335 from patricks/master
Using the new getStationsData() api call
2015-10-27 16:19:30 -04:00
Patrick
909900d025 Using the new getStationsData() api call, the station name is saved to every module name, so we avoid duplicate accessories UUIDs 2015-10-27 20:41:24 +01:00
Patrick
06c7356aef updated to new netatmo node module 2015-10-27 19:24:43 +01:00
Nick Farina
c591cf6899 Merge pull request #334 from dvinc/philips-hue-read
Philips Hue: Read lightbulb status / update to new HAP-NodeJS Service API
2015-10-27 10:29:50 -04:00
Nick Farina
890f6acff0 Merge pull request #331 from KraigM/feature/Wink
Enhancements to Wink Platform including addition of Locks
2015-10-27 10:20:38 -04:00
Vincent Drevelle
debbc14fef Fix case in hap-nodejs module require statements 2015-10-27 15:07:15 +01:00
Vincent Drevelle
5e9c3a3b62 Merge with upstream 2015-10-27 11:02:27 +01:00
Kraig McConaghy
1f8e6e5c8b Added WinkLockAccessory 2015-10-26 22:49:36 -05:00
Kraig McConaghy
35ecb9e170 Switched to use the new HAP APIs, extracted base class WinkAccessory, and changed update system to properly notify changes on a timed basis. Functionality wise, left the LightBulb implementation the same. 2015-10-26 22:48:02 -05:00
fcarucci
b65acecb28 Merge remote-tracking branch 'upstream/master' 2015-10-26 13:01:29 -07:00
Nick Farina
e78a68352f Merge pull request #327 from tommasomarchionni/patch-1
Update Openhab.js with new structure.
2015-10-26 13:24:09 -04:00
Nick Farina
b21c37712d Merge pull request #325 from patricks/master
Fixed netatmo problem with duplicate module names
2015-10-26 13:23:45 -04:00
Tommaso Marchionni
65d732415e Update Openhab.js 2015-10-26 00:32:40 +01:00
Patrick
dc6faa9784 fixed netatmo problem with duplicate module names 2015-10-25 21:57:46 +01:00
Nick Farina
3da52388e8 Merge pull request #322 from mvanholstyn/master
adding support for usernames and password in http accessories
2015-10-25 10:03:31 -04:00
Mark Van Holstyn
ecdffbef23 adding support for usernames and password in http accessories 2015-10-24 23:22:23 -04:00
Francesco Carucci
76136589f4 LiftMaster: return current door state 2015-10-24 21:46:24 +00:00
Francesco Carucci
b4acdf3865 Change the garage door accessory name 2015-10-24 21:45:44 +00:00
Nick Farina
9a798855f5 Merge pull request #319 from planetk/netatmo
netatmo weatherstation first implementation
2015-10-24 14:52:30 -04:00
Stefan Kuper
6e156e48fb netatmo first imply 2015-10-24 20:06:30 +02:00
Pierre-Julien Cazaux
de06a2b12d Semi working HTTP garage door implementation
Currently, when use « Siri, open/close garage door » got the correct
answer on logs but my iPhone reply something like « The garage door in
now wrong » and the Insteon+ app icon is not updated.

I quite beginner I would like a hint :) THX
2015-10-21 10:59:13 +02:00
Nick Farina
1779ddd754 Fix "once" import 2015-10-20 12:00:27 -07:00
Nick Farina
d443286dcb Fix platform logger 2015-10-20 11:36:06 -07:00
Nick Farina
74f0f00321 Fix stray comma 2015-10-20 09:31:57 -07:00
Kraig McConaghy
b5210f424f More fixes 2015-10-19 23:16:31 -05:00
Nick Farina
27b39cbfa0 New Plugin API
- Homebridge calls single exported initializer function and passes an
API object
  - No more require() for HAP classes (doesn't play well with plugin
structure)
2015-10-18 22:20:06 -07:00
Nick Farina
a3c0df1c7c Plugin support
- Homebridge is now designed to be `npm install`d globally and
executed via "homebridge" script
  - Remove all specific accessories/platforms except for an example
  - New internal structure and "cli"
2015-10-18 16:34:21 -07:00
Kraig McConaghy
c897913005 Initial work. Still in progress 2015-10-17 20:51:57 -05:00
stipus
c6b248e0ea Merge pull request #1 from nfarina/master
sync with nfarina/homebridge
2015-10-16 17:21:55 +02:00
Vincent Drevelle
84cac442e2 Read Philips Hue lights status from bridge.
Update the PhilipsHue.js platform to use HAP-NodeJS AccessoryInformation and LightBulb Service classes.
2015-09-29 11:59:58 +02:00
64 changed files with 2103 additions and 15362 deletions

14
.gitignore vendored
View File

@@ -7,14 +7,8 @@ node_modules/
npm-debug.log
.node-version
# Intellij
.idea/
*.iml
# Ignore any extra plugins in the example directory that aren't in Git already
# (this is a sandbox for the user)
example-plugins
# HomeBridge
config.json
config.test.json
persist/
.AppleDouble
.idea

0
.gitmodules vendored
View File

198
README.md
View File

@@ -1,120 +1,164 @@
[![Slack Status](https://homebridge-slackin.herokuapp.com/badge.svg)](https://slackin-xiwztisllv.now.sh)
# Homebridge
Homebridge is a lightweight NodeJS server you can run on your home network that emulates the iOS HomeKit API. It includes a set of "shims" (found in the [accessories](accessories/) and [platforms](platforms/) folders) that provide a basic bridge from HomeKit to various 3rd-party APIs provided by manufacturers of "smart home" devices.
![](https://media.giphy.com/media/10l79ICohTu4iQ/giphy.gif)
Since Siri supports devices added through HomeKit, this means that with Homebridge you can ask Siri to control devices that don't have any support for HomeKit at all. For instance, using the included shims, you can say things like:
Homebridge is a lightweight NodeJS server you can run on your home network that emulates the iOS HomeKit API. It supports Plugins, which are community-contributed modules that provide a basic bridge from HomeKit to various 3rd-party APIs provided by manufacturers of "smart home" devices.
* _Siri, unlock the front door._ ([Lockitron](https://lockitron.com))
* _Siri, open the garage door._ ([LiftMaster MyQ](https://www.myliftmaster.com))
* _Siri, turn on the Leaf._ ([Carwings](http://www.nissanusa.com/innovations/carwings.article.html))
* _Siri, turn off the Speakers._ ([Sonos](http://www.sonos.com))
* _Siri, turn on the Dehumidifier._ ([WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/))
* _Siri, turn on Away Mode._ ([Xfinity Home](http://www.comcast.com/home-security.html))
* _Siri, turn on the living room lights._ ([Wink](http://www.wink.com), [SmartThings](http://www.smartthings.com), [X10](http://github.com/edc1591/rest-mochad), [Philips Hue](http://meethue.com), [Home Assistant](http://home-assistant.io) [LimitlessLED/MiLight/Easybulb](http://www.limitlessled.com/), [LIFx](http://www.lifx.com/))
* _Siri, set the movie scene._ ([Logitech Harmony](http://myharmony.com/))
Since Siri supports devices added through HomeKit, this means that with Homebridge you can ask Siri to control devices that don't have any support for HomeKit at all. For instance, using just some of the available plugins, you can say:
If you would like to support any other devices, please write a shim and create a pull request and I'd be happy to add it to this official list.
* _Siri, unlock the back door._ [pictured above]
* _Siri, open the garage door._
* _Siri, turn on the coffee maker._
* _Siri, turn on the living room lights._
* _Siri, good morning!_
# Shim types
There are 2 types of shims supported in Homebridge.
You can explore all available plugins at the NPM website by [searching for the keyword `homebridge-plugin`](https://www.npmjs.com/search?q=homebridge-plugin).
* Accessory - Individual device
* Platform - A full bridge to another system
# Community
## Accessories
If you're having an issue with a particular plugin, open an issue in that plugin's Github repository. If you're having an issue with Homebridge itself, feel free to open issues and PRs here.
Accessories are individual devices you would like to bridge to HomeKit. You set them up by declaring them individually in your `config.json` file. Generally, you specify them by `name` or `id` and which system they use.
You can also chat with us in our nascent [Slack instance](http://homebridge-slackin.herokuapp.com).
## Platforms
# Installation
Platforms bridge entire systems to HomeKit. Platforms can be things like Wink or SmartThings or Vera. By adding a platform to your `config.json`, Homebridge will automatically detect all of your devices for you.
**Note:** If you're running on Linux, you'll need to make sure you have the `libavahi-compat-libdnssd-dev` package installed. If you're running on a Raspberry Pi, you should have a look at the [Wiki](https://github.com/nfarina/homebridge/wiki/Running-HomeBridge-on-a-Raspberry-Pi).
All you have to do is add the right config options so Homebridge can authenticate and communicate with your other system, and voila, your devices will be available to HomeKit via Homebridge.
Homebridge is published through [NPM](https://www.npmjs.com/package/homebridge) and should be installed "globally" by typing:
# Why?
sudo npm install -g --unsafe-perm homebridge
Technically, the device manufacturers should be the ones implementing the HomeKit API. And I'm sure they will - eventually. When they do, these shims will be obsolete, and I hope that happens soon. In the meantime, this server is a fun way to get a taste of the future, for those who just can't bear to wait until "real" HomeKit devices are on the market.
You may need to use the `--unsafe-perm` flag if you receive an error similar to this:
# Credit
gyp WARN EACCES user "root" does not have permission to access the dev dir "/root/.node-gyp/5.5.0"
Homebridge itself is basically just a set of shims and a README. The actual HomeKit API work was done by [KhaosT](http://twitter.com/khaost) in his [HAP-NodeJS](https://github.com/KhaosT/HAP-NodeJS) project. Additionally, many of the shims benefit from amazing NodeJS projects out there like `sonos` and `wemo` that implement all the interesting functionality.
Now you should be able to run Homebridge:
# Before you Begin
$ homebridge
No plugins found. See the README for information on installing plugins.
I would call this project a "novelty" in its current form, and is for **intrepid hackers only**. To make any of this work, you'll need:
Homebridge will complain if you don't have any Plugins installed, since it will essentially be useless, although you can still "pair" with it. See the next section "Installing Plugins" for more info.
* An app on your iOS device that can manage your HomeKit database.
* An always-running server (like a Raspberry Pi) on which you can install NodeJS.
* Knowledge of Git submodules and npm.
Once you've installed a Plugin or two, you can run Homebridge again:
You'll also need some patience, as Siri can be very strict about sentence structure, and occasionally she will forget about HomeKit altogether. But it's not surprising that HomeKit isn't rock solid, since almost no one can actually use it today besides developers who are creating hardware accessories for it. There are, to my knowledge, exactly zero licensed HomeKit devices on the market right now, so Apple can easily get away with this all being a work in progress.
# Getting Started
OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up.
**Note:** If you're running on Linux, you'll need to make sure you have the `libavahi-compat-libdnssd-dev` package installed.
First, clone this repo:
$ git clone https://github.com/nfarina/homebridge.git
$ cd homebridge
$ npm install
**Note**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load.
Now you should be able to run the homebridge server:
$ cd homebridge
$ npm run start
Starting Homebridge server...
$ homebridge
Couldn't find a config.json file [snip]
The server won't do anything until you've created a `config.json` file containing your home devices (or _accessories_ in HomeKit parlance) or platforms you wish to make available to iOS. You can start by copying and modifying the included `config-sample.json` file which includes declarations for all supported accessories and platforms.
However, Homebridge won't do anything until you've created a `config.json` file containing your accessories and/or platforms. You can start by copying and modifying the included `config-sample.json` file which includes declarations for some example accessories and platforms. Each Plugin will have its own expected configuration; the documentation for Plugins should give you some real-world examples for that plugin.
Once you've added your devices and/or platforms, you should be able to run the server again and see them initialize:
**NOTE**: Your `config.json` file MUST be inside of `.homebridge`, which is inside of your home folder. On macOS and Linux, the full path for your `config.json` would be `~/.homebridge/config.json`. Any error messages will contain the exact path where your config is expected to be found.
$ npm run start
Starting Homebridge server...
Loading 6 accessories...
[Speakers] Initializing 'Sonos' accessory...
[Coffee Maker] Initializing 'WeMo' accessory...
[Speakers] Initializing 'Sonos' accessory...
[Coffee Maker] Initializing 'WeMo' accessory...
[Wink] Initializing Wink platform...
[Wink] Fetching Wink devices.
[Wink] Initializing device with name Living Room Lamp...
**REALLY IMPORTANT**: You must use a "plain text" editor to create or modify `config.json`. Do NOT use apps like TextEdit on Mac or Wordpad on Windows. Apps like these will corrupt the formatting of the file in hard-to-debug ways, making improper `"` signs is an example. I suggest using the free [Atom text editor](http://atom.io).
Your server is now ready to receive commands from iOS.
Once you've added your config file, you should be able to run Homebridge again:
# Adding your devices to iOS
$ homebridge
Loaded plugin: homebridge-lockitron
Registering accessory 'Lockitron'
---
Loaded config.json with 1 accessories and 0 platforms.
---
Loading 0 platforms...
Loading 1 accessories...
[Back Door] Initializing Lockitron accessory...
HomeKit is actually not an app; it's a "database" similar to HealthKit and PassKit. But where HealthKit has the companion _Health_ app and PassKit has _Passbook_, Apple has supplied no app for managing your HomeKit database (at least [not yet](http://9to5mac.com/2015/05/20/apples-planned-ios-9-home-app-uses-virtual-rooms-to-manage-homekit-accessories/)). However, the HomeKit API is open for developers to write their own apps for adding devices to HomeKit.
Homebridge is now ready to receive commands from iOS.
Fortunately, there are now a few apps in the App Store that can manage your HomeKit devices. The most comprehensive one I've used is [MyTouchHome](https://itunes.apple.com/us/app/mytouchhome/id965142360?mt=8&at=11lvmd&ct=mhweb) which costs $2.
# Installing Plugins
There are also some free apps that work OK. Try [Insteon+](https://itunes.apple.com/US/app/id919270334?mt=8) or [Lutron](https://itunes.apple.com/us/app/lutron-app-for-caseta-wireless/id886753021?mt=8) or a number of others.
Plugins are NodeJS modules published through NPM and tagged with the keyword `homebridge-plugin`. They must have a name with the prefix `homebridge-`, like **homebridge-mysmartlock**.
If you are a member of the iOS developer program, I highly recommend Apple's [HomeKit Catalog](https://developer.apple.com/library/ios/samplecode/HomeKitCatalog/Introduction/Intro.html) app, as it is reliable and comprehensive and free (and open source).
Plugins can publish Accessories and/or Platforms. Accessories are individual devices, like a smart switch or a garage door. Platforms act like a single device but can expose a set of devices, like a house full of smart lightbulbs.
## Adding HomeKit Accessories
You install Plugins the same way you installed Homebridge - as a global NPM module. For example:
Once you've gotten a HomeKit app running on your iOS device, you can use it to add your Homebridge devices. The app should "discover" the single accessory "Homebridge", assuming that you're still running the Homebridge server and you're on the same Wifi network. Adding this accessory will automatically add all accessories and platforms defined in `config.json`.
sudo npm install -g homebridge-lockitron
When you attempt to add Homebridge, it will ask for a "PIN code". The default code is `031-45-154` (but this can be changed, see `config-sample.json`). This process will create some files in the `persist` directory of the Homebridge server, which stores the pairing relationship.
You can explore all available plugins at the NPM website by [searching for the keyword `homebridge-plugin`](https://www.npmjs.com/search?q=homebridge-plugin).
**IMPORTANT**: Many of the plugins that Homebridge used to include with its default installation have been moved to the single plugin [homebridge-legacy-plugins](https://www.npmjs.com/package/homebridge-legacy-plugins).
# Adding Homebridge to iOS
HomeKit itself is actually not an app; it's a "database" similar to HealthKit and PassKit. Where HealthKit has the companion _Health_ app and PassKit has _Passbook_, HomeKit has the _Home_ app, introduced with iOS 10.
If you are a member of the iOS developer program, you might also find Apple's [HomeKit Catalog](https://developer.apple.com/library/ios/samplecode/HomeKitCatalog/Introduction/Intro.html) app to be useful, as it provides straightforward and comprehensive management of all HomeKit database "objects".
Using the Home app (or most other HomeKit apps), you should be able to add the single accessory "Homebridge", assuming that you're still running Homebridge and you're on the same Wifi network. Adding this accessory will automatically add all accessories and platforms defined in `config.json`.
When you attempt to add Homebridge, it will ask for a "PIN code". The default code is `031-45-154` (but this can be changed, see `config-sample.json`).
# Interacting with your Devices
Once your device has been added to HomeKit, you should be able to tell Siri to control your devices. However, realize that Siri is a cloud service, and iOS may need some time to synchronize your device information with iCloud.
One final thing to remember is that Siri will almost always prefer its default phrase handling over HomeKit devices. For instance, if you name your Sonos device "Radio" and try saying "Siri, turn on the Radio" then Siri will probably start playing an iTunes Radio station on your phone. Even if you name it "Sonos" and say "Siri, turn on the Sonos", Siri will probably just launch the Sonos app instead. This is why, for instance, the suggested `name` for the Sonos shim in `config-samples.json` is "Speakers".
One final thing to remember is that Siri will almost always prefer its default phrase handling over HomeKit devices. For instance, if you name your Sonos device "Radio" and try saying "Siri, turn on the Radio" then Siri will probably start playing an iTunes Radio station on your phone. Even if you name it "Sonos" and say "Siri, turn on the Sonos", Siri will probably just launch the Sonos app instead. This is why, for instance, the suggested `name` for the Sonos accessory is "Speakers".
# Final Notes
# Writing Plugins
HomeKit is definitely amazing when it works. Speaking to Siri is often much quicker and easier than launching whatever app your device manufacturer provides.
We don't have a lot of documentation right now for creating plugins, but there are many existing plugins you can study.
I welcome any suggestions or pull requests, but keep in mind that it's likely not possible to support all the things you might want to do with a device through HomeKit. For instance, you might want to hack the Sonos shim to play the specific kind of music you want and that's great, but it might not be appropriate to merge those specific changes into this repository. The shims here should be mostly simple "canonical examples" and easily hackable by others.
The best place to start is the included [Example Plugins](https://github.com/nfarina/homebridge/tree/master/example-plugins). Right now this contains a single plugin that registers a platform that offers fake light accessories. This will show you how to use the Homebridge Plugin API.
Good luck!
For more example on how to construct HomeKit Services and Characteristics, see the many Accessories in the [Legacy Plugins](https://github.com/nfarina/homebridge-legacy-plugins/tree/master/accessories) repository.
You can also view the [full list of supported HomeKit Services and Characteristics in the HAP-NodeJS protocol repository](https://github.com/KhaosT/HAP-NodeJS/blob/master/lib/gen/HomeKitTypes.js).
And you can find an example plugin that publishes an individual accessory at [here](https://github.com/nfarina/homebridge/tree/6500912f54a70ff479e63e2b72760ab589fa558a/example-plugins/homebridge-lockitron).
See more examples on how to create Platform classes in the [Legacy Plugins](https://github.com/nfarina/homebridge-legacy-plugins/tree/master/platforms) repository.
# Plugin Development
When writing your plugin, you'll want Homebridge to load it from your development directory instead of publishing it to `npm` each time. You can tell Homebridge to look for your plugin at a specific location using the command-line parameter `-P`. For example, if you are in the Homebridge directory (as checked out from Github), you might type:
```sh
DEBUG=* ./bin/homebridge -D -P ../my-great-plugin/
```
This will start up Homebridge and load your in-development plugin from a nearby directory. Note that you can also direct Homebridge to load your configuration from somewhere besides the default `~/.homebridge`, for example:
```sh
DEBUG=* ./bin/homebridge -D -U ~/.homebridge-dev -P ../my-great-plugin/
```
This is very useful when you are already using your development machine to host a "real" Homebridge instance (with all your accessories) that you don't want to disturb.
# Common Issues
### My iOS App Can't Find Homebridge
Two reasons why Homebridge may not be discoverable:
1. Homebridge server thinks it's been paired with, but iOS thinks otherwise. Fix: deleted `persist/` directory which is next to your `config.json`.
2. iOS device has gotten your Homebridge `username` (looks like a MAC address) "stuck" somehow, where it's in the database but inactive. Fix: change your `username` in the "bridge" section of `config.json` to be some new value.
### Errors on startup
The following errors are experienced when starting Homebridge and can be safely ignored. The cost of removing the issue at the core of the errors isn't worth the effort.
```
*** WARNING *** The program 'nodejs' uses the Apple Bonjour compatibility layer of Avahi
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see http://0pointerde/avahi-compat?s=libdns_sd&e=nodejs
*** WARNING *** The program 'nodejs' called 'DNSServiceRegister()' which is not supported (or only supported partially) in the Apple Bonjour compatibility layer of Avahi
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see http://0pointerde/avahi-compat?s=libdns_sd&e=nodejs&f=DNSServiceRegister
```
### Limitations
* One installation of Homebridge can only expose 100 accessories due to a HomeKit limit. You can however run multiple Homebridge instances by pointing them to different config and persistence paths (see issue #827).
* Once an accessory has been added to the Home app, changing its name via Homebridge won't be automatically reflected in iOS. You must change it via the Home app as well.
# Why Homebridge?
Technically, the device manufacturers should be the ones implementing the HomeKit API. And I'm sure they will - eventually. When they do, this project will be obsolete, and I hope that happens soon. In the meantime, Homebridge is a fun way to get a taste of the future, for those who just can't bear to wait until "real" HomeKit devices are on the market.
# Credit
The original HomeKit API work was done by [KhaosT](http://twitter.com/khaost) in his [HAP-NodeJS](https://github.com/KhaosT/HAP-NodeJS) project.

View File

@@ -1,245 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var AD2USB = require('ad2usb');
var CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8";
function AD2USBAccessory(log, config) {
this.log = log;
this.name = config["name"];
this.host = config["host"];
this.port = config["port"];
this.pin = config["pin"];
var that = this;
this.currentArmState = 2;
this.currentStateCharacteristic = undefined;
this.targetStateCharacteristic = undefined;
this.lcdCharacteristic = undefined;
var alarm = AD2USB.connect(this.host, this.port, function() {
// Send an initial empty character to get status
alarm.send('');
// Armed Away
alarm.on('armedAway', function() {
that.log("Armed to AWAY");
if (that.currentStateCharacteristic) {
that.currentStateCharacteristic.updateValue(0, null);
}
if (that.targetStateCharacteristic) {
that.targetStateCharacteristic.updateValue(1, null);
}
});
// Armed Stay
alarm.on('armedStay', function() {
that.log("Armed to STAY");
if (that.currentStateCharacteristic) {
that.currentStateCharacteristic.updateValue(0, null);
}
if (that.targetStateCharacteristic) {
that.targetStateCharacteristic.updateValue(0, null);
}
});
// Armed Night
alarm.on('armedNight', function() {
that.log("Armed to NIGHT");
if (that.currentStateCharacteristic) {
that.currentStateCharacteristic.updateValue(0, null);
}
if (that.targetStateCharacteristic) {
that.targetStateCharacteristic.updateValue(2, null);
}
});
// Disarmed
alarm.on('disarmed', function() {
that.log("Disarmed");
if (that.currentStateCharacteristic) {
that.currentStateCharacteristic.updateValue(1, null);
}
if (that.targetStateCharacteristic) {
that.targetStateCharacteristic.updateValue(3, null);
}
});
// Text Change
alarm.on('lcdtext', function(newText) {
that.log("LCD: " + newText);
if (that.lcdCharacteristic) {
that.lcdCharacteristic.updateValue(newText, null);
}
});
});
this.alarm = alarm;
}
AD2USBAccessory.prototype = {
setArmState: function(targetArmState) {
var that = this;
that.log("Desired target arm state: " + targetArmState);
// TARGET
// 0 - Stay
// 1 - Away
// 2 - Night
// 3 - Disarm
if (targetArmState == 0) {
that.alarm.armStay(that.pin);
}
else if (targetArmState == 1) {
that.alarm.armAway(that.pin);
}
else if (targetArmState == 2) {
that.alarm.armNight(that.pin);
}
else if (targetArmState == 3) {
that.alarm.disarm(that.pin);
}
// CURRENT
// 0 - Armed
// 1 - Disarmed
// 2 - Hold
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Nutech",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "AD2USB",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "AD2USBIF",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.ALARM_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.ALARM_CURRENT_STATE_CTYPE,
onUpdate: null,
onRegister: function(characteristic) {
that.currentStateCharacteristic = characteristic;
characteristic.eventEnabled = true;
},
perms: ["pr","ev"],
format: "int",
initialValue: 2,
supportEvents: true,
supportBonjour: false,
manfDescription: "Alarm current arm state",
designedMaxLength: 1
},{
cType: types.ALARM_TARGET_STATE_CTYPE,
onUpdate: function(value) { that.setArmState(value); },
onRegister: function(characteristic) {
that.targetStateCharacteristic = characteristic;
characteristic.eventEnabled = true;
},
perms: ["pw","pr","ev"],
format: "int",
initialValue: 1,
supportEvents: true,
supportBonjour: false,
manfDescription: "Alarm target arm state",
designedMaxLength: 1
},
{
cType: CUSTOM_PANEL_LCD_TEXT_CTYPE,
onUpdate: null,
onRegister: function(characteristic) {
that.lcdCharacteristic = characteristic;
characteristic.eventEnabled = true;
},
perms: ["pr","ev"],
format: "string",
initialValue: "Unknown",
supportEvents: false,
supportBonjour: false,
manfDescription: "Keypad Text",
designedMaxLength: 64
}]
}];
}
};
module.exports.accessory = AD2USBAccessory;

View File

@@ -1,126 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var carwings = require("carwingsjs");
function CarwingsAccessory(log, config) {
this.log = log;
this.name = config["name"];
this.username = config["username"];
this.password = config["password"];
}
CarwingsAccessory.prototype = {
setPowerState: function(powerOn) {
var that = this;
carwings.login(this.username, this.password, function(err, result) {
if (!err) {
that.vin = result.vin;
that.log("Got VIN: " + that.vin);
if (powerOn) {
carwings.startClimateControl(that.vin, null, function(err, result) {
if (!err)
that.log("Started climate control.");
else
that.log("Error starting climate control: " + err);
});
}
else {
carwings.stopClimateControl(that.vin, function(err, result) {
if (!err)
that.log("Stopped climate control.");
else
that.log("Error stopping climate control: " + err);
});
}
}
else {
that.log("Error logging in: " + err);
}
});
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Nissan",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-1",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.SWITCH_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.setPowerState(value); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the power state of the car",
designedMaxLength: 1
}]
}];
}
};
module.exports.accessory = CarwingsAccessory;

View File

@@ -1,126 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var elkington = require("elkington");
function ElkM1Accessory(log, config) {
this.log = log;
this.name = config["name"];
this.zone = config["zone"];
this.host = config["host"];
this.port = config["port"];
this.pin = config["pin"];
this.arm = config["arm"];
}
ElkM1Accessory.prototype = {
setPowerState: function(alarmOn) {
var that = this;
if (!alarmOn)
{
return;
}
var elk = elkington.createConnection({
port: that.port,
host: that.host,
});
switch (that.arm)
{
case 'Away':
elk.armAway({area: that.zone, code: that.pin});
break;
case 'Stay':
elk.armStay({area: that.zone, code: that.pin});
break;
case 'Night':
elk.armNightInstant({area: that.zone, code: that.pin});
break;
default:
break;
}
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Elk",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "M1",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.SWITCH_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.setPowerState(value); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Alarm the Zone",
designedMaxLength: 1
}]
}];
}
};
module.exports.accessory = ElkM1Accessory;

View File

@@ -1,76 +0,0 @@
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var chokidar = require("chokidar");
var debug = require("debug")("FileSensorAccessory");
var crypto = require("crypto");
module.exports = {
accessory: FileSensorAccessory
}
function FileSensorAccessory(log, config) {
this.log = log;
// url info
this.name = config["name"];
this.path = config["path"];
this.window_seconds = config["window_seconds"] || 5;
this.sensor_type = config["sensor_type"] || "m";
this.inverse = config["inverse"] || false;
if(config["sn"]){
this.sn = config["sn"];
} else {
var shasum = crypto.createHash('sha1');
shasum.update(this.path);
this.sn = shasum.digest('base64');
debug('Computed SN ' + this.sn);
}
}
FileSensorAccessory.prototype = {
getServices: function() {
// you can OPTIONALLY create an information service if you wish to override
// the default values for things like serial number, model, etc.
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Name, this.name)
.setCharacteristic(Characteristic.Manufacturer, "Homebridge")
.setCharacteristic(Characteristic.Model, "File Sensor")
.setCharacteristic(Characteristic.SerialNumber, this.sn);
var service, changeAction;
if(this.sensor_type === "c"){
service = new Service.ContactSensor();
changeAction = function(newState){
service.getCharacteristic(Characteristic.ContactSensorState)
.setValue(newState ? Characteristic.ContactSensorState.CONTACT_DETECTED : Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);
};
} else {
service = new Service.MotionSensor();
changeAction = function(newState){
service.getCharacteristic(Characteristic.MotionDetected)
.setValue(newState);
};
}
var changeHandler = function(path, stats){
var d = new Date();
if(d.getTime() - stats.mtime.getTime() <= (this.window_seconds * 1000)){
var newState = this.inverse ? false : true;
changeAction(newState);
if(this.timer !== undefined) clearTimeout(this.timer);
this.timer = setTimeout(function(){changeAction(!newState);}, this.window_seconds * 1000);
}
}.bind(this);
var watcher = chokidar.watch(this.path, {alwaysStat: true});
watcher.on('add', changeHandler);
watcher.on('change', changeHandler);
return [informationService, service];
}
};

View File

@@ -1,58 +0,0 @@
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var SerialPort = require("serialport").SerialPort;
module.exports = {
accessory: GenericRS232DeviceAccessory
}
function GenericRS232DeviceAccessory(log, config) {
this.log = log;
this.id = config["id"];
this.name = config["name"];
this.model_name = config["model_name"];
this.manufacturer = config["manufacturer"];
this.on_command = config["on_command"];
this.off_command = config["off_command"];
this.device = config["device"];
this.baudrate = config["baudrate"];
}
GenericRS232DeviceAccessory.prototype = {
setPowerState: function(powerOn, callback) {
var that = this;
var command = powerOn ? that.on_command : that.off_command;
var serialPort = new SerialPort(that.device, { baudrate: that.baudrate }, false);
serialPort.open(function (error) {
if (error) {
callback(new Error('Can not communicate with ' + that.name + " (" + error + ")"))
} else {
serialPort.write(command, function(err, results) {
if (error) {
callback(new Error('Can not send power command to ' + that.name + " (" + err + ")"))
} else {
callback()
}
});
}
});
},
getServices: function() {
var switchService = new Service.Switch(this.name);
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, this.manufacturer)
.setCharacteristic(Characteristic.Model, this.model_name)
.setCharacteristic(Characteristic.SerialNumber, this.id);
switchService
.getCharacteristic(Characteristic.On)
.on('set', this.setPowerState.bind(this));
return [informationService, switchService];
}
}
module.exports.accessory = GenericRS232DeviceAccessory;

View File

@@ -1,141 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function HomeMatic(log, config) {
this.log = log;
this.name = config["name"];
this.ccuID = config["ccu_id"];
this.ccuIP = config["ccu_ip"];
}
HomeMatic.prototype = {
setPowerState: function(powerOn) {
var binaryState = powerOn ? 1 : 0;
var that = this;
this.log("Setting power state of CCU to " + powerOn);
this.log(this.ccuID+ powerOn);
request.put({
url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+this.ccuID+"&new_value="+ powerOn,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
that.log("State change complete.");
}
else {
that.log("Error '"+err+"' setting lock state: " + body);
}
});
},
getPowerState: function(callback) {
var that = this;
this.log("Getting Power State of CCU");
request.get({
url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuID,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
//that.log("Response:"+response.body);
var responseString = response.body.substring(83,87);
//that.log(responseString);
switch(responseString){
case "true": {modvalue = "1";break;}
case "fals": {modvalue = "0";break;}
}
callback(parseInt(modvalue));
that.log("Getting Power State complete.");
}
else {
that.log("Error '"+err+"' getting Power State: " + body);
}
});
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "WeMo",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-1",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.SWITCH_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.setPowerState(value); },
onRead: function(callback) { that.getPowerState(callback); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the power state of a Variable",
designedMaxLength: 1
}]
}];
}
};
module.exports.accessory = HomeMatic;

View File

@@ -1,264 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function HomeMaticThermo(log, config) {
this.log = log;
this.name = config["name"];
this.ccuIDTargetTemp = config["ccu_id_TargetTemp"];
this.ccuIDCurrentTemp = config["ccu_id_CurrentTemp"];
this.ccuIDControlMode = config["ccu_id_ControlMode"];
this.ccuIDManuMode = config["ccu_id_ManuMode"];
this.ccuIDAutoMode = config["ccu_id_AutoMode"];
this.ccuIP = config["ccu_ip"];
}
HomeMaticThermo.prototype = {
setTargetTemperature: function(value) {
var that = this;
this.log("Setting target Temperature of CCU to " + value);
this.log(this.ccuIDTargetTemp + " " + value);
request.put({
url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+this.ccuIDTargetTemp+"&new_value="+ value,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
that.log("State change complete.");
}
else {
that.log("Error '"+err+"' setting Temperature: " + body);
}
});
},
getCurrentTemperature: function(callback) {
var that = this;
this.log("Getting current Temperature of CCU");
request.get({
url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDCurrentTemp,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
//that.log("Response:"+response.body);
var responseString = response.body.substring(83,87);
//that.log(responseString);
callback(parseFloat(responseString));
//that.log("Getting current temperature complete.");
}
else {
that.log("Error '"+err+"' getting Temperature: " + body);
}
});
},
getTargetTemperature: function(callback) {
var that = this;
this.log("Getting target Temperature of CCU");
request.get({
url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDTargetTemp,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
//that.log("Response:"+response.body);
var responseString = response.body.substring(83,87);
//that.log(responseString);
callback(parseFloat(responseString));
//that.log("Getting target temperature complete.");
}
else {
that.log("Error '"+err+"' getting Temperature: " + body);
}
});
},
getMode: function(callback) {
var that = this;
//this.log("Getting target Mode of CCU");
//this.log(this.ccuID+ value);
request.get({
url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDControlMode,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
//that.log("Response:"+response.body);
var responseInt = response.body.substring(83,84);
//that.log(responseString);
if (responseInt == 1)
{ callback(parseInt("0")); }
if (responseInt == 0)
{ callback(parseInt("1")); }
//that.log("Getting mode complete.");
}
else {
that.log("Error '"+err+"' getting Mode: " + body);
}
});
},
setMode: function(value) {
var that = this;
//this.log("Seting target Mode of CCU:" + value);
var modvalue;
var dpID;
switch(value) {
case 3: {modvalue = "true";dpID=this.ccuIDAutoMode;break;} //auto
case 1: {modvalue = "true";dpID=this.ccuIDAutoMode;break;} //heating => auto
default: {modvalue = "1";dpID=this.ccuIDManuMode;} //default => off (manual)
}
request.put({
url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+dpID+"&new_value="+ modvalue,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
//that.log("Setting Mode complete.");
}
else {
that.log("Error '"+err+"' setting Mode: " + body);
}
});
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "test",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "test",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NREF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.THERMOSTAT_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.CURRENTHEATINGCOOLING_CTYPE,
onRead: function(callback) { that.getMode(callback); },
perms: ["pr","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Current Mode",
designedMaxLength: 1,
designedMinValue: 0,
designedMaxValue: 2,
designedMinStep: 1,
},{
cType: types.TARGETHEATINGCOOLING_CTYPE,
onRead: function(callback) { that.getMode(callback); },
onUpdate: function(value) { that.setMode(value);},
perms: ["pw","pr","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Target Mode",
designedMinValue: 0,
designedMaxValue: 3,
designedMinStep: 1,
},{
cType: types.CURRENT_TEMPERATURE_CTYPE,
onRead: function(callback) { that.getCurrentTemperature(callback); },
onUpdate: null,
perms: ["pr","ev"],
format: "float",
initialValue: 13.0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Current Temperature",
unit: "celsius"
},{
cType: types.TARGET_TEMPERATURE_CTYPE,
onUpdate: function(value) { that.setTargetTemperature(value); },
onRead: function(callback) { that.getTargetTemperature(callback); },
perms: ["pw","pr","ev"],
format: "float",
initialValue: 19.0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Target Temperature",
designedMinValue: 4,
designedMaxValue: 25,
designedMinStep: 0.1,
unit: "celsius"
},{
cType: types.TEMPERATURE_UNITS_CTYPE,
onUpdate: null,
perms: ["pr","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Unit"
}]
}];
}
};
module.exports.accessory = HomeMaticThermo;

View File

@@ -1,123 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
function HomeMaticWindow(log, config) {
this.log = log;
this.name = config["name"];
this.ccuID = config["ccu_id"];
this.ccuIP = config["ccu_ip"];
}
HomeMaticWindow.prototype = {
getPowerState: function(callback) {
var that = this;
this.log("Getting Window State of CCU");
request.get({
url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuID,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
//that.log("Response:"+response.body);
var responseString = response.body.substring(83,84);
//that.log(responseString);
switch(responseString){
case "0": {callback(Characteristic.ContactSensorState.CONTACT_DETECTED);break;}
case "1": {callback(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);break;}
case "2": {callback(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);break;}
}
that.log("Getting Window State complete.");
}
else {
that.log("Error '"+err+"' getting Window State: " + body);
}
});
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Homematic",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "HM-Sec-RHS",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.CONTACT_SENSOR_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.CONTACT_SENSOR_STATE_CTYPE,
onRead: function(callback) { that.getPowerState(callback); },
perms: ["pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Get Window state of a Variable",
designedMaxLength: 1
}]
}];
}
};
module.exports.accessory = HomeMaticWindow;

View File

@@ -1,100 +0,0 @@
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
module.exports = {
accessory: HttpAccessory
}
function HttpAccessory(log, config) {
this.log = log;
// url info
this.on_url = config["on_url"];
this.off_url = config["off_url"];
this.brightness_url = config["brightness_url"];
this.http_method = config["http_method"];
}
HttpAccessory.prototype = {
httpRequest: function(url, method, callback) {
request({
url: url,
method: method
},
function (error, response, body) {
callback(error, response, body)
})
},
setPowerState: function(powerOn, callback) {
var url;
if (powerOn) {
url = this.on_url;
this.log("Setting power state to on");
}
else {
url = this.off_url;
this.log("Setting power state to off");
}
this.httpRequest(url, this.http_method, function(error, response, body) {
if (error) {
this.log('HTTP power function failed: %s', error.message);
callback(error);
}
else {
this.log('HTTP power function succeeded!');
callback();
}
}.bind(this));
},
setBrightness: function(level, callback) {
var url = this.brightness_url.replace("%b", level)
this.log("Setting brightness to %s", level);
this.httpRequest(url, this.http_method, function(error, response, body) {
if (error) {
this.log('HTTP brightness function failed: %s', error);
callback(error);
}
else {
this.log('HTTP brightness function succeeded!');
callback();
}
}.bind(this));
},
identify: function(callback) {
this.log("Identify requested!");
callback(); // success
},
getServices: function() {
// you can OPTIONALLY create an information service if you wish to override
// the default values for things like serial number, model, etc.
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer")
.setCharacteristic(Characteristic.Model, "HTTP Model")
.setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number");
var lightbulbService = new Service.Lightbulb();
lightbulbService
.getCharacteristic(Characteristic.On)
.on('set', this.setPowerState.bind(this));
lightbulbService
.addCharacteristic(new Characteristic.Brightness())
.on('set', this.setBrightness.bind(this));
return [informationService, lightbulbService];
}
};

View File

@@ -1,71 +0,0 @@
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
module.exports = {
accessory: HygrometerAccessory
}
function HygrometerAccessory(log, config) {
this.log = log;
// url info
this.url = config["url"];
this.http_method = config["http_method"];
}
HygrometerAccessory.prototype = {
httpRequest: function(url, method, callback) {
request({
url: url,
method: method
},
function (error, response, body) {
callback(error, response, body)
})
},
identify: function(callback) {
this.log("Identify requested!");
callback(); // success
},
getCurrentRelativeHumidity: function (callback) {
var that = this;
that.log ("getting CurrentCurrentRelativeHumidity");
this.httpRequest(this.url, this.http_method, function(error, response, body) {
if (error) {
this.log('HTTP function failed: %s', error);
callback(error);
}
else {
this.log('HTTP function succeeded - %s', body);
callback(null, Number(body));
}
}.bind(this));
},
getServices: function() {
// you can OPTIONALLY create an information service if you wish to override
// the default values for things like serial number, model, etc.
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer")
.setCharacteristic(Characteristic.Model, "HTTP Hygrometer")
.setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number");
var humidityService = new Service.HumiditySensor();
humidityService
.getCharacteristic(Characteristic.CurrentRelativeHumidity)
.on('get', this.getCurrentRelativeHumidity.bind(this));
return [informationService, humidityService];
}
};

View File

@@ -1,79 +0,0 @@
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
module.exports = {
accessory: ThermometerAccessory
}
function ThermometerAccessory(log, config) {
this.log = log;
// url info
this.url = config["url"];
this.http_method = config["http_method"];
}
ThermometerAccessory.prototype = {
httpRequest: function(url, method, callback) {
request({
url: url,
method: method
},
function (error, response, body) {
callback(error, response, body)
})
},
identify: function(callback) {
this.log("Identify requested!");
callback(); // success
},
getCurrentTemperature: function (callback) {
var that = this;
that.log ("getting CurrentTemperature");
this.httpRequest(this.url, this.http_method, function(error, response, body) {
if (error) {
this.log('HTTP function failed: %s', error);
callback(error);
}
else {
this.log('HTTP function succeeded - %s', body);
callback(null, Number(body));
}
}.bind(this));
},
getTemperatureUnits: function (callback) {
var that = this;
that.log ("getTemperature Units");
// 1 = F and 0 = C
callback (null, 0);
},
getServices: function() {
// you can OPTIONALLY create an information service if you wish to override
// the default values for things like serial number, model, etc.
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer")
.setCharacteristic(Characteristic.Model, "HTTP Thermometer")
.setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number");
var temperatureService = new Service.TemperatureSensor();
temperatureService
.getCharacteristic(Characteristic.CurrentTemperature)
.on('get', this.getCurrentTemperature.bind(this));
return [informationService, temperatureService];
}
};

View File

@@ -1,221 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var net = require('net');
var Color = require('color');
function HyperionAccessory(log, config) {
this.log = log;
this.host = config["host"];
this.port = config["port"];
this.name = config["name"];
this.color = Color().hsv([0, 0, 0]);
this.prevColor = Color().hsv([0,0,100]);
}
HyperionAccessory.prototype = {
sendHyperionCommand: function(command, cmdParams, priority) {
var that = this;
var client = new net.Socket();
var data = {};
if (typeof priority === 'undefined') { priority = 100; }
switch (command) {
case 'color':
data = {"command":"color", "priority":priority,"color":cmdParams};
break;
case 'blacklevel':
data = {"command":"transform","transform":{"blacklevel":cmdParams}}
break;
default:
that.log("Hyperion command not found");
return;
}
//that.log(JSON.stringify(data));
client.connect(that.port, that.host, function() {
client.write(JSON.stringify(data) + "\n");
});
client.on('data', function(data){
that.log("Response: " + data.toString().trim());
that.log("***** Color HSV:" + that.color.hsvArray() + "*****");
that.log("***** Color RGB:" + that.color.rgbArray() + "*****");
client.end();
});
},
setPowerState: function(powerOn) {
var that = this;
if (powerOn) {
that.log("Setting power state on the '"+that.name+"' to on");
that.color.rgb(that.prevColor.rgb());
that.sendHyperionCommand('color', that.color.rgbArray());
} else {
that.log("Setting power state on the '"+that.name+"' to off");
that.prevColor.rgb(that.color.rgb());
that.color.value(0);
that.sendHyperionCommand('color', that.color.rgbArray());
that.sendHyperionCommand('blacklevel', [0,0,0]);
}
},
setBrightness: function(level) {
var that = this;
that.color.value(level);
that.log("Setting brightness on the '"+that.name+"' to '" + level + "'");
that.sendHyperionCommand('color', that.color.rgbArray());
},
setHue: function(level) {
var that = this;
that.color.hue(level);
that.prevColor.hue(level);
that.log("Setting hue on the '"+that.name+"' to '" + level + "'");
that.sendHyperionCommand('color', that.color.rgbArray());
},
setSaturation: function(level) {
var that = this;
that.color.saturationv(level);
that.prevColor.saturationv(level);
that.log("Setting saturation on the '"+that.name+"' to '" + level + "'");
that.sendHyperionCommand('color', that.color.rgbArray());
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Hyperion",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-1",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "DEADBEEF",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.LIGHTBULB_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.name,
supportEvents: true,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.setPowerState(value); },
onRead: ((that.color.value() > 0) ? true : false),
perms: ["pw","pr","ev"],
format: "bool",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Turn on the light",
designedMaxLength: 1
},{
cType: types.BRIGHTNESS_CTYPE,
onUpdate: function(value) { that.setBrightness(value); },
onRead: that.color.value(),
perms: ["pw","pr","ev"],
format: "int",
initialValue: that.color.value(),
supportEvents: false,
supportBonjour: false,
manfDescription: "Adjust Brightness",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
},{
cType: types.HUE_CTYPE,
onUpdate: function(value) { that.setHue(value) },
onRead: that.color.hue(),
perms: ["pw","pr","ev"],
format: "int",
initialValue: that.color.hue(),
supportEvents: false,
supportBonjour: false,
manfDescription: "Adjust Hue",
designedMinValue: 0,
designedMaxValue: 360,
designedMinStep: 1,
unit: "arcdegrees"
},{
cType: types.SATURATION_CTYPE,
onUpdate: function(value) { that.setSaturation(value) },
onRead: that.color.saturationv(),
perms: ["pw","pr","ev"],
format: "int",
initialValue: that.color.saturationv(),
supportEvents: false,
supportBonjour: false,
manfDescription: "Adjust Saturation",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
}]
}];
}
};
module.exports.accessory = HyperionAccessory;

View File

@@ -1,306 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
// This seems to be the "id" of the official LiftMaster iOS app
var APP_ID = "JVM/G9Nwih5BwKgNCjLxiFUQxQijAebyyg8QUHr7JOrP+tuPb8iHfRHKwTmDzHOu"
function LiftMasterAccessory(log, config) {
this.log = log;
this.name = config["name"];
this.username = config["username"];
this.password = config["password"];
this.requiredDeviceId = config["requiredDeviceId"];
}
LiftMasterAccessory.prototype = {
setState: function(state) {
this.targetState = state;
this.login();
},
login: function() {
var that = this;
// reset our logged-in state hint until we're logged in
this.deviceId = null;
// querystring params
var query = {
appId: APP_ID,
username: this.username,
password: this.password,
culture: "en"
};
// login to liftmaster
request.get({
url: "https://myqexternal.myqdevice.com/api/user/validatewithculture",
qs: query
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
// parse and interpret the response
var json = JSON.parse(body);
that.userId = json["UserId"];
that.securityToken = json["SecurityToken"];
that.log("Logged in with user ID " + that.userId);
that.getDevice();
}
else {
that.log("Error '"+err+"' logging in: " + body);
}
});
},
// find your garage door ID
getDevice: function() {
var that = this;
// querystring params
var query = {
appId: APP_ID,
SecurityToken: this.securityToken,
filterOn: "true"
};
// some necessary duplicated info in the headers
var headers = {
MyQApplicationId: APP_ID,
SecurityToken: this.securityToken
};
// request details of all your devices
request.get({
url: "https://myqexternal.myqdevice.com/api/v4/userdevicedetails/get",
qs: query,
headers: headers
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
// parse and interpret the response
var json = JSON.parse(body);
var devices = json["Devices"];
var foundDoors = [];
// look through the array of devices for an opener
for (var i=0; i<devices.length; i++) {
var device = devices[i];
if (device["MyQDeviceTypeName"] == "GarageDoorOpener" || device["MyQDeviceTypeName"] == "VGDO") {
// If we haven't explicity specified a door ID, we'll loop to make sure we don't have multiple openers, which is confusing
if (!that.requiredDeviceId) {
var thisDeviceId = device.MyQDeviceId;
var thisDoorName = "Unknown";
for (var j = 0; j < device.Attributes.length; j ++) {
var thisAttributeSet = device.Attributes[j];
if (thisAttributeSet.AttributeDisplayName == "desc") {
thisDoorName = thisAttributeSet.Value;
break;
}
}
foundDoors.push(thisDeviceId + " - " + thisDoorName);
that.deviceId = thisDeviceId;
}
// We specified a door ID, sanity check to make sure it's the one we expected
else if (that.requiredDeviceId == device.MyQDeviceId) {
that.deviceId = device.MyQDeviceId;
break;
}
}
}
// If we have multiple found doors, refuse to proceed
if (foundDoors.length > 1) {
that.log("WARNING: You have multiple doors on your MyQ account.");
that.log("WARNING: Specify the ID of the door you want to control using the 'requiredDeviceId' property in your config.json file.");
that.log("WARNING: You can have multiple liftmaster accessories to cover your multiple doors");
for (var j = 0; j < foundDoors.length; j++) {
that.log("Found Door: " + foundDoors[j]);
}
throw "FATAL: Please specify which specific door this Liftmaster accessory should control - you have multiples on your account";
}
// Did we get a device ID?
if (that.deviceId) {
that.log("Found an opener with ID " + that.deviceId +". Ready to send command...");
that.setTargetState();
}
else
{
that.log("Error: Couldn't find a door device, or the ID you specified isn't associated with your account");
}
}
else {
that.log("Error '"+err+"' getting devices: " + body);
}
});
},
setTargetState: function() {
var that = this;
var liftmasterState = (this.targetState + "") == "1" ? "0" : "1";
// querystring params
var query = {
appId: APP_ID,
SecurityToken: this.securityToken,
filterOn: "true"
};
// some necessary duplicated info in the headers
var headers = {
MyQApplicationId: APP_ID,
SecurityToken: this.securityToken
};
// PUT request body
var body = {
AttributeName: "desireddoorstate",
AttributeValue: liftmasterState,
ApplicationId: APP_ID,
SecurityToken: this.securityToken,
MyQDeviceId: this.deviceId
};
// send the state request to liftmaster
request.put({
url: "https://myqexternal.myqdevice.com/api/v4/DeviceAttribute/PutDeviceAttribute",
qs: query,
headers: headers,
body: body,
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
if (json["ReturnCode"] == "0")
that.log("State was successfully set.");
else
that.log("Bad return code: " + json["ReturnCode"]);
that.log("Raw response " + JSON.stringify(json));
}
else {
that.log("Error '"+err+"' setting door state: " + JSON.stringify(json));
}
});
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "LiftMaster",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-1",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.GARAGE_DOOR_OPENER_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Garage Door Opener Control",
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.CURRENT_DOOR_STATE_CTYPE,
onUpdate: function(value) { that.log("Update current state to " + value); },
perms: ["pr","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "BlaBla",
designedMinValue: 0,
designedMaxValue: 4,
designedMinStep: 1,
designedMaxLength: 1
},{
cType: types.TARGET_DOORSTATE_CTYPE,
onUpdate: function(value) { that.setState(value); },
perms: ["pr","pw","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "BlaBla",
designedMinValue: 0,
designedMaxValue: 1,
designedMinStep: 1,
designedMaxLength: 1
},{
cType: types.OBSTRUCTION_DETECTED_CTYPE,
onUpdate: function(value) { that.log("Obstruction detected: " + value); },
perms: ["pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "BlaBla"
}]
}];
}
};
module.exports.accessory = LiftMasterAccessory;

View File

@@ -1,80 +0,0 @@
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
module.exports = {
accessory: LockitronAccessory
}
function LockitronAccessory(log, config) {
this.log = log;
this.name = config["name"];
this.accessToken = config["api_token"];
this.lockID = config["lock_id"];
this.service = new Service.LockMechanism(this.name);
this.service
.getCharacteristic(Characteristic.LockCurrentState)
.on('get', this.getState.bind(this));
this.service
.getCharacteristic(Characteristic.LockTargetState)
.on('get', this.getState.bind(this))
.on('set', this.setState.bind(this));
}
LockitronAccessory.prototype.getState = function(callback) {
this.log("Getting current state...");
request.get({
url: "https://api.lockitron.com/v2/locks/"+this.lockID,
qs: { access_token: this.accessToken }
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
var json = JSON.parse(body);
var state = json.state; // "lock" or "unlock"
this.log("Lock state is %s", state);
var locked = state == "lock"
callback(null, locked); // success
}
else {
this.log("Error getting state (status code %s): %s", response.statusCode, err);
callback(err);
}
}.bind(this));
}
LockitronAccessory.prototype.setState = function(state, callback) {
var lockitronState = (state == Characteristic.LockTargetState.SECURED) ? "lock" : "unlock";
this.log("Set state to %s", lockitronState);
request.put({
url: "https://api.lockitron.com/v2/locks/"+this.lockID,
qs: { access_token: this.accessToken, state: lockitronState }
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
this.log("State change complete.");
// we succeeded, so update the "current" state as well
var currentState = (state == Characteristic.LockTargetState.SECURED) ?
Characteristic.LockCurrentState.SECURED : Characteristic.LockCurrentState.UNSECURED;
this.service
.setCharacteristic(Characteristic.LockCurrentState, currentState);
callback(null); // success
}
else {
this.log("Error '%s' setting lock state. Response: %s", err, body);
callback(err || new Error("Error setting lock state."));
}
}.bind(this));
},
LockitronAccessory.prototype.getServices = function() {
return [this.service];
}

View File

@@ -1,119 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var tesla = require("teslams");
function TeslaAccessory(log, config) {
this.log = log;
this.name = config["name"];
this.username = config["username"];
this.password = config["password"];
}
TeslaAccessory.prototype = {
setPowerState: function(powerOn) {
var that = this;
tesla.get_vid({email: this.username, password: this.password}, function(vehicle) {
if (powerOn) {
tesla.auto_conditioning({id:vehicle, climate: 'start'}, function(response) {
if (response.result)
that.log("Started climate control.");
else
that.log("Error starting climate control: " + response.reason);
});
}
else {
tesla.auto_conditioning({id:vehicle, climate: 'stop'}, function(response) {
if (response.result)
that.log("Stopped climate control.");
else
that.log("Error stopping climate control: " + response.reason);
});
}
})
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Tesla",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-1",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.SWITCH_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.setPowerState(value); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the power state of the car",
designedMaxLength: 1
}]
}];
}
};
module.exports.accessory = TeslaAccessory;

View File

@@ -1,169 +0,0 @@
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var wemo = require('wemo');
module.exports = {
accessory: WeMoAccessory
}
function WeMoAccessory(log, config) {
this.log = log;
this.name = config["name"];
this.service = config["service"] || "Switch";
this.wemoName = config["wemo_name"] || this.name; // fallback to "name" if you didn't specify an exact "wemo_name"
this.device = null; // instance of WeMo, for controlling the discovered device
this.log("Searching for WeMo device with exact name '" + this.wemoName + "'...");
this.search();
}
WeMoAccessory.prototype.search = function() {
wemo.Search(this.wemoName, function(err, device) {
if (!err && device) {
this.log("Found '"+this.wemoName+"' device at " + device.ip);
this.device = new wemo(device.ip, device.port);
}
else {
this.log("Error finding device '" + this.wemoName + "': " + err);
this.log("Continuing search for WeMo device with exact name '" + this.wemoName + "'...");
this.search();
}
}.bind(this));
}
WeMoAccessory.prototype.getMotion = function(callback) {
if (!this.device) {
this.log("No '%s' device found (yet?)", this.wemoName);
callback(new Error("Device not found"), false);
return;
}
this.log("Getting motion state on the '%s'...", this.wemoName);
this.device.getBinaryState(function(err, result) {
if (!err) {
var binaryState = parseInt(result);
var powerOn = binaryState > 0;
this.log("Motion state for the '%s' is %s", this.wemoName, binaryState);
callback(null, powerOn);
}
else {
this.log("Error getting motion state on the '%s': %s", this.wemoName, err.message);
callback(err);
}
}.bind(this));
}
WeMoAccessory.prototype.getPowerOn = function(callback) {
if (!this.device) {
this.log("No '%s' device found (yet?)", this.wemoName);
callback(new Error("Device not found"), false);
return;
}
this.log("Getting power state on the '%s'...", this.wemoName);
this.device.getBinaryState(function(err, result) {
if (!err) {
var binaryState = parseInt(result);
var powerOn = binaryState > 0;
this.log("Power state for the '%s' is %s", this.wemoName, binaryState);
callback(null, powerOn);
}
else {
this.log("Error getting power state on the '%s': %s", this.wemoName, err.message);
callback(err);
}
}.bind(this));
}
WeMoAccessory.prototype.setPowerOn = function(powerOn, callback) {
if (!this.device) {
this.log("No '%s' device found (yet?)", this.wemoName);
callback(new Error("Device not found"));
return;
}
var binaryState = powerOn ? 1 : 0; // wemo langauge
this.log("Setting power state on the '%s' to %s", this.wemoName, binaryState);
this.device.setBinaryState(binaryState, function(err, result) {
if (!err) {
this.log("Successfully set power state on the '%s' to %s", this.wemoName, binaryState);
callback(null);
}
else {
this.log("Error setting power state to %s on the '%s'", binaryState, this.wemoName);
callback(err);
}
}.bind(this));
}
WeMoAccessory.prototype.setTargetDoorState = function(targetDoorState, callback) {
if (!this.device) {
this.log("No '%s' device found (yet?)", this.wemoName);
callback(new Error("Device not found"));
return;
}
this.log("Activating WeMo switch '%s'", this.wemoName);
this.device.setBinaryState(1, function(err, result) {
if (!err) {
this.log("Successfully activated WeMo switch '%s'", this.wemoName);
callback(null);
}
else {
this.log("Error activating WeMo switch '%s'", this.wemoName);
callback(err);
}
}.bind(this));
}
WeMoAccessory.prototype.getServices = function() {
if (this.service == "Switch") {
var switchService = new Service.Switch(this.name);
switchService
.getCharacteristic(Characteristic.On)
.on('get', this.getPowerOn.bind(this))
.on('set', this.setPowerOn.bind(this));
return [switchService];
}
else if (this.service == "GarageDoor") {
var garageDoorService = new Service.GarageDoorOpener("Garage Door Opener");
garageDoorService
.getCharacteristic(Characteristic.TargetDoorState)
.on('set', this.setTargetDoorState.bind(this));
return [garageDoorService];
}
else if (this.service == "Light") {
var lightbulbService = new Service.Lightbulb(this.name);
lightbulbService
.getCharacteristic(Characteristic.On)
.on('get', this.getPowerOn.bind(this))
.on('set', this.setPowerOn.bind(this));
return [lightbulbService];
}
else if (this.service == "MotionSensor") {
var motionSensorService = new Service.MotionSensor(this.name);
motionSensorService
.getCharacteristic(Characteristic.MotionDetected)
.on('get', this.getMotion.bind(this));
return [motionSensorService];
}
else {
throw new Error("Unknown service type '%s'", this.service);
}
}

View File

@@ -1,151 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function X10(log, config) {
this.log = log;
this.ip_address = config["ip_address"];
this.name = config["name"];
this.deviceID = config["device_id"];
this.protocol = config["protocol"];
this.canDim = config["can_dim"];
}
X10.prototype = {
setPowerState: function(powerOn) {
var binaryState = powerOn ? "on" : "off";
var that = this;
this.log("Setting power state of " + this.deviceID + " to " + powerOn);
request.put({
url: "http://"+this.ip_address+"/x10/"+this.deviceID+"/power/"+binaryState+"?protocol="+this.protocol,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
that.log("State change complete.");
}
else {
that.log("Error '"+err+"' setting power state: " + body);
}
});
},
setBrightnessLevel: function(value) {
var that = this;
this.log("Setting brightness level of " + this.deviceID + " to " + value);
request.put({
url: "http://"+this.ip_address+"/x10/"+this.deviceID+"/brightness/?protocol="+this.protocol+"&value="+value,
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
that.log("State change complete.");
}
else {
that.log("Error '"+err+"' setting brightness level: " + body);
}
});
},
getServices: function() {
var that = this;
var services = [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "X10",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-1",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.LIGHTBULB_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.setPowerState(value); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the power state of a Variable",
designedMaxLength: 1
}]
}];
if (that.canDim) {
services[1].characteristics.push({
cType: types.BRIGHTNESS_CTYPE,
onUpdate: function(value) { that.setBrightnessLevel(value); },
perms: ["pw","pr","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
});
}
return services;
}
};
module.exports.accessory = X10;

View File

@@ -1,130 +0,0 @@
var iControl = require('node-icontrol').iControl;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
module.exports = {
accessory: iControlAccessory
}
/**
* Provides a Security System accessory for an iControl-based security system like Xfinity Home.
*/
function iControlAccessory(log, config) {
this.log = log;
this.iControl = new iControl({
system: iControl.Systems[config.system],
email: config.email,
password: config.password,
pinCode: config.pin
});
this.iControl.on('change', this._handleChange.bind(this));
this.iControl.on('error', this._handleError.bind(this));
this.log("Logging into iControl...");
this.iControl.login();
this._securitySystem = new Service.SecuritySystem("Security System");
this._securitySystem
.getCharacteristic(Characteristic.SecuritySystemTargetState)
.on('get', this._getTargetState.bind(this))
.on('set', this._setTargetState.bind(this));
this._securitySystem
.getCharacteristic(Characteristic.SecuritySystemCurrentState)
.on('get', this._getCurrentState.bind(this));
}
iControlAccessory.prototype._getTargetState = function(callback) {
this.iControl.getArmState(function(err, armState) {
if (err) return callback(err);
var currentState = this._getHomeKitStateFromArmState(armState);
callback(null, currentState);
}.bind(this));
}
iControlAccessory.prototype._getCurrentState = function(callback) {
this.iControl.getArmState(function(err, armState) {
if (err) return callback(err);
var currentState = this._getHomeKitStateFromArmState(armState);
callback(null, currentState);
}.bind(this));
}
iControlAccessory.prototype._setTargetState = function(targetState, callback, context) {
if (context == "internal") return callback(null); // we set this state ourself, no need to react to it
var armState = this._getArmStateFromHomeKitState(targetState);
this.log("Setting target state to %s", armState);
this.iControl.setArmState(armState, function(err) {
if (err) return callback(err);
this.log("Successfully set target state to %s", armState);
// also update current state
this._securitySystem
.getCharacteristic(Characteristic.SecuritySystemCurrentState)
.setValue(targetState);
callback(null); // success!
}.bind(this));
}
iControlAccessory.prototype._handleChange = function(armState) {
this.log("Arm state changed to %s", armState);
var homeKitState = this._getHomeKitStateFromArmState(armState);
this._securitySystem
.getCharacteristic(Characteristic.SecuritySystemCurrentState)
.setValue(homeKitState);
this._securitySystem
.getCharacteristic(Characteristic.SecuritySystemTargetState)
.setValue(homeKitState, null, "internal"); // these characteristics happen to share underlying values
}
iControlAccessory.prototype._handleError = function(err) {
this.log(err.message);
}
iControlAccessory.prototype.getServices = function() {
return [this._securitySystem];
}
iControlAccessory.prototype._getHomeKitStateFromArmState = function(armState) {
switch (armState) {
case "disarmed": return Characteristic.SecuritySystemCurrentState.DISARMED;
case "away": return Characteristic.SecuritySystemCurrentState.AWAY_ARM;
case "night": return Characteristic.SecuritySystemCurrentState.NIGHT_ARM;
case "stay": return Characteristic.SecuritySystemCurrentState.STAY_ARM;
}
}
iControlAccessory.prototype._getArmStateFromHomeKitState = function(homeKitState) {
switch (homeKitState) {
case Characteristic.SecuritySystemCurrentState.DISARMED: return "disarmed";
case Characteristic.SecuritySystemCurrentState.AWAY_ARM: return "away";
case Characteristic.SecuritySystemCurrentState.NIGHT_ARM: return "night";
case Characteristic.SecuritySystemCurrentState.STAY_ARM: return "stay";
}
}
/**
* TESTING
*/
if (require.main === module) {
var config = JSON.parse(require('fs').readFileSync("config.json")).accessories[0];
var accessory = new iControlAccessory(console.log, config);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,89 +0,0 @@
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
var komponist = require('komponist')
module.exports = {
accessory: MpdClient
}
function MpdClient(log, config) {
this.log = log;
this.host = config["host"] || 'localhost';
this.port = config["port"] || 6600;
}
MpdClient.prototype = {
setPowerState: function(powerOn, callback) {
var log = this.log;
komponist.createConnection(this.port, this.host, function(error, client) {
if (error) {
return callback(error);
}
if (powerOn) {
client.play(function(error) {
log("start playing");
client.destroy();
callback(error);
});
} else {
client.stop(function(error) {
log("stop playing");
client.destroy();
callback(error);
});
}
});
},
getPowerState: function(callback) {
komponist.createConnection(this.port, this.host, function(error, client) {
if (error) {
return callback(error);
}
client.status(function(error, status) {
client.destroy();
if (status['state'] == 'play') {
callback(error, 1);
} else {
callback(error, 0);
}
});
});
},
identify: function(callback) {
this.log("Identify requested!");
callback(); // success
},
getServices: function() {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "MPD")
.setCharacteristic(Characteristic.Model, "MPD Client")
.setCharacteristic(Characteristic.SerialNumber, "81536334");
var switchService = new Service.Switch();
switchService.getCharacteristic(Characteristic.On)
.on('get', this.getPowerState.bind(this))
.on('set', this.setPowerState.bind(this));
return [informationService, switchService];
}
};

223
app.js
View File

@@ -1,223 +0,0 @@
var fs = require('fs');
var path = require('path');
var storage = require('node-persist');
var hap = require("hap-nodejs");
var uuid = require("hap-nodejs").uuid;
var Bridge = require("hap-nodejs").Bridge;
var Accessory = require("hap-nodejs").Accessory;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var accessoryLoader = require("hap-nodejs").AccessoryLoader;
var once = require("hap-nodejs/lib/util/once").once;
console.log("Starting HomeBridge server...");
console.log("_____________________________________________________________________");
console.log("IMPORTANT: Homebridge is in the middle of some big changes.");
console.log(" Read more about it here:");
console.log(" https://github.com/nfarina/homebridge/wiki/Migration-Guide");
console.log("_____________________________________________________________________");
console.log("");
// Look for the configuration file
var configPath = path.join(__dirname, "config.json");
// Complain and exit if it doesn't exist yet
if (!fs.existsSync(configPath)) {
console.log("Couldn't find a config.json file in the same directory as app.js. Look at config-sample.json for examples of how to format your config.js and add your home accessories.");
process.exit(1);
}
// Initialize HAP-NodeJS
hap.init();
// Load up the configuration file
var config;
try {
config = JSON.parse(fs.readFileSync(configPath));
}
catch (err) {
console.log("There was a problem reading your config.json file.");
console.log("Please try pasting your config.json file here to validate it: http://jsonlint.com");
console.log("");
throw err;
}
// pull out our custom Bridge settings from config.json, if any
var bridgeConfig = config.bridge || {};
// Start by creating our Bridge which will host all loaded Accessories
var bridge = new Bridge(bridgeConfig.name || 'Homebridge', uuid.generate("HomeBridge"));
// keep track of async calls we're waiting for callbacks on before we can start up
// this is hacky but this is all going away once we build proper plugin support
var asyncCalls = 0;
var asyncWait = false;
function startup() {
asyncWait = true;
if (config.platforms) loadPlatforms();
if (config.accessories) loadAccessories();
asyncWait = false;
// publish now unless we're waiting on anyone
if (asyncCalls == 0)
publish();
}
function loadAccessories() {
// Instantiate all accessories in the config
console.log("Loading " + config.accessories.length + " accessories...");
for (var i=0; i<config.accessories.length; i++) {
var accessoryConfig = config.accessories[i];
// Load up the class for this accessory
var accessoryType = accessoryConfig["accessory"]; // like "WeMo"
var accessoryModule = require('./accessories/' + accessoryType + ".js"); // like "./accessories/WeMo.js"
var accessoryConstructor = accessoryModule.accessory; // like "WeMoAccessory", a JavaScript constructor
// Create a custom logging function that prepends the device display name for debugging
var accessoryName = accessoryConfig["name"];
var log = createLog(accessoryName);
log("Initializing %s accessory...", accessoryType);
var accessoryInstance = new accessoryConstructor(log, accessoryConfig);
var accessory = createAccessory(accessoryInstance, accessoryName, accessoryType, accessoryConfig.uuid_base); //pass accessoryType for UUID generation, and optional parameter uuid_base which can be used instead of displayName for UUID generation
// add it to the bridge
bridge.addBridgedAccessory(accessory);
}
}
function loadPlatforms() {
console.log("Loading " + config.platforms.length + " platforms...");
for (var i=0; i<config.platforms.length; i++) {
var platformConfig = config.platforms[i];
// Load up the class for this accessory
var platformType = platformConfig["platform"]; // like "Wink"
var platformName = platformConfig["name"];
var platformModule = require('./platforms/' + platformType + ".js"); // like "./platforms/Wink.js"
var platformConstructor = platformModule.platform; // like "WinkPlatform", a JavaScript constructor
// Create a custom logging function that prepends the platform name for debugging
var log = createLog(platformName);
log("Initializing %s platform...", platformType);
var platformInstance = new platformConstructor(log, platformConfig);
loadPlatformAccessories(platformInstance, log, platformType);
}
}
function loadPlatformAccessories(platformInstance, log, platformType) {
asyncCalls++;
platformInstance.accessories(once(function(foundAccessories){
asyncCalls--;
// loop through accessories adding them to the list and registering them
for (var i = 0; i < foundAccessories.length; i++) {
var accessoryInstance = foundAccessories[i];
var accessoryName = accessoryInstance.name; // assume this property was set
log("Initializing platform accessory '%s'...", accessoryName);
var accessory = createAccessory(accessoryInstance, accessoryName, platformType, accessoryInstance.uuid_base);
// add it to the bridge
bridge.addBridgedAccessory(accessory);
}
// were we the last callback?
if (asyncCalls === 0 && !asyncWait)
publish();
}));
}
function createAccessory(accessoryInstance, displayName, accessoryType, uuid_base) {
var services = accessoryInstance.getServices();
if (!(services[0] instanceof Service)) {
// The returned "services" for this accessory is assumed to be the old style: a big array
// of JSON-style objects that will need to be parsed by HAP-NodeJS's AccessoryLoader.
// Create the actual HAP-NodeJS "Accessory" instance
return accessoryLoader.parseAccessoryJSON({
displayName: displayName,
services: services
});
}
else {
// The returned "services" for this accessory are simply an array of new-API-style
// Service instances which we can add to a created HAP-NodeJS Accessory directly.
var accessoryUUID = uuid.generate(accessoryType + ":" + (uuid_base || displayName));
var accessory = new Accessory(displayName, accessoryUUID);
// listen for the identify event if the accessory instance has defined an identify() method
if (accessoryInstance.identify)
accessory.on('identify', function(paired, callback) { accessoryInstance.identify(callback); });
services.forEach(function(service) {
// if you returned an AccessoryInformation service, merge its values with ours
if (service instanceof Service.AccessoryInformation) {
var existingService = accessory.getService(Service.AccessoryInformation);
// pull out any values you may have defined
var manufacturer = service.getCharacteristic(Characteristic.Manufacturer).value;
var model = service.getCharacteristic(Characteristic.Model).value;
var serialNumber = service.getCharacteristic(Characteristic.SerialNumber).value;
if (manufacturer) existingService.setCharacteristic(Characteristic.Manufacturer, manufacturer);
if (model) existingService.setCharacteristic(Characteristic.Model, model);
if (serialNumber) existingService.setCharacteristic(Characteristic.SerialNumber, serialNumber);
}
else {
accessory.addService(service);
}
});
return accessory;
}
}
// Returns the setup code in a scannable format.
function printPin(pin) {
console.log("Scan this code with your HomeKit App on your iOS device:");
console.log("\x1b[30;47m%s\x1b[0m", " ");
console.log("\x1b[30;47m%s\x1b[0m", " ┌────────────┐ ");
console.log("\x1b[30;47m%s\x1b[0m", " │ " + pin + " │ ");
console.log("\x1b[30;47m%s\x1b[0m", " └────────────┘ ");
console.log("\x1b[30;47m%s\x1b[0m", " ");
}
// Returns a logging function that prepends messages with the given name in [brackets].
function createLog(name) {
return function(message) {
var rest = Array.prototype.slice.call(arguments, 1 ); // any arguments after message
var args = ["[%s] " + message, name].concat(rest);
console.log.apply(console, args);
}
}
function publish() {
printPin(bridgeConfig.pin);
bridge.publish({
username: bridgeConfig.username || "CC:22:3D:E3:CE:30",
port: bridgeConfig.port || 51826,
pincode: bridgeConfig.pin || "031-45-154",
category: Accessory.Categories.OTHER
});
}
startup();

17
bin/homebridge Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env node
//
// This executable sets up the environment and runs the HomeBridge CLI.
//
'use strict';
process.title = 'homebridge';
// Find the HomeBridge lib
var path = require('path');
var fs = require('fs');
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
// Run HomeBridge
require(lib + '/cli')();

View File

@@ -6,257 +6,19 @@
"pin": "031-45-154"
},
"description": "This is an example configuration file with all supported devices. You can use this as a template for creating your own configuration file containing devices you actually own.",
"platforms": [
{
"platform" : "Nest",
"name" : "Nest",
"username" : "username",
"password" : "password"
},
{
"platform" : "TelldusLive",
"name" : "Telldus Live!",
"public_key" : "telldus public key",
"private_key" : "telldus private key",
"token" : "telldus token",
"token_secret" : "telldus token secret"
},
{
"platform" : "Telldus",
"name" : "Telldus"
},
{
"platform": "Wink",
"name": "Wink",
"client_id": "YOUR_WINK_API_CLIENT_ID",
"client_secret": "YOUR_WINK_API_CLIENT_SECRET",
"username": "your@email.com",
"password": "WINK_PASSWORD"
},
{
"platform": "SmartThings",
"name": "SmartThings",
"app_id": "JSON SmartApp Id",
"access_token": "JSON SmartApp AccessToken"
},
{
"platform": "Domoticz",
"name": "Domoticz",
"server": "127.0.0.1",
"port": "8080",
"roomid": 0,
"loadscenes": 1
},
{
"platform": "PhilipsHue",
"name": "Phillips Hue",
"username": ""
},
{
"platform": "ISY",
"name": "ISY",
"host": "192.168.1.20",
"port": "8000",
"username": "username",
"password": "password"
},
{
"platform": "LogitechHarmony",
"name": "Living Room Harmony Hub"
},
{
"platform": "Sonos",
"name": "Sonos",
"play_volume": 25
},
{
"platform": "YamahaAVR",
"play_volume": -35,
"setMainInputTo": "AirPlay"
},
{
"platform": "ZWayServer",
"url": "http://192.168.1.10:8083/",
"login": "zwayusername",
"password": "zwayuserpassword",
"poll_interval": 2,
"split_services": false
},
{
"platform": "MiLight",
"name": "MiLight",
"ip_address": "255.255.255.255",
"port": 8899,
"type": "rgbw",
"delay": 30,
"repeat": 3,
"zones":["Kitchen Lamp","Bedroom Lamp","Living Room Lamp","Hallway Lamp"]
},
{
"platform": "HomeAssistant",
"name": "HomeAssistant",
"host": "http://192.168.1.10:8123",
"password": "XXXXX",
"supported_types": ["light", "switch", "media_player", "scene"]
},
{
"platform": "LIFx",
"name": "LIFx",
"access_token": "XXXXXXXX generate at https://cloud.lifx.com/settings"
}
],
"description": "This is an example configuration file with one fake accessory and one fake platform. You can use this as a template for creating your own configuration file containing devices you actually own.",
"accessories": [
{
"accessory": "WeMo",
"name": "Coffee Maker",
"description": "This shim supports Belkin WeMo devices on the same network as this server. You can create duplicate entries for this device and change the 'name' attribute to reflect what device is plugged into the WeMo, for instance 'Air Conditioner' or 'Coffee Maker'. This name will be used by Siri. Make sure to update the 'wemo_name' attribute with the EXACT name of the device in the WeMo app itself. This can be the same value as 'name' but it doesn't have to be.",
"wemo_name": "CoffeeMaker"
},
"name": "Coffee Maker"
}
],
"platforms": [
{
"accessory": "LiftMaster",
"name": "Garage Door",
"description": "This shim supports LiftMaster garage door openers that are already internet-connected to the 'MyQ' service.",
// "requiredDeviceId", "<ID of door if you have multiple doors, prompted by shim during startup if needed>",
"username": "your-liftmaster-username",
"password" : "your-liftmaster-password"
},
{
"accessory": "Lockitron",
"name": "Front Door",
"description": "This shim supports Lockitron locks. It uses the Lockitron cloud API, so the Lockitron must be 'awake' for locking and unlocking to actually happen. You can wake up Lockitron after issuing an lock/unlock command by knocking on the door.",
"lock_id": "your-lock-id",
"api_token" : "your-lockitron-api-access-token"
},
{
"accessory": "Carwings",
"name": "Leaf",
"description": "This shim supports controlling climate control on Nissan cars with Carwings. Note that Carwings is super slow and it may take up to 5 minutes for your command to be processed by the Carwings system.",
"username": "your-carwings-username",
"password" : "your-carwings-password"
},
{
"accessory": "iControl",
"name": "Xfinity Home",
"description": "This shim supports iControl-based security systems like Xfinity Home.",
"system": "XFINITY_HOME",
"email": "your-comcast-email",
"password": "your-comcast-password",
"pin": "your-security-system-pin-code"
},
{
"accessory": "HomeMatic",
"name": "Light",
"description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)",
"ccu_id": "The XMP-API id of your HomeMatic device",
"ccu_ip": "The IP-Adress of your HomeMatic CCU device"
},
{
"accessory": "HomeMaticWindow",
"name": "Contact",
"description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)",
"ccu_id": "The XMP-API id of your HomeMatic device (type HM-Sec-RHS)",
"ccu_ip": "The IP-Adress of your HomeMatic CCU device"
},
{
"accessory": "HomeMaticThermo",
"name": "Contact",
"description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)",
"ccu_id_TargetTemp": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )",
"ccu_id_CurrentTemp": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )",
"ccu_id_ControlMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )",
"ccu_id_ManuMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )",
"ccu_id_AutoMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )",
"ccu_ip": "The IP-Adress of your HomeMatic CCU device"
},
{
"accessory": "X10",
"name": "Lamp",
"ip_address": "localhost:3000",
"device_id": "E1",
"protocol": "pl",
"can_dim": true
},
{
"accessory": "Http",
"name": "Kitchen Lamp",
"on_url": "https://192.168.1.22:3030/devices/23222/on",
"off_url": "https://192.168.1.22:3030/devices/23222/off",
"brightness_url": "https://192.168.1.22:3030/devices/23222/brightness/%b",
"http_method": "POST"
},
{
"accessory": "HttpHygrometer",
"name": "Kitchen",
"url": "http://host/URL",
"http_method": "GET"
},
{
"accessory": "HttpThermometer",
"name": "Garage",
"url": "http://home/URL",
"http_method": "GET"
},
{
"accessory": "ELKM1",
"name": "Security System",
"description": "Allows basic control of Elk M1 security system. You can use 1 of 3 arm modes: Away, Stay, Night. If you need to access all 3, create 3 accessories with different names.",
"zone": "1",
"host": "192.168.1.10",
"port": "2101",
"pin": "1234",
"arm": "Away"
},
{
"accessory": "AD2USB",
"name": "Alarm",
"description": "Arm, disarm, and status monitoring of the default partition for Honeywell/Ademco alarm systems. Requires network configured AD2USB interface",
"host": "192.168.1.200", // IP address of the SER2SOCK service
"port" : 4999, // Port the SER2SOCK process is running on
"pin": "1234" // PIN used for arming / disarming
},
{
"accessory": "Tesla",
"name": "Tesla",
"description": "This shim supports controlling climate control on the Tesla Model S.",
"username": "tesla_email",
"password" : "tesla_password"
},
{
"accessory": "Hyperion",
"name": "TV Backlight",
"description": "Control the Hyperion TV backlight server. https://github.com/tvdzwan/hyperion",
"host": "localhost",
"port": "19444"
},
{
"accessory": "mpdclient",
"name" : "mpd",
"host" : "localhost",
"port" : 6600,
"description": "Allows some control of an MPD server"
},
{
"accessory": "FileSensor",
"name": "File Time Motion Sensor",
"path": "/tmp/CameraDump/",
"window_seconds": 5,
"sensor_type": "m",
"inverse": false
},
{
"accessory": "GenericRS232Device",
"name": "Projector",
"description": "Make sure you set a 'Siri-Name' for your iOS-Device (example: 'Home Cinema') otherwise it might not work.",
"id": "TYDYMU044UVNP",
"baudrate": 9600,
"device": "/dev/tty.usbserial",
"manufacturer": "Acer",
"model_name": "H6510BD",
"on_command": "* 0 IR 001\r",
"off_command": "* 0 IR 002\r"
"platform" : "PhilipsHue",
"name" : "Hue"
}
]
}

View File

@@ -0,0 +1,219 @@
var http = require('http');
var Accessory, Service, Characteristic, UUIDGen;
module.exports = function(homebridge) {
console.log("homebridge API version: " + homebridge.version);
// Accessory must be created from PlatformAccessory Constructor
Accessory = homebridge.platformAccessory;
// Service and Characteristic are from hap-nodejs
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
UUIDGen = homebridge.hap.uuid;
// For platform plugin to be considered as dynamic platform plugin,
// registerPlatform(pluginName, platformName, constructor, dynamic), dynamic must be true
homebridge.registerPlatform("homebridge-samplePlatform", "SamplePlatform", SamplePlatform, true);
}
// Platform constructor
// config may be null
// api may be null if launched from old homebridge version
function SamplePlatform(log, config, api) {
log("SamplePlatform Init");
var platform = this;
this.log = log;
this.config = config;
this.accessories = [];
this.requestServer = http.createServer(function(request, response) {
if (request.url === "/add") {
this.addAccessory(new Date().toISOString());
response.writeHead(204);
response.end();
}
if (request.url == "/reachability") {
this.updateAccessoriesReachability();
response.writeHead(204);
response.end();
}
if (request.url == "/remove") {
this.removeAccessory();
response.writeHead(204);
response.end();
}
}.bind(this));
this.requestServer.listen(18081, function() {
platform.log("Server Listening...");
});
if (api) {
// Save the API object as plugin needs to register new accessory via this object.
this.api = api;
// Listen to event "didFinishLaunching", this means homebridge already finished loading cached accessories
// Platform Plugin should only register new accessory that doesn't exist in homebridge after this event.
// Or start discover new accessories
this.api.on('didFinishLaunching', function() {
platform.log("DidFinishLaunching");
}.bind(this));
}
}
// Function invoked when homebridge tries to restore cached accessory
// Developer can configure accessory at here (like setup event handler)
// Update current value
SamplePlatform.prototype.configureAccessory = function(accessory) {
this.log(accessory.displayName, "Configure Accessory");
var platform = this;
// set the accessory to reachable if plugin can currently process the accessory
// otherwise set to false and update the reachability later by invoking
// accessory.updateReachability()
accessory.reachable = true;
accessory.on('identify', function(paired, callback) {
platform.log(accessory.displayName, "Identify!!!");
callback();
});
if (accessory.getService(Service.Lightbulb)) {
accessory.getService(Service.Lightbulb)
.getCharacteristic(Characteristic.On)
.on('set', function(value, callback) {
platform.log(accessory.displayName, "Light -> " + value);
callback();
});
}
this.accessories.push(accessory);
}
//Handler will be invoked when user try to config your plugin
//Callback can be cached and invoke when nessary
SamplePlatform.prototype.configurationRequestHandler = function(context, request, callback) {
this.log("Context: ", JSON.stringify(context));
this.log("Request: ", JSON.stringify(request));
// Check the request response
if (request && request.response && request.response.inputs && request.response.inputs.name) {
this.addAccessory(request.response.inputs.name);
// Invoke callback with config will let homebridge save the new config into config.json
// Callback = function(response, type, replace, config)
// set "type" to platform if the plugin is trying to modify platforms section
// set "replace" to true will let homebridge replace existing config in config.json
// "config" is the data platform trying to save
callback(null, "platform", true, {"platform":"SamplePlatform", "otherConfig":"SomeData"});
return;
}
// - UI Type: Input
// Can be used to request input from user
// User response can be retrieved from request.response.inputs next time
// when configurationRequestHandler being invoked
var respDict = {
"type": "Interface",
"interface": "input",
"title": "Add Accessory",
"items": [
{
"id": "name",
"title": "Name",
"placeholder": "Fancy Light"
}//,
// {
// "id": "pw",
// "title": "Password",
// "secure": true
// }
]
}
// - UI Type: List
// Can be used to ask user to select something from the list
// User response can be retrieved from request.response.selections next time
// when configurationRequestHandler being invoked
// var respDict = {
// "type": "Interface",
// "interface": "list",
// "title": "Select Something",
// "allowMultipleSelection": true,
// "items": [
// "A","B","C"
// ]
// }
// - UI Type: Instruction
// Can be used to ask user to do something (other than text input)
// Hero image is base64 encoded image data. Not really sure the maximum length HomeKit allows.
// var respDict = {
// "type": "Interface",
// "interface": "instruction",
// "title": "Almost There",
// "detail": "Please press the button on the bridge to finish the setup.",
// "heroImage": "base64 image data",
// "showActivityIndicator": true,
// "showNextButton": true,
// "buttonText": "Login in browser",
// "actionURL": "https://google.com"
// }
// Plugin can set context to allow it track setup process
context.ts = "Hello";
//invoke callback to update setup UI
callback(respDict);
}
// Sample function to show how developer can add accessory dynamically from outside event
SamplePlatform.prototype.addAccessory = function(accessoryName) {
this.log("Add Accessory");
var platform = this;
var uuid;
uuid = UUIDGen.generate(accessoryName);
var newAccessory = new Accessory(accessoryName, uuid);
newAccessory.on('identify', function(paired, callback) {
platform.log(accessory.displayName, "Identify!!!");
callback();
});
// Plugin can save context on accessory
// To help restore accessory in configureAccessory()
// newAccessory.context.something = "Something"
// Make sure you provided a name for service otherwise it may not visible in some HomeKit apps.
newAccessory.addService(Service.Lightbulb, "Test Light")
.getCharacteristic(Characteristic.On)
.on('set', function(value, callback) {
platform.log(accessory.displayName, "Light -> " + value);
callback();
});
this.accessories.push(newAccessory);
this.api.registerPlatformAccessories("homebridge-samplePlatform", "SamplePlatform", [newAccessory]);
}
SamplePlatform.prototype.updateAccessoriesReachability = function() {
this.log("Update Reachability");
for (var index in this.accessories) {
var accessory = this.accessories[index];
accessory.updateReachability(false);
}
}
// Sample function to show how developer can remove accessory dynamically from outside event
SamplePlatform.prototype.removeAccessory = function() {
this.log("Remove Accessory");
this.api.unregisterPlatformAccessories("homebridge-samplePlatform", "SamplePlatform", this.accessories);
this.accessories = [];
}

View File

@@ -0,0 +1,20 @@
{
"name": "homebridge-samplePlatform",
"version": "0.0.1",
"description": "Sample Platform plugin for homebridge: https://github.com/nfarina/homebridge",
"license": "ISC",
"keywords": [
"homebridge-plugin"
],
"repository": {
"type": "git",
"url": "git://github.com/example/homebridge.git"
},
"bugs": {
"url": "http://github.com/example/homebridge/issues"
},
"engines": {
"node": ">=0.12.0",
"homebridge": ">=0.2.0"
}
}

174
lib/api.js Normal file
View File

@@ -0,0 +1,174 @@
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
var hap = require("hap-nodejs");
var hapLegacyTypes = require("hap-nodejs/accessories/types.js");
var log = require("./logger")._system;
var User = require("./user").User;
var PlatformAccessory = require("./platformAccessory").PlatformAccessory;
var serverVersion = require("./version");
// The official homebridge API is the object we feed the plugin's exported initializer function.
module.exports = {
API: API
}
function API() {
this._accessories = {}; // this._accessories[pluginName.accessoryName] = accessory constructor
this._platforms = {}; // this._platforms[pluginName.platformName] = platform constructor
this._configurableAccessories = {};
this._dynamicPlatforms = {}; // this._dynamicPlatforms[pluginName.platformName] = platform constructor
// expose the homebridge API version
this.version = 2.2;
// expose the homebridge server version
this.serverVersion = serverVersion;
// expose the User class methods to plugins to get paths. Example: homebridge.user.storagePath()
this.user = User;
// expose HAP-NodeJS in its entirely for plugins to use instead of making Plugins
// require() it as a dependency - it's a heavy dependency so we don't want it in
// every single plugin.
this.hap = hap;
// we also need to "bolt on" the legacy "types" constants for older accessories/platforms
// still using the "object literal" style JSON.
this.hapLegacyTypes = hapLegacyTypes;
this.platformAccessory = PlatformAccessory;
}
inherits(API, EventEmitter);
API.prototype.accessory = function(name) {
// if you passed the "short form" name like "Lockitron" instead of "homebridge-lockitron.Lockitron",
// see if it matches exactly one accessory.
if (name.indexOf('.') == -1) {
var found = [];
for (var fullName in this._accessories) {
if (fullName.split(".")[1] == name)
found.push(fullName);
}
if (found.length == 1) {
return this._accessories[found[0]];
}
else if (found.length > 1) {
throw new Error("The requested accessory '" + name + "' has been registered multiple times. Please be more specific by writing one of: " + found.join(", "));
}
else {
throw new Error("The requested accessory '" + name + "' was not registered by any plugin.");
}
}
else {
if (!this._accessories[name])
throw new Error("The requested accessory '" + name + "' was not registered by any plugin.");
return this._accessories[name];
}
}
API.prototype.registerAccessory = function(pluginName, accessoryName, constructor, configurationRequestHandler) {
var fullName = pluginName + "." + accessoryName;
if (this._accessories[fullName])
throw new Error("Attempting to register an accessory '" + fullName + "' which has already been registered!");
log.info("Registering accessory '%s'", fullName);
this._accessories[fullName] = constructor;
// The plugin supports configuration
if (configurationRequestHandler) {
this._configurableAccessories[fullName] = configurationRequestHandler;
}
}
API.prototype.publishCameraAccessories = function(pluginName, accessories) {
for (var index in accessories) {
var accessory = accessories[index];
if (!(accessory instanceof PlatformAccessory)) {
throw new Error(pluginName + " attempt to register an accessory that isn\'t PlatformAccessory!");
}
accessory._associatedPlugin = pluginName;
}
this.emit('publishCameraAccessories', accessories);
}
API.prototype.platform = function(name) {
// if you passed the "short form" name like "Lockitron" instead of "homebridge-lockitron.Lockitron",
// see if it matches exactly one platform.
if (name.indexOf('.') == -1) {
var found = [];
for (var fullName in this._platforms) {
if (fullName.split(".")[1] == name)
found.push(fullName);
}
if (found.length == 1) {
return this._platforms[found[0]];
}
else if (found.length > 1) {
throw new Error("The requested platform '" + name + "' has been registered multiple times. Please be more specific by writing one of: " + found.join(", "));
}
else {
throw new Error("The requested platform '" + name + "' was not registered by any plugin.");
}
}
else {
if (!this._platforms[name])
throw new Error("The requested platform '" + name + "' was not registered by any plugin.");
return this._platforms[name];
}
}
API.prototype.registerPlatform = function(pluginName, platformName, constructor, dynamic) {
var fullName = pluginName + "." + platformName;
if (this._platforms[fullName])
throw new Error("Attempting to register a platform '" + fullName + "' which has already been registered!");
log.info("Registering platform '%s'", fullName);
this._platforms[fullName] = constructor;
if (dynamic) {
this._dynamicPlatforms[fullName] = constructor;
}
}
API.prototype.registerPlatformAccessories = function(pluginName, platformName, accessories) {
for (var index in accessories) {
var accessory = accessories[index];
if (!(accessory instanceof PlatformAccessory)) {
throw new Error(pluginName + " - " + platformName + " attempt to register an accessory that isn\'t PlatformAccessory!");
}
accessory._associatedPlugin = pluginName;
accessory._associatedPlatform = platformName;
}
this.emit('registerPlatformAccessories', accessories);
}
API.prototype.updatePlatformAccessories = function(accessories) {
this.emit('updatePlatformAccessories', accessories);
}
API.prototype.unregisterPlatformAccessories = function(pluginName, platformName, accessories) {
for (var index in accessories) {
var accessory = accessories[index];
if (!(accessory instanceof PlatformAccessory)) {
throw new Error(pluginName + " - " + platformName + " attempt to unregister an accessory that isn\'t PlatformAccessory!");
}
}
this.emit('unregisterPlatformAccessories', accessories);
}

96
lib/bridgeSetupManager.js Normal file
View File

@@ -0,0 +1,96 @@
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var SetupSession = require("./bridgeSetupSession").SetupSession;
'use strict';
module.exports = {
BridgeSetupManager: BridgeSetupManager
}
function BridgeSetupManager() {
this.session;
this.service = new Service(null, "49FB9D4D-0FEA-4BF1-8FA6-E7B18AB86DCE");
this.stateCharacteristic = new Characteristic("State", "77474A2F-FA98-485E-97BE-4762458774D8", {
format: Characteristic.Formats.UINT8,
minValue: 0,
maxValue: 1,
minStep: 1,
perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY]
});
this.stateCharacteristic.value = 0;
this.service.addCharacteristic(this.stateCharacteristic);
this.versionCharacteristic = new Characteristic("Version", "FD9FE4CC-D06F-4FFE-96C6-595D464E1026", {
format: Characteristic.Formats.STRING,
perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY]
});
this.versionCharacteristic.value = "1.0";
this.service.addCharacteristic(this.versionCharacteristic);
this.controlPointCharacteristic = new Characteristic("Control Point", "5819A4C2-E1B0-4C9D-B761-3EB1AFF43073", {
format: Characteristic.Formats.DATA,
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY]
})
this.controlPointCharacteristic.on('get', function(callback, context) {
this.handleReadRequest(callback, context);
}.bind(this));
this.controlPointCharacteristic.on('set', function(newValue, callback, context) {
this.handleWriteRequest(newValue, callback, context);
}.bind(this));
this.controlPointCharacteristic.value = null;
this.service.addCharacteristic(this.controlPointCharacteristic);
}
inherits(BridgeSetupManager, EventEmitter);
BridgeSetupManager.prototype.handleReadRequest = function(callback, context) {
if (!context) {
return;
}
if (!this.session) {
callback(null, null);
} else {
this.session.handleReadRequest(callback);
}
}
BridgeSetupManager.prototype.handleWriteRequest = function(value, callback, context) {
if (!context) {
callback();
return;
}
var data = new Buffer(value, 'base64');
var request = JSON.parse(data.toString());
callback();
if (!this.session || this.session.sessionUUID !== request.sid) {
if (this.session) {
this.session.removeAllListeners();
this.session.validSession = false;
}
this.session = new SetupSession(this.stateCharacteristic, this.controlPointCharacteristic);
this.session.configurablePlatformPlugins = this.configurablePlatformPlugins;
this.session.on('newConfig', function(type, name, replace, config) {
this.emit('newConfig', type, name, replace, config);
}.bind(this));
this.session.on('requestCurrentConfig', function(callback) {
this.emit('requestCurrentConfig', callback);
}.bind(this));
this.session.on('end', function() {
this.session = null;
}.bind(this));
}
this.session.handleWriteRequest(request);
}

191
lib/bridgeSetupSession.js Normal file

File diff suppressed because one or more lines are too long

41
lib/cli.js Normal file
View File

@@ -0,0 +1,41 @@
var program = require('commander');
var hap = require("hap-nodejs");
var version = require('./version');
var Server = require('./server').Server;
var Plugin = require('./plugin').Plugin;
var User = require('./user').User;
var log = require("./logger")._system;
'use strict';
module.exports = function() {
var insecureAccess = false;
program
.version(version)
.option('-P, --plugin-path [path]', 'look for plugins installed at [path] as well as the default locations ([path] can also point to a single plugin)', function(p) { Plugin.addPluginPath(p); })
.option('-U, --user-storage-path [path]', 'look for homebridge user files at [path] instead of the default location (~/.homebridge)', function(p) { User.setStoragePath(p); })
.option('-D, --debug', 'turn on debug level logging', function() { require('./logger').setDebugEnabled(true) })
.option('-I, --insecure', 'allow unauthenticated requests (for easier hacking)', function() { insecureAccess = true })
.parse(process.argv);
// Initialize HAP-NodeJS with a custom persist directory
hap.init(User.persistPath());
var server = new Server(insecureAccess);
var signals = { 'SIGINT': 2, 'SIGTERM': 15 };
Object.keys(signals).forEach(function (signal) {
process.on(signal, function () {
log.info("Got %s, shutting down Homebridge...", signal);
// Save cached accessories to persist storage.
server._updateCachedAccessories();
process.exit(128 + signals[signal]);
});
});
server.run();
}

92
lib/logger.js Normal file
View File

@@ -0,0 +1,92 @@
var chalk = require('chalk');
var util = require('util');
'use strict';
module.exports = {
Logger: Logger,
setDebugEnabled: setDebugEnabled,
_system: new Logger() // system logger, for internal use only
}
var DEBUG_ENABLED = false;
// Turns on debug level logging
function setDebugEnabled(enabled) {
DEBUG_ENABLED = enabled;
}
// global cache of logger instances by plugin name
var loggerCache = {};
/**
* Logger class
*/
function Logger(prefix) {
this.prefix = prefix;
}
Logger.prototype.debug = function(msg) {
if (DEBUG_ENABLED)
this.log.apply(this, ['debug'].concat(Array.prototype.slice.call(arguments)));
}
Logger.prototype.info = function(msg) {
this.log.apply(this, ['info'].concat(Array.prototype.slice.call(arguments)));
}
Logger.prototype.warn = function(msg) {
this.log.apply(this, ['warn'].concat(Array.prototype.slice.call(arguments)));
}
Logger.prototype.error = function(msg) {
this.log.apply(this, ['error'].concat(Array.prototype.slice.call(arguments)));
}
Logger.prototype.log = function(level, msg) {
msg = util.format.apply(util, Array.prototype.slice.call(arguments, 1));
func = console.log;
if (level == 'debug') {
msg = chalk.gray(msg);
}
else if (level == 'warn') {
msg = chalk.yellow(msg);
func = console.error;
}
else if (level == 'error') {
msg = chalk.bold.red(msg);
func = console.error;
}
// prepend prefix if applicable
if (this.prefix)
msg = chalk.cyan("[" + this.prefix + "]") + " " + msg;
// prepend timestamp
var date = new Date();
msg = chalk.white("[" + date.toLocaleString() + "]") + " " + msg;
func(msg);
}
Logger.withPrefix = function(prefix) {
if (!loggerCache[prefix]) {
// create a class-like logger thing that acts as a function as well
// as an instance of Logger.
var logger = new Logger(prefix);
var log = logger.info.bind(logger);
log.debug = logger.debug;
log.info = logger.info;
log.warn = logger.warn;
log.error = logger.error;
log.log = logger.log;
log.prefix = logger.prefix;
loggerCache[prefix] = log;
}
return loggerCache[prefix];
}

228
lib/platformAccessory.js Normal file
View File

@@ -0,0 +1,228 @@
var uuid = require("hap-nodejs").uuid;
var Accessory = require("hap-nodejs").Accessory;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
'use strict';
module.exports = {
PlatformAccessory: PlatformAccessory
}
function PlatformAccessory(displayName, UUID, category) {
if (!displayName) throw new Error("Accessories must be created with a non-empty displayName.");
if (!UUID) throw new Error("Accessories must be created with a valid UUID.");
if (!uuid.isValid(UUID)) throw new Error("UUID '" + UUID + "' is not a valid UUID. Try using the provided 'generateUUID' function to create a valid UUID from any arbitrary string, like a serial number.");
this.displayName = displayName;
this.UUID = UUID;
this.category = category || Accessory.Categories.OTHER;
this.services = [];
this.reachable = false;
this.context = {};
this._associatedPlugin;
this._associatedPlatform;
this._associatedHAPAccessory;
this
.addService(Service.AccessoryInformation)
.setCharacteristic(Characteristic.Name, displayName)
.setCharacteristic(Characteristic.Manufacturer, "Default-Manufacturer")
.setCharacteristic(Characteristic.Model, "Default-Model")
.setCharacteristic(Characteristic.SerialNumber, "Default-SerialNumber");
}
inherits(PlatformAccessory, EventEmitter);
PlatformAccessory.prototype.addService = function(service) {
// service might be a constructor like `Service.AccessoryInformation` instead of an instance
// of Service. Coerce if necessary.
if (typeof service === 'function')
service = new (Function.prototype.bind.apply(service, arguments));
// check for UUID+subtype conflict
for (var index in this.services) {
var existing = this.services[index];
if (existing.UUID === service.UUID) {
// OK we have two Services with the same UUID. Check that each defines a `subtype` property and that each is unique.
if (!service.subtype)
throw new Error("Cannot add a Service with the same UUID '" + existing.UUID + "' as another Service in this Accessory without also defining a unique 'subtype' property.");
if (service.subtype.toString() === existing.subtype.toString())
throw new Error("Cannot add a Service with the same UUID '" + existing.UUID + "' and subtype '" + existing.subtype + "' as another Service in this Accessory.");
}
}
this.services.push(service);
if (this._associatedHAPAccessory) {
this._associatedHAPAccessory.addService(service);
}
return service;
}
PlatformAccessory.prototype.removeService = function(service) {
var targetServiceIndex;
for (var index in this.services) {
var existingService = this.services[index];
if (existingService === service) {
targetServiceIndex = index;
break;
}
}
if (targetServiceIndex) {
this.services.splice(targetServiceIndex, 1);
service.removeAllListeners();
if (this._associatedHAPAccessory) {
this._associatedHAPAccessory.removeService(service);
}
}
}
/**
* searchs for a Service in the services collection and returns the first Service object that matches.
* If multiple services of the same type are present in one accessory, use getServiceByUUIDAndSubType instead.
* @param {ServiceConstructor|string} name
* @returns Service
*/
PlatformAccessory.prototype.getService = function(name) {
for (var index in this.services) {
var service = this.services[index];
if (typeof name === 'string' && (service.displayName === name || service.name === name))
return service;
else if (typeof name === 'function' && ((service instanceof name) || (name.UUID === service.UUID)))
return service;
}
}
/**
* searchs for a Service in the services collection and returns the first Service object that matches.
* If multiple services of the same type are present in one accessory, use getServiceByUUIDAndSubType instead.
* @param {string} UUID Can be an UUID, a service.displayName, or a constructor of a Service
* @param {string} subtype A subtype string to match
* @returns Service
*/
PlatformAccessory.prototype.getServiceByUUIDAndSubType = function(UUID, subtype) {
for (var index in this.services) {
var service = this.services[index];
if (typeof UUID === 'string' && (service.displayName === UUID || service.name === UUID) && service.subtype === subtype )
return service;
else if (typeof UUID === 'function' && ((service instanceof UUID) || (UUID.UUID === service.UUID)) && service.subtype === subtype)
return service;
}
}
PlatformAccessory.prototype.updateReachability = function(reachable) {
this.reachable = reachable;
if (this._associatedHAPAccessory) {
this._associatedHAPAccessory.updateReachability(reachable);
}
}
PlatformAccessory.prototype.configureCameraSource = function(cameraSource) {
this.cameraSource = cameraSource;
for (var index in cameraSource.services) {
var service = cameraSource.services[index];
this.addService(service);
}
}
PlatformAccessory.prototype._prepareAssociatedHAPAccessory = function () {
this._associatedHAPAccessory = new Accessory(this.displayName, this.UUID);
if (this.cameraSource) {
this._associatedHAPAccessory.configureCameraSource(this.cameraSource);
}
this._associatedHAPAccessory._sideloadServices(this.services);
this._associatedHAPAccessory.category = this.category;
this._associatedHAPAccessory.reachable = this.reachable;
this._associatedHAPAccessory.on('identify', function(paired, callback) {
if (this.listeners('identify').length > 0) {
// allow implementors to identify this Accessory in whatever way is appropriate, and pass along
// the standard callback for completion.
this.emit('identify', paired, callback);
} else {
callback();
}
}.bind(this));
}
PlatformAccessory.prototype._dictionaryPresentation = function() {
var accessory = {};
accessory.plugin = this._associatedPlugin;
accessory.platform = this._associatedPlatform;
accessory.displayName = this.displayName;
accessory.UUID = this.UUID;
accessory.category = this.category;
accessory.context = this.context;
var services = [];
for (var index in this.services) {
var service = this.services[index];
var servicePresentation = {};
servicePresentation.displayName = service.displayName;
servicePresentation.UUID = service.UUID;
servicePresentation.subtype = service.subtype;
var characteristics = [];
for (var cIndex in service.characteristics) {
var characteristic = service.characteristics[cIndex];
var characteristicPresentation = {};
characteristicPresentation.displayName = characteristic.displayName;
characteristicPresentation.UUID = characteristic.UUID;
characteristicPresentation.props = characteristic.props;
characteristicPresentation.value = characteristic.value;
characteristicPresentation.eventOnlyCharacteristic = characteristic.eventOnlyCharacteristic;
characteristics.push(characteristicPresentation);
}
servicePresentation.characteristics = characteristics;
services.push(servicePresentation);
}
accessory.services = services;
return accessory;
}
PlatformAccessory.prototype._configFromData = function(data) {
this._associatedPlugin = data.plugin;
this._associatedPlatform = data.platform;
this.displayName = data.displayName;
this.UUID = data.UUID;
this.category = data.category;
this.context = data.context;
this.reachable = false;
var services = [];
for (var index in data.services) {
var service = data.services[index];
var hapService = new Service(service.displayName, service.UUID, service.subtype);
var characteristics = [];
for (var cIndex in service.characteristics) {
var characteristic = service.characteristics[cIndex];
var hapCharacteristic = new Characteristic(characteristic.displayName, characteristic.UUID, characteristic.props);
hapCharacteristic.eventOnlyCharacteristic = characteristic.eventOnlyCharacteristic;
hapCharacteristic.value = characteristic.value;
characteristics.push(hapCharacteristic);
}
hapService._sideloadCharacteristics(characteristics);
services.push(hapService);
}
this.services = services;
}

206
lib/plugin.js Normal file
View File

@@ -0,0 +1,206 @@
var path = require('path');
var fs = require('fs');
var semver = require('semver');
var User = require('./user').User;
var version = require('./version');
var log = require("./logger")._system;
'use strict';
module.exports = {
Plugin: Plugin
}
/**
* Homebridge Plugin.
*
* Allows for discovering and loading installed Homebridge plugins.
*/
function Plugin(pluginPath) {
this.pluginPath = pluginPath; // like "/usr/local/lib/node_modules/homebridge-lockitron"
this.initializer; // exported function from the plugin that initializes it
}
Plugin.prototype.name = function() {
return path.basename(this.pluginPath);
}
Plugin.prototype.load = function(options) {
options = options || {};
// does this plugin exist at all?
if (!fs.existsSync(this.pluginPath)) {
throw new Error("Plugin " + this.pluginPath + " was not found. Make sure the module '" + this.pluginPath + "' is installed.");
}
// attempt to load package.json
var pjson = Plugin.loadPackageJSON(this.pluginPath);
// very temporary fix for first wave of plugins
if (pjson.peerDepdendencies && (!pjson.engines || !pjson.engines.homebridge)) {
var engines = pjson.engines || {}
engines.homebridge = pjson.peerDepdendencies.homebridge;
pjson.engines = engines;
}
// pluck out the HomeBridge version requirement
if (!pjson.engines || !pjson.engines.homebridge) {
throw new Error("Plugin " + this.pluginPath + " does not contain the 'homebridge' package in 'engines'.");
}
var versionRequired = pjson.engines.homebridge;
// make sure the version is satisfied by the currently running version of HomeBridge
if (!semver.satisfies(version, versionRequired)) {
throw new Error("Plugin " + this.pluginPath + " requires a HomeBridge version of " + versionRequired + " which does not satisfy the current HomeBridge version of " + version + ". You may need to upgrade your installation of HomeBridge.");
}
// figure out the main module - index.js unless otherwise specified
var main = pjson.main || "./index.js";
var mainPath = path.join(this.pluginPath, main);
// try to require() it and grab the exported initialization hook
this.initializer = require(mainPath);
}
Plugin.loadPackageJSON = function(pluginPath) {
// check for a package.json
var pjsonPath = path.join(pluginPath, "package.json");
var pjson = null;
if (!fs.existsSync(pjsonPath)) {
throw new Error("Plugin " + pluginPath + " does not contain a package.json.");
}
try {
// attempt to parse package.json
pjson = JSON.parse(fs.readFileSync(pjsonPath));
}
catch (err) {
throw new Error("Plugin " + pluginPath + " contains an invalid package.json. Error: " + err);
}
// make sure the name is prefixed with 'homebridge-'
if (!pjson.name || pjson.name.indexOf('homebridge-') != 0) {
throw new Error("Plugin " + pluginPath + " does not have a package name that begins with 'homebridge-'.");
}
// verify that it's tagged with the correct keyword
if (!pjson.keywords || pjson.keywords.indexOf("homebridge-plugin") == -1) {
throw new Error("Plugin " + pluginPath + " package.json does not contain the keyword 'homebridge-plugin'.");
}
return pjson;
}
Plugin.getDefaultPaths = function() {
var win32 = process.platform === 'win32';
var paths = [];
// add the paths used by require()
paths = paths.concat(require.main.paths);
// THIS SECTION FROM: https://github.com/yeoman/environment/blob/master/lib/resolver.js
// Adding global npm directories
// We tried using npm to get the global modules path, but it haven't work out
// because of bugs in the parseable implementation of `ls` command and mostly
// performance issues. So, we go with our best bet for now.
if (process.env.NODE_PATH) {
paths = process.env.NODE_PATH.split(path.delimiter)
.filter(function(p) { return !!p; }) // trim out empty values
.concat(paths);
} else {
// Default paths for each system
if (win32) {
paths.push(path.join(process.env.APPDATA, 'npm/node_modules'));
} else {
paths.push('/usr/local/lib/node_modules');
paths.push('/usr/lib/node_modules');
const exec = require('child_process').execSync;
paths.push(exec('/bin/echo -n "$(npm -g prefix)/lib/node_modules"').toString('utf8'));
}
}
return paths;
}
// All search paths we will use to discover installed plugins
Plugin.paths = Plugin.getDefaultPaths();
Plugin.addPluginPath = function(pluginPath) {
Plugin.paths.unshift(path.resolve(process.cwd(), pluginPath));
}
// Gets all plugins installed on the local system
Plugin.installed = function() {
var plugins = [];
var pluginsByName = {}; // don't add duplicate plugins
var searchedPaths = {}; // don't search the same paths twice
// search for plugins among all known paths, in order
for (var index in Plugin.paths) {
var requirePath = Plugin.paths[index];
// did we already search this path?
if (searchedPaths[requirePath])
continue;
searchedPaths[requirePath] = true;
// just because this path is in require.main.paths doesn't mean it necessarily exists!
if (!fs.existsSync(requirePath))
continue;
var names = fs.readdirSync(requirePath);
// does this path point inside a single plugin and not a directory containing plugins?
if (fs.existsSync(path.join(requirePath, "package.json")))
names = [""];
// read through each directory in this node_modules folder
for (var index2 in names) {
var name = names[index2];
// reconstruct full path
var pluginPath = path.join(requirePath, name);
try {
// we only care about directories
if (!fs.statSync(pluginPath).isDirectory()) continue;
} catch (e) {
continue;
}
// does this module contain a package.json?
var pjson;
try {
// throws an Error if this isn't a homebridge plugin
pjson = Plugin.loadPackageJSON(pluginPath);
}
catch (err) {
// is this "trying" to be a homebridge plugin? if so let you know what went wrong.
if (!name || name.indexOf('homebridge-') == 0) {
log.warn(err.message);
}
// skip this module
continue;
}
// get actual name if this path points inside a single plugin
if (!name) name = pjson.name;
// add it to the return list
if (!pluginsByName[name]) {
pluginsByName[name] = pluginPath;
plugins.push(new Plugin(pluginPath));
}
else {
log.warn("Warning: skipping plugin found at '" + pluginPath + "' since we already loaded the same plugin from '" + pluginsByName[name] + "'.");
}
}
}
return plugins;
}

589
lib/server.js Normal file
View File

@@ -0,0 +1,589 @@
var path = require('path');
var fs = require('fs');
var uuid = require("hap-nodejs").uuid;
var accessoryStorage = require('node-persist').create();
var Bridge = require("hap-nodejs").Bridge;
var Accessory = require("hap-nodejs").Accessory;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var AccessoryLoader = require("hap-nodejs").AccessoryLoader;
var once = require("hap-nodejs/lib/util/once").once;
var Plugin = require('./plugin').Plugin;
var User = require('./user').User;
var API = require('./api').API;
var PlatformAccessory = require("./platformAccessory").PlatformAccessory;
var BridgeSetupManager = require("./bridgeSetupManager").BridgeSetupManager;
var log = require("./logger")._system;
var Logger = require('./logger').Logger;
var mac = require("./util/mac");
var chalk = require('chalk');
'use strict';
module.exports = {
Server: Server
}
function Server(insecureAccess, opts) {
opts = opts || {};
// Setup Accessory Cache Storage
accessoryStorage.initSync({ dir: User.cachedAccessoryPath() });
this._api = new API(); // object we feed to Plugins
this._api.on('registerPlatformAccessories', function(accessories) {
this._handleRegisterPlatformAccessories(accessories);
}.bind(this));
this._api.on('updatePlatformAccessories', function(accessories) {
this._handleUpdatePlatformAccessories(accessories);
}.bind(this));
this._api.on('unregisterPlatformAccessories', function(accessories) {
this._handleUnregisterPlatformAccessories(accessories);
}.bind(this));
this._api.on('publishCameraAccessories', function(accessories) {
this._handlePublishCameraAccessories(accessories);
}.bind(this));
this._plugins = this._loadPlugins(); // plugins[name] = Plugin instance
this._config = opts.config || this._loadConfig();
this._cachedPlatformAccessories = this._loadCachedPlatformAccessories();
this._bridge = this._createBridge();
this._activeDynamicPlugins = {};
this._configurablePlatformPlugins = {};
this._publishedCameras = {};
this._setupManager = new BridgeSetupManager();
this._setupManager.on('newConfig', this._handleNewConfig.bind(this));
this._setupManager.on('requestCurrentConfig', function(callback) {
callback(this._config);
}.bind(this));
// Server is "secure by default", meaning it creates a top-level Bridge accessory that
// will not allow unauthenticated requests. This matches the behavior of actual HomeKit
// accessories. However you can set this to true to allow all requests without authentication,
// which can be useful for easy hacking. Note that this will expose all functions of your
// bridged accessories, like changing charactersitics (i.e. flipping your lights on and off).
this._allowInsecureAccess = insecureAccess || false;
}
Server.prototype.run = function() {
// keep track of async calls we're waiting for callbacks on before we can start up
this._asyncCalls = 0;
this._asyncWait = true;
if (this._config.platforms) this._loadPlatforms();
if (this._config.accessories) this._loadAccessories();
this._loadDynamicPlatforms();
this._configCachedPlatformAccessories();
this._setupManager.configurablePlatformPlugins = this._configurablePlatformPlugins;
this._bridge.addService(this._setupManager.service);
this._asyncWait = false;
// publish now unless we're waiting on anyone
if (this._asyncCalls == 0)
this._publish();
this._api.emit('didFinishLaunching');
}
Server.prototype._publish = function() {
// pull out our custom Bridge settings from config.json, if any
var bridgeConfig = this._config.bridge || {};
var info = this._bridge.getService(Service.AccessoryInformation);
if (bridgeConfig.manufacturer)
info.setCharacteristic(Characteristic.Manufacturer, bridgeConfig.manufacturer);
if (bridgeConfig.model)
info.setCharacteristic(Characteristic.Model, bridgeConfig.model);
if (bridgeConfig.serialNumber)
info.setCharacteristic(Characteristic.SerialNumber, bridgeConfig.serialNumber);
this._printPin(bridgeConfig.pin);
this._bridge.on('listening', function(port) {
log.info("Homebridge is running on port %s.", port);
});
this._bridge.publish({
username: bridgeConfig.username || "CC:22:3D:E3:CE:30",
port: bridgeConfig.port || 0,
pincode: bridgeConfig.pin || "031-45-154",
category: Accessory.Categories.BRIDGE
}, this._allowInsecureAccess);
}
Server.prototype._loadPlugins = function(accessories, platforms) {
var plugins = {};
var foundOnePlugin = false;
// load and validate plugins - check for valid package.json, etc.
Plugin.installed().forEach(function(plugin) {
// attempt to load it
try {
plugin.load();
}
catch (err) {
log.error("====================")
log.error("ERROR LOADING PLUGIN " + plugin.name() + ":")
log.error(err.stack);
log.error("====================")
plugin.loadError = err;
}
if (!plugin.loadError) {
// add it to our dict for easy lookup later
plugins[plugin.name()] = plugin;
log.info("Loaded plugin: " + plugin.name());
// call the plugin's initializer and pass it our API instance
plugin.initializer(this._api);
log.info("---");
foundOnePlugin = true;
}
}.bind(this));
// Complain if you don't have any plugins.
if (!foundOnePlugin) {
log.warn("No plugins found. See the README for information on installing plugins.")
}
return plugins;
}
Server.prototype._loadConfig = function() {
// Look for the configuration file
var configPath = User.configPath();
// Complain and exit if it doesn't exist yet
if (!fs.existsSync(configPath)) {
log.warn("config.json (%s) not found.", configPath);
var config = {};
config.bridge = {
"name": "Homebridge",
"username": "CC:22:3D:E3:CE:30",
"pin": "031-45-154"
};
return config;
// log.error("Couldn't find a config.json file at '"+configPath+"'. Look at config-sample.json for examples of how to format your config.js and add your home accessories.");
// process.exit(1);
}
// Load up the configuration file
var config;
try {
config = JSON.parse(fs.readFileSync(configPath));
}
catch (err) {
log.error("There was a problem reading your config.json file.");
log.error("Please try pasting your config.json file here to validate it: http://jsonlint.com");
log.error("");
throw err;
}
var accessoryCount = (config.accessories && config.accessories.length) || 0;
var username = config.bridge.username;
var validMac = /^([0-9A-F]{2}:){5}([0-9A-F]{2})$/;
if (!validMac.test(username)){
throw new Error('Not a valid username: ' + username + '. Must be 6 pairs of colon-' +
'separated hexadecimal chars (A-F 0-9), like a MAC address.');
}
var accessoryCount = (config.accessories && config.accessories.length) || 0;
var platformCount = (config.platforms && config.platforms.length) || 0;
log.info("Loaded config.json with %s accessories and %s platforms.", accessoryCount, platformCount);
log.info("---");
return config;
}
Server.prototype._loadCachedPlatformAccessories = function() {
var cachedAccessories = accessoryStorage.getItem("cachedAccessories");
var platformAccessories = [];
if (cachedAccessories) {
for (var index in cachedAccessories) {
var serializedAccessory = cachedAccessories[index];
var platformAccessory = new PlatformAccessory(serializedAccessory.displayName, serializedAccessory.UUID, serializedAccessory.category);
platformAccessory._configFromData(serializedAccessory);
platformAccessories.push(platformAccessory);
}
}
return platformAccessories;
}
Server.prototype._createBridge = function() {
// pull out our custom Bridge settings from config.json, if any
var bridgeConfig = this._config.bridge || {};
// Create our Bridge which will host all loaded Accessories
return new Bridge(bridgeConfig.name || 'Homebridge', uuid.generate("HomeBridge"));
}
Server.prototype._loadAccessories = function() {
// Instantiate all accessories in the config
log.info("Loading " + this._config.accessories.length + " accessories...");
for (var i=0; i<this._config.accessories.length; i++) {
var accessoryConfig = this._config.accessories[i];
// Load up the class for this accessory
var accessoryType = accessoryConfig["accessory"]; // like "Lockitron"
var accessoryConstructor = this._api.accessory(accessoryType); // like "LockitronAccessory", a JavaScript constructor
if (!accessoryConstructor)
throw new Error("Your config.json is requesting the accessory '" + accessoryType + "' which has not been published by any installed plugins.");
// Create a custom logging function that prepends the device display name for debugging
var accessoryName = accessoryConfig["name"];
var accessoryLogger = Logger.withPrefix(accessoryName);
accessoryLogger("Initializing %s accessory...", accessoryType);
var accessoryInstance = new accessoryConstructor(accessoryLogger, accessoryConfig);
var accessory = this._createAccessory(accessoryInstance, accessoryName, accessoryType, accessoryConfig.uuid_base); //pass accessoryType for UUID generation, and optional parameter uuid_base which can be used instead of displayName for UUID generation
// add it to the bridge
this._bridge.addBridgedAccessory(accessory);
}
}
Server.prototype._loadPlatforms = function() {
log.info("Loading " + this._config.platforms.length + " platforms...");
for (var i=0; i<this._config.platforms.length; i++) {
var platformConfig = this._config.platforms[i];
// Load up the class for this accessory
var platformType = platformConfig["platform"]; // like "Wink"
var platformName = platformConfig["name"];
var platformConstructor = this._api.platform(platformType); // like "WinkPlatform", a JavaScript constructor
if (!platformConstructor)
throw new Error("Your config.json is requesting the platform '" + platformType + "' which has not been published by any installed plugins.");
// Create a custom logging function that prepends the platform name for debugging
var platformLogger = Logger.withPrefix(platformName);
platformLogger("Initializing %s platform...", platformType);
var platformInstance = new platformConstructor(platformLogger, platformConfig, this._api);
if (platformInstance.configureAccessory == undefined) {
// Plugin 1.0, load accessories
this._loadPlatformAccessories(platformInstance, platformLogger, platformType);
} else {
this._activeDynamicPlugins[platformType] = platformInstance;
}
if (platformInstance.configurationRequestHandler != undefined) {
this._configurablePlatformPlugins[platformType] = platformInstance;
}
}
}
Server.prototype._loadDynamicPlatforms = function() {
for (var dynamicPluginName in this._api._dynamicPlatforms) {
if (!this._activeDynamicPlugins[dynamicPluginName] && !this._activeDynamicPlugins[dynamicPluginName.split(".")[1]]) {
console.log("Load " + dynamicPluginName);
var platformConstructor = this._api._dynamicPlatforms[dynamicPluginName];
var platformLogger = Logger.withPrefix(dynamicPluginName);
var platformInstance = new platformConstructor(platformLogger, null, this._api);
this._activeDynamicPlugins[dynamicPluginName] = platformInstance;
if (platformInstance.configurationRequestHandler != undefined) {
this._configurablePlatformPlugins[dynamicPluginName] = platformInstance;
}
}
}
}
Server.prototype._configCachedPlatformAccessories = function() {
for (var index in this._cachedPlatformAccessories) {
var accessory = this._cachedPlatformAccessories[index];
if (!(accessory instanceof PlatformAccessory)) {
console.log("Unexpected Accessory!");
continue;
}
var fullName = accessory._associatedPlugin + "." + accessory._associatedPlatform;
var platformInstance = this._activeDynamicPlugins[fullName];
if (!platformInstance) {
platformInstance = this._activeDynamicPlugins[accessory._associatedPlatform];
}
if (platformInstance) {
platformInstance.configureAccessory(accessory);
} else {
console.log("Failed to find plugin to handle accessory " + accessory.displayName);
}
accessory._prepareAssociatedHAPAccessory();
this._bridge.addBridgedAccessory(accessory._associatedHAPAccessory);
}
}
Server.prototype._loadPlatformAccessories = function(platformInstance, log, platformType) {
this._asyncCalls++;
platformInstance.accessories(once(function(foundAccessories){
this._asyncCalls--;
// loop through accessories adding them to the list and registering them
for (var i = 0; i < foundAccessories.length; i++) {
var accessoryInstance = foundAccessories[i];
var accessoryName = accessoryInstance.name; // assume this property was set
log("Initializing platform accessory '%s'...", accessoryName);
var accessory = this._createAccessory(accessoryInstance, accessoryName, platformType, accessoryInstance.uuid_base);
// add it to the bridge
this._bridge.addBridgedAccessory(accessory);
}
// were we the last callback?
if (this._asyncCalls === 0 && !this._asyncWait)
this._publish();
}.bind(this)));
}
Server.prototype._createAccessory = function(accessoryInstance, displayName, accessoryType, uuid_base) {
var services = accessoryInstance.getServices();
if (!(services[0] instanceof Service)) {
// The returned "services" for this accessory is assumed to be the old style: a big array
// of JSON-style objects that will need to be parsed by HAP-NodeJS's AccessoryLoader.
// Create the actual HAP-NodeJS "Accessory" instance
return AccessoryLoader.parseAccessoryJSON({
displayName: displayName,
services: services
});
}
else {
// The returned "services" for this accessory are simply an array of new-API-style
// Service instances which we can add to a created HAP-NodeJS Accessory directly.
var accessoryUUID = uuid.generate(accessoryType + ":" + (uuid_base || displayName));
var accessory = new Accessory(displayName, accessoryUUID);
// listen for the identify event if the accessory instance has defined an identify() method
if (accessoryInstance.identify)
accessory.on('identify', function(paired, callback) { accessoryInstance.identify(callback); });
services.forEach(function(service) {
// if you returned an AccessoryInformation service, merge its values with ours
if (service instanceof Service.AccessoryInformation) {
var existingService = accessory.getService(Service.AccessoryInformation);
// pull out any values you may have defined
var manufacturer = service.getCharacteristic(Characteristic.Manufacturer).value;
var model = service.getCharacteristic(Characteristic.Model).value;
var serialNumber = service.getCharacteristic(Characteristic.SerialNumber).value;
if (manufacturer) existingService.setCharacteristic(Characteristic.Manufacturer, manufacturer);
if (model) existingService.setCharacteristic(Characteristic.Model, model);
if (serialNumber) existingService.setCharacteristic(Characteristic.SerialNumber, serialNumber);
}
else {
accessory.addService(service);
}
});
return accessory;
}
}
Server.prototype._handleRegisterPlatformAccessories = function(accessories) {
var hapAccessories = [];
for (var index in accessories) {
var accessory = accessories[index];
accessory._prepareAssociatedHAPAccessory();
hapAccessories.push(accessory._associatedHAPAccessory);
this._cachedPlatformAccessories.push(accessory);
}
this._bridge.addBridgedAccessories(hapAccessories);
this._updateCachedAccessories();
}
Server.prototype._handleUpdatePlatformAccessories = function(accessories) {
// Update persisted accessories
this._updateCachedAccessories();
}
Server.prototype._handleUnregisterPlatformAccessories = function(accessories) {
var hapAccessories = [];
for (var index in accessories) {
var accessory = accessories[index];
if (accessory._associatedHAPAccessory) {
hapAccessories.push(accessory._associatedHAPAccessory);
}
for (var targetIndex in this._cachedPlatformAccessories) {
var existing = this._cachedPlatformAccessories[targetIndex];
if (existing.UUID === accessory.UUID) {
this._cachedPlatformAccessories.splice(targetIndex, 1);
break;
}
}
}
this._bridge.removeBridgedAccessories(hapAccessories);
this._updateCachedAccessories();
}
Server.prototype._handlePublishCameraAccessories = function(accessories) {
var accessoryPin = (this._config.bridge || {}).pin || "031-45-154";
for (var index in accessories) {
var accessory = accessories[index];
accessory._prepareAssociatedHAPAccessory();
var hapAccessory = accessory._associatedHAPAccessory;
var advertiseAddress = mac.generate(accessory.UUID);
if (this._publishedCameras[advertiseAddress]) {
throw new Error("Camera accessory " + accessory.displayName + " experienced an address collision.");
} else {
this._publishedCameras[advertiseAddress] = accessory;
}
hapAccessory.on('listening', function(port) {
log.info("%s is running on port %s.", accessory.displayName, port);
});
hapAccessory.publish({
username: advertiseAddress,
pincode: accessoryPin,
category: accessory.category
}, this._allowInsecureAccess);
}
}
Server.prototype._updateCachedAccessories = function() {
var serializedAccessories = [];
for (var index in this._cachedPlatformAccessories) {
var accessory = this._cachedPlatformAccessories[index];
serializedAccessories.push(accessory._dictionaryPresentation());
}
accessoryStorage.setItemSync("cachedAccessories", serializedAccessories);
}
Server.prototype._handleNewConfig = function(type, name, replace, config) {
if (type === "accessory") {
// TODO: Load new accessory
if (!this._config.accessories) {
this._config.accessories = [];
}
if (!replace) {
this._config.accessories.push(config);
} else {
var targetName;
if (name.indexOf('.') !== -1) {
targetName = name.split(".")[1];
}
var found = false;
for (var index in this._config.accessories) {
var accessoryConfig = this._config.accessories[index];
if (accessoryConfig.accessory === name) {
this._config.accessories[index] = config;
found = true;
break;
}
if (targetName && (accessoryConfig.accessory === targetName)) {
this._config.accessories[index] = config;
found = true;
break;
}
}
if (!found) {
this._config.accessories.push(config);
}
}
} else if (type === "platform") {
if (!this._config.platforms) {
this._config.platforms = [];
}
if (!replace) {
this._config.platforms.push(config);
} else {
var targetName;
if (name.indexOf('.') !== -1) {
targetName = name.split(".")[1];
}
var found = false;
for (var index in this._config.platforms) {
var platformConfig = this._config.platforms[index];
if (platformConfig.platform === name) {
this._config.platforms[index] = config;
found = true;
break;
}
if (targetName && (platformConfig.platform === targetName)) {
this._config.platforms[index] = config;
found = true;
break;
}
}
if (!found) {
this._config.platforms.push(config);
}
}
}
var serializedConfig = JSON.stringify(this._config, null, ' ');
var configPath = User.configPath();
fs.writeFileSync(configPath, serializedConfig, 'utf8');
}
// Returns the setup code in a scannable format.
Server.prototype._printPin = function(pin) {
console.log("Scan this code with your HomeKit App on your iOS device to pair with Homebridge:");
console.log(chalk.black.bgWhite(" "));
console.log(chalk.black.bgWhite(" ┌────────────┐ "));
console.log(chalk.black.bgWhite(" │ " + pin + " │ "));
console.log(chalk.black.bgWhite(" └────────────┘ "));
console.log(chalk.black.bgWhite(" "));
}

47
lib/user.js Normal file
View File

@@ -0,0 +1,47 @@
var path = require('path');
var fs = require('fs');
'use strict';
module.exports = {
User: User
}
/**
* Manages user settings and storage locations.
*/
// global cached config
var config;
// optional custom storage path
var customStoragePath;
function User() {
}
User.config = function() {
return config || (config = Config.load(User.configPath()));
}
User.storagePath = function() {
if (customStoragePath) return customStoragePath;
var home = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
return path.join(home, ".homebridge");
}
User.configPath = function() {
return path.join(User.storagePath(), "config.json");
}
User.persistPath = function() {
return path.join(User.storagePath(), "persist");
}
User.cachedAccessoryPath = function() {
return path.join(User.storagePath(), "accessories");
}
User.setStoragePath = function(path) {
customStoragePath = path;
}

18
lib/util/mac.js Normal file
View File

@@ -0,0 +1,18 @@
var crypto = require('crypto');
'use strict';
module.exports = {
generate: generate
}
function generate(data) {
var sha1sum = crypto.createHash('sha1');
sha1sum.update(data);
var s = sha1sum.digest('hex');
var i = -1;
return 'xx:xx:xx:xx:xx:xx'.replace(/[x]/g, function(c) {
i += 1;
return s[i];
}).toUpperCase();
};

12
lib/version.js Normal file
View File

@@ -0,0 +1,12 @@
var fs = require('fs');
var path = require('path');
'use strict';
module.exports = getVersion();
function getVersion() {
var packageJSONPath = path.join(__dirname, '../package.json');
var packageJSON = JSON.parse(fs.readFileSync(packageJSONPath));
return packageJSON.version;
}

View File

@@ -1,49 +1,33 @@
{
"name": "homebridge",
"description": "HomeKit support for the impatient",
"version": "0.1.1",
"version": "0.4.22",
"scripts": {
"start": "DEBUG=* node app.js || true"
"dev": "DEBUG=* ./bin/homebridge -D -P example-plugins/ || true"
},
"author": {
"name": "Nick Farina"
},
"repository": {
"type": "git",
"url": "git://github.com/nfarina/homebridge.git"
},
"bugs": {
"url": "http://github.com/nfarina/homebridge/issues"
},
"license": "ISC",
"bin": {
"homebridge": "bin/homebridge"
},
"engines": {
"node": ">=4.3.2"
},
"preferGlobal": true,
"dependencies": {
"ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local",
"async": "^1.4.2",
"carwingsjs": "0.0.x",
"chokidar": "^1.0.5",
"color": "0.10.x",
"debug": "^2.2.0",
"eibd": "^0.3.1",
"elkington": "kevinohara80/elkington",
"hap-nodejs": "^0.0.2",
"harmonyhubjs-client": "^1.1.6",
"harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git",
"isy-js": "",
"komponist": "0.1.0",
"lifx": "git+https://github.com/magicmonkey/lifxjs.git",
"lifx-api": "^1.0.1",
"mdns": "^2.2.4",
"node-hue-api": "^1.0.5",
"node-icontrol": "^0.1.5",
"node-milight-promise": "0.0.x",
"node-persist": "0.0.x",
"node-xmpp-client": "1.0.0-alpha23",
"q": "1.4.x",
"queue": "^3.1.0",
"request": "2.49.x",
"sonos": "0.8.x",
"telldus-live": "^0.2.1",
"teslams": "1.0.1",
"tough-cookie": "^2.0.0",
"unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98",
"wemo": "0.2.x",
"wink-js": "0.0.5",
"xml2js": "0.4.x",
"xmldoc": "0.1.x",
"yamaha-nodejs": "0.4.x"
"chalk": "^1.1.1",
"commander": "2.8.1",
"hap-nodejs": "0.4.27",
"semver": "5.0.3",
"node-persist": "^0.0.8"
}
}

View File

@@ -1,378 +0,0 @@
// Domoticz Platform Shim for HomeBridge
// Written by Joep Verhaeg (http://www.joepverhaeg.nl)
//
// Revisions:
//
// 12 June 2015 [GizMoCuz]
// - Added support for RGB lights
// - Added support for Scenes
// - Sorting device names
//
// 22 July 2015 [lukeredpath]
// - Added SSL and basic auth support
//
// 26 August 2015 [EddyK69]
// - Added parameter in config.json: 'loadscenes' for enabling/disabling loading scenes
// - Fixed issue with dimmer-range; was 0-100, should be 0-16
//
// 27 August 2015 [EddyK69]
// - Fixed issue that 'on/off'-type lights showed as dimmers in HomeKit. Checking now on SwitchType instead of HaveDimmer
// - Fixed issue that 'on-off'-type lights would not react on Siri 'Switch on/off light'; On/Off types are now handled as Lights instead of Switches
// (Cannot determine if 'on/off'-type device is a Light or a Switch :( )
//
// 14 September 2015 [lukeredpath]
// - Fixed incorrect dimmer range for LightwaveRF lights (0-32 required, MaxDimLevel should be honored)
//
//
// Domoticz JSON API required
// https://www.domoticz.com/wiki/Domoticz_API/JSON_URL's#Lights_and_switches
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// "platform": "Domoticz",
// "name": "Domoticz",
// "server": "127.0.0.1",
// "port": "8080",
// "roomid": 123, (0=no roomplan)
// "loadscenes": 1 (0=disable scenes)
// }
// ],
//
// If your server uses HTTPS, you can specify "ssl": true in your config. If
// your server uses a self-signed certificate, you'll need to run the following
// before starting the server or you will get an error:
//
// export NODE_TLS_REJECT_UNAUTHORIZED=0
//
// For basic auth support, specify the "user" and "password" in your config.
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
//
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function DomoticzPlatform(log, config){
this.log = log;
this.user = config["user"];
this.password = config["password"];
this.server = config["server"];
this.port = config["port"];
this.protocol = config["ssl"] ? "https" : "http";
this.roomid = 0;
if (typeof config["roomid"] != 'undefined') {
this.roomid = config["roomid"];
}
this.loadscenes = 1;
if (typeof config["loadscenes"] != 'undefined') {
this.loadscenes = config["loadscenes"];
}
}
function sortByKey(array, key) {
return array.sort(function(a, b) {
var x = a[key]; var y = b[key];
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
DomoticzPlatform.prototype = {
urlForQuery: function(query) {
var serverString = this.server;
if (this.user && this.password) {
serverString = this.user + ":" + this.password + "@" + serverString;
}
return this.protocol + "://" + serverString + ":" + this.port + "/json.htm?" + query;
},
accessories: function(callback) {
this.log("Fetching Domoticz lights and switches...");
var that = this;
var foundAccessories = [];
// mechanism to ensure callback is only executed once all requests complete
var asyncCalls = 0;
function callbackLater() { if (--asyncCalls == 0) callback(foundAccessories); }
if (this.roomid == 0) {
//Get Lights
asyncCalls++;
request.get({
url: this.urlForQuery("type=devices&filter=light&used=true&order=Name"),
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
if (json['result'] != undefined) {
var sArray=sortByKey(json['result'],"Name");
sArray.map(function(s) {
var havedimmer = (s.SwitchType == 'Dimmer')
accessory = new DomoticzAccessory(that.log, that, false, s.idx, s.Name, havedimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW"));
foundAccessories.push(accessory);
})
}
callbackLater();
} else {
that.log("There was a problem connecting to Domoticz. (" + err + ")");
}
});
}
else {
//Get all devices specified in the room
asyncCalls++;
request.get({
url: this.urlForQuery("type=devices&plan=" + this.roomid),
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
if (json['result'] != undefined) {
var sArray=sortByKey(json['result'],"Name");
sArray.map(function(s) {
//only accept switches for now
if (typeof s.SwitchType != 'undefined') {
var havedimmer = (s.SwitchType == 'Dimmer')
accessory = new DomoticzAccessory(that.log, that, false, s.idx, s.Name, havedimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW"));
foundAccessories.push(accessory);
}
})
}
callbackLater();
} else {
that.log("There was a problem connecting to Domoticz.");
}
});
}
//Get Scenes
if (this.loadscenes == 1) {
asyncCalls++;
request.get({
url: this.urlForQuery("type=scenes"),
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
if (json['result'] != undefined) {
var sArray=sortByKey(json['result'],"Name");
sArray.map(function(s) {
accessory = new DomoticzAccessory(that.log, that, true, s.idx, s.Name, false, 0, false);
foundAccessories.push(accessory);
})
}
callbackLater();
} else {
that.log("There was a problem connecting to Domoticz.");
}
});
}
}
}
function DomoticzAccessory(log, platform, IsScene, idx, name, HaveDimmer, MaxDimLevel, HaveRGB) {
// device info
this.IsScene = IsScene;
this.idx = idx;
this.name = name;
this.HaveDimmer = HaveDimmer;
this.MaxDimLevel = MaxDimLevel;
this.HaveRGB = HaveRGB;
this.log = log;
this.platform = platform;
}
DomoticzAccessory.prototype = {
command: function(c,value) {
this.log(this.name + " sending command " + c + " with value " + value);
if (this.IsScene == false) {
//Lights
if (c == "On" || c == "Off") {
url = this.platform.urlForQuery("type=command&param=switchlight&idx=" + this.idx + "&switchcmd=" + c + "&level=0");
}
else if (c == "setHue") {
url = this.platform.urlForQuery("type=command&param=setcolbrightnessvalue&idx=" + this.idx + "&hue=" + value + "&brightness=100" + "&iswhite=false");
}
else if (c == "setLevel") {
value = this.dimmerLevelForValue(value)
url = this.platform.urlForQuery("type=command&param=switchlight&idx=" + this.idx + "&switchcmd=Set%20Level&level=" + value);
}
else if (value != undefined) {
this.log(this.name + " Unhandled Light command! cmd=" + c + ", value=" + value);
}
}
else {
//Scenes
if (c == "On" || c == "Off") {
url = this.platform.urlForQuery("type=command&param=switchscene&idx=" + this.idx + "&switchcmd=" + c);
}
else if (value != undefined) {
this.log(this.name + " Unhandled Scene command! cmd=" + c + ", value=" + value);
}
}
var that = this;
request.put({ url: url }, function(err, response) {
if (err) {
that.log("There was a problem sending command " + c + " to" + that.name);
that.log(url);
} else {
that.log(that.name + " sent command " + c + " (value: " + value + ")");
}
})
},
// translates the HomeKit dim level as a percentage to whatever scale the device requires
dimmerLevelForValue: function(value) {
if (this.MaxDimLevel == 100) {
return value;
}
return Math.round((value / 100.0) * this.MaxDimLevel)
},
informationCharacteristics: function() {
return [
{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Domoticz",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-1",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}
]
},
controlCharacteristics: function(that) {
cTypes = [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: true,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
}]
if (this.idx != undefined) {
cTypes.push({
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) {
if (value == 0) {
that.command("Off")
} else {
that.command("On")
}
},
perms: ["pw","pr","ev"],
format: "bool",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Change the power state",
designedMaxLength: 1
})
}
if (this.HaveDimmer == true) {
cTypes.push({
cType: types.BRIGHTNESS_CTYPE,
onUpdate: function(value) { that.command("setLevel", value); },
perms: ["pw","pr","ev"],
format: "int",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: this.MaxDimLevel,
designedMinStep: 1,
unit: "%"
})
}
if (this.HaveRGB == true) {
cTypes.push({
cType: types.HUE_CTYPE,
onUpdate: function(value) { that.command("setHue", value); },
perms: ["pw","pr","ev"],
format: "int",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Adjust Hue of Light",
designedMinValue: 0,
designedMaxValue: 360,
designedMinStep: 1,
unit: "arcdegrees"
})
}
return cTypes
},
sType: function() {
//if (this.HaveDimmer == true) {
return types.LIGHTBULB_STYPE
//} else {
// return types.SWITCH_STYPE
//}
},
getServices: function() {
var that = this;
var services = [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: this.informationCharacteristics(),
},
{
sType: this.sType(),
characteristics: this.controlCharacteristics(that)
}];
this.log("Loaded services for " + this.name)
return services;
}
};
module.exports.accessory = DomoticzAccessory;
module.exports.platform = DomoticzPlatform;

File diff suppressed because it is too large Load Diff

View File

@@ -1,253 +0,0 @@
// Fibaro Home Center 2 Platform Shim for HomeBridge
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// "platform": "FibaroHC2",
// "name": "FibaroHC2",
// "host": "PUT IP ADDRESS OF YOUR HC2 HERE",
// "username": "PUT USERNAME OF YOUR HC2 HERE",
// "password": "PUT PASSWORD OF YOUR HC2 HERE"
// }
// ],
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
var types = require("hap-nodejs/accessories/types.js");
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
function FibaroHC2Platform(log, config){
this.log = log;
this.host = config["host"];
this.username = config["username"];
this.password = config["password"];
this.auth = "Basic " + new Buffer(this.username + ":" + this.password).toString("base64");
this.url = "http://"+this.host+"/api/devices";
startPollingUpdate( this );
}
FibaroHC2Platform.prototype = {
accessories: function(callback) {
this.log("Fetching Fibaro Home Center devices...");
var that = this;
var foundAccessories = [];
request.get({
url: this.url,
headers : {
"Authorization" : this.auth
},
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
if (json != undefined) {
json.map(function(s) {
that.log("Found: " + s.type);
if (s.visible == true) {
var accessory = null;
if (s.type == "com.fibaro.multilevelSwitch")
accessory = new FibaroAccessory(new Service.Lightbulb(s.name), [Characteristic.On, Characteristic.Brightness]);
else if (s.type == "com.fibaro.FGRM222" || s.type == "com.fibaro.FGR221")
accessory = new FibaroAccessory(new Service.WindowCovering(s.name), [Characteristic.CurrentPosition, Characteristic.TargetPosition, Characteristic.PositionState]);
else if (s.type == "com.fibaro.binarySwitch" || s.type == "com.fibaro.developer.bxs.virtualBinarySwitch")
accessory = new FibaroAccessory(new Service.Switch(s.name), [Characteristic.On]);
else if (s.type == "com.fibaro.FGMS001" || s.type == "com.fibaro.motionSensor")
accessory = new FibaroAccessory(new Service.MotionSensor(s.name), [Characteristic.MotionDetected]);
else if (s.type == "com.fibaro.temperatureSensor")
accessory = new FibaroAccessory(new Service.TemperatureSensor(s.name), [Characteristic.CurrentTemperature]);
else if (s.type == "com.fibaro.doorSensor")
accessory = new FibaroAccessory(new Service.ContactSensor(s.name), [Characteristic.ContactSensorState]);
else if (s.type == "com.fibaro.lightSensor")
accessory = new FibaroAccessory(new Service.LightSensor(s.name), [Characteristic.CurrentAmbientLightLevel]);
else if (s.type == "com.fibaro.FGWP101")
accessory = new FibaroAccessory(new Service.Outlet(s.name), [Characteristic.On, Characteristic.OutletInUse]);
if (accessory != null) {
accessory.getServices = function() {
return that.getServices(accessory);
};
accessory.platform = that;
accessory.remoteAccessory = s;
accessory.id = s.id;
accessory.name = s.name;
accessory.model = s.type;
accessory.manufacturer = "Fibaro";
accessory.serialNumber = "<unknown>";
foundAccessories.push(accessory);
}
}
})
}
callback(foundAccessories);
} else {
that.log("There was a problem connecting with FibaroHC2.");
}
});
},
command: function(c,value, that) {
var url = "http://"+this.host+"/api/devices/"+that.id+"/action/"+c;
var body = value != undefined ? JSON.stringify({
"args": [ value ]
}) : null;
var method = "post";
request({
url: url,
body: body,
method: method,
headers: {
"Authorization" : this.auth
},
}, function(err, response) {
if (err) {
that.platform.log("There was a problem sending command " + c + " to" + that.name);
that.platform.log(url);
} else {
that.platform.log(that.name + " sent command " + c);
that.platform.log(url);
}
});
},
getAccessoryValue: function(callback, returnBoolean, homebridgeAccessory, powerValue) {
var url = "http://"+homebridgeAccessory.platform.host+"/api/devices/"+homebridgeAccessory.id+"/properties/";
if (powerValue)
url = url + "power";
else
url = url + "value";
request.get({
headers : {
"Authorization" : homebridgeAccessory.platform.auth
},
json: true,
url: url
}, function(err, response, json) {
homebridgeAccessory.platform.log(url);
if (!err && response.statusCode == 200) {
if (powerValue) {
callback(undefined, parseFloat(json.value) > 1.0 ? true : false);
} else if (returnBoolean)
callback(undefined, json.value == 0 ? 0 : 1);
else
callback(undefined, json.value);
} else {
homebridgeAccessory.platform.log("There was a problem getting value from" + homebridgeAccessory.id);
}
})
},
getInformationService: function(homebridgeAccessory) {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Name, homebridgeAccessory.name)
.setCharacteristic(Characteristic.Manufacturer, homebridgeAccessory.manufacturer)
.setCharacteristic(Characteristic.Model, homebridgeAccessory.model)
.setCharacteristic(Characteristic.SerialNumber, homebridgeAccessory.serialNumber);
return informationService;
},
bindCharacteristicEvents: function(characteristic, homebridgeAccessory) {
var onOff = characteristic.props.format == "bool" ? true : false;
var readOnly = true;
for (var i = 0; i < characteristic.props.perms.length; i++)
if (characteristic.props.perms[i] == "pw")
readOnly = false;
var powerValue = (characteristic.UUID == "00000026-0000-1000-8000-0026BB765291") ? true : false;
subscribeUpdate(characteristic, homebridgeAccessory, onOff);
if (!readOnly) {
characteristic
.on('set', function(value, callback, context) {
if( context !== 'fromFibaro' ) {
if (onOff)
homebridgeAccessory.platform.command(value == 0 ? "turnOff": "turnOn", null, homebridgeAccessory);
else
homebridgeAccessory.platform.command("setValue", value, homebridgeAccessory);
}
callback();
}.bind(this) );
}
characteristic
.on('get', function(callback) {
homebridgeAccessory.platform.getAccessoryValue(callback, onOff, homebridgeAccessory, powerValue);
}.bind(this) );
},
getServices: function(homebridgeAccessory) {
var informationService = homebridgeAccessory.platform.getInformationService(homebridgeAccessory);
for (var i=0; i < homebridgeAccessory.characteristics.length; i++) {
var characteristic = homebridgeAccessory.controlService.getCharacteristic(homebridgeAccessory.characteristics[i]);
if (characteristic == undefined)
characteristic = homebridgeAccessory.controlService.addCharacteristic(homebridgeAccessory.characteristics[i]);
homebridgeAccessory.platform.bindCharacteristicEvents(characteristic, homebridgeAccessory);
}
return [informationService, homebridgeAccessory.controlService];
}
}
function FibaroAccessory(controlService, characteristics) {
this.controlService = controlService;
this.characteristics = characteristics;
}
var lastPoll=0;
var pollingUpdateRunning = false;
function startPollingUpdate( platform )
{
if( pollingUpdateRunning )
return;
pollingUpdateRunning = true;
var updateUrl = "http://"+platform.host+"/api/refreshStates?last="+lastPoll;
request.get({
url: updateUrl,
headers : {
"Authorization" : platform.auth
},
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
if (json != undefined) {
lastPoll = json.last;
if (json.changes != undefined) {
json.changes.map(function(s) {
if (s.value != undefined) {
var value=parseInt(s.value);
if (isNaN(value))
value=(s.value === "true");
for (i=0;i<updateSubscriptions.length; i++) {
var subscription = updateSubscriptions[i];
if (subscription.id == s.id) {
if (s.power != undefined && subscription.characteristic.UUID == "00000026-0000-1000-8000-0026BB765291") {
subscription.characteristic.setValue(parseFloat(s.power) > 1.0 ? true : false, undefined, 'fromFibaro');
} else if ((subscription.onOff && typeof(value) == "boolean") || !subscription.onOff)
subscription.characteristic.setValue(value, undefined, 'fromFibaro');
else
subscription.characteristic.setValue(value == 0 ? false : true, undefined, 'fromFibaro');
}
}
}
})
}
}
} else {
platform.log("There was a problem connecting with FibaroHC2.");
}
pollingUpdateRunning = false;
setTimeout( function(){startPollingUpdate(platform)}, 2000 );
});
}
var updateSubscriptions = [];
function subscribeUpdate(characteristic, accessory, onOff)
{
// TODO: optimized management of updateSubscription data structure (no array with sequential access)
updateSubscriptions.push({ 'id': accessory.id, 'characteristic': characteristic, 'accessory': accessory, 'onOff': onOff });
}
module.exports.platform = FibaroHC2Platform;

View File

@@ -1,542 +0,0 @@
// Home Assistant
//
// Current Support: lights
//
// This is a shim to publish lights maintained by Home Assistant.
// Home Assistant is an open-source home automation platform.
// URL: http://home-assistant.io
// GitHub: https://github.com/balloob/home-assistant
//
// HA accessories supported: Lights, Switches, Media Players, Scenes.
//
// Optional Devices - Edit the supported_types key in the config to pick which
// of the 4 types you would like to expose to HomeKit from
// Home Assistant. light, switch, media_player, scene.
//
//
// Scene Support
//
// You can optionally import your Home Assistant scenes. These will appear to
// HomeKit as switches. You can simply say "turn on party time". In some cases
// scenes names are already rerved in HomeKit...like "Good Morning" and
// "Good Night". You will be able to just say "Good Morning" or "Good Night" to
// have these triggered.
//
// You might want to play with the wording to figure out what ends up working well
// for your scene names. It's also important to not populate any actual HomeKit
// scenes with the same names, as Siri will pick these instead of your Home
// Assistant scenes.
//
//
//
// Media Player Support
//
// Media players on your Home Assistant will be added to your HomeKit as a light.
// While this seems like a hack at first, it's actually quite useful. You can
// turn them on, off, and set their volume (as a function of brightness).
//
// There are some rules to know about how on/off treats your media player. If
// your media player supports play/pause, then turning them on and off via
// HomeKit will play and pause them. If they do not support play/pause but then
// support on/off they will be turned on and then off.
//
// HomeKit does not have a characteristic of Volume or a Speaker type. So we are
// using the light device type here. So to turn your speaker up and down, you
// will need to use the same language you use to set the brighness of a light.
// You can play around with language to see what fits best.
//
//
//
// Examples
//
// Dim the Kitchen Speaker to 40% - sets volume to 40%
// Dim the the Kitchen Speaker 10% - lowers the volume by 10%
// Set the brightness of the Kitchen Speaker to 40%
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// "platform": "HomeAssistant",
// "name": "HomeAssistant",
// "host": "http://192.168.1.50:8123",
// "password": "xxx",
// "supported_types": ["light", "switch", "media_player", "scene"]
// }
// ]
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var url = require('url')
var request = require("request");
var communicationError = new Error('Can not communicate with Home Assistant.')
function HomeAssistantPlatform(log, config){
// auth info
this.host = config["host"];
this.password = config["password"];
this.supportedTypes = config["supported_types"];
this.log = log;
}
HomeAssistantPlatform.prototype = {
_request: function(method, path, options, callback) {
var self = this
var requestURL = this.host + '/api' + path
options = options || {}
options.query = options.query || {}
var reqOpts = {
url: url.parse(requestURL),
method: method || 'GET',
qs: options.query,
body: JSON.stringify(options.body),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'x-ha-access': this.password
}
}
request(reqOpts, function onResponse(error, response, body) {
if (error) {
callback(error, response)
return
}
if (response.statusCode === 401) {
callback(new Error('You are not authenticated'), response)
return
}
json = JSON.parse(body)
callback(error, response, json)
})
},
fetchState: function(entity_id, callback){
this._request('GET', '/states/' + entity_id, {}, function(error, response, data){
if (error) {
callback(null)
}else{
callback(data)
}
})
},
callService: function(domain, service, service_data, callback){
var options = {}
options.body = service_data
this._request('POST', '/services/' + domain + '/' + service, options, function(error, response, data){
if (error) {
callback(null)
}else{
callback(data)
}
})
},
accessories: function(callback) {
this.log("Fetching HomeAssistant devices.");
var that = this;
var foundAccessories = [];
this._request('GET', '/states', {}, function(error, response, data){
for (var i = 0; i < data.length; i++) {
entity = data[i]
entity_type = entity.entity_id.split('.')[0]
// ignore devices that are not in the list of supported types
if (that.supportedTypes.indexOf(entity_type) == -1) {
continue;
}
// ignore hidden devices
if (entity.attributes && entity.attributes.hidden) {
continue;
}
var accessory = null
if (entity_type == 'light') {
accessory = new HomeAssistantLight(that.log, entity, that)
}else if (entity_type == 'switch'){
console.log(JSON.stringify(entity))
console.log("");
console.log("");
accessory = new HomeAssistantSwitch(that.log, entity, that)
}else if (entity_type == 'scene'){
accessory = new HomeAssistantSwitch(that.log, entity, that, 'scene')
}else if (entity_type == 'media_player' && entity.attributes && entity.attributes.supported_media_commands){
accessory = new HomeAssistantMediaPlayer(that.log, entity, that)
}
if (accessory) {
foundAccessories.push(accessory)
}
}
callback(foundAccessories)
})
}
}
function HomeAssistantLight(log, data, client) {
// device info
this.domain = "light"
this.data = data
this.entity_id = data.entity_id
if (data.attributes && data.attributes.friendly_name) {
this.name = data.attributes.friendly_name
}else{
this.name = data.entity_id.split('.').pop().replace(/_/g, ' ')
}
this.client = client
this.log = log;
}
HomeAssistantLight.prototype = {
getPowerState: function(callback){
this.log("fetching power state for: " + this.name);
this.client.fetchState(this.entity_id, function(data){
if (data) {
powerState = data.state == 'on'
callback(null, powerState)
}else{
callback(communicationError)
}
}.bind(this))
},
getBrightness: function(callback){
this.log("fetching brightness for: " + this.name);
this.client.fetchState(this.entity_id, function(data){
if (data && data.attributes) {
brightness = ((data.attributes.brightness || 0) / 255)*100
callback(null, brightness)
}else{
callback(communicationError)
}
}.bind(this))
},
setPowerState: function(powerOn, callback) {
var that = this;
var service_data = {}
service_data.entity_id = this.entity_id
if (powerOn) {
this.log("Setting power state on the '"+this.name+"' to on");
this.client.callService(this.domain, 'turn_on', service_data, function(data){
if (data) {
that.log("Successfully set power state on the '"+that.name+"' to on");
callback()
}else{
callback(communicationError)
}
}.bind(this))
}else{
this.log("Setting power state on the '"+this.name+"' to off");
this.client.callService(this.domain, 'turn_off', service_data, function(data){
if (data) {
that.log("Successfully set power state on the '"+that.name+"' to off");
callback()
}else{
callback(communicationError)
}
}.bind(this))
}
},
setBrightness: function(level, callback) {
var that = this;
var service_data = {}
service_data.entity_id = this.entity_id
service_data.brightness = 255*(level/100.0)
this.log("Setting brightness on the '"+this.name+"' to " + level);
this.client.callService(this.domain, 'turn_on', service_data, function(data){
if (data) {
that.log("Successfully set brightness on the '"+that.name+"' to " + level);
callback()
}else{
callback(communicationError)
}
}.bind(this))
},
getServices: function() {
var lightbulbService = new Service.Lightbulb();
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "Home Assistant")
.setCharacteristic(Characteristic.Model, "Light")
.setCharacteristic(Characteristic.SerialNumber, "xxx");
lightbulbService
.getCharacteristic(Characteristic.On)
.on('get', this.getPowerState.bind(this))
.on('set', this.setPowerState.bind(this));
lightbulbService
.addCharacteristic(Characteristic.Brightness)
.on('get', this.getBrightness.bind(this))
.on('set', this.setBrightness.bind(this));
return [informationService, lightbulbService];
}
}
function HomeAssistantMediaPlayer(log, data, client) {
var SUPPORT_PAUSE = 1
var SUPPORT_SEEK = 2
var SUPPORT_VOLUME_SET = 4
var SUPPORT_VOLUME_MUTE = 8
var SUPPORT_PREVIOUS_TRACK = 16
var SUPPORT_NEXT_TRACK = 32
var SUPPORT_YOUTUBE = 64
var SUPPORT_TURN_ON = 128
var SUPPORT_TURN_OFF = 256
// device info
this.domain = "media_player"
this.data = data
this.entity_id = data.entity_id
this.supportsVolume = false
this.supportedMediaCommands = data.attributes.supported_media_commands
if (data.attributes && data.attributes.friendly_name) {
this.name = data.attributes.friendly_name
}else{
this.name = data.entity_id.split('.').pop().replace(/_/g, ' ')
}
if ((this.supportedMediaCommands | SUPPORT_PAUSE) == this.supportedMediaCommands) {
this.onState = "playing"
this.offState = "paused"
this.onService = "media_play"
this.offService = "media_pause"
}else if ((this.supportedMediaCommands | SUPPORT_TURN_ON) == this.supportedMediaCommands && (this.supportedMediaCommands | SUPPORT_TURN_OFF) == this.supportedMediaCommands) {
this.onState = "on"
this.offState = "off"
this.onService = "turn_on"
this.offService = "turn_off"
}
if ((this.supportedMediaCommands | SUPPORT_VOLUME_SET) == this.supportedMediaCommands) {
this.supportsVolume = true
}
this.client = client
this.log = log;
}
HomeAssistantMediaPlayer.prototype = {
getPowerState: function(callback){
this.log("fetching power state for: " + this.name);
this.client.fetchState(this.entity_id, function(data){
if (data) {
powerState = data.state == this.onState
callback(null, powerState)
}else{
callback(communicationError)
}
}.bind(this))
},
getVolume: function(callback){
this.log("fetching volume for: " + this.name);
that = this
this.client.fetchState(this.entity_id, function(data){
if (data && data.attributes) {
that.log(JSON.stringify(data.attributes))
level = data.attributes.volume_level ? data.attributes.volume_level*100 : 0
callback(null, level)
}else{
callback(communicationError)
}
}.bind(this))
},
setPowerState: function(powerOn, callback) {
var that = this;
var service_data = {}
service_data.entity_id = this.entity_id
if (powerOn) {
this.log("Setting power state on the '"+this.name+"' to on");
this.client.callService(this.domain, this.onService, service_data, function(data){
if (data) {
that.log("Successfully set power state on the '"+that.name+"' to on");
callback()
}else{
callback(communicationError)
}
}.bind(this))
}else{
this.log("Setting power state on the '"+this.name+"' to off");
this.client.callService(this.domain, this.offService, service_data, function(data){
if (data) {
that.log("Successfully set power state on the '"+that.name+"' to off");
callback()
}else{
callback(communicationError)
}
}.bind(this))
}
},
setVolume: function(level, callback) {
var that = this;
var service_data = {}
service_data.entity_id = this.entity_id
service_data.volume_level = level/100.0
this.log("Setting volume on the '"+this.name+"' to " + level);
this.client.callService(this.domain, 'volume_set', service_data, function(data){
if (data) {
that.log("Successfully set volume on the '"+that.name+"' to " + level);
callback()
}else{
callback(communicationError)
}
}.bind(this))
},
getServices: function() {
var lightbulbService = new Service.Lightbulb();
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "Home Assistant")
.setCharacteristic(Characteristic.Model, "Media Player")
.setCharacteristic(Characteristic.SerialNumber, "xxx");
lightbulbService
.getCharacteristic(Characteristic.On)
.on('get', this.getPowerState.bind(this))
.on('set', this.setPowerState.bind(this));
if (this.supportsVolume) {
lightbulbService
.addCharacteristic(Characteristic.Brightness)
.on('get', this.getVolume.bind(this))
.on('set', this.setVolume.bind(this));
}
return [informationService, lightbulbService];
}
}
function HomeAssistantSwitch(log, data, client, type) {
// device info
this.domain = type || "switch"
this.data = data
this.entity_id = data.entity_id
if (data.attributes && data.attributes.friendly_name) {
this.name = data.attributes.friendly_name
}else{
this.name = data.entity_id.split('.').pop().replace(/_/g, ' ')
}
this.client = client
this.log = log;
}
HomeAssistantSwitch.prototype = {
getPowerState: function(callback){
this.log("fetching power state for: " + this.name);
this.client.fetchState(this.entity_id, function(data){
if (data) {
powerState = data.state == 'on'
callback(null, powerState)
}else{
callback(communicationError)
}
}.bind(this))
},
setPowerState: function(powerOn, callback) {
var that = this;
var service_data = {}
service_data.entity_id = this.entity_id
if (powerOn) {
this.log("Setting power state on the '"+this.name+"' to on");
this.client.callService(this.domain, 'turn_on', service_data, function(data){
if (data) {
that.log("Successfully set power state on the '"+that.name+"' to on");
callback()
}else{
callback(communicationError)
}
}.bind(this))
}else{
this.log("Setting power state on the '"+this.name+"' to off");
this.client.callService(this.domain, 'turn_off', service_data, function(data){
if (data) {
that.log("Successfully set power state on the '"+that.name+"' to off");
callback()
}else{
callback(communicationError)
}
}.bind(this))
}
},
getServices: function() {
var switchService = new Service.Switch();
var informationService = new Service.AccessoryInformation();
var model;
switch (this.domain) {
case "scene":
model = "Scene"
break;
default:
model = "Switch"
}
informationService
.setCharacteristic(Characteristic.Manufacturer, "Home Assistant")
.setCharacteristic(Characteristic.Model, model)
.setCharacteristic(Characteristic.SerialNumber, "xxx");
if (this.domain == 'switch') {
switchService
.getCharacteristic(Characteristic.On)
.on('get', this.getPowerState.bind(this))
.on('set', this.setPowerState.bind(this));
}else{
switchService
.getCharacteristic(Characteristic.On)
.on('set', this.setPowerState.bind(this));
}
return [informationService, switchService];
}
}
module.exports.accessory = HomeAssistantLight;
module.exports.accessory = HomeAssistantMediaPlayer;
module.exports.accessory = HomeAssistantSwitch;
module.exports.platform = HomeAssistantPlatform;

File diff suppressed because it is too large Load Diff

View File

@@ -1,385 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var xml2js = require('xml2js');
var request = require('request');
var util = require('util');
var parser = new xml2js.Parser();
var power_state_ctype = {
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { return; },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Change the power state",
designedMaxLength: 1
};
function ISYURL(user, pass, host, port, path) {
return util.format("http://%s:%s@%s:%d%s", user, pass, host, port, encodeURI(path));
}
function ISYPlatform(log, config) {
this.host = config["host"];
this.port = config["port"];
this.user = config["username"];
this.pass = config["password"];
this.log = log;
}
ISYPlatform.prototype = {
accessories: function(callback) {
this.log("Fetching ISY Devices.");
var that = this;
var url = ISYURL(this.user, this.pass, this.host, this.port, "/rest/nodes");
var options = {
url: url,
method: 'GET'
};
var foundAccessories = [];
request(options, function(error, response, body) {
if (error)
{
console.trace("Requesting ISY devices.");
that.log(error);
return error;
}
parser.parseString(body, function(err, result) {
result.nodes.node.forEach(function(obj) {
var enabled = obj.enabled[0] == 'true';
if (enabled)
{
var device = new ISYAccessory(
that.log,
that.host,
that.port,
that.user,
that.pass,
obj.name[0],
obj.address[0],
obj.property[0].$.uom
);
foundAccessories.push(device);
}
});
});
callback(foundAccessories.sort(function (a,b) {
return (a.name > b.name) - (a.name < b.name);
}));
});
}
}
function ISYAccessory(log, host, port, user, pass, name, address, uom) {
this.log = log;
this.host = host;
this.port = port;
this.user = user;
this.pass = pass;
this.name = name;
this.address = address;
this.uom = uom;
}
ISYAccessory.prototype = {
query: function() {
var path = util.format("/rest/status/%s", encodeURI(this.address));
var url = ISYURL(this.user, this.pass, this.host, this.port, path);
var options = { url: url, method: 'GET' };
request(options, function(error, response, body) {
if (error)
{
console.trace("Requesting Device Status.");
that.log(error);
return error;
}
parser.parseString(body, function(err, result) {
var value = result.properties.property[0].$.value;
return value;
});
});
},
command: function(c, value) {
this.log(this.name + " sending command " + c + " with value " + value);
switch (c)
{
case 'On':
path = "/rest/nodes/" + this.address + "/cmd/DFON";
break;
case 'Off':
path = "/rest/nodes/" + this.address + "/cmd/DFOF";
break;
case 'Low':
path = "/rest/nodes/" + this.address + "/cmd/DON/85";
break;
case 'Medium':
path = "/rest/nodes/" + this.address + "/cmd/DON/128";
break;
case 'High':
path = "/rest/nodes/" + this.address + "/cmd/DON/255";
break;
case 'setLevel':
if (value > 0)
{
path = "/rest/nodes/" + this.address + "/cmd/DON/" + Math.floor(255 * (value / 100));
}
break;
default:
this.log("Unimplemented command sent to " + this.name + " Command " + c);
break;
}
if (path)
{
var url = ISYURL(this.user, this.pass, this.host, this.port, path);
var options = {
url: url,
method: 'GET'
};
var that = this;
request(options, function(error, response, body) {
if (error)
{
console.trace("Sending Command.");
that.log(error);
return error;
}
that.log("Sent command " + path + " to " + that.name);
});
}
},
informationCharacteristics: function() {
return [
{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "SmartHome",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-1",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.address,
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}
]
},
controlCharacteristics: function(that) {
cTypes = [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: true,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
}]
if (this.uom == "%/on/off") {
cTypes.push({
cType: types.POWER_STATE_CTYPE,
perms: ["pw","pr","ev"],
format: "bool",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Change the power state",
designedMaxLength: 1,
onUpdate: function(value) {
if (value == 0) {
that.command("Off")
} else {
that.command("On")
}
},
onRead: function() {
return this.query();
}
});
cTypes.push({
cType: types.BRIGHTNESS_CTYPE,
perms: ["pw","pr","ev"],
format: "int",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%",
onUpdate: function(value) {
that.command("setLevel", value);
},
onRead: function() {
var val = this.query();
that.log("Query: " + val);
return val;
}
});
}
else if (this.uom == "off/low/med/high")
{
cTypes.push({
cType: types.POWER_STATE_CTYPE,
perms: ["pw","pr","ev"],
format: "bool",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Change the power state",
designedMaxLength: 1,
onUpdate: function(value) {
if (value == 0) {
that.command("Off")
} else {
that.command("On")
}
},
onRead: function() {
return this.query();
}
});
cTypes.push({
cType: types.ROTATION_SPEED_CTYPE,
perms: ["pw","pr","ev"],
format: "bool",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Change the speed of the fan",
designedMaxLength: 1,
onUpdate: function(value) {
if (value == 0) {
that.command("Off");
} else if (value > 0 && value < 40) {
that.command("Low");
} else if (value > 40 && value < 75) {
that.command("Medium");
} else {
that.command("High");
}
},
onRead: function() {
return this.query();
}
});
}
else if (this.uom == "on/off")
{
cTypes.push({
cType: types.POWER_STATE_CTYPE,
perms: ["pw","pr","ev"],
format: "bool",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Change the power state",
designedMaxLength: 1,
onUpdate: function(value) {
if (value == 0) {
that.command("Off")
} else {
that.command("On")
}
},
onRead: function() {
return this.query();
}
});
}
return cTypes;
},
sType: function() {
if (this.uom == "%/on/off") {
return types.LIGHTBULB_STYPE;
} else if (this.uom == "on/off") {
return types.SWITCH_STYPE;
} else if (this.uom == "off/low/med/high") {
return types.FAN_STYPE;
}
return types.SWITCH_STYPE;
},
getServices: function() {
var that = this;
var services = [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: this.informationCharacteristics(),
},
{
sType: this.sType(),
characteristics: this.controlCharacteristics(that)
}];
//that.log("Loaded services for " + that.name);
return services;
}
};
module.exports.accessory = ISYAccessory;
module.exports.platform = ISYPlatform;

View File

@@ -1,552 +0,0 @@
// Indigo Platform Shim for HomeBridge
// Written by Mike Riccio (https://github.com/webdeck)
// Based on many of the other HomeBridge plartform modules
// See http://www.indigodomo.com/ for more info on Indigo
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// "platform": "Indigo", // required
// "name": "Indigo", // required
// "host": "127.0.0.1", // required
// "port": "8176", // required
// "username": "username", // optional
// "password": "password" // optional
// }
// ],
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
//
var types = require("hap-nodejs/accessories/types.js");
var Characteristic = require("hap-nodejs").Characteristic;
var request = require('request');
var async = require('async');
function IndigoPlatform(log, config) {
this.log = log;
this.baseURL = "http://" + config["host"] + ":" + config["port"];
if (config["username"] && config["password"]) {
this.auth = {
'user': config["username"],
'pass': config["password"],
'sendImmediately': false
};
}
}
IndigoPlatform.prototype = {
accessories: function(callback) {
var that = this;
this.log("Discovering Indigo Devices.");
var options = {
url: this.baseURL + "/devices.json/",
method: 'GET'
};
if (this.auth) {
options['auth'] = this.auth;
}
this.foundAccessories = [];
this.callback = callback;
request(options, function(error, response, body) {
if (error) {
console.trace("Requesting Indigo devices.");
that.log(error);
return error;
}
// Cheesy hack because response may have an extra comma at the start of the array, which is invalid
var firstComma = body.indexOf(",");
if (firstComma < 10) {
body = "[" + body.substr(firstComma + 1);
}
var json = JSON.parse(body);
async.eachSeries(json, function(item, asyncCallback) {
var deviceURL = that.baseURL + item.restURL;
var deviceOptions = {
url: deviceURL,
method: 'GET'
};
if (that.auth) {
deviceOptions['auth'] = that.auth;
}
request(deviceOptions, function(deviceError, deviceResponse, deviceBody) {
if (deviceError) {
console.trace("Requesting Indigo device info: " + deviceURL + "\nError: " + deviceError + "\nResponse: " + deviceBody);
}
else {
try {
var deviceJson = JSON.parse(deviceBody);
that.log("Discovered " + deviceJson.type + ": " + deviceJson.name);
that.foundAccessories.push(
new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson));
}
catch (e) {
that.log("Error parsing Indigo device info: " + deviceURL + "\nException: " + e + "\nResponse: " + deviceBody);
}
}
asyncCallback();
});
}, function(asyncError) {
// This will be called after all the requests complete
if (asyncError) {
console.trace("Requesting Indigo device info.");
that.log(asyncError);
}
that.callback(that.foundAccessories.sort(function (a,b) {
return (a.name > b.name) - (a.name < b.name);
}));
});
});
}
}
function IndigoAccessory(log, auth, deviceURL, json) {
this.log = log;
this.auth = auth;
this.deviceURL = deviceURL;
for (var prop in json) {
if (json.hasOwnProperty(prop)) {
this[prop] = json[prop];
}
}
}
IndigoAccessory.prototype = {
getStatus: function(callback) {
var that = this;
var options = {
url: this.deviceURL,
method: 'GET'
};
if (this.auth) {
options['auth'] = this.auth;
}
request(options, function(error, response, body) {
if (error) {
console.trace("Requesting Device Status.");
that.log(error);
}
else {
that.log("getStatus of " + that.name + ": " + body);
try {
var json = JSON.parse(body);
callback(json);
}
catch (e) {
console.trace("Requesting Device Status.");
that.log("Exception: " + e + "\nResponse: " + body);
}
}
});
},
updateStatus: function(params) {
var that = this;
var options = {
url: this.deviceURL + "?" + params,
method: 'PUT'
};
if (this.auth) {
options['auth'] = this.auth;
}
this.log("updateStatus of " + that.name + ": " + params);
request(options, function(error, response, body) {
if (error) {
console.trace("Updating Device Status.");
that.log(error);
return error;
}
});
},
query: function(prop, callback) {
this.getStatus(function(json) {
callback(json[prop]);
});
},
turnOn: function() {
if (this.typeSupportsOnOff) {
this.updateStatus("isOn=1");
}
},
turnOff: function() {
if (this.typeSupportsOnOff) {
this.updateStatus("isOn=0");
}
},
setBrightness: function(brightness) {
if (this.typeSupportsDim && brightness >= 0 && brightness <= 100) {
this.updateStatus("brightness=" + brightness);
}
},
setSpeedIndex: function(speedIndex) {
if (this.typeSupportsSpeedControl && speedIndex >= 0 && speedIndex <= 3) {
this.updateStatus("speedIndex=" + speedIndex);
}
},
getCurrentHeatingCooling: function(callback) {
this.getStatus(function(json) {
var mode = 0;
if (json["hvacOperatonModeIsHeat"]) {
mode = 1;
}
else if (json["hvacOperationModeIsCool"]) {
mode = 2;
}
else if (json["hvacOperationModeIsAuto"]) {
mode = 3;
}
callback(mode);
});
},
setTargetHeatingCooling: function(mode) {
if (mode == 0) {
param = "Off";
}
else if (mode == 1) {
param = "Heat";
}
else if (mode == 2) {
param = "Cool";
}
else if (mode == 3) {
param = "Auto";
}
if (param) {
this.updateStatus("hvacOperationModeIs" + param + "=true");
}
},
// Note: HomeKit wants all temperature values to be in celsius
getCurrentTemperature: function(callback) {
this.query("displayRawState", function(temperature) {
callback((temperature - 32.0) * 5.0 / 9.0);
});
},
getTargetTemperature: function(callback) {
this.getStatus(function(json) {
var temperature;
if (json["hvacOperatonModeIsHeat"]) {
temperature = json["setpointHeat"];
}
else if (json["hvacOperationModeIsCool"]) {
temperature = json["setpointCool"];
}
else {
temperature = (json["setpointHeat"] + json["setpointCool"]) / 2.0;
}
callback((temperature - 32.0) * 5.0 / 9.0);
});
},
setTargetTemperature: function(temperature) {
var that = this;
var t = (temperature * 9.0 / 5.0) + 32.0;
this.getStatus(function(json) {
if (json["hvacOperatonModeIsHeat"]) {
that.updateStatus("setpointHeat=" + t);
}
else if (json["hvacOperationModeIsCool"]) {
that.updateStatus("setpointCool=" + t);
}
else {
var cool = t + 5;
var heat = t - 5;
that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat);
}
});
},
informationCharacteristics: function() {
return [
{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: [Characteristic.Perms.READ],
format: Characteristic.Formats.STRING,
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: [Characteristic.Perms.READ],
format: Characteristic.Formats.STRING,
initialValue: "Indigo",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: [Characteristic.Perms.READ],
format: Characteristic.Formats.STRING,
initialValue: this.type,
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: [Characteristic.Perms.READ],
format: Characteristic.Formats.STRING,
initialValue: this.addressStr,
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: [Characteristic.Perms.WRITE],
format: Characteristic.Formats.BOOL,
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}
]
},
controlCharacteristics: function(that) {
var hasAType = false;
var cTypes = [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: [Characteristic.Perms.READ],
format: Characteristic.Formats.STRING,
initialValue: that.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
}];
if (that.typeSupportsDim) {
hasAType = true;
cTypes.push({
cType: types.BRIGHTNESS_CTYPE,
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
initialValue: that.brightness,
supportEvents: false,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: Characteristic.Units.PERCENTAGE,
onUpdate: function(value) {
that.setBrightness(value);
},
onRead: function(callback) {
that.query("brightness", callback);
}
});
}
if (that.typeSupportsSpeedControl) {
hasAType = true;
cTypes.push({
cType: types.ROTATION_SPEED_CTYPE,
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the speed of the fan",
designedMaxLength: 1,
designedMinValue: 0,
designedMaxValue: 3,
designedMinStep: 1,
onUpdate: function(value) {
that.setSpeedIndex(value);
},
onRead: function(callback) {
that.query("speedIndex", callback);
}
});
}
if (that.typeSupportsHVAC) {
hasAType = true;
cTypes.push({
cType: types.CURRENTHEATINGCOOLING_CTYPE,
perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Current Mode",
designedMaxLength: 1,
designedMinValue: 0,
designedMaxValue: 3,
designedMinStep: 1,
onUpdate: null,
onRead: function(callback) {
that.getCurrentHeatingCooling(callback);
}
});
cTypes.push({
cType: types.TARGETHEATINGCOOLING_CTYPE,
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Target Mode",
designedMaxLength: 1,
designedMinValue: 0,
designedMaxValue: 3,
designedMinStep: 1,
onUpdate: function(value) {
that.setTargetHeatingCooling(value);
},
onRead: function(callback) {
that.getCurrentHeatingCooling(callback);
}
});
cTypes.push({
cType: types.CURRENT_TEMPERATURE_CTYPE,
perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
designedMinValue: 16,
designedMaxValue: 38,
designedMinStep: 1,
initialValue: 20,
supportEvents: false,
supportBonjour: false,
manfDescription: "Current Temperature",
unit: Characteristic.Units.FAHRENHEIT,
onUpdate: null,
onRead: function(callback) {
that.getCurrentTemperature(callback);
}
});
cTypes.push({
cType: types.TARGET_TEMPERATURE_CTYPE,
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
designedMinValue: 16,
designedMaxValue: 38,
designedMinStep: 1,
initialValue: 20,
supportEvents: false,
supportBonjour: false,
manfDescription: "Target Temperature",
unit: Characteristic.Units.FAHRENHEIT,
onUpdate: function(value) {
that.setTargetTemperature(value);
},
onRead: function(callback) {
that.getTargetTemperature(callback);
}
});
cTypes.push({
cType: types.TEMPERATURE_UNITS_CTYPE,
perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.INT,
initialValue: 1,
supportEvents: false,
supportBonjour: false,
manfDescription: "Unit",
onUpdate: null,
onRead: function(callback) {
callback(1);
}
});
}
if (that.typeSupportsOnOff || !hasAType) {
cTypes.push({
cType: types.POWER_STATE_CTYPE,
perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY],
format: Characteristic.Formats.BOOL,
initialValue: (that.isOn) ? 1 : 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the power state",
designedMaxLength: 1,
onUpdate: function(value) {
if (value == 0) {
that.turnOff();
} else {
that.turnOn();
}
},
onRead: function(callback) {
that.query("isOn", function(isOn) {
callback((isOn) ? 1 : 0);
});
}
});
}
return cTypes;
},
sType: function() {
if (this.typeSupportsHVAC) {
return types.THERMOSTAT_STYPE;
} else if (this.typeSupportsDim) {
return types.LIGHTBULB_STYPE;
} else if (this.typeSupportsSpeedControl) {
return types.FAN_STYPE;
} else if (this.typeSupportsOnOff) {
return types.SWITCH_STYPE;
}
return types.SWITCH_STYPE;
},
getServices: function() {
var that = this;
var services = [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: that.informationCharacteristics(),
},
{
sType: that.sType(),
characteristics: that.controlCharacteristics(that)
}];
that.log("Loaded services for " + that.name);
return services;
}
};
module.exports.accessory = IndigoAccessory;
module.exports.platform = IndigoPlatform;

View File

@@ -1,156 +0,0 @@
{
"bridge": {
"name": "Homebridge",
"username": "CC:22:3D:E3:CE:30",
"port": 51826,
"pin": "031-45-154"
},
"description": "This is an example configuration file for KNX platform shim",
"hint": "Always paste into jsonlint.com validation page before starting your homebridge, saves a lot of frustration",
"hint2": "Replace all group addresses by current addresses of your installation, these are arbitrary examples!",
"hint3": "For valid services and their characteristics have a look at the KNX.md file in folder platforms!",
"platforms": [
{
"platform": "KNX",
"name": "KNX",
"knxd_ip": "192.168.178.205",
"knxd_port": 6720,
"accessories": [
{
"accessory_type": "knxdevice",
"description": "Only generic type knxdevice is supported, all previous knx types have been merged into that.",
"name": "Living Room North Lamp",
"services": [
{
"type": "Lightbulb",
"description": "iOS8 Lightbulb type, supports On (Switch) and Brightness",
"name": "Living Room North Lamp",
"On": {
"Set": "1/1/6",
"Listen": [
"1/1/63"
]
},
"Brightness": {
"Set": "1/1/62",
"Listen": [
"1/1/64"
]
}
}
],
"services-description": "Services is an array, you CAN have multiple service types in one accessory, though it is not fully supported in many iOS HK apps, such as EVE and myTouchHome"
},
{
"accessory_type": "knxdevice",
"name": "Office Temperature",
"description": "iOS8.4.1 TemperatureSensor type, supports CurrentTemperature",
"services": [
{
"type": "TemperatureSensor",
"name": "Raumtemperatur",
"CurrentTemperature": {
"Listen": "3/3/44"
}
}
]
},
{
"accessory_type": "knxdevice",
"name": "Office Window Lock",
"services": [
{
"type": "LockMechanism",
"description": "iOS8 Lock mechanism, Supports LockCurrentState, LockTargetState, append R to the addresses if LOCKED is 1",
"name": "Office Window Lock",
"LockCurrentState": {
"Listen": "5/3/15R"
},
"LockTargetState": {
"Listen": "5/3/16R"
}
}
]
},
{
"accessory_type": "knxdevice",
"description": "sample device with multiple services. Multiple services of different types are widely supported",
"name": "Office",
"services": [
{
"type": "Lightbulb",
"name": "Office Lamp",
"On": {
"Set": "1/3/5"
}
},
{
"type": "Thermostat",
"description": "iOS8 Thermostat type, supports CurrentTemperature, TargetTemperature, CurrentHeatingCoolingState ",
"name": "Raumtemperatur",
"CurrentTemperature": {
"Listen": "3/3/44"
},
"TargetTemperature": {
"Set": "3/3/94"
},
"CurrentHeatingCoolingState": {
"Listen": "3/3/64"
}
},
{
"type": "WindowCovering",
"description": "iOS9 Window covering (blinds etc) type, still WIP",
"name": "Blinds",
"TargetPosition": {
"Set": "1/2/3",
"Listen": "1/2/4"
},
"CurrentPosition": {
"Set": "1/3/1",
"Listen": "1/3/2"
},
"PositionState": {
"Listen": "2/7/1"
}
}
]
},
{
"accessory_type": "knxdevice",
"description": "sample contact sensor device",
"name": "Office Contact",
"services": [
{
"type": "ContactSensor",
"name": "Office Door",
"ContactSensorState": {
"Listen": "5/3/5"
}
}
]
},
{
"accessory_type": "knxdevice",
"description": "sample garage door opener",
"name": "Office Garage",
"services": [
{
"type": "GarageDoorOpener",
"name": "Office Garage Opener",
"CurrentDoorState": {
"Listen": "5/4/5"
},
"TargetDoorState": {
"Listen": "5/4/6"
}
}
]
}
]
}
],
"accessories": [
]
}

View File

@@ -1,205 +0,0 @@
/** Sample platform outline
* based on Sonos platform
*/
'use strict';
var types = require("hap-nodejs/accessories/types.js");
var knxd = require('eibd');
function KNXPlatform(log, config){
this.log = log;
this.config = config;
// initiate connection to bus for listening ==> done with first shim
}
KNXPlatform.prototype = {
accessories: function(callback) {
this.log("Fetching KNX devices.");
var that = this;
// iterate through all devices the platform my offer
// for each device, create an accessory
// read accessories from file !!!!!
var foundAccessories = this.config.accessories;
//create array of accessories
var myAccessories = [];
for (var int = 0; int < foundAccessories.length; int++) {
this.log("parsing acc " + int + " of " + foundAccessories.length);
// instantiate and push to array
switch (foundAccessories[int].accessory_type) {
case "knxdevice":
this.log("push new universal device "+foundAccessories[int].name);
// push knxd connection setting to each device from platform
foundAccessories[int].knxd_ip = this.config.knxd_ip;
foundAccessories[int].knxd_port = this.config.knxd_port;
var accConstructor = require('./../accessories/knxdevice.js');
var acc = new accConstructor.accessory(this.log,foundAccessories[int]);
this.log("created "+acc.name+" universal accessory");
myAccessories.push(acc);
break;
default:
// do something else
this.log("unkown accessory type found");
}
}
// if done, return the array to callback function
this.log("returning "+myAccessories.length+" accessories");
callback(myAccessories);
}
};
/**
* The buscallbacks module is to expose a simple function to listen on the bus and register callbacks for value changes
* of registered addresses.
*
* Usage:
* You can start the monitoring process at any time
startMonitor({host: name-ip, port: port-num });
* You can add addresses to the subscriptions using
registerGA(groupAddress, callback)
* groupAddress has to be an groupAddress in common knx notation string '1/2/3'
* the callback has to be a
* var f = function(value) { handle value update;}
* so you can do a
* registerGA('1/2/3', function(value){
* console.log('1/2/3 got a hit with '+value);
* });
* but of course it is meant to be used programmatically, not literally, otherwise it has no advantage
*
* You can also use arrays of addresses if your callback is supposed to listen to many addresses:
registerGA(groupAddresses[], callback)
* as in
* registerGA(['1/2/3','1/0/0'], function(value){
* console.log('1/2/3 or 1/0/0 got a hit with '+value);
* });
* if you are having central addresses like "all lights off" or additional response objects
*
*
* callbacks can have a signature of
* function(value, src, dest, type) but do not have to support these parameters (order matters)
* src = physical address such as '1.1.20'
* dest = groupAddress hit (you subscribed to that address, remember?), as '1/2/3'
* type = Data point type, as 'DPT1'
*
*
*/
//array of registered addresses and their callbacks
var subscriptions = [];
//check variable to avoid running two listeners
var running;
function groupsocketlisten(opts, callback) {
var conn = knxd.Connection();
conn.socketRemote(opts, function() {
conn.openGroupSocket(0, callback);
});
}
var registerSingleGA = function registerSingleGA (groupAddress, callback, reverse) {
subscriptions.push({address: groupAddress, callback: callback, reverse:reverse });
};
/*
* public busMonitor.startMonitor()
* starts listening for telegrams on KNX bus
*
*/
var startMonitor = function startMonitor(opts) { // using { host: name-ip, port: port-num } options object
if (!running) {
running = true;
} else {
console.log("<< knxd socket listener already running >>");
return null;
}
console.log(">>> knxd groupsocketlisten starting <<<");
groupsocketlisten(opts, function(parser) {
//console.log("knxfunctions.read: in callback parser");
parser.on('write', function(src, dest, type, val){
// search the registered group addresses
//console.log('recv: Write from '+src+' to '+dest+': '+val+' ['+type+'], listeners:' + subscriptions.length);
for (var i = 0; i < subscriptions.length; i++) {
// iterate through all registered addresses
if (subscriptions[i].address === dest) {
// found one, notify
console.log('HIT: Write from '+src+' to '+dest+': '+val+' ['+type+']');
subscriptions[i].callback(val, src, dest, type, subscriptions[i].reverse);
}
}
});
parser.on('response', function(src, dest, type, val) {
// search the registered group addresses
// console.log('recv: resp from '+src+' to '+dest+': '+val+' ['+type+']');
for (var i = 0; i < subscriptions.length; i++) {
// iterate through all registered addresses
if (subscriptions[i].address === dest) {
// found one, notify
// console.log('HIT: Response from '+src+' to '+dest+': '+val+' ['+type+']');
subscriptions[i].callback(val, src, dest, type, subscriptions[i].reverse);
}
}
});
//dont care about reads here
// parser.on('read', function(src, dest) {
// console.log('Read from '+src+' to '+dest);
// });
//console.log("knxfunctions.read: in callback parser at end");
}); // groupsocketlisten parser
}; //startMonitor
/*
* public registerGA(groupAdresses[], callback(value))
* parameters
* callback: function(value, src, dest, type) called when a value is sent on the bus
* groupAddresses: (Array of) string(s) for group addresses
*
*
*
*/
var registerGA = function (groupAddresses, callback) {
// check if the groupAddresses is an array
if (groupAddresses.constructor.toString().indexOf("Array") > -1) {
// handle multiple addresses
for (var i = 0; i < groupAddresses.length; i++) {
if (groupAddresses[i] && groupAddresses[i].match(/(\d*\/\d*\/\d*)/)) { // do not bind empty addresses or invalid addresses
// clean the addresses
registerSingleGA (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0], callback,groupAddresses[i].match(/\d*\/\d*\/\d*(R)/) ? true:false );
}
}
} else {
// it's only one
if (groupAddresses.match(/(\d*\/\d*\/\d*)/)) {
registerSingleGA (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0], callback, groupAddresses.match(/\d*\/\d*\/\d*(R)/) ? true:false);
}
}
// console.log("listeners now: " + subscriptions.length);
};
module.exports.platform = KNXPlatform;
module.exports.registerGA = registerGA;
module.exports.startMonitor = startMonitor;

View File

@@ -1,213 +0,0 @@
# Syntax of the config.json
In the platforms section, you can insert a KNX type platform.
You need to configure all devices directly in the config.json.
````json
"platforms": [
{
"platform": "KNX",
"name": "KNX",
"knxd_ip": "192.168.178.205",
"knxd_port": 6720,
"accessories": [
{
"accessory_type": "knxdevice",
"name": "Living Room North Lamp",
"services": [
{
"type": "Lightbulb",
"description": "iOS8 Lightbulb type, supports On (Switch) and Brightness",
"name": "Living Room North Lamp",
"On": {
"Set": "1/1/6",
"Listen": ["1/1/63"]
},
"Brightness": {
"Set": "1/1/62",
"Listen": ["1/1/64"]
}
}
]
}
]
}
````
In the accessories section (the array within the brackets [ ]) you can insert as many objects as you like in the following form
````json
{
"accessory_type": "knxdevice",
"name": "Here goes your display name, this will be shown in HomeKit apps",
"services": [
{
}
]
}
````
You have to add services in the following syntax:
````json
{
"type": "SERVICENAME",
"description": "This is just for you to remember things",
"name": "beer tap thermostat",
"CHARACTERISTIC1": {
"Set": "1/1/6",
"Listen": [
"1/1/63"
]
},
"CHARACTERISTIC2": {
"Set": "1/1/62",
"Listen": [
"1/1/64"
]
}
}
````
`CHARACTERISTICx` are properties that are dependent on the service type, so they are listed below.
Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group address, to which changes are sent if the service supports changing values. Changes on the bus are listened to, too.
`"Listen":["1/2/3","1/2/4","1/2/5"]` is an array of addresses that are listened to additionally. To these addresses never values get written, but the on startup the service will issue *KNX read requests* to ALL addresses listed in `Set:` and in `Listen:`
For two characteristics there are additional minValue and maxValue attributes. These are CurrentTemperature and TargetTemperature, and are used in TemperatureSensor and Thermostat.
So the charcteristic section may look like:
````json
{
"type": "Thermostat",
"description": "Sample thermostat",
"name": "We need a name for each service, though it usually shows only if multiple services are present in one accessory",
"CurrentTemperature": {
"Set": "1/1/6",
"Listen": [
"1/1/63"
],
"minValue": -18,
"maxValue": 30
},
"TargetTemperature": {
"Set": "1/1/62",
"Listen": [
"1/1/64"
],
"minValue": -4,
"maxValue": 12
}
}
````
## reversal of values for characteristics
In general, all DPT1 types can be reversed. If you need a 1 for "contact" of a contact senser, you can append an "R" to the group address.
Likewise, all percentages of DPT5 can be reversed, if you need a 100% (=255) for window closed, append an "R" to the group address. Do not forget the listening addresses!
````json
{
"type": "ContactSensor",
"description": "Sample ContactSensor with 1 as contact (0 is Apple's default)",
"name": "WindowContact1",
"ContactSensorState": {
"Listen": [
"1/1/100R"
]
}
}
````
# Supported Services and their characteristics
## ContactSensor
- ContactSensorState: DPT 1.002, 0 as contact
- ~~ContactSensorStateContact1: DPT 1.002, 1 as contact~~
- StatusActive: DPT 1.011, 1 as true
- StatusFault: DPT 1.011, 1 as true
- StatusTampered: DPT 1.011, 1 as true
- StatusLowBattery: DPT 1.011, 1 as true
## GarageDoorOpener
- CurrentDoorState: DPT5 integer value in range 0..4
// Characteristic.CurrentDoorState.OPEN = 0;
// Characteristic.CurrentDoorState.CLOSED = 1;
// Characteristic.CurrentDoorState.OPENING = 2;
// Characteristic.CurrentDoorState.CLOSING = 3;
// Characteristic.CurrentDoorState.STOPPED = 4;
- TargetDoorState: DPT5 integer value in range 0..1
// Characteristic.TargetDoorState.OPEN = 0;
// Characteristic.TargetDoorState.CLOSED = 1;
- ObstructionDetected: DPT1, 1 as true
- LockCurrentState: DPT5 integer value in range 0..3
// Characteristic.LockCurrentState.UNSECURED = 0;
// Characteristic.LockCurrentState.SECURED = 1;
// Characteristic.LockCurrentState.JAMMED = 2;
// Characteristic.LockCurrentState.UNKNOWN = 3;
- LockTargetState: DPT5 integer value in range 0..1
// Characteristic.LockTargetState.UNSECURED = 0;
// Characteristic.LockTargetState.SECURED = 1;
## Lightbulb
- On: DPT 1.001, 1 as on, 0 as off
- Brightness: DPT5.001 percentage, 100% (=255) the brightest
## LightSensor
- CurrentAmbientLightLevel: DPT 9.004, 0 to 100000 Lux
## LockMechanism (This is poorly mapped!)
- LockCurrentState: DPT 1, 1 as secured
- ~~LockCurrentStateSecured0: DPT 1, 0 as secured~~
- LockTargetState: DPT 1, 1 as secured
- ~~LockTargetStateSecured0: DPT 1, 0 as secured~~
*ToDo here: correction of mappings, HomeKit reqires lock states UNSECURED=0, SECURED=1, JAMMED = 2, UNKNOWN=3*
## MotionSensor
- MotionDetected: DPT 1.002, 1 as motion detected
- StatusActive: DPT 1.011, 1 as true
- StatusFault: DPT 1.011, 1 as true
- StatusTampered: DPT 1.011, 1 as true
- StatusLowBattery: DPT 1.011, 1 as true
## Outlet
- On: DPT 1.001, 1 as on, 0 as off
- OutletInUse: DPT 1.011, 1 as on, 0 as off
## Switch
- On: DPT 1.001, 1 as on, 0 as off
## TemperatureSensor
- CurrentTemperature: DPT9.001 in °C [listen only]
## Thermostat
- CurrentTemperature: DPT9.001 in °C [listen only], -40 to 80°C if not overriden as shown above
- TargetTemperature: DPT9.001, values 0..40°C only, all others are ignored
- CurrentHeatingCoolingState: DPT20.102 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only]
- TargetHeatingCoolingState: DPT20.102 HVAC, as above
## Window
- CurrentPosition: DPT5.001 percentage
- TargetPosition: DPT5.001 percentage
- PositionState: DPT5.005 value [listen only: 0 Increasing, 1 Decreasing, 2 Stopped]
## WindowCovering
- CurrentPosition: DPT5 percentage
- TargetPosition: DPT5 percentage
- PositionState: DPT5 value [listen only: 0 Closing, 1 Opening, 2 Stopped]
### not yet supported
- HoldPosition
- TargetHorizontalTiltAngle
- TargetVerticalTiltAngle
- CurrentHorizontalTiltAngle
- CurrentVerticalTiltAngle
- ObstructionDetected
# DISCLAIMER
**This is work in progress!**

View File

@@ -1,302 +0,0 @@
'use strict';
// LiFX Platform Shim for HomeBridge
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// "platform": "LIFx", // required
// "name": "LIFx", // required
// "access_token": "access token", // required
// "use_lan": "true" // optional set to "true" (gets and sets over the lan) or "get" (gets only over the lan)
// }
// ],
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
//
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var lifxRemoteObj = require('lifx-api');
var lifx_remote;
var lifxLanObj;
var lifx_lan;
var use_lan;
function LIFxPlatform(log, config){
// auth info
this.access_token = config["access_token"];
lifx_remote = new lifxRemoteObj(this.access_token);
// use remote or lan api ?
use_lan = config["use_lan"] || false;
if (use_lan != false) {
lifxLanObj = require('lifx');
lifx_lan = lifxLanObj.init();
}
this.log = log;
}
LIFxPlatform.prototype = {
accessories: function(callback) {
this.log("Fetching LIFx devices.");
var that = this;
var foundAccessories = [];
lifx_remote.listLights("all", function(body) {
var bulbs = JSON.parse(body);
for(var i = 0; i < bulbs.length; i ++) {
var accessory = new LIFxBulbAccessory(that.log, bulbs[i]);
foundAccessories.push(accessory);
}
callback(foundAccessories)
});
}
}
function LIFxBulbAccessory(log, bulb) {
// device info
this.name = bulb.label;
this.model = bulb.product_name;
this.deviceId = bulb.id;
this.serial = bulb.uuid;
this.capabilities = bulb.capabilities;
this.log = log;
}
LIFxBulbAccessory.prototype = {
getLan: function(type, callback){
var that = this;
if (!lifx_lan.bulbs[this.deviceId]) {
callback(new Error("Device not found"), false);
return;
}
lifx_lan.requestStatus();
lifx_lan.on('bulbstate', function(bulb) {
if (callback == null) {
return;
}
if (bulb.addr.toString('hex') == that.deviceId) {
switch(type) {
case "power":
callback(null, bulb.state.power > 0);
break;
case "brightness":
callback(null, Math.round(bulb.state.brightness * 100 / 65535));
break;
case "hue":
callback(null, Math.round(bulb.state.hue * 360 / 65535));
break;
case "saturation":
callback(null, Math.round(bulb.state.saturation * 100 / 65535));
break;
}
callback = null
}
});
},
getRemote: function(type, callback){
var that = this;
lifx_remote.listLights("id:"+ that.deviceId, function(body) {
var bulb = JSON.parse(body);
if (bulb.connected != true) {
callback(new Error("Device not found"), false);
return;
}
switch(type) {
case "power":
callback(null, bulb.power == "on" ? 1 : 0);
break;
case "brightness":
callback(null, Math.round(bulb.brightness * 100));
break;
case "hue":
callback(null, bulb.color.hue);
break;
case "saturation":
callback(null, Math.round(bulb.color.saturation * 100));
break;
}
});
},
identify: function(callback) {
lifx_remote.breatheEffect("id:"+ this.deviceId, 'green', null, 1, 3, false, true, 0.5, function (body) {
callback();
});
},
setLanColor: function(type, value, callback){
var bulb = lifx_lan.bulbs[this.deviceId];
if (!bulb) {
callback(new Error("Device not found"), false);
return;
}
var state = {
hue: bulb.state.hue,
saturation: bulb.state.saturation,
brightness: bulb.state.brightness,
kelvin: bulb.state.kelvin
};
var scale = type == "hue" ? 360 : 100;
state[type] = Math.round(value * 65535 / scale) & 0xffff;
lifx_lan.lightsColour(state.hue, state.saturation, state.brightness, state.kelvin, 0, bulb);
callback(null);
},
setLanPower: function(state, callback){
var bulb = lifx_lan.bulbs[this.deviceId];
if (!bulb) {
callback(new Error("Device not found"), false);
return;
}
if (state) {
lifx_lan.lightsOn(bulb);
}
else {
lifx_lan.lightsOff(bulb);
}
callback(null);
},
setRemoteColor: function(type, value, callback){
var color;
switch(type) {
case "brightness":
color = "brightness:" + (value / 100);
break;
case "hue":
color = "hue:" + value;
break;
case "saturation":
color = "saturation:" + (value / 100);
break;
}
lifx_remote.setColor("id:"+ this.deviceId, color, 0, null, function (body) {
callback();
});
},
setRemotePower: function(state, callback){
var that = this;
lifx_remote.setPower("id:"+ that.deviceId, (state == 1 ? "on" : "off"), 0, function (body) {
callback();
});
},
getServices: function() {
var that = this;
var services = []
var service = new Service.Lightbulb(this.name);
switch(use_lan) {
case true:
case "true":
// gets and sets over the lan api
service
.getCharacteristic(Characteristic.On)
.on('get', function(callback) { that.getLan("power", callback);})
.on('set', function(value, callback) {that.setLanPower(value, callback);});
service
.addCharacteristic(Characteristic.Brightness)
.on('get', function(callback) { that.getLan("brightness", callback);})
.on('set', function(value, callback) { that.setLanColor("brightness", value, callback);});
if (this.capabilities.has_color == true) {
service
.addCharacteristic(Characteristic.Hue)
.on('get', function(callback) { that.getLan("hue", callback);})
.on('set', function(value, callback) { that.setLanColor("hue", value, callback);});
service
.addCharacteristic(Characteristic.Saturation)
.on('get', function(callback) { that.getLan("saturation", callback);})
.on('set', function(value, callback) { that.setLanColor("saturation", value, callback);});
}
break;
case "get":
// gets over the lan api, sets over the remote api
service
.getCharacteristic(Characteristic.On)
.on('get', function(callback) { that.getLan("power", callback);})
.on('set', function(value, callback) {that.setRemotePower(value, callback);});
service
.addCharacteristic(Characteristic.Brightness)
.on('get', function(callback) { that.getLan("brightness", callback);})
.on('set', function(value, callback) { that.setRemoteColor("brightness", value, callback);});
if (this.capabilities.has_color == true) {
service
.addCharacteristic(Characteristic.Hue)
.on('get', function(callback) { that.getLan("hue", callback);})
.on('set', function(value, callback) { that.setRemoteColor("hue", value, callback);});
service
.addCharacteristic(Characteristic.Saturation)
.on('get', function(callback) { that.getLan("saturation", callback);})
.on('set', function(value, callback) { that.setRemoteColor("saturation", value, callback);});
}
break;
default:
// gets and sets over the remote api
service
.getCharacteristic(Characteristic.On)
.on('get', function(callback) { that.getRemote("power", callback);})
.on('set', function(value, callback) {that.setRemotePower(value, callback);});
service
.addCharacteristic(Characteristic.Brightness)
.on('get', function(callback) { that.getRemote("brightness", callback);})
.on('set', function(value, callback) { that.setRemoteColor("brightness", value, callback);});
if (this.capabilities.has_color == true) {
service
.addCharacteristic(Characteristic.Hue)
.on('get', function(callback) { that.getRemote("hue", callback);})
.on('set', function(value, callback) { that.setRemoteColor("hue", value, callback);});
service
.addCharacteristic(Characteristic.Saturation)
.on('get', function(callback) { that.getRemote("saturation", callback);})
.on('set', function(value, callback) { that.setRemoteColor("saturation", value, callback);});
}
}
services.push(service);
service = new Service.AccessoryInformation();
service
.setCharacteristic(Characteristic.Manufacturer, "LIFX")
.setCharacteristic(Characteristic.Model, this.model)
.setCharacteristic(Characteristic.SerialNumber, this.serial);
services.push(service);
return services;
}
}
module.exports.accessory = LIFxBulbAccessory;
module.exports.platform = LIFxPlatform;

View File

@@ -1,267 +0,0 @@
'use strict';
// Logitech Harmony Remote Platform Shim for HomeBridge
// Based on the Domoticz Platform Shim for HomeBridge by Joep Verhaeg (http://www.joepverhaeg.nl)
// Wriiten by John Wells (https://github.com/madmod)
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// "platform": "LogitechHarmony",
// "name": "Logitech Harmony"
// }
// ],
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
//
var types = require('hap-nodejs/accessories/types.js');
var harmonyDiscover = require('harmonyhubjs-discover');
var harmony = require('harmonyhubjs-client');
var _harmonyHubPort = 61991;
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var Accessory = require("hap-nodejs").Accessory;
var uuid = require("hap-nodejs").uuid;
var inherits = require('util').inherits;
var queue = require('queue');
function sortByKey (array, key) {
return array.sort(function(a, b) {
var x = a[key]; var y = b[key];
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
};
function LogitechHarmonyPlatform (log, config) {
this.log = log;
this.ip_address = config['ip_address'];
};
LogitechHarmonyPlatform.prototype = {
accessories: function (callback) {
var plat = this;
var foundAccessories = [];
var activityAccessories = [];
var hub = null;
var hubIP = null;
var hubQueue = queue();
hubQueue.concurrency = 1;
// Get the first hub
locateHub(function (err, client, clientIP) {
if (err) throw err;
plat.log("Fetching Logitech Harmony devices and activites...");
hub = client;
hubIP = clientIP;
//getDevices(hub);
getActivities();
});
// Find one Harmony remote hub (only support one for now)
function locateHub(callback) {
// Use the ip address in configuration if available
if (plat.ip_address) {
console.log("Using Logitech Harmony hub ip address from configuration");
return createClient(plat.ip_address, callback)
}
plat.log("Searching for Logitech Harmony remote hubs...");
// Discover the harmony hub with bonjour
var discover = new harmonyDiscover(_harmonyHubPort);
// TODO: Support update event with some way to add accessories
// TODO: Have some kind of timeout with an error message. Right now this searches forever until it finds one hub.
discover.on('online', function (hubInfo) {
plat.log("Found Logitech Harmony remote hub: " + hubInfo.ip);
// Stop looking for hubs once we find the first one
// TODO: Support multiple hubs
discover.stop();
createClient(hubInfo.ip, callback);
});
// Start looking for hubs
discover.start();
}
// Connect to a Harmony hub
function createClient(ipAddress, callback) {
plat.log("Connecting to Logitech Harmony remote hub...");
harmony(ipAddress)
.then(function (client) {
plat.log("Connected to Logitech Harmony remote hub");
callback(null, client, ipAddress);
});
}
// Get Harmony Activities
function getActivities() {
plat.log("Fetching Logitech Harmony activities...");
hub.getActivities()
.then(function (activities) {
plat.log("Found activities: \n" + activities.map(function (a) { return "\t" + a.label; }).join("\n"));
hub.getCurrentActivity().then(function (currentActivity) {
var actAccessories = [];
var sArray = sortByKey(activities, "label");
sArray.map(function(s) {
var accessory = createActivityAccessory(s);
if (accessory.id > 0) {
accessory.updateActivityState(currentActivity);
actAccessories.push(accessory);
foundAccessories.push(accessory);
}
});
activityAccessories = actAccessories;
keepAliveRefreshLoop();
callback(foundAccessories);
}).catch(function (err) {
plat.log('Unable to get current activity with error', err);
throw err;
});
});
}
function createActivityAccessory(activity) {
var accessory = new LogitechHarmonyActivityAccessory(plat.log, activity, changeCurrentActivity.bind(plat), -1);
return accessory;
}
var isChangingActivity = false;
function changeCurrentActivity(nextActivity, callback) {
if (!nextActivity) {
nextActivity = -1;
}
plat.log('Queue activity to ' + nextActivity);
executeOnHub(function(h, cb) {
plat.log('Set activity to ' + nextActivity);
h.startActivity(nextActivity)
.then(function () {
cb();
isChangingActivity = false;
plat.log('Finished setting activity to ' + nextActivity);
updateCurrentActivity(nextActivity);
if (callback) callback(null, nextActivity);
})
.catch(function (err) {
cb();
isChangingActivity = false;
plat.log('Failed setting activity to ' + nextActivity + ' with error ' + err);
if (callback) callback(err);
});
}, function(){
callback(Error("Set activity failed too many times"));
});
}
function updateCurrentActivity(currentActivity) {
var actAccessories = activityAccessories;
if (actAccessories instanceof Array) {
actAccessories.map(function(a) { a.updateActivityState(currentActivity); });
}
}
// prevent connection from closing
function keepAliveRefreshLoop() {
setTimeout(function() {
setInterval(function() {
executeOnHub(function(h, cb) {
plat.log("Refresh Status");
h.getCurrentActivity()
.then(function(currentActivity){
cb();
updateCurrentActivity(currentActivity);
})
.catch(cb);
});
}, 20000);
}, 5000);
}
function executeOnHub(func, funcMaxTimeout)
{
if (!func) return;
hubQueue.push(function(cb) {
var tout = setTimeout(function(){
plat.log("Reconnecting to Hub " + hubIP);
createClient(hubIP, function(err, newHub){
if (err) throw err;
hub = newHub;
if (funcMaxTimeout) {
funcMaxTimeout();
}
cb();
});
}, 30000);
func(hub, function(){
clearTimeout(tout);
cb();
});
});
if (!hubQueue.running){
hubQueue.start();
}
}
}
};
function LogitechHarmonyActivityAccessory (log, details, changeCurrentActivity) {
this.log = log;
this.id = details.id;
this.name = details.label;
this.isOn = false;
this.changeCurrentActivity = changeCurrentActivity;
Accessory.call(this, this.name, uuid.generate(this.id));
var self = this;
this.getService(Service.AccessoryInformation)
.setCharacteristic(Characteristic.Manufacturer, "Logitech")
.setCharacteristic(Characteristic.Model, "Harmony")
// TODO: Add hub unique id to this for people with multiple hubs so that it is really a guid.
.setCharacteristic(Characteristic.SerialNumber, this.id);
this.addService(Service.Switch)
.getCharacteristic(Characteristic.On)
.on('get', function(callback) {
// Refreshed automatically by platform
callback(null, self.isOn);
})
.on('set', this.setPowerState.bind(this));
}
inherits(LogitechHarmonyActivityAccessory, Accessory);
LogitechHarmonyActivityAccessory.prototype.parent = Accessory.prototype;
LogitechHarmonyActivityAccessory.prototype.getServices = function() {
return this.services;
};
LogitechHarmonyActivityAccessory.prototype.updateActivityState = function (currentActivity) {
this.isOn = (currentActivity === this.id);
// Force get to trigger 'change' if needed
this.getService(Service.Switch)
.getCharacteristic(Characteristic.On)
.getValue();
};
LogitechHarmonyActivityAccessory.prototype.setPowerState = function (state, callback) {
this.changeCurrentActivity(state ? this.id : null, callback);
};
module.exports.platform = LogitechHarmonyPlatform;

View File

@@ -1,242 +0,0 @@
/*
MiLight platform shim for Homebridge
Written by Sam Edwards (https://samedwards.ca/)
Uses the node-milight-promise library (https://github.com/mwittig/node-milight-promise) which features some code from
applamp.nl (http://www.applamp.nl/service/applamp-api/) and uses other details from (http://www.limitlessled.com/dev/)
Configure in config.json as follows:
"platforms": [
{
"platform":"MiLight",
"name":"MiLight",
"ip_address": "255.255.255.255",
"port": 8899,
"type": "rgbw",
"delay": 30,
"repeat": 3,
"zones":["Kitchen Lamp","Bedroom Lamp","Living Room Lamp","Hallway Lamp"]
}
]
Where the parameters are:
*platform (required): This must be "MiLight", and refers to the name of the accessory as exported from this file
*name (optional): The display name used for logging output by Homebridge. Best to set to "MiLight"
*ip_address (optional): The IP address of the WiFi Bridge. Default to the broadcast address of 255.255.255.255 if not specified
*port (optional): Port of the WiFi bridge. Defaults to 8899 if not specified
*type (optional): One of either "rgbw", "rgb", or "white", depending on the type of bulb being controlled. This applies to all zones. Defaults to rgbw.
*delay (optional): Delay between commands sent over UDP. Default 30ms. May cause delays when sending a lot of commands. Try decreasing to improve.
*repeat (optional): Number of times to repeat the UDP command for better reliability. Default 3
*zones (required): An array of the names of the zones, in order, 1-4. Use null if a zone is skipped. RGB lamps can only have a single zone.
Tips and Tricks:
*Setting the brightness of an rgbw or a white bulb will set it to "night mode", which is dimmer than the lowest brightness setting
*White and rgb bulbs don't support absolute brightness setting, so we just send a brightness up/brightness down command depending
if we got a percentage above/below 50% respectively
*The only exception to the above is that white bulbs support a "maximum brightness" command, so we send that when we get 100%
*Implemented warmer/cooler for white lamps in a similar way to brightnes, except this time above/below 180 degrees on the colour wheel
*I welcome feedback on a better way to work the brightness/hue for white and rgb bulbs
Troubleshooting:
The node-milight-promise library provides additional debugging output when the MILIGHT_DEBUG environmental variable is set
TODO:
*Possibly build in some sort of state logging and persistance so that we can answswer HomeKit status queries to the best of our ability
*/
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var Milight = require('node-milight-promise').MilightController;
var commands = require('node-milight-promise').commands;
module.exports = {
accessory: MiLightAccessory,
platform: MiLightPlatform
}
function MiLightPlatform(log, config) {
this.log = log;
this.config = config;
}
MiLightPlatform.prototype = {
accessories: function(callback) {
var zones = [];
// Various error checking
if (this.config.zones) {
var zoneLength = this.config.zones.length;
} else {
this.log("ERROR: Could not read zones from configuration.");
return;
}
if (!this.config["type"]) {
this.log("INFO: Type not specified, defaulting to rgbw");
this.config["type"] = "rgbw";
}
if (zoneLength == 0) {
this.log("ERROR: No zones found in configuration.");
return;
} else if (this.config["type"] == "rgb" && zoneLength > 1) {
this.log("WARNING: RGB lamps only have a single zone. Only the first defined zone will be used.");
zoneLength = 1;
} else if (zoneLength > 4) {
this.log("WARNING: Only a maximum of 4 zones are supported per bridge. Only recognizing the first 4 zones.");
zoneLength = 4;
}
// Create lamp accessories for all of the defined zones
for (var i=0; i < zoneLength; i++) {
if (!!this.config.zones[i]) {
this.config["name"] = this.config.zones[i];
this.config["zone"] = i+1;
lamp = new MiLightAccessory(this.log, this.config);
zones.push(lamp);
}
}
if (zones.length > 0) {
callback(zones);
} else {
this.log("ERROR: Unable to find any valid zones");
return;
}
}
}
function MiLightAccessory(log, config) {
this.log = log;
// config info
this.ip_address = config["ip_address"];
this.port = config["port"];
this.name = config["name"];
this.zone = config["zone"];
this.type = config["type"];
this.delay = config["delay"];
this.repeat = config["repeat"];
this.light = new Milight({
ip: this.ip_address,
port: this.port,
delayBetweenCommands: this.delay,
commandRepeat: this.repeat
});
}
MiLightAccessory.prototype = {
setPowerState: function(powerOn, callback) {
if (powerOn) {
this.log("["+this.name+"] Setting power state to on");
this.light.sendCommands(commands[this.type].on(this.zone));
} else {
this.log("["+this.name+"] Setting power state to off");
this.light.sendCommands(commands[this.type].off(this.zone));
}
callback();
},
setBrightness: function(level, callback) {
if (level == 0) {
// If brightness is set to 0, turn off the lamp
this.log("["+this.name+"] Setting brightness to 0 (off)");
this.light.sendCommands(commands[this.type].off(this.zone));
} else if (level <= 2 && (this.type == "rgbw" || this.type == "white")) {
// If setting brightness to 2 or lower, instead set night mode for lamps that support it
this.log("["+this.name+"] Setting night mode", level);
this.light.sendCommands(commands[this.type].off(this.zone));
// Ensure we're pausing for 100ms between these commands as per the spec
this.light.pause(100);
this.light.sendCommands(commands[this.type].nightMode(this.zone));
} else {
this.log("["+this.name+"] Setting brightness to %s", level);
// Send on command to ensure we're addressing the right bulb
this.light.sendCommands(commands[this.type].on(this.zone));
// If this is an rgbw lamp, set the absolute brightness specified
if (this.type == "rgbw") {
this.light.sendCommands(commands.rgbw.brightness(level));
} else {
// If this is an rgb or a white lamp, they only support brightness up and down.
// Set brightness up when value is >50 and down otherwise. Not sure how well this works real-world.
if (level >= 50) {
if (this.type == "white" && level == 100) {
// But the white lamps do have a "maximum brightness" command
this.light.sendCommands(commands.white.maxBright(this.zone));
} else {
this.light.sendCommands(commands[this.type].brightUp());
}
} else {
this.light.sendCommands(commands[this.type].brightDown());
}
}
}
callback();
},
setHue: function(value, callback) {
this.log("["+this.name+"] Setting hue to %s", value);
var hue = Array(value, 0, 0);
// Send on command to ensure we're addressing the right bulb
this.light.sendCommands(commands[this.type].on(this.zone));
if (this.type == "rgbw") {
if (value == 0) {
this.light.sendCommands(commands.rgbw.whiteMode(this.zone));
} else {
this.light.sendCommands(commands.rgbw.hue(commands.rgbw.hsvToMilightColor(hue)));
}
} else if (this.type == "rgb") {
this.light.sendCommands(commands.rgb.hue(commands.rgbw.hsvToMilightColor(hue)));
} else if (this.type == "white") {
// Again, white lamps don't support setting an absolue colour temp, so trying to do warmer/cooler step at a time based on colour
if (value >= 180) {
this.light.sendCommands(commands.white.cooler());
} else {
this.light.sendCommands(commands.white.warmer());
}
}
callback();
},
identify: function(callback) {
this.log("["+this.name+"] Identify requested!");
callback(); // success
},
getServices: function() {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "MiLight")
.setCharacteristic(Characteristic.Model, this.type)
.setCharacteristic(Characteristic.SerialNumber, "MILIGHT12345");
var lightbulbService = new Service.Lightbulb();
lightbulbService
.getCharacteristic(Characteristic.On)
.on('set', this.setPowerState.bind(this));
lightbulbService
.addCharacteristic(new Characteristic.Brightness())
.on('set', this.setBrightness.bind(this));
lightbulbService
.addCharacteristic(new Characteristic.Hue())
.on('set', this.setHue.bind(this));
return [informationService, lightbulbService];
}
};

View File

@@ -1,397 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var nest = require('unofficial-nest-api');
function NestPlatform(log, config){
// auth info
this.username = config["username"];
this.password = config["password"];
this.log = log;
}
NestPlatform.prototype = {
accessories: function(callback) {
this.log("Fetching Nest devices.");
var that = this;
var foundAccessories = [];
nest.login(this.username, this.password, function (err, data) {
if (err) {
that.log("There was a problem authenticating with Nest.");
}
else {
nest.fetchStatus(function (data) {
for (var deviceId in data.device) {
if (data.device.hasOwnProperty(deviceId)) {
var device = data.device[deviceId];
// it's a thermostat, adjust this to detect other accessories
if (data.shared[deviceId].hasOwnProperty('current_temperature'))
{
var name = data.shared[deviceId].name
var accessory = new NestThermostatAccessory(that.log, name, device, deviceId);
foundAccessories.push(accessory);
}
}
}
callback(foundAccessories)
});
}
});
}
}
function NestThermostatAccessory(log, name, device, deviceId) {
// device info
if (name) {
this.name = name;
} else {
this.name = "Nest" + device.serial_number;
}
this.model = device.model_version;
this.serial = device.serial_number;
this.deviceId = deviceId;
this.log = log;
}
NestThermostatAccessory.prototype = {
getCurrentHeatingCooling: function(callback){
var that = this;
this.log("Checking current heating cooling for: " + this.name);
nest.fetchStatus(function (data) {
var device = data.device[that.deviceId];
var currentHeatingCooling = 0;
switch(device.current_schedule_mode) {
case "OFF":
targetHeatingCooling = 0;
break;
case "HEAT":
currentHeatingCooling = 1;
break;
case "COOL":
currentHeatingCooling = 2;
break;
case "RANGE":
currentHeatingCooling = 3;
break;
default:
currentHeatingCooling = 0;
}
that.log("Current heating for " + this.name + "is: " + currentHeatingCooling);
callback(currentHeatingCooling);
});
},
getTargetHeatingCoooling: function(callback){
var that = this;
this.log("Checking target heating cooling for: " + this.name);
nest.fetchStatus(function (data) {
var device = data.device[that.deviceId];
var targetHeatingCooling = 0;
switch(device.target_temperature_type) {
case "off":
targetHeatingCooling = 0;
break;
case "heat":
targetHeatingCooling = 1;
break;
case "cool":
targetHeatingCooling = 2;
break;
case "range":
targetHeatingCooling = 3;
break;
default:
targetHeatingCooling = 0;
}
that.log("Current target heating for " + this.name + " is: " + targetHeatingCooling);
callback(targetHeatingCooling);
});
},
getCurrentTemperature: function(callback){
var that = this;
nest.fetchStatus(function (data) {
var device = data.shared[that.deviceId];
that.log("Current temperature for " + this.name + " is: " + device.current_temperature);
callback(device.current_temperature);
});
},
getTargetTemperature: function(callback){
var that = this;
nest.fetchStatus(function (data) {
var device = data.shared[that.deviceId];
that.log("Target temperature for " + this.name + " is: " + device.target_temperature);
callback(device.target_temperature);
});
},
getTemperatureUnits: function(callback){
var that = this;
nest.fetchStatus(function (data) {
var device = data.device[that.deviceId];
var temperatureUnits = 0;
switch(device.temperature_scale) {
case "F":
that.log("Tempature unit for " + this.name + " is: " + "Fahrenheit");
temperatureUnits = 1;
break;
case "C":
that.log("Tempature unit for " + this.name + " is: " + "Celsius");
temperatureUnits = 0;
break;
default:
temperatureUnits = 0;
}
callback(temperatureUnits);
});
},
getCurrentRelativeHumidity: function(callback){
var that = this;
nest.fetchStatus(function (data) {
var device = data.device[that.deviceId];
that.log("Humidity for " + this.name + " is: " + device.current_humidity);
callback(device.current_humidity);
})
},
setTargetHeatingCooling: function(targetHeatingCooling){
var that = this;
var targetTemperatureType = 'off';
switch(targetHeatingCooling) {
case 0:
targetTemperatureType = 'off';
break;
case 1:
targetTemperatureType = 'heat';
break;
case 2:
targetTemperatureType = 'cool';
break;
case 3:
targetTemperatureType = 'range';
break;
default:
targetTemperatureType = 'off';
}
this.log("Setting target heating cooling for " + this.name + " to: " + targetTemperatureType);
nest.setTargetTemperatureType(this.deviceId, targetTemperatureType);
},
setTargetTemperature: function(targetTemperature){
var that = this;
this.log("Setting target temperature for " + this.name + " to: " + targetTemperature);
nest.setTemperature(this.deviceId, targetTemperature);
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Nest",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.model,
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.serial,
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.THERMOSTAT_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of thermostat",
designedMaxLength: 255
},{
cType: types.CURRENTHEATINGCOOLING_CTYPE,
onUpdate: null,
onRead: function(callback) {
that.getCurrentHeatingCooling(function(currentHeatingCooling){
callback(currentHeatingCooling);
});
},
perms: ["pr","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Current Mode",
designedMaxLength: 1,
designedMinValue: 0,
designedMaxValue: 2,
designedMinStep: 1,
},{
cType: types.TARGETHEATINGCOOLING_CTYPE,
onUpdate: function(value) {
that.setTargetHeatingCooling(value);
},
onRead: function(callback) {
that.getTargetHeatingCoooling(function(targetHeatingCooling){
callback(targetHeatingCooling);
});
},
perms: ["pw","pr","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Target Mode",
designedMinValue: 0,
designedMaxValue: 3,
designedMinStep: 1,
},{
cType: types.CURRENT_TEMPERATURE_CTYPE,
onUpdate: null,
onRead: function(callback) {
that.getCurrentTemperature(function(currentTemperature){
callback(currentTemperature);
});
},
perms: ["pr","ev"],
format: "int",
initialValue: 20,
supportEvents: false,
supportBonjour: false,
manfDescription: "Current Temperature",
unit: "celsius"
},{
cType: types.TARGET_TEMPERATURE_CTYPE,
onUpdate: function(value) {
that.setTargetTemperature(value);
},
onRead: function(callback) {
that.getTargetTemperature(function(targetTemperature){
callback(targetTemperature);
});
},
perms: ["pw","pr","ev"],
format: "int",
initialValue: 20,
supportEvents: false,
supportBonjour: false,
manfDescription: "Target Temperature",
designedMinValue: 16,
designedMaxValue: 38,
designedMinStep: 1,
unit: "celsius"
},{
cType: types.TEMPERATURE_UNITS_CTYPE,
onUpdate: null,
onRead: function(callback) {
that.getTemperatureUnits(function(temperatureUnits){
callback(temperatureUnits);
});
},
perms: ["pr","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Unit",
},{
cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE,
onUpdate: null,
onRead: function(callback) {
that.getCurrentRelativeHumidity(function(currentRelativeHumidity){
callback(currentRelativeHumidity);
});
},
perms: ["pr","ev"],
format: "int",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "Humidity",
}]
}];
}
}
module.exports.accessory = NestThermostatAccessory;
module.exports.platform = NestPlatform;

View File

@@ -1,347 +0,0 @@
// OpenHAB Platform Shim for HomeBridge
// Written by Tommaso Marchionni
// Based on many of the other HomeBridge platform modules
//
// Revisions:
//
// 17 October 2015 [tommasomarchionni]
// - Initial release
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// "platform": "Openhab",
// "name": "Openhab",
// "server": "127.0.0.1",
// "port": "8080",
// "sitemap": "demo"
// }
// ],
//
// Example of sitemap in OpenHAB:
// sitemap homekit label="HomeKit" {
// Switch item=Light_1 label="Light 1"
// }
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
//
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
var Service = require("hap-nodejs/lib/Service.js").Service;
var Characteristic = require("hap-nodejs").Characteristic;
function OpenhabPlatform(log, config){
this.log = log;
this.user = config["user"];
this.password = config["password"];
this.server = config["server"];
this.port = config["port"];
this.protocol = "http";
this.sitemap = "demo";
if (typeof config["sitemap"] != 'undefined') {
this.sitemap = config["sitemap"];
}
}
OpenhabPlatform.prototype = {
sitemapUrl: function() {
var serverString = this.server;
//TODO da verificare
if (this.user && this.password) {
serverString = this.user + ":" + this.password + "@" + serverString;
}
return this.protocol + "://" + serverString + ":" + this.port + "/rest/sitemaps/" + this.sitemap + "?type=json";
},
parseSitemap: function(sitemap) {
var widgets = [].concat(sitemap.homepage.widget);
var result = [];
for (var i = 0; i < widgets.length; i++) {
var widget = widgets[i];
if (!widget.item) {
//TODO to handle frame
this.log("WARN: The widget '" + widget.label + "' does not reference an item.");
continue;
}
if (widget.item.type=="SwitchItem" || widget.item.type=="DimmerItem" || widget.item.type == "RollershutterItem"){
accessory = new OpenhabAccessory(this.log,this,widget.widgetId,widget.label,widget.item)
this.log("Accessory Found: " + widget.label);
result.push(accessory);
}
}
return result;
},
accessories: function(callback) {
this.log("Fetching OpenHAB devices.");
var that = this;
url = that.sitemapUrl();
this.log("Connecting to " + url);
request.get({
url: url,
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
callback(that.parseSitemap(json));
} else {
that.log("There was a problem connecting to OpenHAB.");
}
});
}
};
function OpenhabAccessory(log, platform, widgetId, label, detail) {
this.log = log;
this.platform = platform;
this.idx = widgetId;
this.name = label;
this.label = label;
this.type = detail.type;
this.deviceURL = detail.link;
this.addressStr = "n/a";
this.state = detail.state;
if (this.type == "DimmerItem") {
this.typeSupportsOnOff = true;
this.typeSupportsDim = true;
}
if (this.type == "SwitchItem") {
this.typeSupportsOnOff = true;
}
if (this.type == "RollershutterItem") {
this.typeSupportsWindowCovering = true;
}
}
OpenhabAccessory.prototype = {
updateStatus: function(command) {
var that = this;
var options = {
url: this.deviceURL,
method: 'POST',
body: "" + command
};
if (this.auth) {
options['auth'] = this.auth;
}
that.log("eseguo post");
request(options, function(error, response, body) {
if (error) {
console.trace("Updating Device Status.");
that.log(error);
return error;
}
that.log("updateStatus of " + that.name + ": " + command);
});
},
getServiceType: function() {
if (this.typeSupportsWindowCovering){
return new Service.WindowCovering;
} else if (this.typeSupportsDim) {
return new Service.Lightbulb;
} else if (this.typeSupportsOnOff) {
return new Service.Switch;
}
},
updateStatus: function(command, callback) {
var that = this;
var options = {
url: this.deviceURL,
method: 'POST',
body: "" + command
};
if (this.auth) {
options['auth'] = this.auth;
}
request(options, function(error, response, body) {
if (error) {
//console.trace("Updating Device Status.");
//that.log(error);
//return error;
callback(new Error(error));
} else {
that.log("updateStatus of " + that.name + ": " + command);
callback(true);
}
}.bind(this));
},
setPowerState: function(powerOn, callback) {
var that = this;
if (this.typeSupportsOnOff) {
if (powerOn) {
var command = "ON";
} else {
var command = "OFF";
}
this.log("Setting power state on the '"+this.name+"' to " + command);
this.updateStatus(command, function(noError){
if (noError) {
that.log("Successfully set '"+that.name+"' to " + command);
callback();
} else {
callback(new Error('Can not communicate with OpenHAB.'));
}
}.bind(this));
}else{
callback(new Error(this.name + " not supports ONOFF"));
}
},
getStatus: function(callback){
var that = this;
this.log("Fetching status brightness for: " + this.name);
var options = {
url: this.deviceURL + '/state?type=json',
method: 'GET'
};
if (this.auth) {
options['auth'] = this.auth;
}
request(options, function(error, response, body) {
if (error) {
//console.trace("Requesting Device Status.");
//that.log(error);
//return error;
callback(new Error('Can not communicate with Home Assistant.'));
} else {
that.log("getStatus of " + that.name + ": " + body);
callback(null,body);
}
}.bind(this));
},
getCurrentPosition: function(callback){
callback(100);
},
getPositionState: function(callback){
this.log("Fetching position state for: " + this.name);
callback(Characteristic.PositionState.STOPPED);
},
setTargetPosition: function(level, callback) {
var that = this;
this.log("Setting target position on the '"+this.name+"' to " + level);
this.updateStatus(level, function(noError){
if (noError) {
that.log("Successfully set position on the '"+that.name+"' to " + level);
callback();
} else {
callback(new Error('Can not communicate with OpenHAB.'));
}
}.bind(this));
},
setBrightness: function(level, callback) {
var that = this;
if (this.typeSupportsDim && level >= 0 && level <= 100) {
this.log("Setting brightness on the '"+this.name+"' to " + level);
this.updateStatus(level, function(noError){
if (noError) {
that.log("Successfully set brightness on the '"+that.name+"' to " + level);
callback();
} else {
callback(new Error('Can not communicate with OpenHAB.'));
}
}.bind(this));
}
},
getServices: function() {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "OpenHAB")
.setCharacteristic(Characteristic.Model, this.type)
.setCharacteristic(Characteristic.SerialNumber, "1234567890")
.setCharacteristic(Characteristic.Name, this.label);
var otherService = this.getServiceType();
if (this.typeSupportsOnOff) {
otherService
.getCharacteristic(Characteristic.On)
.on('get', this.getStatus.bind(this))
.on('set', this.setPowerState.bind(this));
}
if (this.typeSupportsDim) {
otherService
.addCharacteristic(Characteristic.Brightness)
.on('get', this.getStatus.bind(this))
.on('set', this.setBrightness.bind(this));
}
if (this.typeSupportsWindowCovering) {
var currentPosition = 100;
otherService
.getCharacteristic(Characteristic.CurrentPosition)
.on('get', this.getCurrentPosition.bind(this))
.setValue(currentPosition);
otherService
.getCharacteristic(Characteristic.PositionState)
.on('get', this.getPositionState.bind(this))
.setValue(Characteristic.PositionState.STOPPED);
otherService
.getCharacteristic(Characteristic.TargetPosition)
.on('get', this.getCurrentPosition.bind(this))
.on('set', this.setTargetPosition.bind(this));
}
console.log(informationService);
return [informationService, otherService];
}
}
module.exports.accessory = OpenhabAccessory;
module.exports.platform = OpenhabPlatform;

View File

@@ -1,375 +0,0 @@
// Philips Hue Platform Shim for HomeBridge
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// "platform": "PhilipsHue",
// "name": "Philips Hue",
// "ip_address": "127.0.0.1",
// "username": "252deadbeef0bf3f34c7ecb810e832f"
// }
// ],
//
// If you do not know the IP address of your Hue Bridge, simply leave it blank and your Bridge
// will be discovered automatically.
//
// If you do not have a "username" for your Hue API already, simply leave the field blank and
// you will be prompted to press the link button on your Hue Bridge before running HomeBridge.
// A username will be created for you and printed out, then the server will exit so you may
// enter it in your config.json.
//
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
//
/* jslint node: true */
/* globals require: false */
/* globals config: false */
"use strict";
var hue = require("node-hue-api"),
HueApi = hue.HueApi,
lightState = hue.lightState;
var types = require("hap-nodejs/accessories/types.js");
function PhilipsHuePlatform(log, config) {
this.log = log;
this.ip_address = config["ip_address"];
this.username = config["username"];
}
function PhilipsHueAccessory(log, device, api) {
this.id = device.id;
this.name = device.name;
this.model = device.modelid;
this.device = device;
this.api = api;
this.log = log;
}
// Get the ip address of the first available bridge with meethue.com or a network scan.
var locateBridge = function (callback) {
var that = this;
// Report the results of the scan to the user
var getIp = function (err, bridges) {
if (!bridges || bridges.length === 0) {
that.log("No Philips Hue bridges found.");
callback(err || new Error("No bridges found"));
return;
}
if (bridges.length > 1) {
that.log("Warning: Multiple Philips Hue bridges detected. The first bridge will be used automatically. To use a different bridge, set the `ip_address` manually in the configuration.");
}
that.log(
"Philips Hue bridges found:\n" +
(bridges.map(function (bridge) {
// Bridge name is only returned from meethue.com so use id instead if it isn't there
return "\t" + bridge.ipaddress + ' - ' + (bridge.name || bridge.id);
})).join("\n")
);
callback(null, bridges[0].ipaddress);
};
// Try to discover the bridge ip using meethue.com
that.log("Attempting to discover Philips Hue bridge with meethue.com...");
hue.nupnpSearch(function (locateError, bridges) {
if (locateError) {
that.log("Philips Hue bridge discovery with meethue.com failed. Register your bridge with the meethue.com for more reliable discovery.");
that.log("Attempting to discover Philips Hue bridge with network scan...");
// Timeout after one minute
hue.upnpSearch(60000)
.then(function (bridges) {
that.log("Scan complete");
getIp(null, bridges);
})
.fail(function (scanError) {
that.log("Philips Hue bridge discovery with network scan failed. Check your network connection or set ip_address manually in configuration.");
getIp(new Error("Scan failed: " + scanError.message));
}).done();
} else {
getIp(null, bridges);
}
});
};
PhilipsHuePlatform.prototype = {
accessories: function(callback) {
this.log("Fetching Philips Hue lights...");
var that = this;
var getLights = function () {
var api = new HueApi(that.ip_address, that.username);
// Connect to the API
// Get a dump of all lights, so as not to hit rate limiting for installations with larger amounts of bulbs
api.fullState(function(err, response) {
if (err) throw err;
var foundAccessories = [];
for (var deviceId in response.lights) {
var device = response.lights[deviceId];
device.id = deviceId;
var accessory = new PhilipsHueAccessory(that.log, device, api);
foundAccessories.push(accessory);
}
callback(foundAccessories);
});
};
// Create a new user if needed
function checkUsername() {
if (!that.username) {
var api = new HueApi(that.ip_address);
api.createUser(that.ip_address, null, null, function(err, user) {
// try and help explain this particular error
if (err && err.message == "link button not pressed")
throw "Please press the link button on your Philips Hue bridge, then start the HomeBridge server within 30 seconds.";
if (err) throw err;
throw "Created a new username " + JSON.stringify(user) + " for your Philips Hue. Please add it to your config.json then start the HomeBridge server again: ";
});
}
else {
getLights();
}
}
// Discover the bridge if needed
if (!this.ip_address) {
locateBridge.call(this, function (err, ip_address) {
if (err) throw err;
// TODO: Find a way to persist this
that.ip_address = ip_address;
that.log("Save the Philips Hue bridge ip address "+ ip_address +" to your config to skip discovery.");
checkUsername();
});
} else {
checkUsername();
}
}
};
PhilipsHueAccessory.prototype = {
// Convert 0-65535 to 0-360
hueToArcDegrees: function(value) {
value = value/65535;
value = value*360;
value = Math.round(value);
return value;
},
// Convert 0-360 to 0-65535
arcDegreesToHue: function(value) {
value = value/360;
value = value*65535;
value = Math.round(value);
return value;
},
// Convert 0-255 to 0-100
bitsToPercentage: function(value) {
value = value/255;
value = value*100;
value = Math.round(value);
return value;
},
// Create and set a light state
executeChange: function(api, device, characteristic, value) {
var that = this;
var state = lightState.create();
switch(characteristic.toLowerCase()) {
case 'identify':
state.alert('select');
break;
case 'power':
if (value) {
state.on();
}
else {
state.off();
}
break;
case 'hue':
state.hue(this.arcDegreesToHue(value));
break;
case 'brightness':
state.brightness(value);
break;
case 'saturation':
state.saturation(value);
break;
}
api.setLightState(device.id, state, function(err, lights) {
if (!err) {
that.log(device.name + ", characteristic: " + characteristic + ", value: " + value + ".");
}
else {
if (err.code == "ECONNRESET") {
setTimeout(function() {
that.executeChange(api, device, characteristic, value);
}, 300);
} else {
that.log(err);
}
}
});
},
// Get Services
getServices: function() {
var that = this;
var bulb_characteristics = [
{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) {
that.executeChange(that.api, that.device, "power", value);
},
perms: ["pw","pr","ev"],
format: "bool",
initialValue: that.device.state.on,
supportEvents: false,
supportBonjour: false,
manfDescription: "Turn On the Light",
designedMaxLength: 1
},{
cType: types.BRIGHTNESS_CTYPE,
onUpdate: function(value) {
that.executeChange(that.api, that.device, "brightness", value);
},
perms: ["pw","pr","ev"],
format: "int",
initialValue: that.bitsToPercentage(that.device.state.bri),
supportEvents: false,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
}
];
// Handle the Hue/Hue Lux divergence
if (that.device.state.hasOwnProperty('hue') && that.device.state.hasOwnProperty('sat')) {
bulb_characteristics.push({
cType: types.HUE_CTYPE,
onUpdate: function(value) {
that.executeChange(that.api, that.device, "hue", value);
},
perms: ["pw","pr","ev"],
format: "int",
initialValue: that.hueToArcDegrees(that.device.state.hue),
supportEvents: false,
supportBonjour: false,
manfDescription: "Adjust Hue of Light",
designedMinValue: 0,
designedMaxValue: 360,
designedMinStep: 1,
unit: "arcdegrees"
});
bulb_characteristics.push({
cType: types.SATURATION_CTYPE,
onUpdate: function(value) {
that.executeChange(that.api, that.device, "saturation", value);
},
perms: ["pw","pr","ev"],
format: "int",
initialValue: that.bitsToPercentage(that.device.state.sat),
supportEvents: false,
supportBonjour: false,
manfDescription: "Adjust Saturation of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
});
}
var accessory_data = [
{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [
{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Philips",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.model,
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.device.uniqueid,
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: function(value) {
that.executeChange(that.api, that.device, "identify", value);
},
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}
]
},{
sType: types.LIGHTBULB_STYPE,
// `bulb_characteristics` defined based on bulb type
characteristics: bulb_characteristics
}
];
return accessory_data;
}
};
module.exports.platform = PhilipsHuePlatform;

View File

@@ -1,242 +0,0 @@
// SmartThings JSON API SmartApp required
// https://github.com/jnewland/SmartThings/blob/master/JSON.groovy
//
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function SmartThingsPlatform(log, config){
this.log = log;
this.app_id = config["app_id"];
this.access_token = config["access_token"];
}
SmartThingsPlatform.prototype = {
accessories: function(callback) {
this.log("Fetching SmartThings devices...");
var that = this;
var foundAccessories = [];
request.get({
url: "https://graph.api.smartthings.com/api/smartapps/installations/"+this.app_id+"/devices?access_token="+this.access_token,
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
if (json['switches'] != undefined) {
json['switches'].map(function(s) {
accessory = new SmartThingsAccessory(that.log, s.name, s.commands);
foundAccessories.push(accessory);
})
}
if (json['hues'] != undefined) {
json['hues'].map(function(s) {
accessory = new SmartThingsAccessory(that.log, s.name, s.commands);
foundAccessories.push(accessory);
})
}
callback(foundAccessories);
} else {
that.log("There was a problem authenticating with SmartThings.");
}
});
}
}
function SmartThingsAccessory(log, name, commands) {
// device info
this.name = name;
this.commands = commands;
this.log = log;
}
SmartThingsAccessory.prototype = {
command: function(c,value) {
this.log(this.name + " sending command " + c);
var url = this.commands[c];
if (value != undefined) {
url = this.commands[c] + "&value="+value
}
var that = this;
request.put({
url: url
}, function(err, response) {
if (err) {
that.log("There was a problem sending command " + c + " to" + that.name);
that.log(url);
} else {
that.log(that.name + " sent command " + c);
}
})
},
informationCharacteristics: function() {
return [
{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "SmartThings",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-1",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}
]
},
controlCharacteristics: function(that) {
cTypes = [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: true,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
}]
if (this.commands['on'] != undefined) {
cTypes.push({
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) {
if (value == 0) {
that.command("off")
} else {
that.command("on")
}
},
perms: ["pw","pr","ev"],
format: "bool",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Change the power state",
designedMaxLength: 1
})
}
if (this.commands['on'] != undefined) {
cTypes.push({
cType: types.BRIGHTNESS_CTYPE,
onUpdate: function(value) { that.command("setLevel", value); },
perms: ["pw","pr","ev"],
format: "int",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
})
}
if (this.commands['setHue'] != undefined) {
cTypes.push({
cType: types.HUE_CTYPE,
onUpdate: function(value) { that.command("setHue", value); },
perms: ["pw","pr","ev"],
format: "int",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Adjust Hue of Light",
designedMinValue: 0,
designedMaxValue: 360,
designedMinStep: 1,
unit: "arcdegrees"
})
}
if (this.commands['setSaturation'] != undefined) {
cTypes.push({
cType: types.SATURATION_CTYPE,
onUpdate: function(value) { that.command("setSaturation", value); },
perms: ["pw","pr","ev"],
format: "int",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
})
}
return cTypes
},
sType: function() {
if (this.commands['setLevel'] != undefined) {
return types.LIGHTBULB_STYPE
} else {
return types.SWITCH_STYPE
}
},
getServices: function() {
var that = this;
var services = [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: this.informationCharacteristics(),
},
{
sType: this.sType(),
characteristics: this.controlCharacteristics(that)
}];
this.log("Loaded services for " + this.name)
return services;
}
};
module.exports.accessory = SmartThingsAccessory;
module.exports.platform = SmartThingsPlatform;

View File

@@ -1,188 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var sonos = require('sonos');
function SonosPlatform(log, config){
this.log = log;
this.config = config;
this.name = config["name"];
this.playVolume = config["play_volume"];
// timeout for device discovery
this.discoveryTimeout = (config.deviceDiscoveryTimeout || 10)*1000; // assume 10sec as a default
}
SonosPlatform.prototype = {
accessories: function(callback) {
this.log("Fetching Sonos devices.");
var that = this;
// track found devices so we don't add duplicates
var roomNamesFound = {};
// collector array for the devices from callbacks
var devicesFound = [];
// tell the sonos callbacks if timeout already occured
var timeout = false;
// the timeout event will push the accessories back
setTimeout(function(){
timeout=true;
callback(devicesFound);
}, this.discoveryTimeout);
sonos.search(function (device) {
that.log("Found device at " + device.host);
device.deviceDescription(function (err, description) {
if (description["zoneType"] != '11' && description["zoneType"] != '8' && description["zoneType"] != '4') { // 8 is the Sonos SUB, 4 is the Sonos Bridge
var roomName = description["roomName"];
if (!roomNamesFound[roomName]) {
roomNamesFound[roomName] = true;
that.log("Found playable device - " + roomName);
if (timeout) {
that.log("Ignored: Discovered after timeout (Set deviceDiscoveryTimeout parameter in Sonos section of config.json)");
}
// device is an instance of sonos.Sonos
var accessory = new SonosAccessory(that.log, that.config, device, description);
// add it to the collector array
devicesFound.push(accessory);
}
else {
that.log("Ignoring playable device with duplicate room name - " + roomName);
}
}
});
});
}
};
function SonosAccessory(log, config, device, description) {
this.log = log;
this.config = config;
this.device = device;
this.description = description;
this.name = this.description["roomName"] + " " + this.config["name"];
this.serviceName = this.description["roomName"] + " Speakers";
this.playVolume = this.config["play_volume"];
}
SonosAccessory.prototype = {
setPlaying: function(playing) {
if (!this.device) {
this.log("No device found (yet?)");
return;
}
var that = this;
if (playing) {
this.device.play(function(err, success) {
that.log("Playback attempt with success: " + success);
});
if (this.playVolume) {
this.device.setVolume(this.playVolume, function(err, success) {
if (!err) {
that.log("Set volume to " + that.playVolume);
}
else {
that.log("Problem setting volume: " + err);
}
});
}
}
else {
this.device.stop(function(err, success) {
that.log("Stop attempt with success: " + success);
});
}
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Sonos",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-1",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.SWITCH_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.serviceName,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.setPlaying(value); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the playback state of the sonos",
designedMaxLength: 1
}]
}];
}
};
module.exports.accessory = SonosAccessory;
module.exports.platform = SonosPlatform;

View File

@@ -1,265 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var telldus = require('telldus');
function TelldusPlatform(log, config) {
var that = this;
that.log = log;
}
TelldusPlatform.prototype = {
accessories: function(callback) {
var that = this;
that.log("Fetching devices...");
var devices = telldus.getDevicesSync();
that.log("Found " + devices.length + " devices...");
var foundAccessories = [];
// Clean non device
for (var i = 0; i < devices.length; i++) {
if (devices[i].type != 'DEVICE') {
devices.splice(i, 1);
}
}
for (var i = 0; i < devices.length; i++) {
if (devices[i].type === 'DEVICE') {
TelldusAccessory.create(that.log, devices[i], function(err, accessory) {
if (!!err) that.log("Couldn't load device info");
foundAccessories.push(accessory);
if (foundAccessories.length >= devices.length) {
callback(foundAccessories);
}
});
}
}
}
};
var TelldusAccessory = function TelldusAccessory(log, device) {
this.log = log;
var m = device.model.split(':');
this.dimTimeout = false;
// Set accessory info
this.device = device;
this.id = device.id;
this.name = device.name;
this.manufacturer = "Telldus"; // NOTE: Change this later
this.model = device.model;
this.status = device.status;
switch (device.status.name) {
case 'OFF':
this.state = 0;
this.stateValue = 0;
break;
case 'ON':
this.state = 2;
this.stateValue = 1;
break;
case 'DIM':
this.state = 16;
this.stateValue = device.status.level;
break;
}
};
TelldusAccessory.create = function (log, device, callback) {
callback(null, new TelldusAccessory(log, device));
};
TelldusAccessory.prototype = {
dimmerValue: function() {
if (this.state === 1) {
return 100;
}
if (this.state === 16 && this.stateValue != "unde") {
return parseInt(this.stateValue * 100 / 255);
}
return 0;
},
informationCharacteristics: function() {
var that = this;
informationCharacteristics = [
{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.manufacturer,
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.model,
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: function () {
telldus.turnOff(that.id, function(err){
if (!!err) that.log("Error: " + err.message);
telldus.turnOn(that.id, function(err){
if (!!err) that.log("Error: " + err.message);
telldus.turnOff(that.id, function(err){
if (!!err) that.log("Error: " + err.message);
telldus.turnOn(that.id, function(err){
if (!!err) that.log("Error: " + err.message);
});
});
});
});
},
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}
];
return informationCharacteristics;
},
controlCharacteristics: function() {
var that = this;
cTypes = [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.name,
supportEvents: true,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
}]
cTypes.push({
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) {
if (value) {
telldus.turnOn(that.id, function(err){
if (!!err) {
that.log("Error: " + err.message)
} else {
that.log(that.name + " - Updated power state: ON");
}
});
} else {
telldus.turnOff(that.id, function(err){
if (!!err) {
that.log("Error: " + err.message)
} else {
that.log(that.name + " - Updated power state: OFF");
}
});
}
},
perms: ["pw","pr","ev"],
format: "bool",
initialValue: (that.state != 2 && (that.state === 16 && that.stateValue != 0)) ? 1 : 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Change the power state",
designedMaxLength: 1
})
if (that.model === "selflearning-dimmer") {
cTypes.push({
cType: types.BRIGHTNESS_CTYPE,
onUpdate: function (value) {
if (that.dimTimeout) {
clearTimeout(that.dimTimeout);
}
that.dimTimeout = setTimeout(function(){
telldus.dim(that.id, (255 * (value / 100)), function(err, result){
if (!!err) {
that.log("Error: " + err.message);
} else {
that.log(that.name + " - Updated brightness: " + value);
}
});
that.dimTimeout = false;
}, 250);
},
perms: ["pw", "pr", "ev"],
format: "int",
initialValue: that.dimmerValue(),
supportEvents: true,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
})
}
return cTypes
},
getServices: function() {
var services = [
{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: this.informationCharacteristics()
},
{
sType: types.LIGHTBULB_STYPE,
characteristics: this.controlCharacteristics()
}
];
return services;
}
};
module.exports.platform = TelldusPlatform;
module.exports.accessory = TelldusAccessory;

View File

@@ -1,265 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var TellduAPI = require("telldus-live");
function TelldusLivePlatform(log, config) {
var that = this;
that.log = log;
that.isLoggedIn = false;
// Login to Telldus Live!
that.cloud = new TellduAPI.TelldusAPI({publicKey: config["public_key"], privateKey: config["private_key"]})
.login(config["token"], config["token_secret"], function(err, user) {
if (!!err) that.log("Login error: " + err.message);
that.log("User logged in: " + user.firstname + " " + user.lastname + ", " + user.email);
that.isLoggedIn = true;
}
);
}
TelldusLivePlatform.prototype = {
accessories: function(callback) {
var that = this;
that.log("Fetching devices...");
that.cloud.getDevices(function(err, devices) {
if (!!err) return that.log('getDevices: ' + err.message);
var foundAccessories = [];
// Clean non device
for (var i = 0; i < devices.length; i++) {
if (devices[i].type != 'device') {
devices.splice(i, 1);
}
}
for (var i = 0; i < devices.length; i++) {
if (devices[i].type === 'device') {
TelldusLiveAccessory.create(that.log, devices[i], that.cloud, function(err, accessory) {
if (!!err) that.log("Couldn't load device info");
foundAccessories.push(accessory);
if (foundAccessories.length >= devices.length) {
callback(foundAccessories);
}
});
}
}
});
}
};
var TelldusLiveAccessory = function TelldusLiveAccessory(log, cloud, device) {
this.log = log;
this.cloud = cloud;
var m = device.model ? device.model.split(':') : ['unknown', 'unknown'] ;
// Set accessory info
this.device = device;
this.id = device.id;
this.name = device.name;
this.manufacturer = m[1];
this.model = m[0];
this.state = device.state;
this.stateValue = device.stateValue;
this.status = device.status;
};
TelldusLiveAccessory.create = function (log, device, cloud, callback) {
cloud.getDeviceInfo(device, function(err, device) {
if (!!err) that.log("Couldn't load device info");
callback(err, new TelldusLiveAccessory(log, cloud, device));
});
};
TelldusLiveAccessory.prototype = {
dimmerValue: function() {
if (this.state === 1) {
return 100;
}
if (this.state === 16 && this.stateValue != "unde") {
return parseInt(this.stateValue * 100 / 255);
}
return 0;
},
informationCharacteristics: function() {
var that = this;
informationCharacteristics = [
{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.manufacturer,
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.model,
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: function () {
that.cloud.onOffDevice(that.device, true, function(err, result) {
if (!!err) that.log("Error: " + err.message);
that.cloud.onOffDevice(that.device, false, function(err, result) {
if (!!err) that.log("Error: " + err.message);
that.cloud.onOffDevice(that.device, true, function(err, result) {
if (!!err) that.log("Error: " + err.message);
that.cloud.onOffDevice(that.device, false, function(err, result) {
if (!!err) that.log("Error: " + err.message);
that.cloud.onOffDevice(that.device, true, function(err, result) {
if (!!err) that.log("Error: " + err.message);
})
})
})
})
})
},
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}
];
return informationCharacteristics;
},
controlCharacteristics: function() {
var that = this;
cTypes = [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: that.name,
supportEvents: true,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
}]
cTypes.push({
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) {
if (value == 1) {
that.cloud.onOffDevice(that.device, value, function(err, result) {
if (!!err) {
that.log("Error: " + err.message)
} else {
that.log(that.name + " - Updated power state: " + (value === true ? 'ON' : 'OFF'));
}
});
} else {
that.cloud.onOffDevice(that.device, value, function(err, result) {
if (!!err) {
that.log("Error: " + err.message)
} else {
that.log(that.name + " - Updated power state: " + (value === true ? 'ON' : 'OFF'));
}
});
}
},
perms: ["pw","pr","ev"],
format: "bool",
initialValue: (that.state != 2 && (that.state === 16 && that.stateValue != "0")) ? 1 : 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Change the power state",
designedMaxLength: 1
})
if (that.model === "selflearning-dimmer") {
cTypes.push({
cType: types.BRIGHTNESS_CTYPE,
onUpdate: function (value) {
that.cloud.dimDevice(that.device, (255 * (value / 100)), function (err, result) {
if (!!err) {
that.log("Error: " + err.message);
} else {
that.log(that.name + " - Updated brightness: " + value);
}
});
},
perms: ["pw", "pr", "ev"],
format: "int",
initialValue: that.dimmerValue(),
supportEvents: true,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
})
}
return cTypes
},
getServices: function() {
var services = [
{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: this.informationCharacteristics()
},
{
sType: types.LIGHTBULB_STYPE,
characteristics: this.controlCharacteristics()
}
];
return services;
}
};
module.exports.platform = TelldusLivePlatform;
module.exports.accessory = TelldusLiveAccessory;

View File

@@ -1,252 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var wink = require('wink-js');
var model = {
light_bulbs: require('wink-js/lib/model/light')
};
function WinkPlatform(log, config){
// auth info
this.client_id = config["client_id"];
this.client_secret = config["client_secret"];
this.username = config["username"];
this.password = config["password"];
this.log = log;
}
WinkPlatform.prototype = {
accessories: function(callback) {
this.log("Fetching Wink devices.");
var that = this;
var foundAccessories = [];
wink.init({
"client_id": this.client_id,
"client_secret": this.client_secret,
"username": this.username,
"password": this.password
}, function(auth_return) {
if ( auth_return === undefined ) {
that.log("There was a problem authenticating with Wink.");
} else {
// success
wink.user().devices('light_bulbs', function(devices) {
for (var i=0; i<devices.data.length; i++){
device = model.light_bulbs(devices.data[i], wink)
accessory = new WinkAccessory(that.log, device);
foundAccessories.push(accessory);
}
callback(foundAccessories);
});
}
});
}
}
function WinkAccessory(log, device) {
// device info
this.name = device.name;
this.device = device;
this.log = log;
}
WinkAccessory.prototype = {
getPowerState: function(callback){
if (!this.device) {
this.log("No '"+this.name+"' device found (yet?)");
return;
}
var that = this;
this.log("checking power state for: " + this.name);
wink.user().device(this.name, function(light_obj){
powerState = light_obj.desired_state.powered
that.log("power state for " + that.name + " is: " + powerState)
callback(powerState);
});
},
getBrightness: function(callback){
if (!this.device) {
this.log("No '"+this.name+"' device found (yet?)");
return;
}
var that = this;
this.log("checking brightness level for: " + this.name);
wink.user().device(this.name, function(light_obj){
level = light_obj.desired_state.brightness * 100
that.log("brightness level for " + that.name + " is: " + level)
callback(level);
});
},
setPowerState: function(powerOn) {
if (!this.device) {
this.log("No '"+this.name+"' device found (yet?)");
return;
}
var that = this;
if (powerOn) {
this.log("Setting power state on the '"+this.name+"' to on");
this.device.power.on(function(response) {
if (response === undefined) {
that.log("Error setting power state on the '"+that.name+"'")
} else {
that.log("Successfully set power state on the '"+that.name+"' to on");
}
});
}else{
this.log("Setting power state on the '"+this.name+"' to off");
this.device.power.off(function(response) {
if (response === undefined) {
that.log("Error setting power state on the '"+that.name+"'")
} else {
that.log("Successfully set power state on the '"+that.name+"' to off");
}
});
}
},
setBrightness: function(level) {
if (!this.device) {
this.log("No '"+this.name+"' device found (yet?)");
return;
}
var that = this;
this.log("Setting brightness on the '"+this.name+"' to " + level);
this.device.brightness(level, function(response) {
if (response === undefined) {
that.log("Error setting brightness on the '"+that.name+"'")
} else {
that.log("Successfully set brightness on the '"+that.name+"' to " + level);
}
});
},
getServices: function() {
var that = this;
return [{
sType: types.ACCESSORY_INFORMATION_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of the accessory",
designedMaxLength: 255
},{
cType: types.MANUFACTURER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Wink",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-1",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "A1S2NASF88EW",
supportEvents: false,
supportBonjour: false,
manfDescription: "SN",
designedMaxLength: 255
},{
cType: types.IDENTIFY_CTYPE,
onUpdate: null,
perms: ["pw"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Identify Accessory",
designedMaxLength: 1
}]
},{
sType: types.LIGHTBULB_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.name,
supportEvents: true,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) {
that.setPowerState(value);
},
onRead: function(callback) {
that.getPowerState(function(powerState){
callback(powerState);
});
},
perms: ["pw","pr","ev"],
format: "bool",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Change the power state of the Bulb",
designedMaxLength: 1
},{
cType: types.BRIGHTNESS_CTYPE,
onUpdate: function(value) {
that.setBrightness(value);
},
onRead: function(callback) {
that.getBrightness(function(level){
callback(level);
});
},
perms: ["pw","pr","ev"],
format: "int",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Adjust Brightness of Light",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
}]
}];
}
};
module.exports.accessory = WinkAccessory;
module.exports.platform = WinkPlatform;

View File

@@ -1,238 +0,0 @@
var types = require("hap-nodejs/accessories/types.js");
var inherits = require('util').inherits;
var debug = require('debug')('YamahaAVR');
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var Yamaha = require('yamaha-nodejs');
var Q = require('q');
var mdns = require('mdns');
//workaround for raspberry pi
var sequence = [
mdns.rst.DNSServiceResolve(),
'DNSServiceGetAddrInfo' in mdns.dns_sd ? mdns.rst.DNSServiceGetAddrInfo() : mdns.rst.getaddrinfo({families:[4]}),
mdns.rst.makeAddressesUnique()
];
function YamahaAVRPlatform(log, config){
this.log = log;
this.config = config;
this.playVolume = config["play_volume"];
this.minVolume = config["min_volume"] || -50.0;
this.maxVolume = config["max_volume"] || -20.0;
this.gapVolume = this.maxVolume - this.minVolume;
this.setMainInputTo = config["setMainInputTo"];
this.expectedDevices = config["expected_devices"] || 100;
this.discoveryTimeout = config["discovery_timeout"] || 30;
this.manualAddresses = config["manual_addresses"] || {};
this.browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence});
}
// Custom Characteristics and service...
YamahaAVRPlatform.AudioVolume = function() {
Characteristic.call(this, 'Audio Volume', '00001001-0000-1000-8000-135D67EC4377');
this.setProps({
format: Characteristic.Formats.UINT8,
unit: Characteristic.Units.PERCENTAGE,
maxValue: 100,
minValue: 0,
minStep: 1,
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY]
});
this.value = this.getDefaultValue();
};
inherits(YamahaAVRPlatform.AudioVolume, Characteristic);
YamahaAVRPlatform.Muting = function() {
Characteristic.call(this, 'Muting', '00001002-0000-1000-8000-135D67EC4377');
this.setProps({
format: Characteristic.Formats.UINT8,
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY]
});
this.value = this.getDefaultValue();
};
inherits(YamahaAVRPlatform.Muting, Characteristic);
YamahaAVRPlatform.AudioDeviceService = function(displayName, subtype) {
Service.call(this, displayName, '00000001-0000-1000-8000-135D67EC4377', subtype);
// Required Characteristics
this.addCharacteristic(YamahaAVRPlatform.AudioVolume);
// Optional Characteristics
this.addOptionalCharacteristic(YamahaAVRPlatform.Muting);
};
inherits(YamahaAVRPlatform.AudioDeviceService, Service);
YamahaAVRPlatform.prototype = {
accessories: function(callback) {
this.log("Getting Yamaha AVR devices.");
var that = this;
var browser = this.browser;
browser.stop();
browser.removeAllListeners('serviceUp'); // cleanup listeners
var accessories = [];
var timer, timeElapsed = 0, checkCyclePeriod = 5000;
// Hmm... seems we need to prevent double-listing via manual and Bonjour...
var sysIds = {};
var setupFromService = function(service){
var name = service.name;
//console.log('Found HTTP service "' + name + '"');
// We can't tell just from mdns if this is an AVR...
if (service.port != 80) return; // yamaha-nodejs assumes this, so finding one on another port wouldn't do any good anyway.
var yamaha = new Yamaha(service.host);
yamaha.getSystemConfig().then(
function(sysConfig){
var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0];
var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0];
if(sysIds[sysId]){
this.log("WARN: Got multiple systems with ID " + sysId + "! Omitting duplicate!");
return;
}
sysIds[sysId] = true;
this.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\"");
var accessory = new YamahaAVRAccessory(this.log, this.config, name, yamaha, sysConfig);
accessories.push(accessory);
if(accessories.length >= this.expectedDevices)
timeoutFunction(); // We're done, call the timeout function now.
}.bind(this)
);
}.bind(this);
// process manually specified devices...
for(var key in this.manualAddresses){
if(!this.manualAddresses.hasOwnProperty(key)) continue;
setupFromService({
name: key,
host: this.manualAddresses[key],
port: 80
});
}
browser.on('serviceUp', setupFromService);
browser.start();
// The callback can only be called once...so we'll have to find as many as we can
// in a fixed time and then call them in.
var timeoutFunction = function(){
if(accessories.length >= that.expectedDevices){
clearTimeout(timer);
} else {
timeElapsed += checkCyclePeriod;
if(timeElapsed > that.discoveryTimeout * 1000){
that.log("Waited " + that.discoveryTimeout + " seconds, stopping discovery.");
} else {
timer = setTimeout(timeoutFunction, checkCyclePeriod);
return;
}
}
browser.stop();
browser.removeAllListeners('serviceUp');
that.log("Discovery finished, found " + accessories.length + " Yamaha AVR devices.");
callback(accessories);
};
timer = setTimeout(timeoutFunction, checkCyclePeriod);
}
};
function YamahaAVRAccessory(log, config, name, yamaha, sysConfig) {
this.log = log;
this.config = config;
this.yamaha = yamaha;
this.sysConfig = sysConfig;
this.nameSuffix = config["name_suffix"] || " Speakers";
this.name = name;
this.serviceName = name + this.nameSuffix;
this.setMainInputTo = config["setMainInputTo"];
this.playVolume = this.config["play_volume"];
this.minVolume = config["min_volume"] || -50.0;
this.maxVolume = config["max_volume"] || -20.0;
this.gapVolume = this.maxVolume - this.minVolume;
}
YamahaAVRAccessory.prototype = {
setPlaying: function(playing) {
var that = this;
var yamaha = this.yamaha;
if (playing) {
return yamaha.powerOn().then(function(){
if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10);
else return Q();
}).then(function(){
if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo);
else return Q();
}).then(function(){
if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver(
'<YAMAHA_AV cmd="PUT"><AirPlay><Play_Control><Playback>Play</Playback></Play_Control></AirPlay></YAMAHA_AV>'
);
else return Q();
});
}
else {
return yamaha.powerOff();
}
},
getServices: function() {
var that = this;
var informationService = new Service.AccessoryInformation();
var yamaha = this.yamaha;
informationService
.setCharacteristic(Characteristic.Name, this.name)
.setCharacteristic(Characteristic.Manufacturer, "Yamaha")
.setCharacteristic(Characteristic.Model, this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0])
.setCharacteristic(Characteristic.SerialNumber, this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]);
var switchService = new Service.Switch("Power State");
switchService.getCharacteristic(Characteristic.On)
.on('get', function(callback, context){
yamaha.isOn().then(function(result){
callback(false, result);
}.bind(this));
}.bind(this))
.on('set', function(powerOn, callback){
this.setPlaying(powerOn).then(function(){
callback(false, powerOn);
}, function(error){
callback(error, !powerOn); //TODO: Actually determine and send real new status.
});
}.bind(this));
var audioDeviceService = new YamahaAVRPlatform.AudioDeviceService("Audio Functions");
audioDeviceService.getCharacteristic(YamahaAVRPlatform.AudioVolume)
.on('get', function(callback, context){
yamaha.getBasicInfo().done(function(basicInfo){
var v = basicInfo.getVolume()/10.0;
var p = 100 * ((v - that.minVolume) / that.gapVolume);
p = p < 0 ? 0 : p > 100 ? 100 : Math.round(p);
debug("Got volume percent of " + p + "%");
callback(false, p);
});
})
.on('set', function(p, callback){
var v = ((p / 100) * that.gapVolume) + that.minVolume;
v = Math.round(v*10.0);
debug("Setting volume to " + v);
yamaha.setVolumeTo(v).then(function(){
callback(false, p);
});
})
.getValue(null, null); // force an asynchronous get
return [informationService, switchService, audioDeviceService];
}
};
module.exports.accessory = YamahaAVRAccessory;
module.exports.platform = YamahaAVRPlatform;

File diff suppressed because it is too large Load Diff

View File

@@ -1,763 +0,0 @@
/*
ISY-JS
ISY-99 REST / WebSockets based HomeBridge shim.
Supports the following Insteon devices: Lights (dimmable and non-dimmable), Fans, Outlets, Door/Window Sensors, MorningLinc locks, Inline Lincs and I/O Lincs.
Also supports ZWave based locks. If elkEnabled is set to true then this will also expose your Elk Alarm Panel and all of your Elk Sensors.
Turns out that HomeBridge platforms can only return a maximum of 100 devices. So if you end up exposing more then 100 devices through HomeBridge the HomeKit
software will fail adding the HomeBridge to your HomeKit network. To address this issue this platform provides an option to screen out devices based on
criteria specified in the config.
Configuration sample:
"platforms": [
{
"platform": "isy-js",
"name": "isy-js",
"host": "10.0.1.12",
"username": "admin",
"password": "password",
"elkEnabled": true,
"ignoreDevices": [
{ "nameContains": "ApplianceLinc", "lastAddressDigit": "", "address": ""},
{ "nameContains": "Bedroom.Side Gate", "lastAddressDigit": "", "address": ""},
{ "nameContains": "Remote", "lastAddressDigit": "", "address": "" },
{ "nameContains": "Keypad", "lastAddressDigit": "2", "address": "" },
]
}
]
Fields:
"platform" - Must be set to isy-js
"name" - Can be set to whatever you want
"host" - IP address of the ISY
"username" - Your ISY username
"password" - Your ISY password
"elkEnabled" - true if there is an elk alarm panel connected to your ISY
"ignoreDevices" - Array of objects specifying criteria for screening out devices from the network. nameContains is the only required criteria. If the other criteria
are blank all devices will match those criteria (providing they match the name criteria).
"nameContains" - Specifies a substring to check against the names of the ISY devices. Required field for the criteria.
"lastAddressDigit" - Specifies a single digit in the ISY address of a device which should be used to match the device. Example use of this is for composite
devices like keypads so you can screen out the non-main buttons.
"address" - ISY address to match.
Examples:
{ "nameContains": "Keypad", "lastAddressDigit": "2", "address": "" } - Ignore all devices which have the word Keypad in their name and whose last address digit is 2.
{ "nameContains": "Remote", "lastAddressDigit": "", "address": "" } - Ignore all devices which have the word Remote in their name
{ "nameContains": "", "lastAddressDigit": "", "address": "15 5 3 2"} - Ignore the device with an ISY address of 15 5 3 2.
TODOS: Implement identify functions (beep perhaps?) and more device types.
*/
var types = require("hap-nodejs/accessories/types.js");
var isy = require('isy-js');
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var inherits = require('util').inherits;
// Global device map. Needed to map incoming notifications to the corresponding HomeKit device for update.
var deviceMap = {};
// This function responds to changes in devices from the isy-js library. Uses the global device map to update
// the state.
// TODO: Move this to a member function of the ISYPlatform object so we don't need a global map.
function ISYChangeHandler(isy,device) {
var deviceToUpdate = deviceMap[device.address];
if(deviceToUpdate != null) {
deviceToUpdate.handleExternalChange();
}
}
// Helper function to have ISYJSDEBUG control if debug output appears
function ISYJSDebugMessage(isy,message) {
if(process.env.ISYJSDEBUG != undefined) {
isy.log(message);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// PLATFORM
// Construct the ISY platform. log = Logger, config = homebridge cofnig
function ISYPlatform(log,config) {
this.log = log;
this.config = config;
this.host = config.host;
this.username = config.username;
this.password = config.password;
this.elkEnabled = config.elkEnabled;
this.isy = new isy.ISY(this.host, this.username,this.password, config.elkEnabled, ISYChangeHandler);
}
// Checks the device against the configuration to see if it should be ignored.
ISYPlatform.prototype.shouldIgnore = function(device) {
var deviceAddress = device.address;
var deviceName = device.name;
for(var index = 0; index < this.config.ignoreDevices.length; index++) {
var rule = this.config.ignoreDevices[index];
if(rule.nameContains != "") {
if(deviceName.indexOf(rule.nameContains) == -1) {
continue;
}
}
if(rule.lastAddressDigit != "") {
if(deviceAddress.indexOf(rule.lastAddressDigit,deviceAddress.length-2) == -1) {
continue;
}
}
if(rule.address != "") {
if(deviceAddress != rule.address) {
continue;
}
}
ISYJSDebugMessage(this,"Ignoring device: "+deviceName+" ["+deviceAddress+"] because of rule ["+rule.nameContains+"] ["+rule.lastAddressDigit+"] ["+rule.address+"]");
return true;
}
return false;
}
// Calls the isy-js library, retrieves the list of devices, and maps them to appropriate ISYXXXXAccessory devices.
ISYPlatform.prototype.accessories = function(callback) {
var that = this;
this.isy.initialize(function() {
var results = [];
var deviceList = that.isy.getDeviceList();
for(var index = 0; index < deviceList.length; index++) {
var device = deviceList[index];
var homeKitDevice = null;
if(!that.shouldIgnore(device)) {
if(device.deviceType == that.isy.DEVICE_TYPE_LIGHT || device.deviceType == that.isy.DEVICE_TYPE_DIMMABLE_LIGHT) {
homeKitDevice = new ISYLightAccessory(that.log,device);
} else if(device.deviceType == that.isy.DEVICE_TYPE_LOCK || device.deviceType == that.isy.DEVICE_TYPE_SECURE_LOCK) {
homeKitDevice = new ISYLockAccessory(that.log,device);
} else if(device.deviceType == that.isy.DEVICE_TYPE_OUTLET) {
homeKitDevice = new ISYOutletAccessory(that.log,device);
} else if(device.deviceType == that.isy.DEVICE_TYPE_FAN) {
homeKitDevice = new ISYFanAccessory(that.log,device);
} else if(device.deviceType == that.isy.DEVICE_TYPE_DOOR_WINDOW_SENSOR) {
homeKitDevice = new ISYDoorWindowSensorAccessory(that.log,device);
} else if(device.deviceType == that.isy.DEVICE_TYPE_ALARM_DOOR_WINDOW_SENSOR) {
homeKitDevice = new ISYDoorWindowSensorAccessory(that.log,device);
} else if(device.deviceType == that.isy.DEVICE_TYPE_ALARM_PANEL) {
homeKitDevice = new ISYElkAlarmPanelAccessory(that.log,device);
}
if(homeKitDevice != null) {
// Make sure the device is address to the global map
deviceMap[device.address] = homeKitDevice;
results.push(homeKitDevice);
}
}
}
if(that.isy.elkEnabled) {
var panelDevice = that.isy.getElkAlarmPanel();
var panelDeviceHK = new ISYElkAlarmPanelAccessory(that.log,panelDevice);
deviceMap[panelDevice.address] = panelDeviceHK;
results.push(panelDeviceHK);
}
ISYJSDebugMessage(that,"Filtered device has: "+results.length+" devices");
callback(results);
});
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// BASE FOR ALL DEVICES
// Provides common constructor tasks
function ISYAccessoryBaseSetup(accessory,log,device) {
accessory.log = log;
accessory.device = device;
accessory.address = device.address;
accessory.name = device.name;
accessory.uuid_base = device.isy.address+":"+device.address;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// FANS - ISYFanAccessory
// Implemetnts the fan service for an isy fan device.
// Constructs a fan accessory object. device is the isy-js device object and log is the logger.
function ISYFanAccessory(log,device) {
ISYAccessoryBaseSetup(this,log,device);
}
ISYFanAccessory.prototype.identify = function(callback) {
// Do the identify action
callback();
}
// Translates the fan speed as an isy-js string into the corresponding homekit constant level.
// Homekit doesn't have steps for the fan speed and needs to have a value from 0 to 100. We
// split the range into 4 steps and map them to the 4 isy-js levels.
ISYFanAccessory.prototype.translateFanSpeedToHK = function(fanSpeed) {
if(fanSpeed == this.device.FAN_LEVEL_OFF) {
return 0;
} else if(fanSpeed == this.device.FAN_LEVEL_LOW) {
return 32;
} else if(fanSpeed == this.device.FAN_LEVEL_MEDIUM) {
return 67;
} else if(fanSpeed == this.device.FAN_LEVEL_HIGH) {
return 100;
} else {
ISYJSDebugMessage(this,"!!!! ERROR: Unknown fan speed: "+fanSpeed);
return 0;
}
}
// Translates the fan level from homebridge into the isy-js level. Maps from the 0-100
// to the four isy-js fan speed levels.
ISYFanAccessory.prototype.translateHKToFanSpeed = function(fanStateHK) {
if(fanStateHK == 0) {
return this.device.FAN_LEVEL_OFF;
} else if(fanStateHK > 0 && fanStateHK <=32) {
return this.device.FAN_LEVEL_LOW;
} else if(fanStateHK >= 33 && fanStateHK <= 67) {
return this.device.FAN_LEVEL_MEDIUM;
} else if(fanStateHK > 67) {
return this.device.FAN_LEVEL_HIGH;
} else {
ISYJSDebugMessage(this,"ERROR: Unknown fan state!");
return this.device.FAN_LEVEL_OFF;
}
}
// Returns the current state of the fan from the isy-js level to the 0-100 level of HK.
ISYFanAccessory.prototype.getFanRotationSpeed = function(callback) {
callback(null,this.translateFanSpeedToHK(this.device.getCurrentFanState()));
}
// Sets the current state of the fan from the 0-100 level of HK to the isy-js level.
ISYFanAccessory.prototype.setFanRotationSpeed = function(fanStateHK,callback) {
var newFanState = this.translateHKToFanSpeed(fanStateHK);
ISYJSDebugMessage(this,"Sending command to set fan state to: "+newFanState);
if(newFanState != this.device.getCurrentFanState()) {
this.device.sendFanCommand(newFanState, function(result) {
callback();
});
} else {
ISYJSDebugMessage(this,"Fan command does not change actual speed");
callback();
}
}
// Returns true if the fan is on
ISYFanAccessory.prototype.getIsFanOn = function() {
return (this.device.getCurrentFanState() != "Off");
}
// Returns the state of the fan to the homebridge system for the On characteristic
ISYFanAccessory.prototype.getFanOnState = function(callback) {
callback(null,this.getIsFanOn());
}
// Sets the fan state based on the value of the On characteristic. Default to Medium for on.
ISYFanAccessory.prototype.setFanOnState = function(onState,callback) {
if(onState != this.getIsFanOn()) {
if(onState) {
this.setFanRotationSpeed(this.translateFanSpeedToHK(this.device.FAN_LEVEL_MEDIUM), callback);
} else {
this.setFanRotationSpeed(this.translateFanSpeedToHK(this.device.FAN_LEVEL_OFF), callback);
}
} else {
ISYJSDebugMessage(this,"Fan command does not change actual state");
callback();
}
}
// Mirrors change in the state of the underlying isj-js device object.
ISYFanAccessory.prototype.handleExternalChange = function() {
this.fanService
.setCharacteristic(Characteristic.On, this.getIsFanOn());
this.fanService
.setCharacteristic(Characteristic.RotationSpeed, this.translateFanSpeedToHK(this.device.getCurrentFanState()));
}
// Returns the services supported by the fan device.
ISYFanAccessory.prototype.getServices = function() {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "SmartHome")
.setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName)
.setCharacteristic(Characteristic.SerialNumber, this.device.address);
var fanService = new Service.Fan();
this.fanService = fanService;
this.informationService = informationService;
fanService
.getCharacteristic(Characteristic.On)
.on('set', this.setFanOnState.bind(this));
fanService
.getCharacteristic(Characteristic.On)
.on('get', this.getFanOnState.bind(this));
fanService
.addCharacteristic(new Characteristic.RotationSpeed())
.on('get', this.getFanRotationSpeed.bind(this));
fanService
.getCharacteristic(Characteristic.RotationSpeed)
.on('set', this.setFanRotationSpeed.bind(this));
return [informationService, fanService];
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// OUTLETS - ISYOutletAccessory
// Implements the Outlet service for ISY devices.
// Constructs an outlet. log = HomeBridge logger, device = isy-js device to wrap
function ISYOutletAccessory(log,device) {
ISYAccessoryBaseSetup(this,log,device);
}
// Handles the identify command
ISYOutletAccessory.prototype.identify = function(callback) {
// Do the identify action
callback();
}
// Handles a request to set the outlet state. Ignores redundant sets based on current states.
ISYOutletAccessory.prototype.setOutletState = function(outletState,callback) {
ISYJSDebugMessage(this,"Sending command to set outlet state to: "+outletState);
if(outletState != this.device.getCurrentOutletState()) {
this.device.sendOutletCommand(outletState, function(result) {
callback();
});
} else {
callback();
}
}
// Handles a request to get the current outlet state based on underlying isy-js device object.
ISYOutletAccessory.prototype.getOutletState = function(callback) {
callback(null,this.device.getCurrentOutletState());
}
// Handles a request to get the current in use state of the outlet. We set this to true always as
// there is no way to deterine this through the isy.
ISYOutletAccessory.prototype.getOutletInUseState = function(callback) {
callback(null, true);
}
// Mirrors change in the state of the underlying isj-js device object.
ISYOutletAccessory.prototype.handleExternalChange = function() {
this.outletService
.setCharacteristic(Characteristic.On, this.device.getCurrentOutletState());
}
// Returns the set of services supported by this object.
ISYOutletAccessory.prototype.getServices = function() {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "SmartHome")
.setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName)
.setCharacteristic(Characteristic.SerialNumber, this.device.address);
var outletService = new Service.Outlet();
this.outletService = outletService;
this.informationService = informationService;
outletService
.getCharacteristic(Characteristic.On)
.on('set', this.setOutletState.bind(this));
outletService
.getCharacteristic(Characteristic.On)
.on('get', this.getOutletState.bind(this));
outletService
.getCharacteristic(Characteristic.OutletInUse)
.on('get', this.getOutletInUseState.bind(this));
return [informationService, outletService];
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// LOCKS - ISYLockAccessory
// Implements the lock service for isy-js devices.
// Constructs a lock accessory. log = homebridge logger, device = isy-js device object being wrapped
function ISYLockAccessory(log,device) {
ISYAccessoryBaseSetup(this,log,device);
}
// Handles an identify request
ISYLockAccessory.prototype.identify = function(callback) {
callback();
}
// Handles a set to the target lock state. Will ignore redundant commands.
ISYLockAccessory.prototype.setTargetLockState = function(lockState,callback) {
ISYJSDebugMessage(this,"Sending command to set lock state to: "+lockState);
if(lockState != this.getDeviceCurrentStateAsHK()) {
var targetLockValue = (lockState == 0) ? false : true;
this.device.sendLockCommand(targetLockValue, function(result) {
callback();
});
} else {
callback();
}
}
// Translates underlying lock state into the corresponding homekit state
ISYLockAccessory.prototype.getDeviceCurrentStateAsHK = function() {
return (this.device.getCurrentLockState() ? 1 : 0);
}
// Handles request to get the current lock state for homekit
ISYLockAccessory.prototype.getLockCurrentState = function(callback) {
callback(null, this.getDeviceCurrentStateAsHK());
}
// Handles request to get the target lock state for homekit
ISYLockAccessory.prototype.getTargetLockState = function(callback) {
this.getLockCurrentState(callback);
}
// Mirrors change in the state of the underlying isj-js device object.
ISYLockAccessory.prototype.handleExternalChange = function() {
this.lockService
.setCharacteristic(Characteristic.LockTargetState, this.getDeviceCurrentStateAsHK());
this.lockService
.setCharacteristic(Characteristic.LockCurrentState, this.getDeviceCurrentStateAsHK());
}
// Returns the set of services supported by this object.
ISYLockAccessory.prototype.getServices = function() {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "SmartHome")
.setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName)
.setCharacteristic(Characteristic.SerialNumber, this.device.address);
var lockMechanismService = new Service.LockMechanism();
this.lockService = lockMechanismService;
this.informationService = informationService;
lockMechanismService
.getCharacteristic(Characteristic.LockTargetState)
.on('set', this.setTargetLockState.bind(this));
lockMechanismService
.getCharacteristic(Characteristic.LockTargetState)
.on('get', this.getTargetLockState.bind(this));
lockMechanismService
.getCharacteristic(Characteristic.LockCurrentState)
.on('get', this.getLockCurrentState.bind(this));
return [informationService, lockMechanismService];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
// LIGHTS
// Implements the Light service for homekit based on an underlying isy-js device. Is dimmable or not depending
// on if the underlying device is dimmable.
// Constructs the light accessory. log = homebridge logger, device = isy-js device object being wrapped
function ISYLightAccessory(log,device) {
ISYAccessoryBaseSetup(this,log,device);
this.dimmable = (this.device.deviceType == "DimmableLight");
}
// Handles the identify command
ISYLightAccessory.prototype.identify = function(callback) {
this.device.sendLightCommand(true, function(result) {
this.device.sendLightCommand(false, function(result) {
callback();
});
});
}
// Handles request to set the current powerstate from homekit. Will ignore redundant commands.
ISYLightAccessory.prototype.setPowerState = function(powerOn,callback) {
ISYJSDebugMessage(this,"Setting powerstate to %s", powerOn);
if(powerOn != this.device.getCurrentLightState()) {
ISYJSDebugMessage(this,"Changing powerstate to "+powerOn);
this.device.sendLightCommand(powerOn, function(result) {
callback();
});
} else {
ISYJSDebugMessage(this,"Ignoring redundant setPowerState");
callback();
}
}
// Mirrors change in the state of the underlying isj-js device object.
ISYLightAccessory.prototype.handleExternalChange = function() {
ISYJSDebugMessage(this,"Handling external change for light");
this.lightService
.setCharacteristic(Characteristic.On, this.device.getCurrentLightState());
if(this.device.deviceType == this.device.isy.DEVICE_TYPE_DIMMABLE_LIGHT) {
this.lightService
.setCharacteristic(Characteristic.Brightness, this.device.getCurrentLightDimState() );
}
}
// Handles request to get the current on state
ISYLightAccessory.prototype.getPowerState = function(callback) {
callback(null,this.device.getCurrentLightState());
}
// Handles request to set the brightness level of dimmable lights. Ignore redundant commands.
ISYLightAccessory.prototype.setBrightness = function(level,callback) {
ISYJSDebugMessage(this,"Setting brightness to %s", level);
if(level != this.device.getCurrentLightDimState()) {
ISYJSDebugMessage(this,"Changing Brightness to "+level);
this.device.sendLightDimCommand(level, function(result) {
callback();
});
} else {
ISYJSDebugMessage(this,"Ignoring redundant setBrightness");
callback();
}
}
// Handles a request to get the current brightness level for dimmable lights.
ISYLightAccessory.prototype.getBrightness = function(callback) {
callback(null,this.device.getCurrentLightDimState());
}
// Returns the set of services supported by this object.
ISYLightAccessory.prototype.getServices = function() {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "SmartHome")
.setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName)
.setCharacteristic(Characteristic.SerialNumber, this.device.address);
var lightBulbService = new Service.Lightbulb();
this.informationService = informationService;
this.lightService = lightBulbService;
lightBulbService
.getCharacteristic(Characteristic.On)
.on('set', this.setPowerState.bind(this));
lightBulbService
.getCharacteristic(Characteristic.On)
.on('get', this.getPowerState.bind(this));
if(this.dimmable) {
lightBulbService
.addCharacteristic(new Characteristic.Brightness())
.on('get', this.getBrightness.bind(this));
lightBulbService
.getCharacteristic(Characteristic.Brightness)
.on('set', this.setBrightness.bind(this));
}
return [informationService, lightBulbService];
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// CONTACT SENSOR - ISYDoorWindowSensorAccessory
// Implements the ContactSensor service.
// Constructs a Door Window Sensor (contact sensor) accessory. log = HomeBridge logger, device = wrapped isy-js device.
function ISYDoorWindowSensorAccessory(log,device) {
ISYAccessoryBaseSetup(this,log,device);
this.doorWindowState = false;
}
// Handles the identify command.
ISYDoorWindowSensorAccessory.prototype.identify = function(callback) {
// Do the identify action
callback();
}
// Translates the state of the underlying device object into the corresponding homekit compatible state
ISYDoorWindowSensorAccessory.prototype.translateCurrentDoorWindowState = function() {
return (this.device.getCurrentDoorWindowState()) ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED;
}
// Handles the request to get he current door window state.
ISYDoorWindowSensorAccessory.prototype.getCurrentDoorWindowState = function(callback) {
callback(null,this.translateCurrentDoorWindowState());
}
// Mirrors change in the state of the underlying isj-js device object.
ISYDoorWindowSensorAccessory.prototype.handleExternalChange = function() {
this.sensorService
.setCharacteristic(Characteristic.ContactSensorState, this.translateCurrentDoorWindowState());
}
// Returns the set of services supported by this object.
ISYDoorWindowSensorAccessory.prototype.getServices = function() {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "SmartHome")
.setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName)
.setCharacteristic(Characteristic.SerialNumber, this.device.address);
var sensorService = new Service.ContactSensor();
this.sensorService = sensorService;
this.informationService = informationService;
sensorService
.getCharacteristic(Characteristic.ContactSensorState)
.on('get', this.getCurrentDoorWindowState.bind(this));
return [informationService, sensorService];
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// ELK SENSOR PANEL - ISYElkAlarmPanelAccessory
// Implements the SecuritySystem service for an elk security panel connected to the isy system
// Constructs the alarm panel accessory. log = HomeBridge logger, device = underlying isy-js device being wrapped
function ISYElkAlarmPanelAccessory(log,device) {
ISYAccessoryBaseSetup(this,log,device);
}
// Handles the identify command
ISYElkAlarmPanelAccessory.prototype.identify = function(callback) {
callback();
}
// Handles the request to set the alarm target state
ISYElkAlarmPanelAccessory.prototype.setAlarmTargetState = function(targetStateHK,callback) {
ISYJSDebugMessage(this,"Sending command to set alarm panel state to: "+targetStateHK);
var targetState = this.translateHKToAlarmTargetState(targetStateHK);
ISYJSDebugMessage(this,"Would send the target state of: "+targetState);
if(this.device.getAlarmMode() != targetState) {
this.device.sendSetAlarmModeCommand(targetState, function(result) {
callback();
});
} else {
ISYJSDebugMessage(this,"Redundant command, already in that state.");
callback();
}
}
// Translates from the current state of the elk alarm system into a homekit compatible state. The elk panel has a lot more
// possible states then can be directly represented by homekit so we map them. If the alarm is going off then it is tripped.
// If it is arming or armed it is considered armed. Stay maps to the state state, away to the away state, night to the night
// state.
ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() {
var tripState = this.device.getAlarmTripState();
var sourceAlarmState = this.device.getAlarmState();
var sourceAlarmMode = this.device.getAlarmMode();
if(tripState >= this.device.ALARM_TRIP_STATE_TRIPPED) {
return Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED;
} else if(sourceAlarmState == this.device.ALARM_STATE_NOT_READY_TO_ARM ||
sourceAlarmState == this.device.ALARM_STATE_READY_TO_ARM ||
sourceAlarmState == this.device.ALARM_STATE_READY_TO_ARM_VIOLATION) {
return Characteristic.SecuritySystemCurrentState.DISARMED;
} else {
if(sourceAlarmMode == this.device.ALARM_MODE_STAY || sourceAlarmMode == this.device.ALARM_MODE_STAY_INSTANT ) {
return Characteristic.SecuritySystemCurrentState.STAY_ARM;
} else if(sourceAlarmMode == this.device.ALARM_MODE_AWAY || sourceAlarmMode == this.device.ALARM_MODE_VACATION) {
return Characteristic.SecuritySystemCurrentState.AWAY_ARM;
} else if(sourceAlarmMode == this.device.ALARM_MODE_NIGHT || sourceAlarmMode == this.device.ALARM_MODE_NIGHT_INSTANT) {
return Characteristic.SecuritySystemCurrentState.NIGHT_ARM;
} else {
ISYJSDebugMessage(this,"Setting to disarmed because sourceAlarmMode is "+sourceAlarmMode);
return Characteristic.SecuritySystemCurrentState.DISARMED;
}
}
}
// Translates the current target state of hthe underlying alarm into the appropriate homekit value
ISYElkAlarmPanelAccessory.prototype.translateAlarmTargetStateToHK = function() {
var sourceAlarmState = this.device.getAlarmMode();
if(sourceAlarmState == this.device.ALARM_MODE_STAY || sourceAlarmState == this.device.ALARM_MODE_STAY_INSTANT ) {
return Characteristic.SecuritySystemTargetState.STAY_ARM;
} else if(sourceAlarmState == this.device.ALARM_MODE_AWAY || sourceAlarmState == this.device.ALARM_MODE_VACATION) {
return Characteristic.SecuritySystemTargetState.AWAY_ARM;
} else if(sourceAlarmState == this.device.ALARM_MODE_NIGHT || sourceAlarmState == this.device.ALARM_MODE_NIGHT_INSTANT) {
return Characteristic.SecuritySystemTargetState.NIGHT_ARM;
} else {
return Characteristic.SecuritySystemTargetState.DISARM;
}
}
// Translates the homekit version of the alarm target state into the appropriate elk alarm panel state
ISYElkAlarmPanelAccessory.prototype.translateHKToAlarmTargetState = function(state) {
if(state == Characteristic.SecuritySystemTargetState.STAY_ARM) {
return this.device.ALARM_MODE_STAY;
} else if(state == Characteristic.SecuritySystemTargetState.AWAY_ARM) {
return this.device.ALARM_MODE_AWAY;
} else if(state == Characteristic.SecuritySystemTargetState.NIGHT_ARM) {
return this.device.ALARM_MODE_NIGHT;
} else {
return this.device.ALARM_MODE_DISARMED;
}
}
// Handles request to get the target alarm state
ISYElkAlarmPanelAccessory.prototype.getAlarmTargetState = function(callback) {
callback(null,this.translateAlarmTargetStateToHK());
}
// Handles request to get the current alarm state
ISYElkAlarmPanelAccessory.prototype.getAlarmCurrentState = function(callback) {
callback(null,this.translateAlarmCurrentStateToHK());
}
// Mirrors change in the state of the underlying isj-js device object.
ISYElkAlarmPanelAccessory.prototype.handleExternalChange = function() {
ISYJSDebugMessage(this,"Source device. Currenty state locally -"+this.device.getAlarmStatusAsText());
ISYJSDebugMessage(this,"Got alarm change notification. Setting HK target state to: "+this.translateAlarmTargetStateToHK()+" Setting HK Current state to: "+this.translateAlarmCurrentStateToHK());
this.alarmPanelService
.setCharacteristic(Characteristic.SecuritySystemTargetState, this.translateAlarmTargetStateToHK());
this.alarmPanelService
.setCharacteristic(Characteristic.SecuritySystemCurrentState, this.translateAlarmCurrentStateToHK());
}
// Returns the set of services supported by this object.
ISYElkAlarmPanelAccessory.prototype.getServices = function() {
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "SmartHome")
.setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName)
.setCharacteristic(Characteristic.SerialNumber, this.device.address);
var alarmPanelService = new Service.SecuritySystem();
this.alarmPanelService = alarmPanelService;
this.informationService = informationService;
alarmPanelService
.getCharacteristic(Characteristic.SecuritySystemTargetState)
.on('set', this.setAlarmTargetState.bind(this));
alarmPanelService
.getCharacteristic(Characteristic.SecuritySystemTargetState)
.on('get', this.getAlarmTargetState.bind(this));
alarmPanelService
.getCharacteristic(Characteristic.SecuritySystemCurrentState)
.on('get', this.getAlarmCurrentState.bind(this));
return [informationService, alarmPanelService];
}
module.exports.platform = ISYPlatform;
module.exports.accessory = ISYFanAccessory;
module.exports.accessory = ISYLightAccessory;
module.exports.accessory = ISYLockAccessory;
module.exports.accessory = ISYOutletAccessory;
module.exports.accessory = ISYDoorWindowSensorAccessory;
module.exports.accessory = ISYElkAlarmPanelAccessory;