462 Commits

Author SHA1 Message Date
Jon Maddox
febed4cae6 derped the ignore 2015-10-24 11:34:41 -04:00
Jon Maddox
17f13a51a4 merge master 2015-10-24 11:14:36 -04:00
Nick Farina
d2fd510aa4 Merge pull request #317 from doob/master
Handle where no model is returned from Telldus Live
2015-10-24 09:53:18 -04:00
Nick Farina
45af486cfd Merge pull request #314 from KraigM/feature/PhilipsHue
Fixed initial color of Philips Hue
2015-10-24 09:52:57 -04:00
Nick Farina
d866f9abb6 Merge pull request #313 from fcarucci/master
Fix MyQ Garage Opener and multiple Nests configuration
2015-10-24 09:52:02 -04:00
Dennis Soderstrom
e72309fab0 Handle where no model is returned from Telldus Live 2015-10-24 12:39:43 +02:00
Kraig McConaghy
1ad323b0e8 Fixed initial color calc to range from 0-360 (as documented) instead of 0-100 (likely typo) 2015-10-23 19:53:02 -05:00
Francesco Carucci
1f7db5e661 MyQ: check MyQDeviceTypeName against 'VGDO' as well 2015-10-23 23:56:54 +00:00
Francesco Carucci
e2157aed9c Append id to nest accessory name 2015-10-23 13:09:24 -07:00
Nick Farina
48e0f8b812 Merge pull request #310 from snowdd1/master
Fix vindex variable scope for address registration
2015-10-23 06:50:22 -07:00
Snowdd1
b09835600d Variable out of scope 2015-10-23 12:04:27 +02:00
Snowdd1
afef06467d Merge remote-tracking branch 'refs/remotes/nfarina/master' 2015-10-23 12:01:12 +02:00
Nick Farina
386636975b Merge pull request #299 from KraigM/feature/Harmony
Enhanced and Fixed Logitech Harmony
2015-10-21 21:15:22 -07:00
Kraig McConaghy
bfc151e70a Updated to official version of harmonyhubjs-client package now that the fix has been merged/released 2015-10-21 18:01:43 -05:00
Nick Farina
8d7f986f84 Merge pull request #305 from mariodrengner/patch-1
Ignore Bridge
2015-10-21 06:23:19 -07:00
Mario Drengner
997906bab8 Ignore Bridge
No need to control the bridge
2015-10-21 14:35:35 +02:00
Nick Farina
1a95978e0a Merge pull request #301 from SphtKr/zway-door-options
Z-Way Door sensor options
2015-10-20 06:34:41 -07:00
S'pht'Kr
13e8846138 Support Door, Window, and ContactSensor for sensorBinary.Door/Window 2015-10-20 07:04:18 +02:00
Nick Farina
7c7ac5d828 Merge pull request #294 from rodtoll/master
Enhanced ISY-994i support
2015-10-19 19:48:21 -07:00
rodtoll
70ec9a8891 Fixed bug in alarm state. Was using alarm state instead of mode to translate current state. 2015-10-19 19:38:14 -07:00
rodtoll
00574f60b4 Brought in latest 2015-10-19 19:01:53 -07:00
Nick Farina
2c36f1745d Merge pull request #298 from justme-1968/master
this is the latest update to the FHEM platform shim
2015-10-19 13:26:29 -07:00
Andre Schröter
8ebd6ecdd2 hap-nodejs vs. HAP-NodeJS fix 2015-10-19 22:08:07 +02:00
Andre Schröter
e9e637c43d Merge remote-tracking branch 'upstream/master' 2015-10-19 21:16:35 +02:00
Andre Schröter
d7bca45e70 debug browser formating change 2015-10-19 21:13:44 +02:00
Kraig McConaghy
e8553c7203 Merge branch 'feature/Harmony'
Conflicts:
	package.json
2015-10-19 14:02:27 -05:00
Kraig McConaghy
626871680c Reorganized dependencies to help with merge 2015-10-19 13:46:06 -05:00
Andre Schröter
42f88864f9 check for hap-nodejs and HAP-NodeJS
allow longpoll to multiple fhem servers
increase reconnect timeout after subsequent longpoll disconnect and error
added target door state for HM-SEC-KEY -> allows open to be send
added current door state to contact sensor -> allows to ask for open windows with siri
some cleanups
2015-10-19 20:00:02 +02:00
rodtoll
0cb57d2a44 Improper constants for the fan speed. Fixing. 2015-10-19 08:03:42 -07:00
Nick Farina
92667b5666 Merge pull request #297 from tommasomarchionni/master
Integrate OpenHAB Platform
2015-10-19 07:03:14 -07:00
tommasomarchionni
7b24d142b4 Create Openhab.js 2015-10-19 14:52:08 +02:00
rodtoll
74e37cc488 Added more documentation. 2015-10-18 21:09:59 -07:00
rodtoll
8fcd5d39a6 Moved all debug logging to be controlled by the ISYJSDEBUG environment variable AND moved all log messages to go through the global logger offered by homebridge. 2015-10-18 19:47:30 -07:00
Kraig McConaghy
6604c2b69a Changed structure to handle numerous issues plus add enhancements. No activity off properly calls "Power Off" (and thus "Power Off" has been removed). HomeKit responses are properly set back when activity finishes switching. Some amount of handling has been added for timeouts while switching activities as well as request sync (you don't seem to be able to send multiple requests to the hub simultaneously, and the activity switching sometimes times out the connection, so a reconnect system was added). 2015-10-18 19:08:46 -05:00
Nick Farina
6e5c35ec88 Restore telldus-live
See #277 for discussion.
2015-10-18 14:23:00 -07:00
Kraig McConaghy
e30b504583 Added handling to keep track of activity state 2015-10-18 16:21:57 -05:00
Nick Farina
7b110191f9 Merge pull request #289 from MSchmidt/master
Change all HAP-NodeJS require calls to lowercase
2015-10-18 14:17:03 -07:00
Nick Farina
74a72c1096 Merge pull request #287 from webdeck/indigo
Graceful handling of JSON parse errors from Indigo responses.
2015-10-18 14:16:16 -07:00
Nick Farina
5066b108d2 Merge pull request #286 from dgarozzo/master
add async dependency for Indigo
2015-10-18 14:16:01 -07:00
Kraig McConaghy
6008374b9e Converted LogitechHarmony accessory to be based on new HAP api 2015-10-18 14:00:06 -05:00
Kraig McConaghy
3c53b319cb Updated to fixed harmonyhubjs-client 2015-10-18 13:58:10 -05:00
rodtoll
2d80e5f838 Updated to latest version of hap base library. Root project migrated to lower case version. Also added new uuid_base required field which is now needed. Refactored device constructors to use common code. 2015-10-18 11:19:08 -07:00
Kraig McConaghy
b2aa70d40a Merge commit '2652f33a0a2de8566df0d346ec19c2bc1ceaff9d' into feature/Harmony 2015-10-18 13:04:51 -05:00
rodtoll
a45779cf48 Merge branch 'master' of https://github.com/nfarina/homebridge
pulling down latest changes.
2015-10-18 10:19:14 -07:00
Andre Schröter
bf125078a1 added first version of HM-SEC-KEY-S
some bug fixes
2015-10-18 18:25:31 +02:00
Nick Farina
7ce190a7d0 Merge pull request #285 from masonjames/master
Update hap-nodejs requires
2015-10-18 08:16:51 -07:00
Matthias Schmidt
915e674583 Change all HAP-NodeJS require calls to lowercase 2015-10-18 13:29:50 +02:00
Mike Riccio
ad62bd4b15 Graceful handling of JSON parse errors from Indigo responses.
Make initial discover queries serialized to not overwhelm slow Indigo servers.
2015-10-17 20:27:04 -07:00
David Garozzo
15e9219c80 add async dependency for Indigo 2015-10-17 21:43:43 -04:00
Mason James
38cb94c012 Update hap-nodejs requires
Update remaining platforms and accessories
2015-10-17 18:41:57 -04:00
Nick Farina
ffc4edbc2a Merge pull request #284 from williambout/patch-1
Update Hyperion.js
2015-10-17 09:44:06 -07:00
William Bout
196cda8063 Update Hyperion.js
Fix require error
2015-10-17 18:12:11 +02:00
Nick Farina
3d03824e6a Merge pull request #282 from tommasomarchionni/patch-1
Update app.js
2015-10-17 05:49:21 -07:00
tommasomarchionni
385908fa89 Update app.js 2015-10-17 13:23:30 +02:00
tommasomarchionni
f2d22584fc Update app.js 2015-10-17 12:34:40 +02:00
Nick Farina
97cb18389f Merge pull request #279 from webdeck/indigo
Fix hap-nodejs import for Indigo platform
2015-10-16 22:21:26 -07:00
Mike Riccio
896d6b40d9 Fixed fahrenheit units, was giving error. 2015-10-16 20:36:32 -07:00
Mike Riccio
c5499c122e Add devices even if discovery errors.
Don't assign everything POWER_STATE_CTYPE.
2015-10-16 20:21:30 -07:00
Nick Farina
831480d035 Remove telldus for now 2015-10-16 10:40:04 -07:00
Nick Farina
286f700622 Merge pull request #273 from webdeck/indigo
Add platform support for Indigo server (http://www.indigodomo.com/)
2015-10-16 10:31:14 -07:00
Nick Farina
ecf9afee64 Merge pull request #275 from MSchmidt/master
Support for case-sensitive file systems
2015-10-16 10:21:11 -07:00
Matthias Schmidt
bb1c193bc0 Use npm release of hap-nodejs 2015-10-16 19:10:14 +02:00
Mike Riccio
c22c14584d Added more logging when there's an error getting device details. 2015-10-16 09:36:18 -07:00
Nick Farina
2df47b9893 Merge pull request #276 from stipus/master
GarageDoorOpener and Lock support
2015-10-16 06:11:18 -07:00
stipus
23f190e3d8 GarageDoorOpener and Lock support
- Smoke sensor battery fix
 - Added offEventGroup && offEventName to events (turn <event> on launches one HS event. turn <event> off can launch another HS event)
 - Added GarageDoorOpener support
 - Added Lock support
2015-10-16 13:00:18 +02:00
Matthias Schmidt
7233c5bf74 Support for case-sensitive file systems and current HAP-NodeJS with nodejs 4 support 2015-10-16 10:50:55 +02:00
Mike Riccio
aad811fe6e Fixed thermostats to report celsius. 2015-10-15 21:31:08 -07:00
Nick Farina
bab1eb730e Version bump to node-icontrol 2015-10-15 20:08:25 -07:00
Mike Riccio
6fa69c5c4b Removed tabs 2015-10-15 16:47:59 -07:00
Mike Riccio
9ad8a111c6 Added intro comment. 2015-10-15 15:07:14 -07:00
Mike Riccio
e40f10b82e Initial support for Indigo server (http://www.indigodomo.com) 2015-10-15 14:25:20 -07:00
S'pht'Kr
d5e4714551 Merge branch 'nfarina/master' 2015-10-15 05:52:26 +02:00
S'pht'Kr
81dbb285c7 Merge branch 'nfarina/master' 2015-10-15 05:50:52 +02:00
Nick Farina
99910960ae Merge pull request #268 from stipus/master
Added CarbonMonoxide and CarbonDioxide sensor support
2015-10-14 13:45:13 -07:00
stipus
a056b16c35 Added CarbonMonoxide and CarbonDioxide support
- Added CarbonMonoxide and CarbonDioxide sensor support
- Added onValues option to all binary sensors
- Added uuid_base parameter to all HomeSeer accessories
2015-10-14 22:06:11 +02:00
Nick Farina
a05ba696b6 Merge pull request #261 from iRaven/master
Extend/Add Homematic Accessories (get state for switch, thermostat & window support)
2015-10-13 10:01:44 -07:00
Nick Farina
60c48fc4cb Merge pull request #260 from SphtKr/zway-motionsensor-and-fixes
Z-Way Motion Sensor support and minor fixes/updates
2015-10-13 09:30:00 -07:00
iRaven
d8a3596326 Added HomematicThermo, HomematicWindow 2015-10-13 17:39:28 +02:00
iRaven
7c7ceb6453 Rename HomeMaticThermo to HomeMaticThermo.js 2015-10-13 13:06:11 +02:00
S'pht'Kr
fdbd33f29d Updated for new tweaked "setProps" API, removed cruft. 2015-10-13 06:50:23 +02:00
S'pht'Kr
c48c750917 MotionDetector added, with new needed configuration tags. 2015-10-13 05:37:44 +02:00
Nick Farina
d1e186ed99 Merge pull request #259 from stipus/master
Added support for humidity sensors, battery level sensors, low battery flag for all sensors, and ability to run HomeSeer events
2015-10-12 16:03:59 -07:00
stipus
1f8db79324 Added support for humidity sensors, battery level sensors, low battery flag for all sensors, and ability to run HomeSeer events
- Added Humidity sensor support
- Added Battery support
- Added low battery support for all sensors
- Added HomeSeer event support (using HomeKit switches...)
2015-10-13 00:53:19 +02:00
iRaven
d4279c5ef5 Added Support for Thermostat HM-CC-RT-DN 2015-10-12 18:12:14 +02:00
S'pht'Kr
e7ce658c3d Merge branch 'nfarina/master' into zway-motionsensor-and-fixes 2015-10-12 05:44:32 +02:00
Nick Farina
ff5762ad70 Merge pull request #258 from stipus/master
HomeSeer.js - Added thermostat support
2015-10-11 19:25:36 -07:00
stipus
c59b87d17d Update HomeSeer.js 2015-10-12 02:15:41 +02:00
stipus
41e6c20a61 Update HomeSeer.js 2015-10-12 02:11:24 +02:00
stipus
c506c44d60 Update HomeSeer.js 2015-10-12 02:02:37 +02:00
rodtoll
6030928c12 Fixed elk panel state transitions. 2015-10-11 13:34:58 -07:00
rodtoll
40a96808b2 Added docs to the isy-js file. 2015-10-11 08:45:18 -07:00
Nick Farina
59692df1a0 Merge pull request #248 from theodorton/telldus
Add support for local Telldus control
2015-10-11 08:21:44 -07:00
Nick Farina
ac1da10ea8 Merge pull request #251 from stipus/master
Fix for HomeSeer occupancy sensor
2015-10-11 08:21:15 -07:00
Nick Farina
55a17f7919 Merge pull request #253 from stevetrease/master
Added readonly thermometer and hygrometer accessories
2015-10-11 08:21:01 -07:00
Nick Farina
c6ed47cf38 Merge pull request #252 from SphtKr/zway-more-tags
Several additional tag-based configuration options
2015-10-11 08:20:47 -07:00
Nick Farina
a435cf2b00 Merge pull request #254 from SphtKr/yamaha-issue-247
YamahaAVR Issue #247
2015-10-11 08:19:23 -07:00
rodtoll
e4fa276de2 Adding isy-js based ISY platform support 2015-10-11 08:14:48 -07:00
S'pht'Kr
a274ae4eda Switches to the *new* new Characteristics API format for the two custom Characteristics.
Fixes #247
2015-10-11 05:44:44 +02:00
iRaven
05e811cdd6 Added HM-Sec-RHS Support as contact 2015-10-10 15:56:51 +02:00
S'pht'Kr
25299a7863 Last bits… 2015-10-10 15:45:27 +02:00
stevetrease@gmail.com
4a831422eb Added two new accessories for a readonly thermometer and hygrometer (humidity meter) based on HttpAccessory. 2015-10-10 14:41:00 +01:00
iRaven
b4f4f58519 Added Get-State Function 2015-10-10 14:05:16 +02:00
stipus
4fbd7eb775 Fix for HomeSeer occupancy sensor 2015-10-10 12:38:46 +02:00
Theodor Tonum
c0dfc9a8cd Add support for local Telldus control 2015-10-10 01:14:10 +02:00
S'pht'Kr
d5c4dfce1f Merge branch 'nfarina/master' into zway-more-tags 2015-10-09 06:05:44 +02:00
Nick Farina
fd420c7795 Merge pull request #223 from SphtKr/zway-rgb
RGB(+W+W) support
2015-10-08 13:37:58 -07:00
Nick Farina
8fe5f8b2aa Merge pull request #242 from ethansinjin/patch-1
Simple fix for undefined Nest device names
2015-10-08 10:57:24 -07:00
Ethan Gill
d8a21133e9 Simple fix for undefined Nest device names 2015-10-08 13:50:34 -04:00
Andre Schröter
490ed5db40 added EnOcean blinds 2015-10-08 18:55:30 +02:00
Nick Farina
9096e64891 Merge pull request #239 from stipus/master
Create HomeSeer.js
2015-10-08 09:40:11 -07:00
stipus
772f35efac Create HomeSeer.js 2015-10-08 09:07:33 +02:00
Snowdd1
d914b334a9 Merge remote-tracking branch 'nfarina/master' 2015-10-07 21:52:52 +02:00
Nick Farina
2b84fc1783 Merge pull request #238 from SphtKr/yamahaavr-manual-ip
YamahaAVR manual address specification
2015-10-07 12:51:20 -07:00
Nick Farina
64864bac85 Merge pull request #233 from snowdd1/UUID-creation
New UUID generation
2015-10-07 12:39:36 -07:00
Snowdd1
ed8a3c8062 uuid_base in constructor 2015-10-07 10:39:00 +02:00
S'pht'Kr
a15c026f2f Manual address specification working now 2015-10-07 06:59:04 +02:00
S'pht'Kr
d66ab79e8e Merge branch 'nfarina/master' 2015-10-07 05:38:46 +02:00
S'pht'Kr
af79ea4fbf Beginning tag support enhancements
Generalized tag recognition, tags are now `Homebridge.*` instead of
`Homebridge:*`, initial attempt at `IsPrimary` but probably not working
right yet.
2015-10-07 05:38:24 +02:00
Snowdd1
e6afda55d6 New UUID generation
addresses #203

# This is a breaking change for all configured homekit databases, as it
will remove room/area assignments and other things configured in iOS
apps
... unless your accessory type was called `Object` before

Changes:
- Without any changes to the configuration file all accessories UUIDs
will be generated by  `uuid.generate(accessoryType + ":"` instead of the
constructor name which turned out to be unreliable in that context. So
about all UUIDs will change.
- new parameter uuid_base can be used for accessories in config.json to
use a different base for UUID generation than the displayName, which
might not be unique
- platforms can add the same property to their accessories before
returned, just as they have a name property right now.
2015-10-06 20:47:07 +02:00
Snowdd1
b9fb57dd7a Merge remote-tracking branch 'nfarina/master' 2015-10-06 18:11:16 +02:00
Nick Farina
feca9fbf0d Support WeMo "Light" services 2015-10-05 17:29:53 -07:00
Nick Farina
b66610ce93 Update Lockitron state on successful change 2015-10-05 17:12:06 -07:00
Andre Schröter
c24a94c072 added ROOMMATE devices 2015-10-05 12:00:43 +02:00
Mike Enriquez
2652f33a0a send command every 20s to prevent timing out 2015-10-03 22:04:51 -04:00
Nick Farina
2eb82bcc98 Merge pull request #226 from snowdd1/master
Fix json in example in KNX.md
2015-10-03 07:37:35 -07:00
Raoul
ac62a44ce2 Updates to wrong json Examples in KNX.md doc file
Missing quotes around new key names.
2015-10-03 14:48:06 +02:00
Raoul
a55b166131 Merge pull request #6 from nfarina/master
Update to nfarina/master
2015-10-03 14:38:57 +02:00
Nick Farina
d3f0c73393 Merge pull request #225 from snowdd1/knx-dev
Update of handling of binary and percentage type KNX values
2015-10-02 09:22:36 -07:00
Snowdd1
c36ad9d631 Merge remote-tracking branch 'nfarina/master' into knx-dev 2015-10-02 18:06:23 +02:00
Snowdd1
3031ba7e0f Refactoring of reversely used group addresses
The use of 0/1 for binary objects depends on installation, a contact
sensor can have contact=1 or open=1
The same applies to percentages:  a window can be 100% closed or 100%
open.

For a more general usage of reversed values, a suffix ("R") can now be
attached to all DPT1 and DPT5 group addresses.
Please see updated KNX.md for examples and details.

Note: outdated characteristic-workaround throw an exception at boot-up:
-  ContactSensorStateContact1
-  LockCurrentStateSecured0
-  LockTargetStateSecured0
Please see error message and KNX.md for reference.
2015-10-02 18:05:40 +02:00
Snowdd1
fd72307384 Fix hint in KNX-sample-config.json 2015-10-02 08:17:59 +02:00
S'pht'Kr
d04d417344 Initial read/write support for RGB bulbs complete
Needs testing, but seems to work with my Aeon bulb…taking into account
the wonkiness of that bulb with Z-Way, at least.
2015-10-02 06:19:59 +02:00
Mike Enriquez
98519c84dd Fix crash when trying to get/set power state
- Call getPowerState on the object instead of passing the function to
  fix undefined isActivity error
- Remove callback param from setPowerState since it is not called with a
  callback param.
- startActivity expects the activity id as a parameter. We can't
  actually toggle activities off and on. There is a "PowerOff" activity
  that will turn everything off.
2015-10-01 23:08:36 -04:00
Mike Enriquez
3bca9f70f5 freeze node-xmpp-client version to working version 2015-10-01 20:39:00 -04:00
Nick Farina
1bb8d00884 Merge pull request #221 from Mikulas/config-lifx
add lifx to sample config
2015-10-01 14:25:50 -07:00
Mikulas
45f40874ef add lifx to sample config 2015-10-01 23:22:17 +02:00
Jon Maddox
fe5db08081 Merge pull request #219 from nfarina/ignore-hidden-ha-devices
Ignore Hidden Home Assistant Devices
2015-10-01 01:30:28 -04:00
Jon Maddox
1fce5c8754 ignore hiddent Home Assistant devices 2015-10-01 01:29:42 -04:00
Nick Farina
8e23024ef0 Merge pull request #217 from snowdd1/knx-dev
Characteristic.setProps() adaptation
2015-09-30 14:37:11 -07:00
Snowdd1
98ee9f8f95 Return value guarding; Cleaner logs 2015-09-30 22:07:33 +02:00
Snowdd1
bd979ea1f8 Merge remote-tracking branch 'nfarina/master' into knx-dev 2015-09-30 20:40:03 +02:00
Snowdd1
54f0c2f0cb Doc updates 2015-09-30 20:03:20 +02:00
Raoul
ba5722c517 props update minValue, maxValue
used in Temperature devices (Apple's default values too narrow for beer coolers I've heard)

adapted call syntax for "Simplify Characteristic properties"
1d84d128d1 (diff-cb84de3a1478a38b2cf8388d709f1c1cR108)
2015-09-30 09:21:59 +02:00
Andre Schröter
2d88dafa11 disabled thermostat mode
some cleanups
added timestamp to longpoll console output
2015-09-29 22:45:34 +02:00
S'pht'Kr
99da61d30a Lost some subtype parameters in deconfliction 2015-09-29 07:06:05 +02:00
S'pht'Kr
0b3930e458 Erm...oops. 2015-09-29 06:59:40 +02:00
S'pht'Kr
ce35600b34 Merge remote-tracking branch 'nfarina/master' into zway-rgb
Conflicts:
	platforms/ZWayServer.js
2015-09-29 06:58:06 +02:00
S'pht'Kr
7aa758cb04 Working towards getting the extra dimmers in one accessory
Having trouble with service subtypes…hmm…
2015-09-29 06:53:22 +02:00
S'pht'Kr
bd6b8478a9 Merge branch 'nfarina/master' 2015-09-29 05:24:25 +02:00
Nick Farina
2e24e98227 Merge pull request #210 from SphtKr/yamaha-issue-153
YamahaAVR: Fix for multiple devices, refactor, and custom Service+Characteristic
2015-09-28 11:02:33 -07:00
S'pht'Kr
817f7ecf20 Merge branch 'nfarina/master' 2015-09-28 06:27:09 +02:00
S'pht'Kr
2a66eac058 Fix for multiple devices, refactor, and custom Service+Characteristic 2015-09-28 06:12:25 +02:00
Nick Farina
b54d37fb2d Merge pull request #174 from ilcato/ilcato-fibarohc2
Platform shim for Fibaro Home Center 2
2015-09-27 06:49:56 -07:00
ilcato
c6c45d9e3d Fixed incompatibility with new version of HAP-Node.js 2015-09-27 13:00:57 +02:00
Andre Schröter
78394bc95d added CurrentAmbientLightLevel characteristic 2015-09-25 22:43:57 +02:00
Andre Schröter
bfbaa74375 added firmware revision characteristic
fix for genericDeviceType ignore
some more fixes
set uuid_base to real device serial number if available
2015-09-25 19:20:54 +02:00
Nick Farina
bb3381e10a Update README.md 2015-09-24 22:15:58 -07:00
Nick Farina
5323b0afe1 Merge pull request #204 from derekforeman/master
Noted updated for requirement to successfully get HAP-NodeJS installed.
2015-09-24 22:10:30 -07:00
Derek Foreman
ca232f3111 Noted updated for requirement to successfully get HAP-NodeJS installed. 2015-09-25 00:26:07 -04:00
Andre Schröter
c9166f6ff0 Merge remote-tracking branch 'upstream/master' 2015-09-22 20:46:56 +02:00
Andre Schröter
afcb86ef0e changed harmony device to a single accessiory with mutliple switches
added hue colormode & xy to hsv conversion
some small cleanups
2015-09-22 18:53:19 +02:00
Andre Schröter
2a63a9ff2d adde MAX ShutterContact 2015-09-21 19:48:51 +02:00
Andre Schröter
1b69cffeb2 removed debug output 2015-09-21 18:50:40 +02:00
Andre Schröter
7695eced0d publish accessories only once
parse min/max/step values for thermostats
use this/bind instead of that
added genericType ignore
added batter state charactersitic
preliminary thermostat controll mode
use new characteristic.setProps() method
some minor cleanups
2015-09-21 18:29:01 +02:00
Nick Farina
625b9f47df Bump HAP-NodeJS
Closes #181
2015-09-21 08:17:48 -07:00
Nick Farina
c3ad5f26da Merge pull request #191 from luxus/patch-1
fixing thermostat.
2015-09-20 16:28:57 -07:00
Kai
67fb6e8b4d fixing thermostat.
switch the order of the characteristics
now my stellaZ thermostat is shown correct in apps like eve and insteon+
2015-09-21 00:59:26 +02:00
Nick Farina
b24a611fab Merge pull request #189 from snowdd1/knx-dev
KNX Platform: "Garage Door Opener" and "Motion Sensor" services
2015-09-19 13:00:48 -07:00
Snowdd1
80b2a047d4 Typo fixes 2015-09-19 21:56:20 +02:00
Snowdd1
48f613f8ae New Motion Sensor service 2015-09-19 21:47:07 +02:00
Snowdd1
84c51aa27a GarageDoorOpener fixed 2015-09-19 21:24:55 +02:00
Snowdd1
eec663a5c8 Routines for unsigned int values, such as enumeration like types 2015-09-19 18:37:19 +02:00
Snowdd1
387e7ec9ce KNX support for Garage Door Opener
The garage door opener device MUST adhere to HomeKit numeric
conventions, see KNX.md documentation
2015-09-19 15:19:11 +02:00
S'pht'Kr
6b0d701570 Merge branch 'nfarina/master' 2015-09-19 13:00:16 +02:00
S'pht'Kr
656a8057ac Initial work on reading, seems to work okay. 2015-09-19 13:00:01 +02:00
ilcato
24fbe4158d Added support for Fibaro Outlet 2015-09-19 10:12:35 +02:00
Nick Farina
1dcfe4a1fb Merge pull request #188 from snowdd1/knx-dev
KNX cleanup
2015-09-18 09:25:04 -07:00
ilcato
32877abc98 Complete refactoring and adoption of new API
Added support for LightSensor
2015-09-18 17:33:36 +02:00
Jon Maddox
2b11db4560 guard 2015-09-18 10:10:09 -04:00
Snowdd1
91f6ccb2d4 Added LightSensor Service 2015-09-18 13:18:27 +02:00
Snowdd1
93ea0deded Cleanup and new devices
Added services:
-  Switch
-  Outlet
2015-09-18 10:28:31 +02:00
Snowdd1
f802d84509 Even more markdown formatting 2015-09-18 09:47:52 +02:00
Snowdd1
24a8dcb8cc more markdown formatting 2015-09-18 09:45:08 +02:00
Snowdd1
76a5fd3b7f markdown \<BR\> test 2015-09-18 09:40:38 +02:00
Snowdd1
c7ab475dd4 Cleanup KNX file names and markdown
I found that the description was wrongly named and in the wrong folder.
It is now named after the platform shim to appear next to it in
listings, also the config-sample that comes along with it.

As long as we do not go for own repositories and npm packages, should we
consider folders for additional files if required?
2015-09-18 09:37:10 +02:00
Snowdd1
6f77e55c7b Merge remote-tracking branch 'nfarina/master' into knx-dev 2015-09-18 09:25:35 +02:00
Snowdd1
e7f35dfcbb Merge remote-tracking branch 'nfarina/master' 2015-09-18 08:52:55 +02:00
Jon Maddox
cf885b6d29 Merge branch 'master' into osx-launchd 2015-09-17 19:39:39 -04:00
Andre Schröter
ddf9fa01cf fixed PossibleSets regex again... 2015-09-17 21:58:51 +02:00
Nick Farina
73148b060d Remove serial port dependency for now 2015-09-17 12:51:55 -07:00
Nick Farina
5720e82abd Remove test code from WeMo 2015-09-17 12:50:29 -07:00
Andre Schröter
6f14108905 fix for PossibleSets regex
first version of logitech harmony support (device and activity level)
2015-09-17 20:37:11 +02:00
Nick Farina
6fe4fe8249 Merge pull request #184 from Cosmo/master
Print scannable setup code to terminal on startup
2015-09-17 09:00:01 -07:00
Devran Ünal
a05a4b6f71 Print the setup code just before publishing the bridge 2015-09-17 17:36:29 +02:00
Devran Ünal
2135e7eccb Print scannable setup code to terminal on startup 2015-09-17 17:00:52 +02:00
Nick Farina
dbb130be02 Merge pull request #182 from Cosmo/master
Add shim for devices with serial connection
2015-09-17 06:23:12 -07:00
Devran Ünal
72b9175b78 Set siri name based on accessory name 2015-09-17 15:22:02 +02:00
Devran Ünal
65ec517fd8 Update to modern API and return error messages on failed connections 2015-09-17 11:37:20 +02:00
Devran Ünal
aa823baa8e Add shim for devices with serial connection (video projectors, screens, receivers, ..) 2015-09-17 02:03:53 +02:00
Nick Farina
118a4529b9 Merge pull request #179 from snowdd1/knx-dev
More iOS9 services
2015-09-16 13:01:09 -07:00
ilcato
9580d259db Fixed status update 2015-09-16 21:56:09 +02:00
Snowdd1
ace364644e MOve config-sample-knx.json to platforms/ 2015-09-16 21:22:49 +02:00
Snowdd1
0da4fe5d22 Welcome iOS9
More services, documentation, and cleanups.
2015-09-16 20:05:02 +02:00
Snowdd1
cd33f2e6c8 Merge remote-tracking branch 'nfarina/master' into knx-dev 2015-09-16 19:15:56 +02:00
Snowdd1
68f764864d Merge remote-tracking branch 'nfarina/master' 2015-09-16 19:12:00 +02:00
Jon Maddox
267470c871 Merge branch 'master' into osx-launchd 2015-09-16 10:49:13 -04:00
Nick Farina
2c118e9649 Pull in latest HAP fixes 2015-09-16 07:45:46 -07:00
Nick Farina
66439f6353 Merge pull request #177 from nfarina/ha-scenes
Home Assistant Scenes
2015-09-16 07:17:40 -07:00
Jon Maddox
773eb8fd0e some docs 2015-09-16 03:21:24 -04:00
Jon Maddox
651cdfa786 close it 2015-09-16 03:16:49 -04:00
Jon Maddox
0f89a6ae36 add new supported_types key for HA to sample config 2015-09-16 03:15:58 -04:00
Jon Maddox
ec8b556618 return informationServices for HA devices 2015-09-16 03:15:37 -04:00
Jon Maddox
eb6c881d28 support loading scenes from HA 2015-09-16 03:14:22 -04:00
Jon Maddox
7f753f79f6 HA switch takes a type argument
This lets you pass the domain to the switch to let it be other types of
objects, like say, a scene.
2015-09-16 03:14:09 -04:00
Jon Maddox
f3e08b0a15 add device types from Home Assistant based on the supported types declared in the config
Instead of matching with a regex, we use a config of supported device
types to attempt to load in. This allows the user to whitelist just the
ones they want to add.
2015-09-16 03:12:56 -04:00
S'pht'Kr
e06e982063 Merge branch 'nfarina/master' 2015-09-16 05:49:53 +02:00
Nick Farina
f9465ebc8e Merge pull request #175 from justme-1968/master
switched fhem platform shim to new api
2015-09-15 14:07:07 -07:00
Andre Schröter
2a430ad75c Merge remote-tracking branch 'upstream/master' 2015-09-15 22:58:01 +02:00
Andre Schröter
8ceaccfca4 cleanups, fixes and more new api stuff 2015-09-15 22:03:17 +02:00
Nick Farina
d6b3fc7667 Bump HAP-NodeJS with fixes for Node 4.0.0 2015-09-15 10:58:30 -07:00
Nick Farina
abd848fd31 Merge pull request #173 from SphtKr/filedate-sensor
File-based motion or contact sensor
2015-09-15 10:25:03 -07:00
ilcato
a041407104 Added automatic status update through event mechanism 2015-09-15 19:03:08 +02:00
ilcato
d36827f92d Fixed comments 2015-09-15 14:26:42 +02:00
ilcato
983f136271 Create FibaroHC2.js 2015-09-15 13:57:21 +02:00
S'pht'Kr
13d1ed75cf File-based motion or contact sensor
This module creates a motion sensor accessory based on watching a
directory or file.
2015-09-15 06:55:53 +02:00
Jon Maddox
ae2d53262f document the new scripts 2015-09-14 23:56:22 -04:00
Jon Maddox
241b22db87 warn pi users 2015-09-14 23:46:08 -04:00
Jon Maddox
8d94366e3c do it 2015-09-14 23:39:46 -04:00
Jon Maddox
950e6a8211 support install/uninstall on linux with SysVinit 2015-09-14 23:39:42 -04:00
Jon Maddox
f3cab9a529 SysVinit template for linux 2015-09-14 23:39:18 -04:00
Jon Maddox
25981deac4 conditionalize the install/uninstall scripts to support linux or darwin. 2015-09-14 23:20:13 -04:00
Jon Maddox
6540c25baf add launchd plist 2015-09-14 22:54:01 -04:00
Jon Maddox
6f71faf355 add Scripts to Rule Them All 2015-09-14 22:53:49 -04:00
Jon Maddox
8131f6936e add forever env configs 2015-09-14 22:53:32 -04:00
Jon Maddox
ac0c967ec0 ignore logs dir 2015-09-14 22:53:05 -04:00
Jon Maddox
030fec8cbf Merge pull request #170 from nfarina/ha-media-players
Home Assistant Media Players
2015-09-14 22:23:34 -04:00
Jon Maddox
be589d4fb5 you can do this too 2015-09-14 20:46:51 -04:00
Jon Maddox
1df32fca3d add some docs 2015-09-14 20:20:26 -04:00
Jon Maddox
e894b1a1a1 support media players to be addressable as lights 2015-09-14 20:14:27 -04:00
Andre Schröter
f4160c2d01 added identify 2015-09-14 22:43:37 +02:00
Andre Schröter
9f39b49dd5 complete switch to new api
basic device (light,hue,swap,thermometer,thermostat) are working
still problems with custom characteristic volume
2015-09-14 22:10:14 +02:00
Nick Farina
f658ca90c1 Merge pull request #169 from lukeredpath/fix-domoticz-dimmer-handling
Fix domoticz dimmer handling
2015-09-14 12:00:41 -07:00
Luke Redpath
7f14df0434 Honor the MaxDimLevel property of accessories.
LightwaveRF lights could not be dimmed properly as they require a dim level
of betwene 0-32. The Domoticz documentation incorrectly states this should be
0-16.

Other devices may use different values which is why the MaxDimLevel property
should be used rather than hardcoded value.

Also refactored the code slightly.
2015-09-14 19:40:04 +01:00
Luke Redpath
c88d01c9a9 Updated CHANGELOG with my previous changes 2015-09-14 19:29:02 +01:00
S'pht'Kr
b0e3829dfb Merge branch 'nfarina/master' into filedate-sensor 2015-09-14 19:39:38 +02:00
Nick Farina
adbe116a5a [Lockitron] Create an Error if necessary 2015-09-14 07:48:42 -07:00
Nick Farina
bb39f5f73e [Lockitron] err could be null 2015-09-14 07:47:21 -07:00
Nick Farina
db3f32c577 Fix name from config 2015-09-14 07:45:29 -07:00
Nick Farina
13347d1879 Upgrade Lockitron accessory to new API 2015-09-14 07:43:32 -07:00
Nick Farina
4feec29d5c Merge pull request #165 from dwery/master
add mpd client accessory
2015-09-14 07:25:34 -07:00
Nick Farina
0df809e395 Merge pull request #164 from jmtatsch/patch-1
Fix 406 error for lifxjs
2015-09-14 07:25:00 -07:00
Jon Maddox
293f56e826 Merge pull request #166 from nfarina/home-assistant
Home Assistant Shim
2015-09-14 01:45:31 -04:00
Jon Maddox
c1e3d45fa1 scan in switches 2015-09-14 01:40:09 -04:00
Jon Maddox
69d948e0fa add switch 2015-09-14 01:40:03 -04:00
Jon Maddox
488456c108 ohhhhhhh the callback signature 2015-09-14 01:30:55 -04:00
Jon Maddox
025bca7a43 factor things out of the accessory and make it a Light 2015-09-14 01:16:34 -04:00
Jon Maddox
544124fbab clean it up and get on that modern tip 2015-09-14 00:20:28 -04:00
Nick Farina
178430d25c Merge pull request #167 from SphtKr/zway-thermostat-mode-writable
Z-Way thermostat mode writable
2015-09-13 21:14:09 -07:00
Jon Maddox
167a983068 handle missing friendly name 2015-09-14 00:14:02 -04:00
S'pht'Kr
b39b33726d Make TargetHeatingCoolingState writable
Apparently, if TargetHeatingCoolingState is not writable, you can’t add
a thermostat to a scene. This fix makes it writable from HomeKit, but
it still always remains set to HEAT.
2015-09-14 05:43:11 +02:00
Jon Maddox
a6d61cc93a add link to HA in readme 2015-09-13 22:30:47 -04:00
Jon Maddox
ec550d1638 add sample config for home assistant 2015-09-13 22:24:54 -04:00
Jon Maddox
f5cc6cf6fb add home assistant shim 2015-09-13 22:24:39 -04:00
Alessandro Zummo
b1d0ef57ac add mpd client accessory 2015-09-14 01:15:56 +02:00
S'pht'Kr
80f73f8324 Merge branch 'nfarina/master' 2015-09-13 16:06:36 +02:00
jmtatsch
45ae56cf12 Fix 406 error for lifxjs
by git+https
2015-09-13 14:53:35 +02:00
Andre Schröter
1f4ae9e875 removed speaker stype 2015-09-13 13:30:04 +02:00
Andre Schröter
6c34301c13 sonos volume fix 2015-09-13 12:21:50 +02:00
Andre Schröter
428186eb86 fixed debug messages 2015-09-13 10:24:28 +02:00
Andre Schröter
5358ef94a8 catch undefined values in reading2homekit 2015-09-13 09:14:13 +02:00
Andre Schröter
ca941bd350 now for real... 2015-09-12 20:21:37 +02:00
Andre Schröter
b5c2ba2c14 Merge remote-tracking branch 'upstream/master' 2015-09-12 20:11:31 +02:00
Andre Schröter
197311534b started porting to new bridge api 2015-09-12 20:07:18 +02:00
Nick Farina
a246472401 Merge pull request #121 from SphtKr/z-way-server-2
Z-Way server support
2015-09-12 06:37:54 -07:00
S'pht'Kr
7e9793e20e Merge branch 'nfarina/master' into z-way-server-2
Conflicts:
	config-sample.json
2015-09-12 14:26:27 +02:00
S'pht'Kr
2b1aa5e296 Erm...missing modifications that should have been in the last commit. 2015-09-12 14:24:31 +02:00
S'pht'Kr
17573524ce Prep for initial release.
Cleaned up some more old cruft, added config-sample.json entry, and now
there’s a different default grouping of characteristics, which makes
for more optional characteristics on fewer services. The older behavior
(more services per accessory) can be switched on in config.json. The
new default works better in Eve, other clients not so much.
2015-09-12 14:23:17 +02:00
Nick Farina
be31cce972 Merge pull request #159 from devbobo/master
[LIFX] implement the LIFX LAN API
2015-09-11 06:32:58 -07:00
David Parry
7d5a992c98 minor cleanup 2015-09-11 17:30:25 +10:00
S'pht'Kr
3da6fcb510 FIX: Prevent light sensor values from going out of bounds 2015-09-11 05:43:33 +02:00
David Parry
17fc8f1829 implement the LiFX LAN API as a configurable option for higher lantency connections 2015-09-11 13:04:09 +10:00
Snowdd1
e15fc7d4e8 Merge branch 'master' of https://github.com/snowdd1/homebridge 2015-09-10 17:27:56 +02:00
Snowdd1
4ca372f9d9 Merge remote-tracking branch 'nfarina/master' 2015-09-10 17:27:07 +02:00
Nick Farina
fdadd068b8 Merge pull request #158 from devbobo/master
[LiFX] fix/enhance the LiFX platform
2015-09-10 07:26:08 -07:00
David Parry
7e6df6191e [LiFX] fix/enhance the LiFX platform 2015-09-10 22:19:41 +10:00
S'pht'Kr
3c35311c4a Added Luminiscence sensors
Though there doesn’t seem to be much app support for it yet…and my math
(% -> lux) is complete guesswork.
2015-09-10 06:55:07 +02:00
Nick Farina
b2ea770afe Merge pull request #157 from devbobo/master
[WeMo] add support for WeMo motion sensor
2015-09-09 20:32:06 -07:00
Nick Farina
a1343ed57e Merge pull request #156 from nmelo/master
LIFx Bulb Platform implementation
2015-09-09 20:16:51 -07:00
David Parry
09f5e2bed0 add support for WeMo motion sensor 2015-09-10 12:50:55 +10:00
Nelson Melo
4b16371522 Added LIFx on Readme 2015-09-09 15:31:11 -04:00
Nelson Melo
7866b582f9 Merge branch 'master' of https://github.com/nmelo/homebridge
* 'master' of https://github.com/nmelo/homebridge:
  [MiLight] Swap cooler/warmer direction for white bulbs, and add note about delay
  [MiLight] Update config-sample.json to replace MiLight accessory with MiLight platform
  [MiLight] Add missing callback from hue function
  [MiLight] Modify logging to show the zone name when used as a platform accessory
  [MiLight] Converted accessory to platform. Not fully tested yet
2015-09-09 15:26:47 -04:00
Nelson Melo
c73e22984d Implemented LIFx bulb platform 2015-09-09 15:26:21 -04:00
Nick Farina
e9cca92dee Merge pull request #155 from dotsam/milight-platform
[MiLight] Convert to platform
2015-09-09 08:55:30 -07:00
Sam Edwards
d6e31b4aa7 [MiLight] Swap cooler/warmer direction for white bulbs, and add note about delay 2015-09-09 08:13:22 -07:00
S'pht'Kr
e75726436e Merge branch 'nfarina/master' into z-way-server-2 2015-09-09 06:50:22 +02:00
S'pht'Kr
62cabc23f3 Added Door/Window sensors and ancillary temperature sensors.
Door/Window sensors are still implemented as garage door openers,
because that seems to make the most sense at the moment.
2015-09-09 06:48:33 +02:00
S'pht'Kr
4170b8a533 Polling!
Updates from ZWay are now reflected in HomeKit!
2015-09-09 06:28:17 +02:00
Sam Edwards
7dc168e9dc [MiLight] Update config-sample.json to replace MiLight accessory with MiLight platform 2015-09-08 11:33:17 -07:00
Sam Edwards
18333242ff [MiLight] Add missing callback from hue function 2015-09-08 11:20:36 -07:00
Sam Edwards
5cccd3f916 [MiLight] Modify logging to show the zone name when used as a platform accessory 2015-09-08 11:01:30 -07:00
Sam Edwards
abe88b7502 [MiLight] Converted accessory to platform. Not fully tested yet 2015-09-08 10:41:03 -07:00
Nick Farina
cd8ee6a7b9 Merge pull request #154 from snowdd1/patch-1
TESTING NEEDED: Sonos platform multiple devices (for #95, #153)
2015-09-08 06:27:29 -07:00
Raoul
1df411d916 TESTING NEEDED
Proposing idea to de-asynchronize Sonos device discovery with a timeout event for push.
I have no IDE at hand right now, so please check syntax before merging!
I have no Sonos devices, so please check somebody with the hardware at hand!
Thanks
Raoul
2015-09-08 09:25:44 +02:00
Nick Farina
b05ee83e9f Merge pull request #152 from dotsam/master
[MiLight] Fix scope issue that prevented config from working
2015-09-07 17:30:41 -07:00
Sam Edwards
2710412ca6 [MiLight] Correctly reference light object, and also fix bug with brightness=0 setting the lamp to night mode 2015-09-07 16:45:27 -07:00
Sam Edwards
a4c3f73eb5 [MiLight] Fix scope issue that prevented config from working, added documentation, and added correct pause for night mode commands 2015-09-07 13:29:51 -07:00
S'pht'Kr
78987a775f Might be stable now for Switches, Dimmers, & Thermostats
Basically, I needed to provide a default value instead of a
`getDefaultValue` function. Keeping with this for a while before trying
battery stats and door sensors again, but I think I figured out my
compliance problems…don’t know how it ever successfully added before,
actually.

Also removed some more cruft from the earlier old-API version and
started laying some groundwork for polling/updating from ZWay.
2015-09-07 19:16:51 +02:00
Nick Farina
08fa203027 Merge pull request #150 from snowdd1/patch-1
missing comma
2015-09-07 07:24:49 -07:00
Raoul
6614705d6c missing comma
should have used fsonlint.com after adding the comments and secriptions
2015-09-07 15:27:30 +02:00
Nick Farina
5ba7b46c26 Merge pull request #144 from luxus/patch-1
adding resolver chain workaround for raspberry pi
2015-09-06 15:40:24 -07:00
Raoul
7c6d6ba0ac Merge pull request #1 from nfarina/master
Merge new master from Nick
2015-09-06 22:22:08 +02:00
Nick Farina
1f44801f45 Merge pull request #145 from snowdd1/knx-dev
KNX platform and accessory shim
2015-09-06 07:50:59 -07:00
Snowdd1
9d8afc4bcb cleanup of sample.json and platform shim
removed non-existent old device types from platform shim.
2015-09-06 11:41:33 +02:00
Snowdd1
09516acaf3 package.json
update eibd dependency to include future versions (^0.3.1)
2015-09-06 11:11:58 +02:00
Snowdd1
86d548b8d9 Resolve package.json conflict
Merged WEMO update
2015-09-06 09:39:51 +02:00
Snowdd1
dd0aa6e2c8 Merge remote-tracking branch 'nfarina/master' into knx-dev
Conflicts:
	package.json
2015-09-06 09:38:34 +02:00
Snowdd1
6f5e6b6a0b More devices for KNX bus
Now supports
- Lightbulb
- Thermostat
- TemperatureSensor (as of iOS 8.4.1)
- LockMechanism
2015-09-06 09:37:03 +02:00
S'pht'Kr
2c1d9f6efb Merge branch 'nfarina/master' into z-way-server-2 2015-09-06 06:37:31 +02:00
Kai
70b5a9142a adding resolver chain workaround for raspberry pi
adding the workaround from https://github.com/agnat/node_mdns/issues/130 to fix #140
2015-09-06 04:31:05 +02:00
Nick Farina
94ef18c94d Bump HAP-NodeJS with fixes for WeMo 2015-09-05 15:43:11 -07:00
Nick Farina
3cc1f381a3 Fix for WeMo switch names 2015-09-05 14:04:31 -07:00
Nick Farina
6c6b5bf85f Pull in iControl fix 2015-09-05 12:47:56 -07:00
Nick Farina
816728c0cf Upgrade WeMo accessory
- Use new Service API
  - Experimental preliminary support for WeMo "Garage Door Openers"
(via WeMo Maker). No Sensor support yet.
2015-09-05 12:42:21 -07:00
Snowdd1
e2ef8fc0b6 package.json
- removed subtypes to be passed by default.
- package.json to inbclude "eibd"
2015-09-05 21:31:48 +02:00
Snowdd1
085ae0a22e Merge remote-tracking branch 'nfarina/master' into knx-dev 2015-09-05 20:13:29 +02:00
Snowdd1
64635833d6 Sample config file
config-sample-knx.jsnon
2015-09-05 20:11:06 +02:00
Snowdd1
ea1c1f6fce Working Version
Refactored version. KNX.js is used as platform, while knxdevice is
called for each accessory to add.
2015-09-05 19:08:28 +02:00
Nick Farina
fe4cd285d0 Use once() to guard multiple callbacks
Mentioned in #95
2015-09-04 10:05:37 -07:00
Nick Farina
03417e249a Merge pull request #138 from nfarina/new-xfinity-home-platform
New xfinity home platform
2015-09-04 10:05:07 -07:00
Snowdd1
116dd1b315 still WIP
with Thermostat stub. Somehow stops working after a few lines of
console.log
2015-09-04 17:39:46 +02:00
Snowdd1
1a98a6c9ac got it straight
does not use PR#15 for node-eibd any more.
2015-09-04 16:35:11 +02:00
Snowdd1
bad0ba0c3b initial test version 2015-09-04 15:45:19 +02:00
S'pht'Kr
a677edb2cf Trying for a stable baseline
Switched Door sensors back to GarageDoorOpener services for now, and
disabled battery service…lets see if we can get this stabilized.
2015-09-04 06:18:23 +02:00
Nick Farina
a3cbf5a380 Another node-icontrol bump 2015-09-02 18:51:23 -07:00
Nick Farina
86e17a3922 Bump node-icontrol with fixes 2015-09-02 07:20:36 -07:00
S'pht'Kr
2a5c4c76fe Merge branch 'nfarina/master' into z-way-server-2 2015-09-02 06:28:02 +02:00
S'pht'Kr
30a705e79f Fixes, cleanup
Got rid of old code I clearly don’t need anymore. Switched `this.log`s
to `debug`s. Fix for picking wrong vDev for thermostat current temp.
Added configuration for `Name` characteristic…which I guess became
required.
2015-09-02 06:27:13 +02:00
Nick Farina
01d2c21aac New iControl accessory, supports Xfinity Home.
- No more mysterious "dsig" param
2015-08-31 21:39:10 -07:00
Nick Farina
dcc224f898 Merge pull request #135 from dotsam/milight
[MiLight] Update to address correct bulbs and implement all features
2015-08-31 16:27:05 -07:00
Sam Edwards
47f000ecff [MiLight] Sent on command before brightness and hue to ensure we're targeting the right lamp, and implement night mode at brightness <= 2, and implement maximum brightness command for white lamps 2015-08-31 15:40:02 -07:00
S'pht'Kr
f287c9ee36 Merge branch 'nfarina/master' into z-way-server-2 2015-08-30 14:27:07 +02:00
S'pht'Kr
b56d9346c8 Refactored for new API.
Mostly working, but door sensors need further work, battery service still isn't right, and I'm losing the bridge periodically...merging from master to see if I need some bugfixes.
2015-08-30 14:24:43 +02:00
Nick Farina
d067711974 HAP-NodeJS bump 2015-08-29 13:14:55 -07:00
Nick Farina
2c50d76cb2 Bump HAP-NodeJS 2015-08-29 09:34:58 -07:00
Nick Farina
5bc1109c19 Merge pull request #132 from KhaosT/master
[Philips Hue] retry if command failed due to api rate limit.
2015-08-28 22:29:52 -07:00
Khaos Tian
bf2216209d [Philips Hue] retry if command failed due to api rate limit. 2015-08-28 21:49:16 -07:00
Nick Farina
2ca6410e0a Merge pull request #130 from EddyK69/HaveDimmer
[Domoticz] Fixed misc. dimmer issues (SwitchType instead of HaveDimmer etc)
2015-08-27 07:04:09 -07:00
EddyK69
76eaca8f78 Fixed misc. dimmer issues
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
2015-08-27 12:49:22 +02:00
EddyK69
2bab8bad8b Merge remote-tracking branch 'nfarina/master' into HaveDimmer 2015-08-27 12:46:08 +02:00
Nick Farina
5ecfb25314 Merge pull request #129 from EddyK69/master
Added parameter 'loadscenes' & fixed dimmer-bug
2015-08-26 21:39:20 -07:00
EddyK69
b170e81059 Implemented: config.json parameter loadscenes & fixed dimmer-bug
Added parameter in config.json: 'loadscenes' for enabling/disabling
loading scenes
Fixed issue with dimmer-range; was 0-100, should be 0-16
2015-08-26 23:20:42 +02:00
EddyK69
4c088d235c Merge remote-tracking branch 'nfarina/master' 2015-08-26 15:13:30 +02:00
Nick Farina
9c8a6ee90f Merge pull request #127 from dotsam/milight-new-api
Updated MiLight Shim
2015-08-25 20:44:03 -07:00
Sam Edwards
d7d80b8618 Don't actually add a name characteristic here 2015-08-25 17:46:28 -07:00
Sam Edwards
1050b4e550 Add a name to the accessory as well 2015-08-25 12:12:58 -07:00
S'pht'Kr
13d983aa28 Merge branch 'nfarina/master' into z-way-server-2 2015-08-25 20:23:33 +02:00
Sam Edwards
7d5caae96d Add the hue characteristics for all lamps, as we're trying to handle this in some way now 2015-08-25 10:46:57 -07:00
Sam Edwards
f81e594ff0 Update readme with reference to milight under lights section 2015-08-25 10:34:45 -07:00
Sam Edwards
11f71e076c Update MiLight module for new API and add improved (or some at all) handling of lamp types other than rgbw. Also remove invalid comments from json file, and support future versions of node-milight-promise 2015-08-25 10:28:08 -07:00
Nick Farina
b3675f4be3 Merge pull request #125 from nfarina/possible-domoticz-async-fix
Fix for Domoticz platform async callbacks
2015-08-24 22:12:24 -07:00
Nick Farina
7e84806846 Merge pull request #116 from nfarina/support-new-api
Support new API in getServices()
2015-08-24 19:28:49 -07:00
Nick Farina
77fbe5b53c Fix for Domoticz platform async callbacks 2015-08-24 19:23:43 -07:00
S'pht'Kr
f72cb43043 Numerous fixes and improvements.
Committing before merging back to master, since the upstream branches have merged into master!
2015-08-24 06:48:34 +02:00
S'pht'Kr
9d7a6768b8 Working at the moment.
Support for several device types. Gotta be careful to not throw the bridge out of compliance!
2015-08-24 06:48:34 +02:00
S'pht'Kr
c6877193cc Early, but lots done
This works (sometimes at least) but has lots of flaws, including ones
that make the whole bridge unreachable.
2015-08-24 06:48:34 +02:00
S'pht'Kr
5782ff997f Initial commit of ZWayServer platform. Not much working, having problems with authenticating. 2015-08-24 06:48:34 +02:00
S'pht'Kr
ea9801fa77 Merge branch 'nfarina/master' 2015-08-24 06:46:46 +02:00
S'pht'Kr
0dd76134ee Merge branch 'nfarina/master' 2015-08-24 06:45:19 +02:00
Nick Farina
76e7ef2677 Fix identify() in Http 2015-08-23 11:45:34 -07:00
Nick Farina
dfdbc865c8 Support AccessoryInformation service 2015-08-23 10:07:31 -07:00
Nick Farina
d8e27910cc Support new API in getServices()
See `getServices()` implementation in `accessories/Http.js` for an
example of how to use.

Fixes #114
Fixes #57
2015-08-22 21:37:42 -07:00
Nick Farina
d2547f7ae6 Fix HAP-NodeJS version 2015-08-22 08:51:20 -07:00
Nick Farina
9ebf41415c Version bump for npm 2015-08-22 08:13:11 -07:00
Nick Farina
7b873bdf48 Bump HAP-NodeJS 2015-08-21 07:56:44 -07:00
Nick Farina
64c665cebb Better error display for "npm run" and config.json 2015-08-20 21:38:29 -07:00
Nick Farina
5f1df2792f Better error handling for config.json problems 2015-08-20 21:25:29 -07:00
Nick Farina
b45318c3a3 Change default bridge username 2015-08-20 11:53:40 -07:00
Nick Farina
84fe474ccf Add startup note about migration 2015-08-20 11:52:24 -07:00
Nick Farina
5df7ffb40e README update; npm run start uses DEBUG 2015-08-19 22:05:08 -07:00
Nick Farina
9c63ce5017 Allow bridge to be configured in config.json
Fixes #104
2015-08-19 21:51:25 -07:00
Nick Farina
8a58328ad7 Merge pull request #94 from nfarina/use-hap-refactor
Use WIP version of HAP-NodeJS refactor
2015-08-19 21:50:31 -07:00
Nick Farina
bf3d746089 Use now-official refactored HAP-NodeJS 2015-08-19 21:45:16 -07:00
Nick Farina
84c08d360c Bump HAP with new types 2015-08-19 16:23:09 -07:00
Nick Farina
867aec36d8 One more bump 2015-08-19 14:08:53 -07:00
Nick Farina
9f0aaa53d6 HAP bump for socket timeouts 2015-08-19 13:48:27 -07:00
Andre Schröter
c98e7a1d54 added zwave model
more cleanups
2015-08-14 10:13:23 +02:00
Nick Farina
8bda79bcb5 Merge branch 'master' into use-hap-refactor 2015-08-13 20:25:17 -07:00
Nick Farina
4d9897ff26 Fix LiftMaster shim
For those of us without multiple garage doors...
2015-08-13 20:25:06 -07:00
Nick Farina
e1647d22ad Bump HAP-NodeJS with socket fix 2015-08-13 10:10:55 -07:00
Nick Farina
3a819e202d Bump HAP-NodeJS 2015-08-13 09:28:50 -07:00
Nick Farina
2c94d72afe Merge pull request #101 from Danimal4326/master
Add Hyperion Accessory
2015-08-13 08:47:12 -07:00
Danimal4326
0a2f2865fb Add accessory shim for controlling hyperion TV backlight.
(https://github.com/tvdzwan/hyperion)
2015-08-13 10:34:35 -05:00
Danimal4326
e9d43a7bdd clean up entry for MiLight 2015-08-13 10:28:36 -05:00
Danimal4326
a5aa5b6b90 Merge remote-tracking branch 'nfarina/master' 2015-08-13 10:05:55 -05:00
Nick Farina
50f8c6a3c9 Merge branch 'master' into use-hap-refactor
Conflicts:
	platforms/Sonos.js
2015-08-09 11:16:22 -07:00
Nick Farina
9d8c5f309f Merge branch 'master' of https://github.com/nfarina/homebridge 2015-08-09 08:24:07 -07:00
Nick Farina
667ad7a3aa [Sonos] don't add duplicate rooms
Fixes #98
2015-08-09 08:22:00 -07:00
Nick Farina
11560d3d42 Merge pull request #96 from justme-1968/master
update FHEM platform to the current version
2015-08-07 08:41:21 -07:00
Nick Farina
6fff108ccd Bump HAP-NodeJS 2015-08-06 09:58:03 -07:00
Andre Schröter
7cb835e4ea more cleanups
handle sonos volume caching
added garage door opener
started with homematic keymatic (HM-SEC-KEY)
even more cleanups :)
rgb hue/sat/bri fix
added identify for lightbulbs
2015-08-06 10:02:09 +02:00
Nick Farina
43c0346436 [Sonos] SUB isn't playable 2015-08-05 08:55:19 -07:00
Nick Farina
9653a3173d Bump HAP-NodeJS 2015-08-04 20:32:05 -07:00
Nick Farina
c9aea1b731 Initialize HAP storage 2015-08-04 16:44:34 -07:00
Nick Farina
f7f953b8d1 Fix name/log var corruption 2015-08-04 16:21:06 -07:00
Nick Farina
c756ea9456 HAP-NodeJS Bump for stable IDs 2015-08-04 16:00:52 -07:00
Nick Farina
b1e6536a95 Use latest refactor commit 2015-08-04 15:49:50 -07:00
Nick Farina
f71730cff6 Restore platform support 2015-08-04 15:48:52 -07:00
Nick Farina
2ad9643bee Update HAP-NodeJS version 2015-08-04 09:24:35 -07:00
Nick Farina
6e61287f59 Use WIP version of HAP-NodeJS refactor 2015-08-03 08:33:41 -07:00
Andre Schröter
86322c30c8 ignore state set_ in addition to set- 2015-08-02 18:52:03 +02:00
Andre Schröter
d9d55c34ad winmatic bug fix
addef FHTTK contact sensor
2015-08-02 17:40:10 +02:00
Andre Schröter
2e3a26c32f more cleanups (renamed endpoints to mappings)
added longpoll reconnect on close
added winmatic (HM-SEC-WIN)
2015-08-02 13:37:12 +02:00
Andre Schröter
40e264a79d started cleanup of has... use endpoints instead 2015-08-02 01:09:53 +02:00
Andre Schröter
e0de3f2a82 don't show on/off for blinds 2015-08-01 20:15:59 +02:00
Andre Schröter
939f79b2b0 some cleanups
try to detect closed longpoll connection
added last event timestamp
2015-08-01 17:47:42 +02:00
Nick Farina
ab71dc352f Merge pull request #93 from justme-1968/master
added FHEM.js platform shim for the FHEM home automation system
2015-07-31 14:50:21 -07:00
Andre Schröter
a29cabcd91 enhanced debug browser 2015-07-31 22:44:50 +02:00
Andre Schröter
4dcc26bf50 fix for rgb vs RGB issue 2015-07-30 17:10:29 +02:00
Andre Schröter
431199a388 added event map for state on/off 2015-07-30 12:37:46 +02:00
Andre Schröter
a268583803 fixed racecondition 2015-07-29 23:41:18 +02:00
Nick Farina
1099cb50e7 Merge pull request #88 from SphtKr/yamaha-avr
Initial attempt at a Yamaha AVR control module, like Sonos.
2015-07-29 14:17:54 -07:00
Andre Schröter
f050e51518 more on EnOcean switches 2015-07-29 21:40:29 +02:00
S'pht'Kr
b5f778051e First working version, probably could be merged if no one sees anything glaringly wrong.
Config can set the play volume (in decibels per the AVR display) and the default input. If "AirPlay" is the default input, then the play command will be sent to the AVR.
2015-07-29 20:34:38 +02:00
Andre Schröter
ed6d4baba2 internal cleanups 2015-07-28 21:34:05 +02:00
S'pht'Kr
76ad483a5f WIP - Initial attempt at a Yamaha AVR controll module, like Sonos. Not working quite yet, but looks close. 2015-07-28 07:05:35 +02:00
Andre Schröter
0fb6fc736e added FHEM.js platform shim for the FHEM home automation system.
supports switches, lightbulbs (dimmers and rgb/colored lights),
homematic window blinds, max and homematic thermostats, homematic
door&window sensors, PRESENCE and sonos (including volume)

the shim is bidirectional and uses longpoll to push fhem events
back to homekit. fhem events are cached so homekit querys for known
values will not cause a roundtrip to fhem

more:
http://forum.fhem.de/index.php/topic,32652.msg314166.html#msg314166
2015-07-27 09:38:53 +02:00
Nick Farina
d4c544e793 Recommend MyTouchHome, it's generally the best. 2015-07-25 12:45:34 -07:00
Nick Farina
18cc6cbdc0 Merge pull request #77 from bezzers/Sonos-platform
Make Sonos a platform rather than an accessory
2015-07-24 08:47:24 -07:00
Nick Farina
e8769d688a Merge pull request #83 from bezzers/tesla-accessory
Fix logic issue in error reporting
2015-07-24 08:46:55 -07:00
bezzers
275a88d358 Fix logic issue in error reporting 2015-07-24 09:45:52 -04:00
bezzers
443f77196a Add missing connection to config parameter 2015-07-24 08:22:24 -04:00
bezzers
2e689eb264 Merge branch 'master' into Sonos-platform 2015-07-23 20:50:39 -04:00
Nick Farina
da79d67106 Merge pull request #80 from bezzers/tesla-accessory
Add Tesla accessory
2015-07-23 11:50:53 -07:00
Paul Beswick
385b615b81 Creation of an accessory to control the climate control for a Tesla Model S with accompanying changes to package.json (to add the teslams package) and the sample config file. 2015-07-23 13:47:41 -04:00
Nick Farina
f7a2cd5f0c Merge pull request #75 from lukeredpath/feature/add-ssl-and-auth-support-to-domoticz
Add SSL and basic auth support to Domoticz shim
2015-07-23 09:30:29 -07:00
bezzers
8b81001647 Delete Sonos as an accessory
Replaced by new Sonos platform module
2015-07-23 09:41:29 -04:00
bezzers
9325b15437 Moving Sonos configuration example from accessories to platform 2015-07-23 09:39:10 -04:00
bezzers
0b457f9b7f Create a Sonos Platform handler - first version
Creates a platform for Sonos that scans for all Sonos boxes and adds any that are not bridges (type 11) as accessories.
Reads the names of each box from the Sonos configuration and creates device names based on the Sonos room names.
2015-07-23 09:36:37 -04:00
bezzers
0605c5ff86 Create Sonos.js 2015-07-23 09:27:58 -04:00
Luke Redpath
7b01e6c1d0 Won’t be able to do much unless accessories can use SSL/basic auth too. 2015-07-22 16:12:22 +01:00
Luke Redpath
34110039f5 Add SSL and basic auth support for Domoticz platform. 2015-07-22 15:27:52 +01:00
Nick Farina
38f3d9b82e Merge pull request #70 from hachidorii/master
Add support for Nest platform.
2015-07-18 14:42:56 -07:00
Chloe Stars
511b1873f7 I forked unofficial-nest-api and added support for 'off'.
For now the package location has been changed to reflect this. I've submitted a pull request but it doesn't look like it's been touched in a while so I'm not sure it's going to get merged.
2015-07-17 18:29:23 -07:00
Chloe Stars
e76269fcdc Merge https://github.com/nfarina/homebridge 2015-07-17 18:14:56 -07:00
Chloe Stars
c4f9e81828 Oops, made a rookie mistake. Make sure I'm using local variables instead of global variables. 2015-07-17 01:08:50 -07:00
Nick Farina
db9a1bd441 Merge pull request #66 from cdrx/patch-1
Logitech Harmony should be a platform
2015-07-14 11:01:45 -07:00
Chris R
42ba55e836 Update config-sample.json
Logitech Harmony shouldn't be an accessory
2015-07-14 18:57:33 +01:00
Chloe Stars
91602e4f99 Fix heating and cooling.
It works but the API doesn’t reflect the change soon enough for HomeKit
to be aware it worked. Not sure what to do here.
2015-07-13 14:58:39 -07:00
Nick Farina
ca311ae0a8 Merge pull request #43 from madmod/logitech-harmony
Logitech Harmony platform
2015-07-13 13:40:39 -07:00
madmod
1cd697fb8b Updated README.md with Logitech Harmony 2015-07-13 13:16:29 -06:00
madmod
6cb9008424 Add get and set power state for activity 2015-07-11 22:11:08 -06:00
madmod
681cd7d9eb Add Logitech Harmony platform 2015-07-11 19:18:14 -06:00
Chloe Stars
4aafdaea9b Update the sample config file with Nest platform. 2015-07-10 23:58:41 -07:00
Chloe Stars
81cc689b7e Add the required module to package.json. 2015-07-10 23:53:57 -07:00
Chloe Stars
a47c8dd4fd Added preliminary support for the Nest platform.
Some things are missing or may not be working correctly but the main
things like checking the current temperature, the target temperature,
the humidity, setting the target temperature and etc.. I don’t think
the current heating mode is correct.
2015-07-10 23:50:31 -07:00
Nick Farina
02c90cfc51 Merge pull request #64 from hachidorii/master
Add support for getting the state of the power to WeMo accessories.
2015-07-10 16:07:30 -07:00
Chloe Stars
03293409dd Add support for getting the state of the power to WeMo accessories. 2015-07-10 15:50:07 -07:00
Nick Farina
26e833013c Merge pull request #63 from johngson/master
Adding support for Telldus Live platform
2015-07-09 20:31:43 -07:00
John Gustafsson
f3b472b2d0 Added Telldus Live! to config-sample.json 2015-07-10 00:39:58 +02:00
John Gustafsson
54a24b4fdd Added Telldus Live! support (only lights) 2015-07-10 00:36:19 +02:00
Nick Farina
16829a9e4c Merge pull request #61 from dotsam/milight
Initial support for MiLight/LimitlessLED/EasyBulb/etc... bulbs using …
2015-07-07 18:19:45 -07:00
Sam Edwards
7b7b28571d Initial support for MiLight/LimitlessLED/EasyBulb/etc... bulbs using the node-milight-promise node module 2015-07-07 16:10:24 -07:00
60 changed files with 12691 additions and 1007 deletions

4
.gitignore vendored
View File

@@ -13,4 +13,8 @@ npm-debug.log
# HomeBridge
config.json
config.test.json
persist/
log/
.AppleDouble

View File

@@ -1,9 +1,9 @@
# HomeBridge
# 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.
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.
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:
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:
* _Siri, unlock the front door._ ([Lockitron](https://lockitron.com))
* _Siri, open the garage door._ ([LiftMaster MyQ](https://www.myliftmaster.com))
@@ -11,12 +11,13 @@ Since Siri supports devices added through HomeKit, this means that with HomeBrid
* _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))
* _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/))
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.
# Shim types
There are 2 types of shims supported in HomeBridge.
There are 2 types of shims supported in Homebridge.
* Accessory - Individual device
* Platform - A full bridge to another system
@@ -27,9 +28,9 @@ Accessories are individual devices you would like to bridge to HomeKit. You set
## Platforms
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.
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.
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.
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.
# Why?
@@ -37,7 +38,7 @@ Technically, the device manufacturers should be the ones implementing the HomeKi
# Credit
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.
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.
# Before you Begin
@@ -49,31 +50,28 @@ I would call this project a "novelty" in its current form, and is for **intrepid
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.
Additionally, the shims I've created implement the bare minimum of HomeKit needed to provide basic functionality like turning things off and on. I haven't written any kind of good feedback or error handling, and although they support changing state, they don't support reading the current state, so if you ask questions like "Is my door unlocked?" Siri will respond with the default of "Nope!" no matter what.
# 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. First, clone this repo:
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
$ script/bootstrap
**Node**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load.
**Node**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load as well as the `forever` node package..
The server won't do anything until you've edited your `config.json` file containing your home devices (or _accessories_ in HomeKit parlance) or platforms you wish to make available to iOS. The sample configuration has been copied for you into `config.json`. It includes declarations for all supported accessories and platforms. Remove everything except for the accessories and platforms you'll be using.
Now you should be able to run the homebridge server:
$ cd homebridge
$ npm run start
Starting HomeBridge server...
Couldn't find a config.json file [snip]
$ script/server
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.
Once you've added your devices and/or platforms, you should be able to run the server again and see them initialize:
$ npm run start
Starting HomeBridge server...
Starting Homebridge server...
Loading 6 accessories...
[Speakers] Initializing 'Sonos' accessory...
[Coffee Maker] Initializing 'WeMo' accessory...
@@ -85,24 +83,42 @@ Once you've added your devices and/or platforms, you should be able to run the s
Your server is now ready to receive commands from iOS.
# Installing Homebridge to Run at Boot and in the Background
Homebridge can be run at boot and in the background on OS X and any Linux variation that uses SysVinit (/etc/init.d scripts) to launch services. To install homebridge as
a service:
$ script/install
It should load for you in the background. You can find logs in `log/logs.log`. To uninstall it you can run `script/uninstall`. To restart it you can run `script/restart`.
# Upgrading
If you want to upgrade homebridge, simply run:
$ script/upgrade
It will pull the newest version from the repo on GitHub and restart itself.
# Adding your devices to iOS
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.
Fortunately, there are now a few apps in the App Store that can manage your HomeKit devices. 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.
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.
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.
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).
## Adding HomeKit Accessories
Once you've gotten a HomeKit app running on your iOS device, you can begin adding accessories. The app should "discover" the accessories defined in your `config.json` file, assuming that you're still running the HomeBridge server and you're on the same Wifi network.
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`.
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`. Adding the device should create some files in the `persist` directory of the HomeBridge server, which stores the pairing relationship.
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.
# 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.
Also, keep in mind HomeKit is not very robust yet, and it is common for it to fail intermittently ("Sorry, I wasn't able to control your devices" etc.) then start working again for no reason. Also I've noticed that it will get cranky and stop working altogether sometimes. The usual voodoo applies here: reboot your device, restart the homebridge server, run your HomeKit iOS app and poke around, etc.
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".
# Final Notes

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var AD2USB = require('ad2usb');
var CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8";

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var carwings = require("carwingsjs");
function CarwingsAccessory(log, config) {

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var elkington = require("elkington");
function ElkM1Accessory(log, config) {

76
accessories/FileSensor.js Normal file
View File

@@ -0,0 +1,76 @@
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

@@ -0,0 +1,58 @@
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;

27
accessories/HomeMatic.js Executable file → Normal file
View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function HomeMatic(log, config) {
@@ -30,7 +30,31 @@ HomeMatic.prototype = {
}
});
},
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 [{
@@ -101,6 +125,7 @@ HomeMatic.prototype = {
},{
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,

View File

@@ -0,0 +1,264 @@
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,64 +1,41 @@
var types = require("HAP-NodeJS/accessories/types.js");
var sonos = require('sonos');
var types = require("hap-nodejs/accessories/types.js");
var Characteristic = require("hap-nodejs").Characteristic;
var request = require("request");
function SonosAccessory(log, config) {
function HomeMaticWindow(log, config) {
this.log = log;
this.name = config["name"];
this.playVolume = config["play_volume"];
this.device = null;
this.search();
this.ccuID = config["ccu_id"];
this.ccuIP = config["ccu_ip"];
}
SonosAccessory.prototype = {
HomeMaticWindow.prototype = {
search: function() {
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) {
sonos.search(function(device) {
that.log("Found device at " + device.host);
device.deviceDescription(function (err, description) {
if (description["zoneType"] == '3') {
that.log("Found playable device");
// device is an instance of sonos.Sonos
that.device = device;
}
});
});
},
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);
}
});
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 {
this.device.stop(function(err, success) {
that.log("Stop attempt with success: " + success);
});
}
else {
that.log("Error '"+err+"' getting Window State: " + body);
}
});
},
getServices: function() {
@@ -80,7 +57,7 @@ SonosAccessory.prototype = {
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Sonos",
initialValue: "Homematic",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
@@ -90,7 +67,7 @@ SonosAccessory.prototype = {
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-1",
initialValue: "HM-Sec-RHS",
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
@@ -117,30 +94,30 @@ SonosAccessory.prototype = {
designedMaxLength: 1
}]
},{
sType: types.SWITCH_STYPE,
sType: types.CONTACT_SENSOR_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Speakers",
initialValue: this.name,
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"],
cType: types.CONTACT_SENSOR_STATE_CTYPE,
onRead: function(callback) { that.getPowerState(callback); },
perms: ["pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the playback state of the sonos",
manfDescription: "Get Window state of a Variable",
designedMaxLength: 1
}]
}];
}
};
module.exports.accessory = SonosAccessory;
module.exports.accessory = HomeMaticWindow;

View File

@@ -1,6 +1,11 @@
var types = require("HAP-NodeJS/accessories/types.js");
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;
@@ -9,9 +14,6 @@ function HttpAccessory(log, config) {
this.off_url = config["off_url"];
this.brightness_url = config["brightness_url"];
this.http_method = config["http_method"];
// device info
this.name = config["name"];
}
HttpAccessory.prototype = {
@@ -26,135 +28,73 @@ HttpAccessory.prototype = {
})
},
setPowerState: function(powerOn) {
setPowerState: function(powerOn, callback) {
var url;
if (powerOn) {
url = this.on_url
this.log("Setting power state on the '"+this.name+"' to on");
}else{
url = this.off_url
this.log("Setting power state on the '"+this.name+"' to off");
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){
this.httpRequest(url, this.http_method, function(error, response, body) {
if (error) {
return console.error('http power function failed:', error);
}else{
return console.log('http power function succeeded!');
this.log('HTTP power function failed: %s', error.message);
callback(error);
}
});
else {
this.log('HTTP power function succeeded!');
callback();
}
}.bind(this));
},
setBrightness: function(level) {
setBrightness: function(level, callback) {
var url = this.brightness_url.replace("%b", level)
this.log("Setting brightness on the '"+this.name+"' to " + level);
this.log("Setting brightness to %s", level);
this.httpRequest(url, this.http_method, function(error, response, body){
this.httpRequest(url, this.http_method, function(error, response, body) {
if (error) {
return console.error('http brightness function failed:', error);
}else{
return console.log('http brightness function succeeded!');
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() {
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: "Http",
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); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Change the power state",
designedMaxLength: 1
},{
cType: types.BRIGHTNESS_CTYPE,
onUpdate: function(value) { that.setBrightness(value); },
perms: ["pw","pr","ev"],
format: "int",
initialValue: 0,
supportEvents: true,
supportBonjour: false,
manfDescription: "Adjust Brightness",
designedMinValue: 0,
designedMaxValue: 100,
designedMinStep: 1,
unit: "%"
}]
}];
// 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];
}
};
module.exports.accessory = HttpAccessory;

View File

@@ -0,0 +1,71 @@
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

@@ -0,0 +1,79 @@
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];
}
};

221
accessories/Hyperion.js Normal file
View File

@@ -0,0 +1,221 @@
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,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
// This seems to be the "id" of the official LiftMaster iOS app
@@ -89,10 +89,10 @@ LiftMasterAccessory.prototype = {
for (var i=0; i<devices.length; i++) {
var device = devices[i];
if (device["MyQDeviceTypeName"] == "GarageDoorOpener") {
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 == undefined) {
if (!that.requiredDeviceId) {
var thisDeviceId = device.MyQDeviceId;
var thisDoorName = "Unknown";
for (var j = 0; j < device.Attributes.length; j ++) {
@@ -103,6 +103,7 @@ LiftMasterAccessory.prototype = {
}
}
foundDoors.push(thisDeviceId + " - " + thisDoorName);
that.deviceId = thisDeviceId;
}
// We specified a door ID, sanity check to make sure it's the one we expected
@@ -302,4 +303,4 @@ LiftMasterAccessory.prototype = {
}
};
module.exports.accessory = LiftMasterAccessory;
module.exports.accessory = LiftMasterAccessory;

View File

@@ -1,196 +1,80 @@
var types = require("HAP-NodeJS/accessories/types.js");
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.lockID = config["lock_id"];
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...");
var that = this;
var query = {
access_token: this.accessToken
};
request.get({
url: "https://api.lockitron.com/v2/locks/"+this.lockID,
qs: query
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
var json = JSON.parse(body);
var state = json.state; // "lock" or "unlock"
var locked = state == "lock"
callback(locked);
}
else {
that.log("Error getting state (status code "+response.statusCode+"): " + err)
callback(undefined);
}
});
},
LockitronAccessory.prototype.getState = function(callback) {
this.log("Getting current state...");
setState: function(state) {
this.log("Set state to " + 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";
var lockitronState = (state == 1) ? "lock" : "unlock";
var that = this;
this.log("Set state to %s", lockitronState);
var query = {
access_token: this.accessToken,
state: lockitronState
};
request.put({
url: "https://api.lockitron.com/v2/locks/"+this.lockID,
qs: { access_token: this.accessToken, state: lockitronState }
}, function(err, response, body) {
request.put({
url: "https://api.lockitron.com/v2/locks/"+this.lockID,
qs: query
}, 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));
},
if (!err && response.statusCode == 200) {
that.log("State change complete.");
}
else {
that.log("Error '"+err+"' setting lock 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: "Apigee",
supportEvents: false,
supportBonjour: false,
manfDescription: "Manufacturer",
designedMaxLength: 255
},{
cType: types.MODEL_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Rev-2",
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.LOCK_MECHANISM_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Lock Mechanism",
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE,
onRead: function(callback) { that.getState(callback); },
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: 3,
designedMinStep: 1,
designedMaxLength: 1
},{
cType: types.TARGET_LOCK_MECHANISM_STATE_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
}]
},{
sType: types.LOCK_MANAGEMENT_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Lock Management",
supportEvents: false,
supportBonjour: false,
manfDescription: "Name of service",
designedMaxLength: 255
},{
cType: types.LOCK_MANAGEMENT_CONTROL_POINT_CTYPE,
onUpdate: function(value) { that.log("Update control point to " + value); },
perms: ["pw"],
format: "data",
initialValue: 0,
supportEvents: false,
supportBonjour: false,
manfDescription: "BlaBla",
designedMaxLength: 255
},{
cType: types.VERSION_CTYPE,
onUpdate: function(value) { that.log("Update version to " + value); },
perms: ["pr"],
format: "string",
initialValue: "1.0",
supportEvents: false,
supportBonjour: false,
manfDescription: "BlaBla",
designedMaxLength: 255
}]
}];
}
};
module.exports.accessory = LockitronAccessory;
LockitronAccessory.prototype.getServices = function() {
return [this.service];
}

119
accessories/Tesla.js Normal file
View File

@@ -0,0 +1,119 @@
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,139 +1,169 @@
var types = require("HAP-NodeJS/accessories/types.js");
var Service = require("hap-nodejs").Service;
var Characteristic = require("hap-nodejs").Characteristic;
var wemo = require('wemo');
// extend our search timeout from 5 seconds to 60
wemo.SearchTimeout = 60000;
wemo.timeout = wemo.SearchTimeout // workaround for a bug in wemo.js v0.0.4
module.exports = {
accessory: WeMoAccessory
}
function WeMoAccessory(log, config) {
this.log = log;
this.name = config["name"];
this.wemoName = config["wemo_name"];
this.device = null;
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() {
var that = this;
wemo.Search(this.wemoName, function(err, device) {
if (!err && device) {
that.log("Found '"+that.wemoName+"' device at " + device.ip);
that.device = new wemo(device.ip, device.port);
}
else {
that.log("Error finding device '" + that.wemoName + "': " + err);
that.log("Continuing search for WeMo device with exact name '" + that.wemoName + "'...");
that.search();
}
});
},
setPowerState: function(powerOn) {
if (!this.device) {
this.log("No '"+this.wemoName+"' device found (yet?)");
return;
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));
}
var binaryState = powerOn ? 1 : 0;
var that = this;
WeMoAccessory.prototype.getMotion = function(callback) {
this.log("Setting power state on the '"+this.wemoName+"' to " + binaryState);
this.device.setBinaryState(binaryState, function(err, result) {
if (!err) {
that.log("Successfully set power state on the '"+that.wemoName+"' to " + binaryState);
}
else {
that.log("Error setting power state on the '"+that.wemoName+"'")
}
});
},
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); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Change the power state of the WeMo",
designedMaxLength: 1
}]
}];
if (!this.device) {
this.log("No '%s' device found (yet?)", this.wemoName);
callback(new Error("Device not found"), false);
return;
}
};
module.exports.accessory = WeMoAccessory;
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,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function X10(log, config) {

View File

@@ -1,284 +0,0 @@
var types = require("HAP-NodeJS/accessories/types.js");
var request = require("request");
var xmldoc = require("xmldoc");
function XfinityHomeAccessory(log, config) {
this.log = log;
this.name = config["name"];
this.email = config["email"];
this.password = config["password"];
this.dsig = config["dsig"];
this.pinCode = config["pin"];
}
XfinityHomeAccessory.prototype = {
armWithType: function(armed, type) {
this.log("Arming with type " + type + " = " + armed + "...");
this.targetArmed = armed;
this.targetArmType = type;
this.getLoginToken();
},
getLoginToken: function() {
this.log("Retrieving login token...");
var that = this;
request.post({
url: "https://login.comcast.net/api/login",
form: {
appkey:"iControl",
dsig: this.dsig,
u: this.email,
p: this.password
}
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
var doc = new xmldoc.XmlDocument(body);
that.loginToken = doc.valueWithPath("LoginToken");
that.refreshLoginCookie();
}
else {
that.log("Error '"+err+"' getting login token: " + body);
}
});
},
refreshLoginCookie: function() {
this.log("Refreshing login cookie...");
var that = this;
request.post({
url: "https://www.xfinityhomesecurity.com/rest/icontrol/login",
form: {
token: this.loginToken
}
}, function(err, response, body) {
if (!err && response.statusCode == 200) {
// extract our "site" from the login response
var json = JSON.parse(body);
that.siteHref = json["login"]["site"]["href"];
// manual cookie handling
that.loginCookie = response.headers["set-cookie"];
that.getInstances();
}
else {
that.log("Error '"+err+"' refreshing login cookie: " + body);
}
});
},
getInstances: function() {
this.log("Getting instances for site " + this.siteHref + "...");
this.panelHref = null;
var that = this;
request.get({
url: "https://www.xfinityhomesecurity.com/"+that.siteHref+"/network/instances",
headers: { Cookie: this.loginCookie },
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
// extract our "instance" from the response. look for the first "panel"
var instances = json["instances"]["instance"];
for (var i=0; i<instances.length; i++) {
var instance = instances[i];
if (instance["mediaType"] == "instance/panel") {
that.panelHref = instance.href;
}
}
if (that.panelHref) {
that.log("Found panel " + that.panelHref + ". Ready to arm.");
that.finishArm();
}
else {
that.log("Couldn't find a panel.");
}
}
else {
that.log("Error '"+err+"' getting instances: " + JSON.stringify(json));
}
});
},
finishArm: function() {
this.log("Finish arming with type " + this.targetArmType + " = " + this.targetArmed + "...");
var path, form;
var that = this;
if (!this.targetArmed) {
path = this.panelHref + "/functions/disarm";
form = {code: this.pinCode};
}
else {
path = this.panelHref + "/functions/arm";
form = {code: this.pinCode, armType: this.targetArmType };
}
request.post({
url: "https://www.xfinityhomesecurity.com"+path,
headers: { Cookie: this.loginCookie },
form: form
}, function(err, response, body) {
if (!err && response.statusCode >= 200 && response.statusCode < 300) {
that.log("Arm response: " + response);
}
else {
that.log("Error '"+err+"' performing arm request: " + 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: "Comcast",
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: "Away Mode",
supportEvents: false,
supportBonjour: false,
manfDescription: "Away Mode service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.armWithType(value, "away"); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Turn on the Away alarm",
designedMaxLength: 1
}]
},{
sType: types.SWITCH_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Night Mode",
supportEvents: false,
supportBonjour: false,
manfDescription: "Night Mode service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.armWithType(value, "night"); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Turn on the Night alarm",
designedMaxLength: 1
}]
},{
sType: types.SWITCH_STYPE,
characteristics: [{
cType: types.NAME_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: "Stay Mode",
supportEvents: false,
supportBonjour: false,
manfDescription: "Stay Mode service",
designedMaxLength: 255
},{
cType: types.POWER_STATE_CTYPE,
onUpdate: function(value) { that.armWithType(value, "stay"); },
perms: ["pw","pr","ev"],
format: "bool",
initialValue: false,
supportEvents: false,
supportBonjour: false,
manfDescription: "Turn on the Stay alarm",
designedMaxLength: 1
}]
}];
}
};
// Enable cookie handling and append our expected headers
request = request.defaults({
headers: {
"X-appkey": "comcastTokenKey",
"X-ClientInfo": "5.2.51",
"X-format": "json"
}
});
module.exports.accessory = XfinityHomeAccessory;

130
accessories/iControl.js Normal file
View File

@@ -0,0 +1,130 @@
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);
}

1032
accessories/knxdevice.js Normal file

File diff suppressed because it is too large Load Diff

89
accessories/mpdclient.js Normal file
View File

@@ -0,0 +1,89 @@
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];
}
};

286
app.js
View File

@@ -1,10 +1,24 @@
var fs = require('fs');
var path = require('path');
var storage = require('node-persist');
var crypto = require('crypto');
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");
@@ -14,176 +28,196 @@ if (!fs.existsSync(configPath)) {
process.exit(1);
}
// Initialize persistent storage
storage.initSync();
// Initialize HAP-NodeJS
hap.init();
// Load up the configuration file
var config = JSON.parse(fs.readFileSync(configPath));
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;
}
// Just to prevent them getting garbage collected
var accessories = [];
// 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 accessoryName = accessoryConfig["accessory"]; // like "WeMo"
var accessoryModule = require('./accessories/' + accessoryName + ".js"); // like "./accessories/WeMo.js"
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 name = accessoryConfig["name"];
var log = function(name) { return function(s) { console.log("[" + name + "] " + s); }; }(name);
var accessoryName = accessoryConfig["name"];
var log = createLog(accessoryName);
log("Initializing " + accessoryName + " accessory...");
var accessory = new accessoryConstructor(log, accessoryConfig);
accessories.push(accessory);
// Extract the raw "services" for this accessory which is a big array of objects describing the various
// hooks in and out of HomeKit for the HAP-NodeJS server.
var services = accessory.getServices();
// Create the HAP server for this accessory
createHAPServer(name, services, accessory.transportCategory);
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 platformName = platformConfig["platform"]; // like "Wink"
var platformModule = require('./platforms/' + platformName + ".js"); // like "./platforms/Wink.js"
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 display name for debugging
var name = platformConfig["name"];
var log = function(name) { return function(s) { console.log("[" + name + "] " + s); }; }(name);
// Create a custom logging function that prepends the platform name for debugging
var log = createLog(platformName);
log("Initializing " + platformName + " platform...");
log("Initializing %s platform...", platformType);
var platform = new platformConstructor(log, platformConfig);
// query for devices
platform.accessories(function(foundAccessories){
// loop through accessories adding them to the list and registering them
for (var i = 0; i < foundAccessories.length; i++) {
accessory = foundAccessories[i]
accessories.push(accessory);
log("Initializing device with name " + accessory.name + "...")
// Extract the raw "services" for this accessory which is a big array of objects describing the various
// hooks in and out of HomeKit for the HAP-NodeJS server.
var services = accessory.getServices();
// Create the HAP server for this accessory
createHAPServer(accessory.name, services, accessory.transportCategory);
}
accessories.push.apply(accessories, foundAccessories);
})
var platformInstance = new platformConstructor(log, platformConfig);
loadPlatformAccessories(platformInstance, log, platformType);
}
}
//
// Creates the actual HAP servers which listen on different sockets
//
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);
// Pull in required HAP-NodeJS stuff
var accessory_Factor = new require("HAP-NodeJS/Accessory.js");
var accessoryController_Factor = new require("HAP-NodeJS/AccessoryController.js");
var service_Factor = new require("HAP-NodeJS/Service.js");
var characteristic_Factor = new require("HAP-NodeJS/Characteristic.js");
// Each accessory has its own little server. We'll need to allocate some ports for these servers
var nextPort = 51826;
var nextServer = 0;
var accessoryServers = [];
var accessoryControllers = [];
var usernames = {};
function createHAPServer(name, services, transportCategory) {
var accessoryController = new accessoryController_Factor.AccessoryController();
//loop through services
for (var j = 0; j < services.length; j++) {
var service = new service_Factor.Service(services[j].sType);
//loop through characteristics
for (var k = 0; k < services[j].characteristics.length; k++) {
var options = {
onRead: services[j].characteristics[k].onRead,
onRegister: services[j].characteristics[k].onRegister,
type: services[j].characteristics[k].cType,
perms: services[j].characteristics[k].perms,
format: services[j].characteristics[k].format,
initialValue: services[j].characteristics[k].initialValue,
supportEvents: services[j].characteristics[k].supportEvents,
supportBonjour: services[j].characteristics[k].supportBonjour,
manfDescription: services[j].characteristics[k].manfDescription,
designedMaxLength: services[j].characteristics[k].designedMaxLength,
designedMinValue: services[j].characteristics[k].designedMinValue,
designedMaxValue: services[j].characteristics[k].designedMaxValue,
designedMinStep: services[j].characteristics[k].designedMinStep,
unit: services[j].characteristics[k].unit
};
var characteristic = new characteristic_Factor.Characteristic(options, services[j].characteristics[k].onUpdate);
service.addCharacteristic(characteristic);
}
accessoryController.addService(service);
}
// create a unique "username" for this accessory based on the default display name
var username = createUsername(name);
if (usernames[username]) {
console.log("Cannot create another accessory with the same name '" + name + "'. The 'name' property must be unique for each accessory.");
return;
}
// remember that we used this name already
usernames[username] = name;
// increment ports for each accessory
nextPort = nextPort + (nextServer*2);
// hardcode the PIN to something random (same PIN as HAP-NodeJS sample accessories)
var pincode = "031-45-154";
var accessory = new accessory_Factor.Accessory(name, username, storage, parseInt(nextPort), pincode, accessoryController, transportCategory);
accessoryServers[nextServer] = accessory;
accessoryControllers[nextServer] = accessoryController;
accessory.publishAccessory();
nextServer++;
// add it to the bridge
bridge.addBridgedAccessory(accessory);
}
// were we the last callback?
if (asyncCalls === 0 && !asyncWait)
publish();
}));
}
// Creates a unique "username" for HomeKit from a hash of the given string
function createUsername(str) {
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.
// Hash str into something like "098F6BCD4621D373CADE4E832627B4F6"
var hash = crypto.createHash('md5').update(str).digest("hex").toUpperCase();
// 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;
}
}
// Turn it into a MAC-address-looking "username" for HomeKit
return hash[0] + hash[1] + ":" +
hash[2] + hash[3] + ":" +
hash[4] + hash[5] + ":" +
hash[6] + hash[7] + ":" +
hash[8] + hash[9] + ":" +
hash[10] + hash[11];
// 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();

View File

@@ -1,7 +1,32 @@
{
"bridge": {
"name": "Homebridge",
"username": "CC:22:3D:E3:CE:30",
"port": 51826,
"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",
@@ -20,7 +45,9 @@
"platform": "Domoticz",
"name": "Domoticz",
"server": "127.0.0.1",
"port": "8005"
"port": "8080",
"roomid": 0,
"loadscenes": 1
},
{
"platform": "PhilipsHue",
@@ -34,6 +61,50 @@
"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"
}
],
@@ -52,12 +123,6 @@
"username": "your-liftmaster-username",
"password" : "your-liftmaster-password"
},
{
"accessory": "Sonos",
"name": "Speakers",
"description": "This shim supports Sonos devices on the same network as this server. It acts as a simple switch that calls play() or pause() on the Sonos, so it's only useful for pausing and resuming tracks or radio stations that are already in the queue. When 'play_volume' is nonzero, the volume will be reset to that value when it turns the Sonos on.",
"play_volume": 25
},
{
"accessory": "Lockitron",
"name": "Front Door",
@@ -73,21 +138,39 @@
"password" : "your-carwings-password"
},
{
"accessory": "XfinityHome",
"accessory": "iControl",
"name": "Xfinity Home",
"description": "This shim supports the 'Xfinity Home' security system. Unfortunately I don't know how to generate the 'dsig' property, so you'll need to figure yours out by running the Xfinity Home app on your iOS device while connected to a proxy server like Charles. If you didn't understand any of that, sorry! I welcome any suggestions for how to figure out dsig automatically.",
"email": "your-comcast-email@example.com",
"description": "This shim supports iControl-based security systems like Xfinity Home.",
"system": "XFINITY_HOME",
"email": "your-comcast-email",
"password": "your-comcast-password",
"dsig": "your-digital-signature",
"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",
@@ -103,7 +186,20 @@
"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.",
@@ -120,6 +216,47 @@
"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"
}
]
}

View File

@@ -1,9 +1,9 @@
{
"name": "homebridge",
"description": "HomeKit support for the impatient",
"version": "0.0.0",
"version": "0.1.1",
"scripts": {
"start": "node app.js"
"start": "DEBUG=* node app.js || true"
},
"repository": {
"type": "git",
@@ -11,17 +11,39 @@
},
"license": "ISC",
"dependencies": {
"hap-nodejs": "git+https://github.com/khaost/HAP-NodeJS#2a1bc8d99a2009317ab5da93faebea34c89f197c",
"ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local",
"request": "2.49.x",
"node-persist": "0.0.x",
"xmldoc": "0.1.x",
"node-hue-api": "^1.0.5",
"xml2js": "0.4.x",
"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",
"elkington": "kevinohara80/elkington"
"xml2js": "0.4.x",
"xmldoc": "0.1.x",
"yamaha-nodejs": "0.4.x"
}
}

View File

@@ -8,6 +8,22 @@
// - 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
//
@@ -18,24 +34,40 @@
// "name": "Domoticz",
// "server": "127.0.0.1",
// "port": "8080",
// "roomid": 123 (0=no roomplan)
// "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 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) {
@@ -46,35 +78,50 @@ function sortByKey(array, key) {
}
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 = [];
if (this.roomid == 0) {
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: "http://" + this.server + ":" + this.port + "/json.htm?type=devices&filter=light&used=true&order=Name",
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) {
accessory = new DomoticzAccessory(that.log, that.server, that.port, false, s.idx, s.Name, s.HaveDimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW"));
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);
})
}
callback(foundAccessories);
callbackLater();
} else {
that.log("There was a problem connecting to Domoticz.");
that.log("There was a problem connecting to Domoticz. (" + err + ")");
}
});
}
else {
}
else {
//Get all devices specified in the room
asyncCalls++;
request.get({
url: "http://" + this.server + ":" + this.port + "/json.htm?type=devices&plan=" + this.roomid,
url: this.urlForQuery("type=devices&plan=" + this.roomid),
json: true
}, function(err, response, json) {
if (!err && response.statusCode == 200) {
@@ -83,40 +130,43 @@ DomoticzPlatform.prototype = {
sArray.map(function(s) {
//only accept switches for now
if (typeof s.SwitchType != 'undefined') {
accessory = new DomoticzAccessory(that.log, that.server, that.port, false, s.idx, s.Name, s.HaveDimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW"));
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);
}
})
}
callback(foundAccessories);
callbackLater();
} else {
that.log("There was a problem connecting to Domoticz.");
}
});
}
}
//Get Scenes
foundAccessories = [];
request.get({
url: "http://" + this.server + ":" + this.port + "/json.htm?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.server, that.port, true, s.idx, s.Name, false, 0, false);
foundAccessories.push(accessory);
})
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.");
}
callback(foundAccessories);
} else {
that.log("There was a problem connecting to Domoticz.");
}
});
});
}
}
}
function DomoticzAccessory(log, server, port, IsScene, idx, name, HaveDimmer, MaxDimLevel, HaveRGB) {
function DomoticzAccessory(log, platform, IsScene, idx, name, HaveDimmer, MaxDimLevel, HaveRGB) {
// device info
this.IsScene = IsScene;
this.idx = idx;
@@ -125,8 +175,7 @@ function DomoticzAccessory(log, server, port, IsScene, idx, name, HaveDimmer, Ma
this.MaxDimLevel = MaxDimLevel;
this.HaveRGB = HaveRGB;
this.log = log;
this.server = server;
this.port = port;
this.platform = platform;
}
DomoticzAccessory.prototype = {
@@ -135,13 +184,14 @@ DomoticzAccessory.prototype = {
if (this.IsScene == false) {
//Lights
if (c == "On" || c == "Off") {
url = "http://" + this.server + ":" + this.port + "/json.htm?type=command&param=switchlight&idx=" + this.idx + "&switchcmd=" + c + "&level=0";
url = this.platform.urlForQuery("type=command&param=switchlight&idx=" + this.idx + "&switchcmd=" + c + "&level=0");
}
else if (c == "setHue") {
url = "http://" + this.server + ":" + this.port + "/json.htm?type=command&param=setcolbrightnessvalue&idx=" + this.idx + "&hue=" + value + "&brightness=100" + "&iswhite=false";
url = this.platform.urlForQuery("type=command&param=setcolbrightnessvalue&idx=" + this.idx + "&hue=" + value + "&brightness=100" + "&iswhite=false");
}
else if (c == "setLevel") {
url = "http://" + this.server + ":" + this.port + "/json.htm?type=command&param=switchlight&idx=" + this.idx + "&switchcmd=Set%20Level&level=" + value;
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);
@@ -150,7 +200,7 @@ DomoticzAccessory.prototype = {
else {
//Scenes
if (c == "On" || c == "Off") {
url = "http://" + this.server + ":" + this.port + "/json.htm?type=command&param=switchscene&idx=" + this.idx + "&switchcmd=" + c;
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);
@@ -163,11 +213,19 @@ DomoticzAccessory.prototype = {
that.log("There was a problem sending command " + c + " to" + that.name);
that.log(url);
} else {
that.log(that.name + " sent command " + c);
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 [
{
@@ -294,11 +352,11 @@ DomoticzAccessory.prototype = {
},
sType: function() {
if (this.HaveDimmer == true) {
//if (this.HaveDimmer == true) {
return types.LIGHTBULB_STYPE
} else {
return types.SWITCH_STYPE
}
//} else {
// return types.SWITCH_STYPE
//}
},
getServices: function() {
@@ -317,4 +375,4 @@ DomoticzAccessory.prototype = {
};
module.exports.accessory = DomoticzAccessory;
module.exports.platform = DomoticzPlatform;
module.exports.platform = DomoticzPlatform;

2159
platforms/FHEM.js Normal file

File diff suppressed because it is too large Load Diff

253
platforms/FibaroHC2.js Normal file
View File

@@ -0,0 +1,253 @@
// 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;

542
platforms/HomeAssistant.js Normal file
View File

@@ -0,0 +1,542 @@
// 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;

1066
platforms/HomeSeer.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var xml2js = require('xml2js');
var request = require('request');
var util = require('util');

552
platforms/Indigo.js Normal file
View File

@@ -0,0 +1,552 @@
// 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

@@ -0,0 +1,156 @@
{
"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": [
]
}

205
platforms/KNX.js Normal file
View File

@@ -0,0 +1,205 @@
/** 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;

213
platforms/KNX.md Normal file
View File

@@ -0,0 +1,213 @@
# 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!**

302
platforms/LIFx.js Normal file
View File

@@ -0,0 +1,302 @@
'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

@@ -0,0 +1,267 @@
'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;

242
platforms/MiLight.js Normal file
View File

@@ -0,0 +1,242 @@
/*
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];
}
};

397
platforms/Nest.js Normal file
View File

@@ -0,0 +1,397 @@
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;

347
platforms/Openhab.js Normal file
View File

@@ -0,0 +1,347 @@
// 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

@@ -33,7 +33,7 @@ var hue = require("node-hue-api"),
HueApi = hue.HueApi,
lightState = hue.lightState;
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
function PhilipsHuePlatform(log, config) {
this.log = log;
@@ -167,7 +167,7 @@ PhilipsHueAccessory.prototype = {
// Convert 0-65535 to 0-360
hueToArcDegrees: function(value) {
value = value/65535;
value = value*100;
value = value*360;
value = Math.round(value);
return value;
},
@@ -216,7 +216,13 @@ PhilipsHueAccessory.prototype = {
that.log(device.name + ", characteristic: " + characteristic + ", value: " + value + ".");
}
else {
that.log(err);
if (err.code == "ECONNRESET") {
setTimeout(function() {
that.executeChange(api, device, characteristic, value);
}, 300);
} else {
that.log(err);
}
}
});
},

View File

@@ -1,7 +1,7 @@
// SmartThings JSON API SmartApp required
// https://github.com/jnewland/SmartThings/blob/master/JSON.groovy
//
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var request = require("request");
function SmartThingsPlatform(log, config){

188
platforms/Sonos.js Normal file
View File

@@ -0,0 +1,188 @@
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;

265
platforms/Telldus.js Normal file
View File

@@ -0,0 +1,265 @@
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;

265
platforms/TelldusLive.js Normal file
View File

@@ -0,0 +1,265 @@
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,4 +1,4 @@
var types = require("HAP-NodeJS/accessories/types.js");
var types = require("hap-nodejs/accessories/types.js");
var wink = require('wink-js');
var model = {

238
platforms/YamahaAVR.js Normal file
View File

@@ -0,0 +1,238 @@
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;

1012
platforms/ZWayServer.js Normal file

File diff suppressed because it is too large Load Diff

763
platforms/isy-js.js Normal file
View File

@@ -0,0 +1,763 @@
/*
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;

33
script/bootstrap Executable file
View File

@@ -0,0 +1,33 @@
#!/bin/sh
set -e
if ! test $(which forever)
then
echo
echo "!!!!"
echo "You don't have forever installed. You need to install it first."
echo
echo "Just install it with this command: "
echo 'sudo npm install forever -g'
echo
exit 1
fi
mkdir -p log
echo "Installing packages..."
if [[ "$OSTYPE" == "linux*" ]]; then
echo "This might take a while on a Raspberry Pi..."
fi
npm install > /dev/null 2>&1
if [ ! -f config.json ]
then
echo
echo "==> Creating your config. Please edit config.json."
echo
cp config-sample.json config.json
fi
echo "Finished setting up Homebridge! run it with script/server or install it with script/install."

44
script/install Executable file
View File

@@ -0,0 +1,44 @@
#!/bin/sh
set -e
echo "Installing Homebridge..."
APP_PATH=`pwd`
FOREVER_PATH=`which forever || true`
USER_NAME=`whoami`
if [[ "$OSTYPE" == "linux*" ]]; then
## install for linux
# copy SysVinit script to the correct path.
sudo cp startup/homebridge /etc/init.d/homebridge
# set exec permissions on script.
sudo chmod +x /etc/init.d/homebridge
# Set the current path for where the app lives.
sudo sed -i '' -e "s#%PATH%#$APP_PATH#g" /etc/init.d/homebridge
# Set the path for where the forever lives.
sudo sed -i '' -e "s#%FOREVER_PATH%#$FOREVER_PATH#g" /etc/init.d/homebridge
# Start it.
sudo /etc/init.d/homebridge start
# tell it to launch at boot
sudo update-rc.d homebridge defaults
elif [[ "$OSTYPE" == "darwin"* ]]; then
## install for OS X
# copy template plist to the correct path.
cp startup/org.homebridge.plist ~/Library/LaunchAgents/org.homebridge.plist
# Set the current user for the script to run as.
sed -i '' -e "s#%USER%#$USER_NAME#g" ~/Library/LaunchAgents/org.homebridge.plist
# Set the current path for where the app lives.
sed -i '' -e "s#%PATH%#$APP_PATH#g" ~/Library/LaunchAgents/org.homebridge.plist
# Tell launchd to start it.
launchctl load -w -F ~/Library/LaunchAgents/org.homebridge.plist
fi

4
script/restart Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
script/uninstall
script/install

12
script/server Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/sh
FOREVER_PATH=`which forever`
test -z "$NODE_ENV" &&
export NODE_ENV='development'
if [ "$NODE_ENV" = "development" ]; then
$FOREVER_PATH -f startup/forever/development.json
else
$FOREVER_PATH start startup/forever/production.json
fi

22
script/uninstall Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/sh
echo "Uninstalling Homebridge..."
# make forever stop the app.
forever stop homebridge > /dev/null 2>&1
if [[ "$OSTYPE" == "linux*" ]]; then
## uninstall for linux
# stop the service.
sudo /etc/init.d/homebridge stop
# remove the service from launching at boot.
sudo update-rc.d -f homebridge remove
elif [[ "$OSTYPE" == "darwin"* ]]; then
## uninstall for OS X
# tell launchd to stop the process.
launchctl unload ~/Library/LaunchAgents/org.homebridge.plist
# remove the launchd script.
rm ~/Library/LaunchAgents/org.homebridge.plist
fi

8
script/upgrade Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
echo "Updating from GitHub..."
BRANCH=$(git rev-parse --abbrev-ref HEAD)
git pull origin $BRANCH
npm update
script/restart

View File

@@ -0,0 +1,4 @@
{
"append": true,
"script": "app.js"
}

View File

@@ -0,0 +1,7 @@
{
"uid": "homebridge",
"append": true,
"script": "app.js",
"outFile": "log/logs.log",
"errFile": "log/error.log"
}

88
startup/homebridge Normal file
View File

@@ -0,0 +1,88 @@
### BEGIN INIT INFO
# If you wish the Daemon to be lauched at boot / stopped at shutdown :
#
# On Debian-based distributions:
# INSTALL : update-rc.d scriptname defaults
# (UNINSTALL : update-rc.d -f scriptname remove)
#
# On RedHat-based distributions (CentOS, OpenSUSE...):
# INSTALL : chkconfig --level 35 scriptname on
# (UNINSTALL : chkconfig --level 35 scriptname off)
#
# chkconfig: 2345 90 60
# Provides: %PATH%/app.js
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: forever running %PATH%/app.js
# Description: %PATH%/app.js
### END INIT INFO
#
# initd a node app
# Based on a script posted by https://gist.github.com/jinze at https://gist.github.com/3748766
#
if [ -e /lib/lsb/init-functions ]; then
# LSB source function library.
. /lib/lsb/init-functions
fi;
pidFile="/var/run/homebridge.pid"
logFile="%PATH%/log/logs.log"
command="node"
nodeApp="%PATH%/app.js"
foreverApp="%FOREVER_PATH%"
start() {
echo "Starting $nodeApp"
# Notice that we change the PATH because on reboot
# the PATH does not include the path to node.
# Launching forever with a full path
# does not work unless we set the PATH.
PATH=/usr/local/bin:$PATH
export NODE_ENV=production
#PORT=80
$foreverApp start --pidFile $pidFile -l $logFile -a -d -c "$command" $nodeApp
RETVAL=$?
}
restart() {
echo -n "Restarting $nodeApp"
$foreverApp restart $nodeApp
RETVAL=$?
}
stop() {
echo -n "Shutting down $nodeApp"
$foreverApp stop $nodeApp
RETVAL=$?
}
status() {
echo -n "Status $nodeApp"
$foreverApp list
RETVAL=$?
}
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status
;;
restart)
restart
;;
*)
echo "Usage: {start|stop|status|restart}"
exit 1
;;
esac
exit $RETVAL

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.homebridge</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin/:/usr/bin:$PATH</string>
<key>NODE_ENV</key>
<string>production</string>
</dict>
<key>Program</key>
<string>script/server</string>
<key>AbandonProcessGroup</key>
<false/>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>WorkingDirectory</key>
<string>%PATH%</string>
<key>StandardErrorPath</key>
<string>/Users/%USER%/Library/Logs/homebridge.log</string>
<key>StandardOutPath</key>
<string>/Users/%USER%/Library/Logs/homebridge.log</string>
</dict>
</plist>