diff --git a/.gitignore b/.gitignore index 1ed5cf8..137b758 100644 --- a/.gitignore +++ b/.gitignore @@ -7,14 +7,6 @@ node_modules/ npm-debug.log .node-version -# Intellij -.idea/ -*.iml - -# HomeBridge -config.json -config.test.json -persist/ - - -.AppleDouble +# Ignore any extra plugins in the example directory that aren't in Git already +# (this is a sandbox for the user) +example-plugins \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29..0000000 diff --git a/README.md b/README.md index ab3b98a..5203244 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,75 @@ +# IMPORTANT + +Homebridge has recently spun off its included accessories into a new module [homebridge-legacy-plugins](https://github.com/nfarina/homebridge-legacy-plugins). Please do not open any issues related to specific devices in this repository; go there instead. + # 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 supports Plugins, which are community-contributed modules that provide a basic bridge from HomeKit to various 3rd-party APIs provided by manufacturers of "smart home" devices. -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 just some of the available plugins, you can say: - * _Siri, unlock the front door._ ([Lockitron](https://lockitron.com)) - * _Siri, open the garage door._ ([LiftMaster MyQ](https://www.myliftmaster.com)) - * _Siri, turn on the Leaf._ ([Carwings](http://www.nissanusa.com/innovations/carwings.article.html)) - * _Siri, turn off the Speakers._ ([Sonos](http://www.sonos.com)) - * _Siri, turn on the Dehumidifier._ ([WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/)) - * _Siri, turn on Away Mode._ ([Xfinity Home](http://www.comcast.com/home-security.html)) - * _Siri, turn on the living room lights._ ([Wink](http://www.wink.com), [SmartThings](http://www.smartthings.com), [X10](http://github.com/edc1591/rest-mochad), [Philips Hue](http://meethue.com), [Home Assistant](http://home-assistant.io) [LimitlessLED/MiLight/Easybulb](http://www.limitlessled.com/), [LIFx](http://www.lifx.com/)) - * _Siri, set the movie scene._ ([Logitech Harmony](http://myharmony.com/)) + * _Siri, unlock the front door._ + * _Siri, open the garage door._ + * _Siri, turn on the coffee maker._ + * _Siri, turn on the living room lights._ + * _Siri, good morning!_ -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. +You can explore all available plugins at the NPM website by [searching for the keyword `homebridge-plugin`](https://www.npmjs.com/browse/keyword/homebridge-plugin). -# Shim types -There are 2 types of shims supported in Homebridge. +# Installation -* Accessory - Individual device -* Platform - A full bridge to another system +**Note:** If you're running on Linux, you'll need to make sure you have the `libavahi-compat-libdnssd-dev` package installed. If you're running on a Raspberry Pi, you should have a look at the [Wiki](/nfarina/homebridge/wiki/Running-HomeBridge-on-a-Raspberry-Pi). -## Accessories +Homebridge is published through [NPM](https://www.npmjs.com/package/homebridge) and should be installed "globally" by typing: -Accessories are individual devices you would like to bridge to HomeKit. You set them up by declaring them individually in your `config.json` file. Generally, you specify them by `name` or `id` and which system they use. + npm install -g homebridge -## Platforms +You may have to execute commands with `sudo` depending on your system. Now you should be able to run Homebridge: -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. + $ `homebridge` + No plugins found. See the README for information on installing plugins. -All you have to do is add the right config options so Homebridge can authenticate and communicate with your other system, and voila, your devices will be available to HomeKit via Homebridge. +Homebridge will complain if you don't have any Plugins installed, since it will essentially be useless, although you can still "pair" with it. See the next section "Installing Plugins" for more info. -# Why? +Once you've installed a Plugin or two, you can run Homebridge again: -Technically, the device manufacturers should be the ones implementing the HomeKit API. And I'm sure they will - eventually. When they do, these shims will be obsolete, and I hope that happens soon. In the meantime, this server is a fun way to get a taste of the future, for those who just can't bear to wait until "real" HomeKit devices are on the market. - -# 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. - -# Before you Begin - -I would call this project a "novelty" in its current form, and is for **intrepid hackers only**. To make any of this work, you'll need: - - * An app on your iOS device that can manage your HomeKit database. - * An always-running server (like a Raspberry Pi) on which you can install NodeJS. - * Knowledge of Git submodules and npm. - -You'll also need some patience, as Siri can be very strict about sentence structure, and occasionally she will forget about HomeKit altogether. But it's not surprising that HomeKit isn't rock solid, since almost no one can actually use it today besides developers who are creating hardware accessories for it. There are, to my knowledge, exactly zero licensed HomeKit devices on the market right now, so Apple can easily get away with this all being a work in progress. - -# Getting Started - -OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up. - -**Note:** If you're running on Linux, you'll need to make sure you have the `libavahi-compat-libdnssd-dev` package installed. - -First, clone this repo: - - $ git clone https://github.com/nfarina/homebridge.git - $ cd homebridge - $ npm install - -**Note**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load. - - -Now you should be able to run the homebridge server: - - $ cd homebridge - $ npm run start - Starting Homebridge server... + $ `homebridge` Couldn't find a config.json file [snip] -The server won't do anything until you've created a `config.json` file containing your home devices (or _accessories_ in HomeKit parlance) or platforms you wish to make available to iOS. You can start by copying and modifying the included `config-sample.json` file which includes declarations for all supported accessories and platforms. +However, Homebridge won't do anything until you've created a `config.json` file containing your accessories and/or platforms. You can start by copying and modifying the included `config-sample.json` file which includes declarations for some example accessories and platforms. Each Plugin will have its own expected configuration; the documentation for Plugins should give you some real-world examples for that plugin. -Once you've added your devices and/or platforms, you should be able to run the server again and see them initialize: +**NOTE**: Your `config.json` file MUST live in your home directory inside `.homebridge`. The full error message will contain the exact path where your config is expected to be found. - $ npm run start - Starting Homebridge server... - Loading 6 accessories... - [Speakers] Initializing 'Sonos' accessory... - [Coffee Maker] Initializing 'WeMo' accessory... - [Speakers] Initializing 'Sonos' accessory... - [Coffee Maker] Initializing 'WeMo' accessory... - [Wink] Initializing Wink platform... - [Wink] Fetching Wink devices. - [Wink] Initializing device with name Living Room Lamp... +Once you've added your config file, you should be able to run the server again: + + $ homebridge + Loaded plugin: homebridge-lockitron + Registering accessory 'Lockitron' + --- + Loaded config.json with 1 accessories and 0 platforms. + --- + Loading 0 platforms... + Loading 1 accessories... + [Back Door] Initializing Lockitron accessory... Your server is now ready to receive commands from iOS. -# Adding your devices to iOS +# Installing Plugins + +Plugins are NodeJS modules published through NPM and tagged with the keyword `homebridge-plugin`. They must have a name with the prefix `homebridge-`, like **homebridge-mysmartlock**. + +Plugins can publish Accessories and/or Platforms. Accessories are individual devices, like a smart switch or a garage door. Platforms act like a single device but can expose a set of devices, like a house full of smart lightbulbs. + +You install Plugins the same way you installed Homebridge - as a global NPM module. For example: + + npm install -g homebridge-lockitron + +You can explore all available plugins at the NPM website by [searching for the keyword `homebridge-plugin`](https://www.npmjs.com/browse/keyword/homebridge-plugin). + +**IMPORTANT**: Many of the plugins that Homebridge used to include with its default installation have been moved to the single plugin [homebridge-legacy-plugins](https://www.npmjs.com/package/homebridge-legacy-plugins). + +# Adding Homebridge to iOS HomeKit 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. @@ -99,11 +79,9 @@ There are also some free apps that work OK. Try [Insteon+](https://itunes.apple. 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, it 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`. -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 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. +When you attempt to add Homebridge, it will ask for a "PIN code". The default code is `031-45-154` (but this can be changed, see `config-sample.json`). # Interacting with your Devices @@ -111,10 +89,10 @@ Once your device has been added to HomeKit, you should be able to tell Siri to c 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 +# Why? -HomeKit is definitely amazing when it works. Speaking to Siri is often much quicker and easier than launching whatever app your device manufacturer provides. +Technically, the device manufacturers should be the ones implementing the HomeKit API. And I'm sure they will - eventually. When they do, these shims will be obsolete, and I hope that happens soon. In the meantime, this server is a fun way to get a taste of the future, for those who just can't bear to wait until "real" HomeKit devices are on the market. -I welcome any suggestions or pull requests, but keep in mind that it's likely not possible to support all the things you might want to do with a device through HomeKit. For instance, you might want to hack the Sonos shim to play the specific kind of music you want and that's great, but it might not be appropriate to merge those specific changes into this repository. The shims here should be mostly simple "canonical examples" and easily hackable by others. +# Credit -Good luck! +The original HomeKit API work was done by [KhaosT](http://twitter.com/khaost) in his [HAP-NodeJS](https://github.com/KhaosT/HAP-NodeJS) project. diff --git a/accessories/AD2USB.js b/accessories/AD2USB.js deleted file mode 100644 index bcef82d..0000000 --- a/accessories/AD2USB.js +++ /dev/null @@ -1,245 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var AD2USB = require('ad2usb'); -var CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8"; - -function AD2USBAccessory(log, config) { - - this.log = log; - this.name = config["name"]; - this.host = config["host"]; - this.port = config["port"]; - this.pin = config["pin"]; - var that = this; - this.currentArmState = 2; - this.currentStateCharacteristic = undefined; - this.targetStateCharacteristic = undefined; - this.lcdCharacteristic = undefined; - - var alarm = AD2USB.connect(this.host, this.port, function() { - - // Send an initial empty character to get status - alarm.send(''); - - // Armed Away - alarm.on('armedAway', function() { - - that.log("Armed to AWAY"); - if (that.currentStateCharacteristic) { - that.currentStateCharacteristic.updateValue(0, null); - } - if (that.targetStateCharacteristic) { - that.targetStateCharacteristic.updateValue(1, null); - } - - }); - - // Armed Stay - alarm.on('armedStay', function() { - - that.log("Armed to STAY"); - if (that.currentStateCharacteristic) { - that.currentStateCharacteristic.updateValue(0, null); - } - if (that.targetStateCharacteristic) { - that.targetStateCharacteristic.updateValue(0, null); - } - - }); - - // Armed Night - alarm.on('armedNight', function() { - - that.log("Armed to NIGHT"); - if (that.currentStateCharacteristic) { - that.currentStateCharacteristic.updateValue(0, null); - } - if (that.targetStateCharacteristic) { - that.targetStateCharacteristic.updateValue(2, null); - } - - }); - - // Disarmed - alarm.on('disarmed', function() { - - that.log("Disarmed"); - if (that.currentStateCharacteristic) { - that.currentStateCharacteristic.updateValue(1, null); - } - if (that.targetStateCharacteristic) { - that.targetStateCharacteristic.updateValue(3, null); - } - - }); - - // Text Change - alarm.on('lcdtext', function(newText) { - - that.log("LCD: " + newText); - if (that.lcdCharacteristic) { - that.lcdCharacteristic.updateValue(newText, null); - } - - }); - - - }); - this.alarm = alarm; - -} - -AD2USBAccessory.prototype = { - - setArmState: function(targetArmState) { - - var that = this; - that.log("Desired target arm state: " + targetArmState); - - // TARGET - // 0 - Stay - // 1 - Away - // 2 - Night - // 3 - Disarm - if (targetArmState == 0) { - that.alarm.armStay(that.pin); - } - else if (targetArmState == 1) { - that.alarm.armAway(that.pin); - } - else if (targetArmState == 2) { - that.alarm.armNight(that.pin); - } - else if (targetArmState == 3) { - that.alarm.disarm(that.pin); - } - - - // CURRENT - // 0 - Armed - // 1 - Disarmed - // 2 - Hold - - }, - - getServices: function() { - var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Nutech", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "AD2USB", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "AD2USBIF", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.ALARM_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.ALARM_CURRENT_STATE_CTYPE, - onUpdate: null, - onRegister: function(characteristic) { - - that.currentStateCharacteristic = characteristic; - characteristic.eventEnabled = true; - - }, - perms: ["pr","ev"], - format: "int", - initialValue: 2, - supportEvents: true, - supportBonjour: false, - manfDescription: "Alarm current arm state", - designedMaxLength: 1 - },{ - cType: types.ALARM_TARGET_STATE_CTYPE, - onUpdate: function(value) { that.setArmState(value); }, - onRegister: function(characteristic) { - - that.targetStateCharacteristic = characteristic; - characteristic.eventEnabled = true; - - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 1, - supportEvents: true, - supportBonjour: false, - manfDescription: "Alarm target arm state", - designedMaxLength: 1 - }, - { - cType: CUSTOM_PANEL_LCD_TEXT_CTYPE, - onUpdate: null, - onRegister: function(characteristic) { - - that.lcdCharacteristic = characteristic; - characteristic.eventEnabled = true; - - }, - perms: ["pr","ev"], - format: "string", - initialValue: "Unknown", - supportEvents: false, - supportBonjour: false, - manfDescription: "Keypad Text", - designedMaxLength: 64 - }] - }]; - } -}; - -module.exports.accessory = AD2USBAccessory; diff --git a/accessories/Carwings.js b/accessories/Carwings.js deleted file mode 100644 index 7d0cd82..0000000 --- a/accessories/Carwings.js +++ /dev/null @@ -1,126 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var carwings = require("carwingsjs"); - -function CarwingsAccessory(log, config) { - this.log = log; - this.name = config["name"]; - this.username = config["username"]; - this.password = config["password"]; -} - -CarwingsAccessory.prototype = { - - setPowerState: function(powerOn) { - var that = this; - - carwings.login(this.username, this.password, function(err, result) { - if (!err) { - that.vin = result.vin; - that.log("Got VIN: " + that.vin); - - if (powerOn) { - carwings.startClimateControl(that.vin, null, function(err, result) { - if (!err) - that.log("Started climate control."); - else - that.log("Error starting climate control: " + err); - }); - } - else { - carwings.stopClimateControl(that.vin, function(err, result) { - if (!err) - that.log("Stopped climate control."); - else - that.log("Error stopping climate control: " + err); - }); - } - } - else { - that.log("Error logging in: " + err); - } - }); - }, - - getServices: function() { - var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Nissan", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Rev-1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.SWITCH_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { that.setPowerState(value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state of the car", - designedMaxLength: 1 - }] - }]; - } -}; - -module.exports.accessory = CarwingsAccessory; diff --git a/accessories/ELKM1.js b/accessories/ELKM1.js deleted file mode 100644 index ead0f17..0000000 --- a/accessories/ELKM1.js +++ /dev/null @@ -1,126 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var elkington = require("elkington"); - -function ElkM1Accessory(log, config) { - this.log = log; - this.name = config["name"]; - this.zone = config["zone"]; - this.host = config["host"]; - this.port = config["port"]; - this.pin = config["pin"]; - this.arm = config["arm"]; -} - -ElkM1Accessory.prototype = { - setPowerState: function(alarmOn) { - var that = this; - - if (!alarmOn) - { - return; - } - - var elk = elkington.createConnection({ - port: that.port, - host: that.host, - }); - - switch (that.arm) - { - case 'Away': - elk.armAway({area: that.zone, code: that.pin}); - break; - case 'Stay': - elk.armStay({area: that.zone, code: that.pin}); - break; - case 'Night': - elk.armNightInstant({area: that.zone, code: that.pin}); - break; - default: - break; - } - }, - - getServices: function() { - var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Elk", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "M1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.SWITCH_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { that.setPowerState(value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Alarm the Zone", - designedMaxLength: 1 - }] - }]; - } -}; - -module.exports.accessory = ElkM1Accessory; diff --git a/accessories/FileSensor.js b/accessories/FileSensor.js deleted file mode 100644 index 112089e..0000000 --- a/accessories/FileSensor.js +++ /dev/null @@ -1,76 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var chokidar = require("chokidar"); -var debug = require("debug")("FileSensorAccessory"); -var crypto = require("crypto"); - -module.exports = { - accessory: FileSensorAccessory -} - -function FileSensorAccessory(log, config) { - this.log = log; - - // url info - this.name = config["name"]; - this.path = config["path"]; - this.window_seconds = config["window_seconds"] || 5; - this.sensor_type = config["sensor_type"] || "m"; - this.inverse = config["inverse"] || false; - - if(config["sn"]){ - this.sn = config["sn"]; - } else { - var shasum = crypto.createHash('sha1'); - shasum.update(this.path); - this.sn = shasum.digest('base64'); - debug('Computed SN ' + this.sn); - } -} - -FileSensorAccessory.prototype = { - - getServices: function() { - - // you can OPTIONALLY create an information service if you wish to override - // the default values for things like serial number, model, etc. - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Name, this.name) - .setCharacteristic(Characteristic.Manufacturer, "Homebridge") - .setCharacteristic(Characteristic.Model, "File Sensor") - .setCharacteristic(Characteristic.SerialNumber, this.sn); - - var service, changeAction; - if(this.sensor_type === "c"){ - service = new Service.ContactSensor(); - changeAction = function(newState){ - service.getCharacteristic(Characteristic.ContactSensorState) - .setValue(newState ? Characteristic.ContactSensorState.CONTACT_DETECTED : Characteristic.ContactSensorState.CONTACT_NOT_DETECTED); - }; - } else { - service = new Service.MotionSensor(); - changeAction = function(newState){ - service.getCharacteristic(Characteristic.MotionDetected) - .setValue(newState); - }; - } - - var changeHandler = function(path, stats){ - var d = new Date(); - if(d.getTime() - stats.mtime.getTime() <= (this.window_seconds * 1000)){ - var newState = this.inverse ? false : true; - changeAction(newState); - if(this.timer !== undefined) clearTimeout(this.timer); - this.timer = setTimeout(function(){changeAction(!newState);}, this.window_seconds * 1000); - } - }.bind(this); - - var watcher = chokidar.watch(this.path, {alwaysStat: true}); - watcher.on('add', changeHandler); - watcher.on('change', changeHandler); - - return [informationService, service]; - } -}; diff --git a/accessories/GenericRS232Device.js b/accessories/GenericRS232Device.js deleted file mode 100644 index 01fe80c..0000000 --- a/accessories/GenericRS232Device.js +++ /dev/null @@ -1,58 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var SerialPort = require("serialport").SerialPort; - -module.exports = { - accessory: GenericRS232DeviceAccessory -} - -function GenericRS232DeviceAccessory(log, config) { - this.log = log; - this.id = config["id"]; - this.name = config["name"]; - this.model_name = config["model_name"]; - this.manufacturer = config["manufacturer"]; - this.on_command = config["on_command"]; - this.off_command = config["off_command"]; - this.device = config["device"]; - this.baudrate = config["baudrate"]; -} - -GenericRS232DeviceAccessory.prototype = { - setPowerState: function(powerOn, callback) { - var that = this; - var command = powerOn ? that.on_command : that.off_command; - var serialPort = new SerialPort(that.device, { baudrate: that.baudrate }, false); - serialPort.open(function (error) { - if (error) { - callback(new Error('Can not communicate with ' + that.name + " (" + error + ")")) - } else { - serialPort.write(command, function(err, results) { - if (error) { - callback(new Error('Can not send power command to ' + that.name + " (" + err + ")")) - } else { - callback() - } - }); - } - }); - }, - - getServices: function() { - var switchService = new Service.Switch(this.name); - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, this.manufacturer) - .setCharacteristic(Characteristic.Model, this.model_name) - .setCharacteristic(Characteristic.SerialNumber, this.id); - - switchService - .getCharacteristic(Characteristic.On) - .on('set', this.setPowerState.bind(this)); - - return [informationService, switchService]; - } -} - -module.exports.accessory = GenericRS232DeviceAccessory; \ No newline at end of file diff --git a/accessories/HomeMatic.js b/accessories/HomeMatic.js deleted file mode 100644 index 23a7ecb..0000000 --- a/accessories/HomeMatic.js +++ /dev/null @@ -1,141 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var request = require("request"); - -function HomeMatic(log, config) { - this.log = log; - this.name = config["name"]; - this.ccuID = config["ccu_id"]; - this.ccuIP = config["ccu_ip"]; -} - -HomeMatic.prototype = { - - setPowerState: function(powerOn) { - - var binaryState = powerOn ? 1 : 0; - var that = this; - - this.log("Setting power state of CCU to " + powerOn); - this.log(this.ccuID+ powerOn); - - request.put({ - url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+this.ccuID+"&new_value="+ powerOn, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - that.log("State change complete."); - } - else { - that.log("Error '"+err+"' setting lock state: " + body); - } - }); - }, - getPowerState: function(callback) { - var that = this; - - this.log("Getting Power State of CCU"); - request.get({ - url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuID, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - //that.log("Response:"+response.body); - var responseString = response.body.substring(83,87); - //that.log(responseString); - switch(responseString){ - case "true": {modvalue = "1";break;} - case "fals": {modvalue = "0";break;} - } - callback(parseInt(modvalue)); - that.log("Getting Power State complete."); - } - else { - that.log("Error '"+err+"' getting Power State: " + body); - } - }); - }, - getServices: function() { - var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "WeMo", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Rev-1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.SWITCH_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { that.setPowerState(value); }, - onRead: function(callback) { that.getPowerState(callback); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state of a Variable", - designedMaxLength: 1 - }] - }]; - } -}; - -module.exports.accessory = HomeMatic; diff --git a/accessories/HomeMaticThermo.js b/accessories/HomeMaticThermo.js deleted file mode 100644 index 86db229..0000000 --- a/accessories/HomeMaticThermo.js +++ /dev/null @@ -1,264 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var request = require("request"); - -function HomeMaticThermo(log, config) { - this.log = log; - this.name = config["name"]; - this.ccuIDTargetTemp = config["ccu_id_TargetTemp"]; - this.ccuIDCurrentTemp = config["ccu_id_CurrentTemp"]; - this.ccuIDControlMode = config["ccu_id_ControlMode"]; - this.ccuIDManuMode = config["ccu_id_ManuMode"]; - this.ccuIDAutoMode = config["ccu_id_AutoMode"]; - this.ccuIP = config["ccu_ip"]; -} - -HomeMaticThermo.prototype = { - - setTargetTemperature: function(value) { - - var that = this; - - this.log("Setting target Temperature of CCU to " + value); - this.log(this.ccuIDTargetTemp + " " + value); - - request.put({ - url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+this.ccuIDTargetTemp+"&new_value="+ value, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - that.log("State change complete."); - } - else { - that.log("Error '"+err+"' setting Temperature: " + body); - } - }); - }, - getCurrentTemperature: function(callback) { - - var that = this; - - this.log("Getting current Temperature of CCU"); - request.get({ - url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDCurrentTemp, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - //that.log("Response:"+response.body); - var responseString = response.body.substring(83,87); - //that.log(responseString); - callback(parseFloat(responseString)); - //that.log("Getting current temperature complete."); - } - else { - that.log("Error '"+err+"' getting Temperature: " + body); - } - }); - }, - getTargetTemperature: function(callback) { - - var that = this; - -this.log("Getting target Temperature of CCU"); - request.get({ - url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDTargetTemp, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - //that.log("Response:"+response.body); - var responseString = response.body.substring(83,87); - //that.log(responseString); - callback(parseFloat(responseString)); - //that.log("Getting target temperature complete."); - } - else { - that.log("Error '"+err+"' getting Temperature: " + body); - } - }); - }, - getMode: function(callback) { - - var that = this; - - //this.log("Getting target Mode of CCU"); - //this.log(this.ccuID+ value); - - request.get({ - url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDControlMode, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - //that.log("Response:"+response.body); - var responseInt = response.body.substring(83,84); - //that.log(responseString); - if (responseInt == 1) - { callback(parseInt("0")); } - if (responseInt == 0) - { callback(parseInt("1")); } - //that.log("Getting mode complete."); - } - else { - that.log("Error '"+err+"' getting Mode: " + body); - } - }); - }, - setMode: function(value) { - - var that = this; - - //this.log("Seting target Mode of CCU:" + value); - var modvalue; - var dpID; - switch(value) { - case 3: {modvalue = "true";dpID=this.ccuIDAutoMode;break;} //auto - case 1: {modvalue = "true";dpID=this.ccuIDAutoMode;break;} //heating => auto - default: {modvalue = "1";dpID=this.ccuIDManuMode;} //default => off (manual) - } - - request.put({ - url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+dpID+"&new_value="+ modvalue, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - //that.log("Setting Mode complete."); - } - else { - that.log("Error '"+err+"' setting Mode: " + body); - } - }); - }, - getServices: function() { - var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "test", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "test", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NREF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.THERMOSTAT_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.CURRENTHEATINGCOOLING_CTYPE, - onRead: function(callback) { that.getMode(callback); }, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Mode", - designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 2, - designedMinStep: 1, - },{ - cType: types.TARGETHEATINGCOOLING_CTYPE, - onRead: function(callback) { that.getMode(callback); }, - onUpdate: function(value) { that.setMode(value);}, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Mode", - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, - },{ - cType: types.CURRENT_TEMPERATURE_CTYPE, - onRead: function(callback) { that.getCurrentTemperature(callback); }, - onUpdate: null, - perms: ["pr","ev"], - format: "float", - initialValue: 13.0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Temperature", - unit: "celsius" - },{ - cType: types.TARGET_TEMPERATURE_CTYPE, - onUpdate: function(value) { that.setTargetTemperature(value); }, - onRead: function(callback) { that.getTargetTemperature(callback); }, - perms: ["pw","pr","ev"], - format: "float", - initialValue: 19.0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Temperature", - designedMinValue: 4, - designedMaxValue: 25, - designedMinStep: 0.1, - unit: "celsius" - },{ - cType: types.TEMPERATURE_UNITS_CTYPE, - onUpdate: null, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Unit" - }] - }]; - } -}; - -module.exports.accessory = HomeMaticThermo; diff --git a/accessories/HomeMaticWindow.js b/accessories/HomeMaticWindow.js deleted file mode 100644 index 6dcaf78..0000000 --- a/accessories/HomeMaticWindow.js +++ /dev/null @@ -1,123 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); - -function HomeMaticWindow(log, config) { - this.log = log; - this.name = config["name"]; - this.ccuID = config["ccu_id"]; - this.ccuIP = config["ccu_ip"]; -} - -HomeMaticWindow.prototype = { - - - getPowerState: function(callback) { - var that = this; - - this.log("Getting Window State of CCU"); - request.get({ - url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuID, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - //that.log("Response:"+response.body); - var responseString = response.body.substring(83,84); - //that.log(responseString); - switch(responseString){ - case "0": {callback(Characteristic.ContactSensorState.CONTACT_DETECTED);break;} - case "1": {callback(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);break;} - case "2": {callback(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);break;} - } - that.log("Getting Window State complete."); - } - else { - that.log("Error '"+err+"' getting Window State: " + body); - } - }); - }, - - getServices: function() { - var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Homematic", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "HM-Sec-RHS", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.CONTACT_SENSOR_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.CONTACT_SENSOR_STATE_CTYPE, - onRead: function(callback) { that.getPowerState(callback); }, - perms: ["pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Get Window state of a Variable", - designedMaxLength: 1 - }] - }]; - } -}; - -module.exports.accessory = HomeMaticWindow; diff --git a/accessories/Http.js b/accessories/Http.js deleted file mode 100644 index 13d07c2..0000000 --- a/accessories/Http.js +++ /dev/null @@ -1,130 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); - -module.exports = { - accessory: HttpAccessory -} - -function HttpAccessory(log, config) { - this.log = log; - - // url info - this.on_url = config["on_url"]; - this.on_body = config["on_body"]; - this.off_url = config["off_url"]; - this.off_body = config["off_body"]; - this.brightness_url = config["brightness_url"]; - this.http_method = config["http_method"]; - this.username = config["username"]; - this.password = config["password"]; - this.service = config["service"] || "Switch"; - this.name = config["name"]; - this.brightnessHandling = config["brightnessHandling"] || "no"; -} - -HttpAccessory.prototype = { - - httpRequest: function(url, body, method, username, password, callback) { - request({ - url: url, - body: body, - method: method, - auth: { - user: username, - pass: password, - sendImmediately: false - } - }, - function(error, response, body) { - callback(error, response, body) - }) - }, - - setPowerState: function(powerOn, callback) { - var url; - var body; - - if (powerOn) { - url = this.on_url; - body = this.on_body; - this.log("Setting power state to on"); - } else { - url = this.off_url; - body = this.off_body; - this.log("Setting power state to off"); - } - - this.httpRequest(url, body, this.http_method, this.username, this.password, function(error, response, responseBody) { - if (error) { - this.log('HTTP power function failed: %s', error.message); - callback(error); - } else { - this.log('HTTP power function succeeded!'); - this.log(response); - this.log(responseBody); - this.log(this.username); - this.log(this.password); - callback(); - } - }.bind(this)); - }, - - setBrightness: function(level, callback) { - var url = this.brightness_url.replace("%b", level) - - this.log("Setting brightness to %s", level); - - this.httpRequest(url, "", this.http_method, this.username, this.password, function(error, response, body) { - if (error) { - this.log('HTTP brightness function failed: %s', error); - callback(error); - } else { - this.log('HTTP brightness function succeeded!'); - callback(); - } - }.bind(this)); - }, - - identify: function(callback) { - this.log("Identify requested!"); - callback(); // success - }, - - getServices: function() { - - // you can OPTIONALLY create an information service if you wish to override - // the default values for things like serial number, model, etc. - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") - .setCharacteristic(Characteristic.Model, "HTTP Model") - .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); - - if (this.service == "Switch") { - var switchService = new Service.Switch(this.name); - - switchService - .getCharacteristic(Characteristic.On) - .on('set', this.setPowerState.bind(this)); - - return [switchService]; - } else if (this.service == "Light") { - var lightbulbService = new Service.Lightbulb(this.name); - - lightbulbService - .getCharacteristic(Characteristic.On) - .on('set', this.setPowerState.bind(this)); - - if (this.brightnessHandling == "yes") { - - lightbulbService - .addCharacteristic(new Characteristic.Brightness()) - .on('set', this.setBrightness.bind(this)); - } - - return [informationService, lightbulbService]; - } - } -}; diff --git a/accessories/HttpGarageDoorOpener.js b/accessories/HttpGarageDoorOpener.js deleted file mode 100644 index d6320a2..0000000 --- a/accessories/HttpGarageDoorOpener.js +++ /dev/null @@ -1,115 +0,0 @@ -/* -{ - "bridge": { - "name": "Homebridge", - "username": "CC:22:3D:E3:CE:30", - "port": 51826, - "pin": "031-45-154" - }, - - "description": "This is an example configuration file with all supported devices. You can use this as a template for creating your own configuration file containing devices you actually own.", - - "platforms": [], - "accessories": [ - { - "accessory": "HttpGarageDoorOpener", - "name": "Porte de Garage", - "description": "", - "open_url": "http://0.0.0.0:3000", - "http_method": "GET" - } - ] -} -*/ - -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; - -var request = require("request"); - -module.exports = { - accessory: HttpGarageDoorOpener -} - -function HttpGarageDoorOpener(log, config) { - this.log = log; - this.open_url = config["open_url"]; - this.http_method = config["http_method"]; - this.garageDoorStatus = Characteristic.CurrentDoorState.CLOSED; -} - -HttpGarageDoorOpener.prototype = { - close: function (callback) { - this.garageDoorStatus = Characteristic.CurrentDoorState.CLOSED; - this.log("Door is", this.getCurrentDoorStateReadable()); - callback(); - }, - - open: function (callback) { - this.garageDoorStatus = Characteristic.CurrentDoorState.OPEN; - this.log("Door is", this.getCurrentDoorStateReadable()); - callback(); - }, - - identify: function() { - console.log("Identify the Door!"); - }, - - getServices: function () { - this.garageDoorOpenerService = new Service.GarageDoorOpener(); - - this.garageDoorOpenerService - .getCharacteristic(Characteristic.CurrentDoorState) - .on('get', this.getCurrentDoorState.bind(this)); - - this.garageDoorOpenerService - .getCharacteristic(Characteristic.TargetDoorState) - .on('set', this.setTargetDoorState.bind(this)); - - /* - garageDoorOpenerService - .getCharacteristic(Characteristic.ObstructionDetected) - .on('get', this.getObstructionDetected.bind(this)) - .on('set', this.setObstructionDetected.bind(this)); - */ - - var informationService = new Service.AccessoryInformation(); - informationService - .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") - .setCharacteristic(Characteristic.Model, "HTTP Model") - .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); - - return [informationService, this.garageDoorOpenerService]; - }, - - getCurrentDoorStateReadable: function () { - var textState = ""; - switch (this.garageDoorStatus) { - case 0: textState = "OPEN"; break; - case 1: textState = "CLOSED"; break; - case 2: textState = "OPENING"; break; - case 3: textState = "CLOSING"; break; - case 4: textState = "STOPPED"; break; - default: this.log("Unhandled CurrentDoorState"); - } - return textState; - }, - - getCurrentDoorState: function(callback) { - - this.log("The door is now", this.getCurrentDoorStateReadable() ,"("+ this.garageDoorStatus + ")"); - - var error = null; - var returnValue = this.state; - - callback(null, returnValue); - }, - - setTargetDoorState: function(value, callback) { - if(value === Characteristic.TargetDoorState.OPEN) { - this.open(callback); - } else { - this.close(callback); - }; - } -}; \ No newline at end of file diff --git a/accessories/HttpHygrometer.js b/accessories/HttpHygrometer.js deleted file mode 100644 index f722fe8..0000000 --- a/accessories/HttpHygrometer.js +++ /dev/null @@ -1,71 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); - -module.exports = { - accessory: HygrometerAccessory -} - -function HygrometerAccessory(log, config) { - this.log = log; - - // url info - this.url = config["url"]; - this.http_method = config["http_method"]; -} - - -HygrometerAccessory.prototype = { - - httpRequest: function(url, method, callback) { - request({ - url: url, - method: method - }, - function (error, response, body) { - callback(error, response, body) - }) - }, - - - identify: function(callback) { - this.log("Identify requested!"); - callback(); // success - }, - - getCurrentRelativeHumidity: function (callback) { - var that = this; - that.log ("getting CurrentCurrentRelativeHumidity"); - - this.httpRequest(this.url, this.http_method, function(error, response, body) { - if (error) { - this.log('HTTP function failed: %s', error); - callback(error); - } - else { - this.log('HTTP function succeeded - %s', body); - callback(null, Number(body)); - } - }.bind(this)); - }, - - getServices: function() { - - // you can OPTIONALLY create an information service if you wish to override - // the default values for things like serial number, model, etc. - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") - .setCharacteristic(Characteristic.Model, "HTTP Hygrometer") - .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); - - var humidityService = new Service.HumiditySensor(); - - humidityService - .getCharacteristic(Characteristic.CurrentRelativeHumidity) - .on('get', this.getCurrentRelativeHumidity.bind(this)); - - return [informationService, humidityService]; - } -}; diff --git a/accessories/HttpThermometer.js b/accessories/HttpThermometer.js deleted file mode 100644 index 9235a57..0000000 --- a/accessories/HttpThermometer.js +++ /dev/null @@ -1,79 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); - -module.exports = { - accessory: ThermometerAccessory -} - -function ThermometerAccessory(log, config) { - this.log = log; - - // url info - this.url = config["url"]; - this.http_method = config["http_method"]; -} - - -ThermometerAccessory.prototype = { - - httpRequest: function(url, method, callback) { - request({ - url: url, - method: method - }, - function (error, response, body) { - callback(error, response, body) - }) - }, - - - identify: function(callback) { - this.log("Identify requested!"); - callback(); // success - }, - - getCurrentTemperature: function (callback) { - var that = this; - that.log ("getting CurrentTemperature"); - - - this.httpRequest(this.url, this.http_method, function(error, response, body) { - if (error) { - this.log('HTTP function failed: %s', error); - callback(error); - } - else { - this.log('HTTP function succeeded - %s', body); - callback(null, Number(body)); - } - }.bind(this)); - }, - - getTemperatureUnits: function (callback) { - var that = this; - that.log ("getTemperature Units"); - // 1 = F and 0 = C - callback (null, 0); - }, - - getServices: function() { - - // you can OPTIONALLY create an information service if you wish to override - // the default values for things like serial number, model, etc. - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") - .setCharacteristic(Characteristic.Model, "HTTP Thermometer") - .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); - - var temperatureService = new Service.TemperatureSensor(); - - temperatureService - .getCharacteristic(Characteristic.CurrentTemperature) - .on('get', this.getCurrentTemperature.bind(this)); - - return [informationService, temperatureService]; - } -}; diff --git a/accessories/Hyperion.js b/accessories/Hyperion.js deleted file mode 100644 index 8767b19..0000000 --- a/accessories/Hyperion.js +++ /dev/null @@ -1,221 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var net = require('net'); -var Color = require('color'); - -function HyperionAccessory(log, config) { - this.log = log; - this.host = config["host"]; - this.port = config["port"]; - this.name = config["name"]; - this.color = Color().hsv([0, 0, 0]); - this.prevColor = Color().hsv([0,0,100]); -} - - -HyperionAccessory.prototype = { - - sendHyperionCommand: function(command, cmdParams, priority) { - var that = this; - var client = new net.Socket(); - var data = {}; - - if (typeof priority === 'undefined') { priority = 100; } - - switch (command) { - case 'color': - data = {"command":"color", "priority":priority,"color":cmdParams}; - break; - case 'blacklevel': - data = {"command":"transform","transform":{"blacklevel":cmdParams}} - break; - default: - that.log("Hyperion command not found"); - return; - } - - //that.log(JSON.stringify(data)); - - client.connect(that.port, that.host, function() { - client.write(JSON.stringify(data) + "\n"); - }); - - client.on('data', function(data){ - that.log("Response: " + data.toString().trim()); - that.log("***** Color HSV:" + that.color.hsvArray() + "*****"); - that.log("***** Color RGB:" + that.color.rgbArray() + "*****"); - client.end(); - }); - }, - - setPowerState: function(powerOn) { - var that = this; - - if (powerOn) { - that.log("Setting power state on the '"+that.name+"' to on"); - that.color.rgb(that.prevColor.rgb()); - that.sendHyperionCommand('color', that.color.rgbArray()); - } else { - that.log("Setting power state on the '"+that.name+"' to off"); - that.prevColor.rgb(that.color.rgb()); - that.color.value(0); - that.sendHyperionCommand('color', that.color.rgbArray()); - that.sendHyperionCommand('blacklevel', [0,0,0]); - } - - }, - - setBrightness: function(level) { - var that = this; - - that.color.value(level); - that.log("Setting brightness on the '"+that.name+"' to '" + level + "'"); - that.sendHyperionCommand('color', that.color.rgbArray()); - - }, - - setHue: function(level) { - var that = this; - - that.color.hue(level); - that.prevColor.hue(level); - that.log("Setting hue on the '"+that.name+"' to '" + level + "'"); - that.sendHyperionCommand('color', that.color.rgbArray()); - - }, - - setSaturation: function(level) { - var that = this; - - that.color.saturationv(level); - that.prevColor.saturationv(level); - that.log("Setting saturation on the '"+that.name+"' to '" + level + "'"); - that.sendHyperionCommand('color', that.color.rgbArray()); - - }, - - getServices: function() { - var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Hyperion", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Rev-1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "DEADBEEF", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.LIGHTBULB_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { that.setPowerState(value); }, - onRead: ((that.color.value() > 0) ? true : false), - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Turn on the light", - designedMaxLength: 1 - },{ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { that.setBrightness(value); }, - onRead: that.color.value(), - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.color.value(), - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Brightness", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - },{ - cType: types.HUE_CTYPE, - onUpdate: function(value) { that.setHue(value) }, - onRead: that.color.hue(), - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.color.hue(), - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Hue", - designedMinValue: 0, - designedMaxValue: 360, - designedMinStep: 1, - unit: "arcdegrees" - },{ - cType: types.SATURATION_CTYPE, - onUpdate: function(value) { that.setSaturation(value) }, - onRead: that.color.saturationv(), - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.color.saturationv(), - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Saturation", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }] - }]; - } -}; - -module.exports.accessory = HyperionAccessory; diff --git a/accessories/LiftMaster.js b/accessories/LiftMaster.js deleted file mode 100644 index 4a61380..0000000 --- a/accessories/LiftMaster.js +++ /dev/null @@ -1,329 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var request = require("request"); - -// This seems to be the "id" of the official LiftMaster iOS app -var APP_ID = "JVM/G9Nwih5BwKgNCjLxiFUQxQijAebyyg8QUHr7JOrP+tuPb8iHfRHKwTmDzHOu" - -function LiftMasterAccessory(log, config) { - this.log = log; - this.name = config["name"]; - this.username = config["username"]; - this.password = config["password"]; - this.requiredDeviceId = config["requiredDeviceId"]; -} - -LiftMasterAccessory.prototype = { - - setState: function(state) { - this.targetState = state; - this.callback = undefined; - this.login(); - }, - - getState: function(callback) { - this.targetState = undefined; - this.callback = callback; - this.login(); - return true; - }, - - login: function() { - var that = this; - - // reset our logged-in state hint until we're logged in - this.deviceId = null; - - // querystring params - var query = { - appId: APP_ID, - username: this.username, - password: this.password, - culture: "en" - }; - - // login to liftmaster - request.get({ - url: "https://myqexternal.myqdevice.com/api/user/validatewithculture", - qs: query - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - // parse and interpret the response - var json = JSON.parse(body); - that.userId = json["UserId"]; - that.securityToken = json["SecurityToken"]; - that.log("Logged in with user ID " + that.userId); - that.getDevice(); - } - else { - that.log("Error '"+err+"' logging in: " + body); - } - }); - }, - - // find your garage door ID - getDevice: function() { - var that = this; - - // querystring params - var query = { - appId: APP_ID, - SecurityToken: this.securityToken, - filterOn: "true" - }; - - // some necessary duplicated info in the headers - var headers = { - MyQApplicationId: APP_ID, - SecurityToken: this.securityToken - }; - - // request details of all your devices - request.get({ - url: "https://myqexternal.myqdevice.com/api/v4/userdevicedetails/get", - qs: query, - headers: headers - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - - // parse and interpret the response - var json = JSON.parse(body); - var devices = json["Devices"]; - var foundDoors = []; - - // look through the array of devices for an opener - for (var i=0; i 1) { - that.log("WARNING: You have multiple doors on your MyQ account."); - that.log("WARNING: Specify the ID of the door you want to control using the 'requiredDeviceId' property in your config.json file."); - that.log("WARNING: You can have multiple liftmaster accessories to cover your multiple doors"); - - for (var j = 0; j < foundDoors.length; j++) { - that.log("Found Door: " + foundDoors[j]); - } - - throw "FATAL: Please specify which specific door this Liftmaster accessory should control - you have multiples on your account"; - - } - - // Did we get a device ID? - if (that.deviceId) { - if (that.targetState != undefined) { - that.log("Found an opener with ID " + that.deviceId +". Ready to send command..."); - that.setTargetState(); - } - if (that.callback != undefined) { - that.log("Found an opener with ID " + that.deviceId + " [doorstate: " + that.deviceState + "]"); - that.getCurrentState(that.callback); - } - } - else - { - that.log("Error: Couldn't find a door device, or the ID you specified isn't associated with your account"); - } - } - else { - that.log("Error '"+err+"' getting devices: " + body); - } - }); - }, - - getCurrentState: function(callback) { - this.log("Getting current state: " + this.deviceState); - callback(this.deviceState == 2); - }, - - setTargetState: function() { - - var that = this; - var liftmasterState = (this.targetState + "") == "1" ? "0" : "1"; - - // querystring params - var query = { - appId: APP_ID, - SecurityToken: this.securityToken, - filterOn: "true" - }; - - // some necessary duplicated info in the headers - var headers = { - MyQApplicationId: APP_ID, - SecurityToken: this.securityToken - }; - - // PUT request body - var body = { - AttributeName: "desireddoorstate", - AttributeValue: liftmasterState, - ApplicationId: APP_ID, - SecurityToken: this.securityToken, - MyQDeviceId: this.deviceId - }; - - // send the state request to liftmaster - request.put({ - url: "https://myqexternal.myqdevice.com/api/v4/DeviceAttribute/PutDeviceAttribute", - qs: query, - headers: headers, - body: body, - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - - if (json["ReturnCode"] == "0") - that.log("State was successfully set."); - else - that.log("Bad return code: " + json["ReturnCode"]); - that.log("Raw response " + JSON.stringify(json)); - } - else { - that.log("Error '"+err+"' setting door state: " + JSON.stringify(json)); - } - }); - }, - - getServices: function() { - var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "LiftMaster", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Rev-1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.GARAGE_DOOR_OPENER_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Garage Door", - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.CURRENT_DOOR_STATE_CTYPE, - onUpdate: function(value) { that.log("Update current state to " + value); }, - onRead: function(callback) { that.getState(callback); }, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "BlaBla", - designedMinValue: 0, - designedMaxValue: 4, - designedMinStep: 1, - designedMaxLength: 1 - },{ - cType: types.TARGET_DOORSTATE_CTYPE, - onUpdate: function(value) { that.setState(value); }, - perms: ["pr","pw","ev"], - format: "int", - initialValue: 1, - supportEvents: false, - supportBonjour: false, - manfDescription: "BlaBla", - designedMinValue: 0, - designedMaxValue: 1, - designedMinStep: 1, - designedMaxLength: 1 - },{ - cType: types.OBSTRUCTION_DETECTED_CTYPE, - onUpdate: function(value) { that.log("Obstruction detected: " + value); }, - perms: ["pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "BlaBla" - }] - }]; - } -}; - -module.exports.accessory = LiftMasterAccessory; diff --git a/accessories/Tesla.js b/accessories/Tesla.js deleted file mode 100644 index 8d96e57..0000000 --- a/accessories/Tesla.js +++ /dev/null @@ -1,119 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var tesla = require("teslams"); - -function TeslaAccessory(log, config) { - this.log = log; - this.name = config["name"]; - this.username = config["username"]; - this.password = config["password"]; -} - -TeslaAccessory.prototype = { - - setPowerState: function(powerOn) { - var that = this; - - tesla.get_vid({email: this.username, password: this.password}, function(vehicle) { - - if (powerOn) { - tesla.auto_conditioning({id:vehicle, climate: 'start'}, function(response) { - if (response.result) - that.log("Started climate control."); - else - that.log("Error starting climate control: " + response.reason); - }); - } - else { - tesla.auto_conditioning({id:vehicle, climate: 'stop'}, function(response) { - if (response.result) - that.log("Stopped climate control."); - else - that.log("Error stopping climate control: " + response.reason); - }); - } - }) - }, - - getServices: function() { - var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Tesla", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Rev-1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.SWITCH_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { that.setPowerState(value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state of the car", - designedMaxLength: 1 - }] - }]; - } -}; - -module.exports.accessory = TeslaAccessory; diff --git a/accessories/WeMo.js b/accessories/WeMo.js deleted file mode 100644 index 48e0808..0000000 --- a/accessories/WeMo.js +++ /dev/null @@ -1,177 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var wemo = require('wemo'); - -module.exports = { - accessory: WeMoAccessory -} - -function WeMoAccessory(log, config) { - this.log = log; - this.name = config["name"]; - this.service = config["service"] || "Switch"; - this.wemoName = config["wemo_name"] || this.name; // fallback to "name" if you didn't specify an exact "wemo_name" - this.device = null; // instance of WeMo, for controlling the discovered device - this.log("Searching for WeMo device with exact name '" + this.wemoName + "'..."); - this.search(); -} - -WeMoAccessory.prototype.search = function() { - wemo.Search(this.wemoName, function(err, device) { - if (!err && device) { - this.log("Found '"+this.wemoName+"' device at " + device.ip); - this.device = new wemo(device.ip, device.port); - } - else { - this.log("Error finding device '" + this.wemoName + "': " + err); - this.log("Continuing search for WeMo device with exact name '" + this.wemoName + "'..."); - this.search(); - } - }.bind(this)); -} - -WeMoAccessory.prototype.getMotion = function(callback) { - - if (!this.device) { - this.log("No '%s' device found (yet?)", this.wemoName); - callback(new Error("Device not found"), false); - return; - } - - this.log("Getting motion state on the '%s'...", this.wemoName); - - this.device.getBinaryState(function(err, result) { - if (!err) { - var binaryState = parseInt(result); - var powerOn = binaryState > 0; - this.log("Motion state for the '%s' is %s", this.wemoName, binaryState); - callback(null, powerOn); - } - else { - this.log("Error getting motion state on the '%s': %s", this.wemoName, err.message); - callback(err); - } - }.bind(this)); -} - -WeMoAccessory.prototype.getPowerOn = function(callback) { - - if (!this.device) { - this.log("No '%s' device found (yet?)", this.wemoName); - callback(new Error("Device not found"), false); - return; - } - - this.log("Getting power state on the '%s'...", this.wemoName); - - this.device.getBinaryState(function(err, result) { - if (!err) { - var binaryState = parseInt(result); - var powerOn = binaryState > 0; - this.log("Power state for the '%s' is %s", this.wemoName, binaryState); - callback(null, powerOn); - } - else { - this.log("Error getting power state on the '%s': %s", this.wemoName, err.message); - callback(err); - } - }.bind(this)); -} - -WeMoAccessory.prototype.setPowerOn = function(powerOn, callback) { - - if (!this.device) { - this.log("No '%s' device found (yet?)", this.wemoName); - callback(new Error("Device not found")); - return; - } - - var binaryState = powerOn ? 1 : 0; // wemo langauge - this.log("Setting power state on the '%s' to %s", this.wemoName, binaryState); - - var callbackWasCalled = false; - - this.device.setBinaryState(binaryState, function(err, result) { - if (callbackWasCalled) { - this.log("WARNING: setBinaryState called its callback more than once! Discarding the second one."); - } - - callbackWasCalled = true; - - 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); - } -} diff --git a/accessories/X10.js b/accessories/X10.js deleted file mode 100644 index 0cf781c..0000000 --- a/accessories/X10.js +++ /dev/null @@ -1,151 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var request = require("request"); - -function X10(log, config) { - this.log = log; - this.ip_address = config["ip_address"]; - this.name = config["name"]; - this.deviceID = config["device_id"]; - this.protocol = config["protocol"]; - this.canDim = config["can_dim"]; -} - -X10.prototype = { - - setPowerState: function(powerOn) { - - var binaryState = powerOn ? "on" : "off"; - var that = this; - - this.log("Setting power state of " + this.deviceID + " to " + powerOn); - request.put({ - url: "http://"+this.ip_address+"/x10/"+this.deviceID+"/power/"+binaryState+"?protocol="+this.protocol, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - that.log("State change complete."); - } - else { - that.log("Error '"+err+"' setting power state: " + body); - } - }); - }, - - setBrightnessLevel: function(value) { - - var that = this; - - this.log("Setting brightness level of " + this.deviceID + " to " + value); - request.put({ - url: "http://"+this.ip_address+"/x10/"+this.deviceID+"/brightness/?protocol="+this.protocol+"&value="+value, - }, function(err, response, body) { - - if (!err && response.statusCode == 200) { - that.log("State change complete."); - } - else { - that.log("Error '"+err+"' setting brightness level: " + body); - } - }); - }, - - getServices: function() { - var that = this; - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "X10", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Rev-1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.LIGHTBULB_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { that.setPowerState(value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state of a Variable", - designedMaxLength: 1 - }] - }]; - if (that.canDim) { - services[1].characteristics.push({ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { that.setBrightnessLevel(value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }); - } - return services; - } -}; - -module.exports.accessory = X10; diff --git a/accessories/iControl.js b/accessories/iControl.js deleted file mode 100644 index a4299b5..0000000 --- a/accessories/iControl.js +++ /dev/null @@ -1,130 +0,0 @@ -var iControl = require('node-icontrol').iControl; -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; - -module.exports = { - accessory: iControlAccessory -} - -/** - * Provides a Security System accessory for an iControl-based security system like Xfinity Home. - */ - -function iControlAccessory(log, config) { - this.log = log; - - this.iControl = new iControl({ - system: iControl.Systems[config.system], - email: config.email, - password: config.password, - pinCode: config.pin - }); - - this.iControl.on('change', this._handleChange.bind(this)); - this.iControl.on('error', this._handleError.bind(this)); - - this.log("Logging into iControl..."); - this.iControl.login(); - - this._securitySystem = new Service.SecuritySystem("Security System"); - - this._securitySystem - .getCharacteristic(Characteristic.SecuritySystemTargetState) - .on('get', this._getTargetState.bind(this)) - .on('set', this._setTargetState.bind(this)); - - this._securitySystem - .getCharacteristic(Characteristic.SecuritySystemCurrentState) - .on('get', this._getCurrentState.bind(this)); -} - -iControlAccessory.prototype._getTargetState = function(callback) { - this.iControl.getArmState(function(err, armState) { - if (err) return callback(err); - - var currentState = this._getHomeKitStateFromArmState(armState); - callback(null, currentState); - - }.bind(this)); -} - -iControlAccessory.prototype._getCurrentState = function(callback) { - this.iControl.getArmState(function(err, armState) { - if (err) return callback(err); - - var currentState = this._getHomeKitStateFromArmState(armState); - callback(null, currentState); - - }.bind(this)); -} - -iControlAccessory.prototype._setTargetState = function(targetState, callback, context) { - if (context == "internal") return callback(null); // we set this state ourself, no need to react to it - - var armState = this._getArmStateFromHomeKitState(targetState); - this.log("Setting target state to %s", armState); - - this.iControl.setArmState(armState, function(err) { - if (err) return callback(err); - - this.log("Successfully set target state to %s", armState); - - // also update current state - this._securitySystem - .getCharacteristic(Characteristic.SecuritySystemCurrentState) - .setValue(targetState); - - callback(null); // success! - - }.bind(this)); -} - -iControlAccessory.prototype._handleChange = function(armState) { - this.log("Arm state changed to %s", armState); - - var homeKitState = this._getHomeKitStateFromArmState(armState); - - this._securitySystem - .getCharacteristic(Characteristic.SecuritySystemCurrentState) - .setValue(homeKitState); - - this._securitySystem - .getCharacteristic(Characteristic.SecuritySystemTargetState) - .setValue(homeKitState, null, "internal"); // these characteristics happen to share underlying values -} - -iControlAccessory.prototype._handleError = function(err) { - this.log(err.message); -} - -iControlAccessory.prototype.getServices = function() { - return [this._securitySystem]; -} - -iControlAccessory.prototype._getHomeKitStateFromArmState = function(armState) { - switch (armState) { - case "disarmed": return Characteristic.SecuritySystemCurrentState.DISARMED; - case "away": return Characteristic.SecuritySystemCurrentState.AWAY_ARM; - case "night": return Characteristic.SecuritySystemCurrentState.NIGHT_ARM; - case "stay": return Characteristic.SecuritySystemCurrentState.STAY_ARM; - } -} - -iControlAccessory.prototype._getArmStateFromHomeKitState = function(homeKitState) { - switch (homeKitState) { - case Characteristic.SecuritySystemCurrentState.DISARMED: return "disarmed"; - case Characteristic.SecuritySystemCurrentState.AWAY_ARM: return "away"; - case Characteristic.SecuritySystemCurrentState.NIGHT_ARM: return "night"; - case Characteristic.SecuritySystemCurrentState.STAY_ARM: return "stay"; - } -} - - -/** - * TESTING - */ - -if (require.main === module) { - var config = JSON.parse(require('fs').readFileSync("config.json")).accessories[0]; - var accessory = new iControlAccessory(console.log, config); -} diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js deleted file mode 100644 index 92bb88c..0000000 --- a/accessories/knxdevice.js +++ /dev/null @@ -1,1032 +0,0 @@ -/** - * This is a KNX universal accessory shim. - * This is NOT the version for dynamic installation - * -New 2015-09-16: Welcome iOS9.0 -new features include: -- services: -- Window -- WindowCovering -- ContactSensor -New 2015-09-18: -- Services Switch and Outlet -- Code cleanup -New 2015-09-19: -- GarageDoorOpener Service -- MotionSensor Service -New 2015-10-02: -- Check for valid group addresses -- new "R" flag allowed for Boolean addresses: 1/2/3R is the boolean not(1/2/3), i.e. 0 and 1 switched on read and write -New 2015-10-07: -- Accept uuid_base parameter from config.json to use as unique identifier in UUIDs instead of name (optional) - * - */ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var knxd = require("eibd"); -var knxd_registerGA = require('../platforms/KNX.js').registerGA; -var knxd_startMonitor = require('../platforms/KNX.js').startMonitor; - -var milliTimeout = 300; // used to block responses while swiping - -var colorOn = "\x1b[30;47m"; -var colorOff = "\x1b[0m"; - -function KNXDevice(log, config) { - this.log = log; - // everything in one object, do not copy individually - this.config = config; - log("Accessory constructor called"); - if (config.name) { - this.name = config.name; - } - if (config.uuid_base) { - this.uuid_base = config.uuid_base; - } - if (config.knxd_ip){ - this.knxd_ip = config.knxd_ip; - } else { - throw new Error("KNX configuration fault: MISSING KNXD IP"); - } - if (config.knxd_port){ - this.knxd_port = config.knxd_port; - } else { - throw new Error("MISSING KNXD PORT"); - } - -} - - -//debugging helper only -//inspects an object and prints its properties (also inherited properties) -var iterate = function nextIteration(myObject, path){ - // this function iterates over all properties of an object and print them to the console - // when finding objects it goes one level deeper - var name; - if (!path){ - console.log("---iterating--------------------") - } - for (name in myObject) { - if (typeof myObject[name] !== 'function') { - if (typeof myObject[name] !== 'object' ) { - console.log((path || "") + name + ': ' + myObject[name]); - } else { - nextIteration(myObject[name], path ? path + name + "." : name + "."); - } - } else { - console.log((path || "") + name + ': (function)' ); - } - } - if (!path) { - console.log("================================"); - } -}; - - -module.exports = { - accessory: KNXDevice -}; - - -KNXDevice.prototype = { - - // all purpose / all types write function - knxwrite: function(callback, groupAddress, dpt, value) { - // this.log("DEBUG in knxwrite"); - var knxdConnection = new knxd.Connection(); - // this.log("DEBUG in knxwrite: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port); - knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() { - var dest = knxd.str2addr(groupAddress); - // this.log("DEBUG got dest="+dest); - knxdConnection.openTGroup(dest, 1, function(err) { - if (err) { - this.log("[ERROR] knxwrite:openTGroup: " + err); - callback(err); - } else { - // this.log("DEBUG opened TGroup "); - var msg = knxd.createMessage('write', dpt, parseFloat(value)); - knxdConnection.sendAPDU(msg, function(err) { - if (err) { - this.log("[ERROR] knxwrite:sendAPDU: " + err); - callback(err); - } else { - this.log("knx data sent: Value "+value+ " for GA "+groupAddress); - callback(); - } - }.bind(this)); - } - }.bind(this)); - }.bind(this)); - }, - // issues an all purpose read request on the knx bus - // DOES NOT WAIT for an answer. Please register the address with a callback using registerGA() function - knxread: function(groupAddress){ - // this.log("DEBUG in knxread"); - if (!groupAddress) { - return null; - } - this.log("[knxdevice:knxread] preparing knx request for "+groupAddress); - var knxdConnection = new knxd.Connection(); - // this.log("DEBUG in knxread: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port); - knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() { - var dest = knxd.str2addr(groupAddress); - // this.log("DEBUG got dest="+dest); - knxdConnection.openTGroup(dest, 1, function(err) { - if (err) { - this.log("[ERROR] knxread:openTGroup: " + err); - } else { - // this.log("DEBUG knxread: opened TGroup "); - var msg = knxd.createMessage('read', 'DPT1', 0); - knxdConnection.sendAPDU(msg, function(err) { - if (err) { - this.log("[ERROR] knxread:sendAPDU: " + err); - } else { - this.log("[knxdevice:knxread] knx request sent for "+groupAddress); - } - }.bind(this)); - } - }.bind(this)); - }.bind(this)); - }, - // issuing multiple read requests at once - knxreadarray: function (groupAddresses) { - if (groupAddresses.constructor.toString().indexOf("Array") > -1) { - // handle multiple addresses - for (var i = 0; i < groupAddresses.length; i++) { - if (groupAddresses[i]) { // do not bind empty addresses - this.knxread (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0]); // clean address - } - } - } else { - // it's only one - this.knxread (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0]); // regex for cleaning address - } - }, - -/** Registering routines - * - */ - // boolean: get 0 or 1 from the bus, write boolean - knxregister_bool: function(addresses, characteristic) { - this.log("knx registering BOOLEAN " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type, reverse){ - this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); -// iterate(characteristic); - - characteristic.setValue(val ? (reverse ? 0:1) : (reverse ? 1:0), undefined, 'fromKNXBus'); - }.bind(this)); - }, -// knxregister_boolReverse: function(addresses, characteristic) { -// this.log("knx registering BOOLEAN REVERSE " + addresses); -// knxd_registerGA(addresses, function(val, src, dest, type, reverse){ -// this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); -//// iterate(characteristic); -// characteristic.setValue(val ? 0 : 1, undefined, 'fromKNXBus'); -// }.bind(this)); -// }, - // percentage: get 0..255 from the bus, write 0..100 to characteristic - knxregister_percent: function(addresses, characteristic) { - this.log("knx registering PERCENT " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type, reverse){ - this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - if (type !== "DPT5") { - this.log("[ERROR] Received value cannot be a percentage value"); - } else { - characteristic.setValue(Math.round(( reverse ? (255-val):val)/255*100), undefined, 'fromKNXBus'); - } - }.bind(this)); - }, - // float - knxregister_float: function(addresses, characteristic) { - // update for props refactor https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR50 - - var validValue = true; - var hk_value = 0.0; - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering FLOAT " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type, reverse){ - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - // make hk_value compliant to properties - if (characteristic.props.minStep) { - // quantize - hk_value = Math.round(val/characteristic.props.minStep)/(1/characteristic.props.minStep); - } else { - hk_value = val; - } - // range check - validValue = true; // assume validity at beginning - if (characteristic.props.minValue) { - validValue = validValue && (hk_value>=characteristic.props.minValue); - } - if (characteristic.props.maxValue) { - validValue = validValue && (hk_value<=characteristic.props.maxValue); - } - if (validValue) { - characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decimal for HomeKit - } else { - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Value %s out of bounds %s...%s ",hk_value, characteristic.props.minValue, characteristic.props.maxValue); - } - }.bind(this)); - }, - //integer - knxregister_int: function(addresses, characteristic) { - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering INT " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type, reverse){ - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - if (val>=(characteristic.props.minValue || 0) && val<=(characteristic.props.maxValue || 255)) { - characteristic.setValue(reverse ? (255-val):val, undefined, 'fromKNXBus'); - } else { - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Value %s out of bounds %s...%s ",hk_value, (characteristic.props.minValue || 0), (characteristic.props.maxValue || 255)); - } - }.bind(this)); - }, - knxregister_HVAC: function(addresses, characteristic) { - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering HVAC " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - var HAPvalue = 0; - switch (val){ - case 0: - HAPvalue = 1; - break; - case 1: - HAPvalue = 1; - break; - case 2: - HAPvalue = 1; - break; - case 3: - HAPvalue = 1; - break; - case 4: - HAPvalue = 0; - break; - default: - HAPvalue = 0; - } - characteristic.setValue(HAPvalue, undefined, 'fromKNXBus'); - }.bind(this)); - }, - /** KNX HVAC (heating, ventilation, and air conditioning) types do not really match to homekit types: -// 0 = Auto -// 1 = Comfort -// 2 = Standby -// 3 = Night -// 4 = Freezing/Heat Protection -// 5 – 255 = not allowed” - // The value property of TargetHeatingCoolingState must be one of the following: -// Characteristic.TargetHeatingCoolingState.OFF = 0; -// Characteristic.TargetHeatingCoolingState.HEAT = 1; -// Characteristic.TargetHeatingCoolingState.COOL = 2; -// Characteristic.TargetHeatingCoolingState.AUTO = 3; - AUTO (3) is not allowed as return type from devices! -*/ - // undefined, has to match! - knxregister: function(addresses, characteristic) { - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ - this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - characteristic.setValue(val, undefined, 'fromKNXBus'); - }.bind(this)); - }, - -/** set methods used for creating callbacks - * such as - * var Characteristic = myService.addCharacteristic(new Characteristic.Brightness()) - * .on('set', function(value, callback, context) { - * this.setPercentage(value, callback, context, this.config[index].Set) - * }.bind(this)); - * - */ - setBooleanState: function(value, callback, context, gaddress, reverseflag) { - if (context === 'fromKNXBus') { -// this.log(gaddress + " event ping pong, exit!"); - if (callback) { - callback(); - } - } else { - var numericValue = reverseflag ? 1:0; - if (value) { - numericValue = reverseflag ? 0:1; // need 0 or 1, not true or something - } - this.log("["+ this.name +"]:Setting "+gaddress+" " + reverseflag ? " (reverse)":""+ " Boolean to %s", numericValue); - this.knxwrite(callback, gaddress,'DPT1',numericValue); - } - - }, -// setBooleanReverseState: function(value, callback, context, gaddress) { -// if (context === 'fromKNXBus') { -//// this.log(gaddress + " event ping pong, exit!"); -// if (callback) { -// callback(); -// } -// } else { -// var numericValue = 0; -// if (!value) { -// numericValue = 1; // need 0 or 1, not true or something -// } -// this.log("["+ this.name +"]:Setting "+gaddress+" Boolean to %s", numericValue); -// this.knxwrite(callback, gaddress,'DPT1',numericValue); -// } -// -// }, - setPercentage: function(value, callback, context, gaddress, reverseflag) { - if (context === 'fromKNXBus') { -// this.log(gaddress + "event ping pong, exit!"); - if (callback) { - callback(); - } - } else { - var numericValue = 0; - value = ( value>=0 ? (value<=100 ? value:100):0 ); //ensure range 0..100 - if (reverseflag) { - numericValue = 255 - Math.round(255*value/100); // convert 0..100 to 255..0 for KNX bus - } else { - numericValue = Math.round(255*value/100); // convert 0..100 to 0..255 for KNX bus - } - this.log("["+ this.name +"]:Setting "+gaddress+" percentage to %s (%s)", value, numericValue); - this.knxwrite(callback, gaddress,'DPT5',numericValue); - } - }, - setInt: function(value, callback, context, gaddress) { - if (context === 'fromKNXBus') { -// this.log(gaddress + "event ping pong, exit!"); - if (callback) { - callback(); - } - } else { - var numericValue = 0; - if (value && value>=0 && value<=255) { - numericValue = value; // assure 0..255 for KNX bus - } - this.log("["+ this.name +"]:Setting "+gaddress+" int to %s (%s)", value, numericValue); - this.knxwrite(callback, gaddress,'DPT5',numericValue); - } - }, - setFloat: function(value, callback, context, gaddress) { - if (context === 'fromKNXBus') { -// this.log(gaddress + " event ping pong, exit!"); - if (callback) { - callback(); - } - } else { - var numericValue = 0; - if (value) { - numericValue = value; // homekit expects precision of 1 decimal - } - this.log("["+ this.name +"]:Setting "+gaddress+" Float to %s", numericValue); - this.knxwrite(callback, gaddress,'DPT9',numericValue); - } - }, - setHVACState: function(value, callback, context, gaddress) { - if (context === 'fromKNXBus') { -// this.log(gaddress + " event ping pong, exit!"); - if (callback) { - callback(); - } - } else { - var numericValue = 0; - switch (value){ - case 0: - KNXvalue = 4; - break; - case 1: - KNXvalue = 1; - break; - case 2: - KNXvalue = 1; - break; - case 3: - KNXvalue = 1; - break; - default: - KNXvalue = 1; - } - - this.log("["+ this.name +"]:Setting "+gaddress+" HVAC to %s", KNXvalue); - this.knxwrite(callback, gaddress,'DPT5',KNXvalue); - } - - }, -/** identify dummy - * - */ - identify: function(callback) { - this.log("["+ this.name +"]:Identify requested!"); - callback(); // success - }, -/** bindCharacteristic - * initializes callbacks for 'set' events (from HK) and for KNX bus reads (to HK) - */ - bindCharacteristic: function(myService, characteristicType, valueType, config, defaultValue) { - var myCharacteristic = myService.getCharacteristic(characteristicType); - var setGA = ""; - var setReverse = false; - if (myCharacteristic === undefined) { - throw new Error("unknown characteristics cannot be bound"); - } - if (defaultValue) { - myCharacteristic.setValue(defaultValue); - } - if (config.Set) { - // can write - // extract address and Reverse flag - setGA = config.Set.match(/\d*\/\d*\/\d*/); - if (setGA===null) { - this.log(colorOn + "["+ this.name +"]:["+myCharacteristic.displayName+"] Error in group adress: ["+ config.Set +"] "+colorOff); - throw new Error("EINVGROUPADRESS - Invalid group address given"); - } else { - setGA=setGA[0]; // first element of returned array is the group address - } - - setReverse = config.Set.match(/\d*\/\d*\/\d*(R)/) ? true:false; - - switch (valueType) { - case "Bool": - myCharacteristic.on('set', function(value, callback, context) { - this.setBooleanState(value, callback, context, setGA, setReverse); //NEW - }.bind(this)); - break; -// case "BoolReverse": -// this.log("["+ this.name +"]:["+myCharacteristic.displayName+"] \x1b[30;47m%s\x1b[0mWARNING in group adress: "+ config.Set +": Legacy BoolReverse used. Use " + config.Set +"R instead"); -// myCharacteristic.on('set', function(value, callback, context) { -// this.setBooleanReverseState(value, callback, context, config.Set); -// }.bind(this)); -// break; - case "Percent": - myCharacteristic.on('set', function(value, callback, context) { - this.setPercentage(value, callback, context, setGA, setReverse); - myCharacteristic.timeout = Date.now()+milliTimeout; - }.bind(this)); - break; - case "Float": - myCharacteristic.on('set', function(value, callback, context) { - this.setFloat(value, callback, context, config.Set); - }.bind(this)); - break; - case "Int": - myCharacteristic.on('set', function(value, callback, context) { - this.setInt(value, callback, context, config.Set); - }.bind(this)); - break; - case "HVAC": - myCharacteristic.on('set', function(value, callback, context) { - this.setHVACState(value, callback, context, config.Set); - }.bind(this)); - break; - default: { - this.log(colorOn + "[ERROR] unknown type passed: [" + valueType+"]"+ colorOff); - throw new Error("[ERROR] unknown type passed"); - } - } - } - if ([config.Set].concat(config.Listen || []).length>0) { - //this.log("Binding LISTEN"); - // can read - switch (valueType) { - case "Bool": - this.knxregister_bool([config.Set].concat(config.Listen || []), myCharacteristic); - break; -// case "BoolReverse": -// this.knxregister_boolReverse([config.Set].concat(config.Listen || []), myCharacteristic); -// break; - case "Percent": - this.knxregister_percent([config.Set].concat(config.Listen || []), myCharacteristic); - break; - case "Float": - this.knxregister_float([config.Set].concat(config.Listen || []), myCharacteristic); - break; - case "Int": - this.knxregister_int([config.Set].concat(config.Listen || []), myCharacteristic); - break; - case "HVAC": - this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic); - break; - default: - this.log(colorOn+ "[ERROR] unknown type passed: ["+valueType+"]"+colorOff); - throw new Error("[ERROR] unknown type passed"); - } - this.log("["+ this.name +"]:["+myCharacteristic.displayName+"]: Issuing read requests on the KNX bus..."); - this.knxreadarray([config.Set].concat(config.Listen || [])); - } - return myCharacteristic; // for chaining or whatsoever - }, -/** - * function getXXXXXXXService(config) - * returns a configured service object to the caller (accessory/device) - * - * @param config - * pass a configuration array parsed from config.json - * specifically for this service - * - */ - getContactSenserService: function(config) { -// Characteristic.ContactSensorState.CONTACT_DETECTED = 0; -// Characteristic.ContactSensorState.CONTACT_NOT_DETECTED = 1; - - // some sanity checks - if (config.type !== "ContactSensor") { - this.log("[ERROR] ContactSensor Service for non 'ContactSensor' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] ContactSensor Service without 'name' property called"); - return undefined; - } - - var myService = new Service.ContactSensor(config.name,config.name); - if (config.ContactSensorState) { - this.log("["+ this.name +"]:ContactSensor ContactSensorState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.ContactSensorState, "Bool", config.ContactSensorState); - } else if (config.ContactSensorStateContact1) { - this.log(colorOn+ "[ERROR] outdated type passed: [ContactSensorStateContact1]"+colorOff); - throw new Error("[ERROR] outdated type passed"); - } - //optionals - if (config.StatusActive) { - this.log("["+ this.name +"]:ContactSensor StatusActive characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusActive); - this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive); - } - if (config.StatusFault) { - this.log("["+ this.name +"]:ContactSensor StatusFault characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusFault); - this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault); - } - if (config.StatusTampered) { - this.log("["+ this.name +"]:ContactSensor StatusTampered characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusTampered); - this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered); - } - if (config.StatusLowBattery) { - this.log("["+ this.name +"]:ContactSensor StatusLowBattery characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusLowBattery); - this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); - } - return myService; - }, - getGarageDoorOpenerService: function(config) { -// // Required Characteristics -// this.addCharacteristic(Characteristic.CurrentDoorState); -// this.addCharacteristic(Characteristic.TargetDoorState); -// this.addCharacteristic(Characteristic.ObstructionDetected); -// Characteristic.CurrentDoorState.OPEN = 0; -// Characteristic.CurrentDoorState.CLOSED = 1; -// Characteristic.CurrentDoorState.OPENING = 2; -// Characteristic.CurrentDoorState.CLOSING = 3; -// Characteristic.CurrentDoorState.STOPPED = 4; -// // -// // Optional Characteristics -// this.addOptionalCharacteristic(Characteristic.LockCurrentState); -// this.addOptionalCharacteristic(Characteristic.LockTargetState); - // The value property of LockCurrentState must be one of the following: -// Characteristic.LockCurrentState.UNSECURED = 0; -// Characteristic.LockCurrentState.SECURED = 1; -// Characteristic.LockCurrentState.JAMMED = 2; -// Characteristic.LockCurrentState.UNKNOWN = 3; - - // some sanity checks - if (config.type !== "GarageDoorOpener") { - this.log("[ERROR] GarageDoorOpener Service for non 'GarageDoorOpener' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] GarageDoorOpener Service without 'name' property called"); - return undefined; - } - - var myService = new Service.GarageDoorOpener(config.name,config.name); - if (config.CurrentDoorState) { - this.log("["+ this.name +"]:GarageDoorOpener CurrentDoorState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.CurrentDoorState, "Int", config.CurrentDoorState); - } - if (config.TargetDoorState) { - this.log("["+ this.name +"]:GarageDoorOpener TargetDoorState characteristic enabled"); - //myService.getCharacteristic(Characteristic.TargetDoorState).minimumValue=0; // - //myService.getCharacteristic(Characteristic.TargetDoorState).maximumValue=4; // - this.bindCharacteristic(myService, Characteristic.TargetDoorState, "Int", config.TargetDoorState); - } - if (config.ObstructionDetected) { - this.log("["+ this.name +"]:GarageDoorOpener ObstructionDetected characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.ObstructionDetected, "Bool", config.ObstructionDetected); - } - //optionals - if (config.LockCurrentState) { - this.log("["+ this.name +"]:GarageDoorOpener LockCurrentState characteristic enabled"); - myService.addCharacteristic(Characteristic.LockCurrentState); - this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Int", config.LockCurrentState); - } - if (config.LockTargetState) { - this.log("["+ this.name +"]:GarageDoorOpener LockTargetState characteristic enabled"); - myService.addCharacteristic(Characteristic.LockTargetState); - this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState); - } - return myService; - }, - getLightbulbService: function(config) { - // some sanity checks - //this.config = config; - - if (config.type !== "Lightbulb") { - this.log("[ERROR] Lightbulb Service for non 'Lightbulb' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] Lightbulb Service without 'name' property called"); - return undefined; - } - var myService = new Service.Lightbulb(config.name,config.name); - // On (and Off) - if (config.On) { - this.log("["+ this.name +"]:Lightbulb on/off characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); - } // On characteristic - // Brightness if available - if (config.Brightness) { - this.log("["+ this.name +"]:Lightbulb Brightness characteristic enabled"); - myService.addCharacteristic(Characteristic.Brightness); // it's an optional - this.bindCharacteristic(myService, Characteristic.Brightness, "Percent", config.Brightness); - } - // Hue and Saturation could be added here if available in KNX lamps - //iterate(myService); - return myService; - }, - getLightSensorService: function(config) { - - // some sanity checks - if (config.type !== "LightSensor") { - this.log("[ERROR] LightSensor Service for non 'LightSensor' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] LightSensor Service without 'name' property called"); - return undefined; - } - var myService = new Service.LightSensor(config.name,config.name); - // CurrentTemperature) - if (config.CurrentAmbientLightLevel) { - this.log("["+ this.name +"]:LightSensor CurrentAmbientLightLevel characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.CurrentAmbientLightLevel, "Float", config.CurrentAmbientLightLevel); - } - return myService; - }, - getLockMechanismService: function(config) { - -/** //this.config = config; -// Characteristic.LockCurrentState.UNSECURED = 0; -// Characteristic.LockCurrentState.SECURED = 1; -*/ - // some sanity checks - if (config.type !== "LockMechanism") { - this.log("[ERROR] LockMechanism Service for non 'LockMechanism' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] LockMechanism Service without 'name' property called"); - return undefined; - } - - var myService = new Service.LockMechanism(config.name,config.name); - // LockCurrentState - if (config.LockCurrentState) { - // for normal contacts: Secured = 1 - this.log("["+ this.name +"]:LockMechanism LockCurrentState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Bool", config.LockCurrentState); - } else if (config.LockCurrentStateSecured0) { - // for reverse contacts Secured = 0 - this.log(colorOn+ "[ERROR] outdated type passed: [LockCurrentStateSecured0]"+colorOff); - throw new Error("[ERROR] outdated type passed"); - } - // LockTargetState - if (config.LockTargetState) { - this.log("["+ this.name +"]:LockMechanism LockTargetState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState); - } else if (config.LockTargetStateSecured0) { - this.log(colorOn+ "[ERROR] outdated type passed: [LockTargetStateSecured0]"+colorOff); - throw new Error("[ERROR] outdated type passed"); - } - - //iterate(myService); - return myService; - }, - getMotionSensorService: function(config) { -// Characteristic.ContactSensorState.CONTACT_DETECTED = 0; -// Characteristic.ContactSensorState.CONTACT_NOT_DETECTED = 1; - - // some sanity checks - if (config.type !== "MotionSensor") { - this.log("[ERROR] MotionSensor Service for non 'MotionSensor' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] MotionSensor Service without 'name' property called"); - return undefined; - } - - var myService = new Service.MotionSensor(config.name,config.name); - if (config.MotionDetected) { - this.log("["+ this.name +"]:MotionSensor MotionDetected characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.MotionDetected, "Bool", config.MotionDetected); - } - //optionals - if (config.StatusActive) { - this.log("["+ this.name +"]:MotionSensor StatusActive characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusActive); - this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive); - } - if (config.StatusFault) { - this.log("["+ this.name +"]:MotionSensor StatusFault characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusFault); - this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault); - } - if (config.StatusTampered) { - this.log("["+ this.name +"]:MotionSensor StatusTampered characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusTampered); - this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered); - } - if (config.StatusLowBattery) { - this.log("["+ this.name +"]:MotionSensor StatusLowBattery characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusLowBattery); - this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); - } - return myService; - }, - getOutletService: function(config) { - /** - * this.addCharacteristic(Characteristic.On); - * this.addCharacteristic(Characteristic.OutletInUse); - */ - // some sanity checks - if (config.type !== "Outlet") { - this.log("[ERROR] Outlet Service for non 'Outlet' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] Outlet Service without 'name' property called"); - return undefined; - } - var myService = new Service.Outlet(config.name,config.name); - // On (and Off) - if (config.On) { - this.log("["+ this.name +"]:Outlet on/off characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); - } // OutletInUse characteristic - if (config.OutletInUse) { - this.log("["+ this.name +"]:Outlet on/off characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.OutletInUse, "Bool", config.OutletInUse); - } - return myService; - }, - getSwitchService: function(config) { - // some sanity checks - if (config.type !== "Switch") { - this.log("[ERROR] Switch Service for non 'Switch' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] Switch Service without 'name' property called"); - return undefined; - } - var myService = new Service.Switch(config.name,config.name); - // On (and Off) - if (config.On) { - this.log("["+ this.name +"]:Switch on/off characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); - } // On characteristic - - return myService; - }, - getThermostatService: function(config) { -/** - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.CurrentRelativeHumidity); - this.addOptionalCharacteristic(Characteristic.TargetRelativeHumidity); - this.addOptionalCharacteristic(Characteristic.CoolingThresholdTemperature); - this.addOptionalCharacteristic(Characteristic.HeatingThresholdTemperature); -*/ - - // some sanity checks - if (config.type !== "Thermostat") { - this.log("[ERROR] Thermostat Service for non 'Thermostat' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] Thermostat Service without 'name' property called"); - return undefined; - } - - var myService = new Service.Thermostat(config.name,config.name); - // CurrentTemperature) - // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 - if (config.CurrentTemperature) { - this.log("["+ this.name +"]:Thermostat CurrentTemperature characteristic enabled"); - myService.getCharacteristic(Characteristic.CurrentTemperature).setProps({ - minValue: config.CurrentTemperature.minValue || -40, - maxValue: config.CurrentTemperature.maxValue || 60 - }); // °C by default - this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature); - } - // TargetTemperature if available - if (config.TargetTemperature) { - this.log("["+ this.name +"]:Thermostat TargetTemperature characteristic enabled"); - // default boundary too narrow for thermostats - // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 - myService.getCharacteristic(Characteristic.TargetTemperature).setProps({ - minValue: config.TargetTemperature.minValue || 0, - maxValue: config.TargetTemperature.maxValue || 40 - }); - - this.bindCharacteristic(myService, Characteristic.TargetTemperature, "Float", config.TargetTemperature); - } - // HVAC - if (config.CurrentHeatingCoolingState) { - this.log("["+ this.name +"]:Thermostat CurrentHeatingCoolingState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.CurrentHeatingCoolingState, "HVAC", config.CurrentHeatingCoolingState); - } - // HVAC - if (config.TargetHeatingCoolingState) { - this.log("["+ this.name +"]:Thermostat TargetHeatingCoolingState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.TargetHeatingCoolingState, "HVAC", config.TargetHeatingCoolingState); - } - return myService; - }, - getTemperatureSensorService: function(config) { - - // some sanity checks - if (config.type !== "TemperatureSensor") { - this.log("[ERROR] TemperatureSensor Service for non 'TemperatureSensor' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] TemperatureSensor Service without 'name' property called"); - return undefined; - } - var myService = new Service.TemperatureSensor(config.name,config.name); - // CurrentTemperature) - // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 - if (config.CurrentTemperature) { - this.log("["+ this.name +"]:TemperatureSensor CurrentTemperature characteristic enabled"); - myService.getCharacteristic(Characteristic.CurrentTemperature).setProps({ - minValue: config.CurrentTemperature.minValue || -40, - maxValue: config.CurrentTemperature.maxValue || 60 - }); // °C by default - this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature); - } - - return myService; - }, - getWindowService: function(config) { -/** - Optional Characteristics - this.addOptionalCharacteristic(Characteristic.HoldPosition); - this.addOptionalCharacteristic(Characteristic.ObstructionDetected); - this.addOptionalCharacteristic(Characteristic.Name); - - PositionState values: The KNX blind actuators I have return only MOVING=1 and STOPPED=0 - Characteristic.PositionState.DECREASING = 0; - Characteristic.PositionState.INCREASING = 1; - Characteristic.PositionState.STOPPED = 2; -*/ - - // some sanity checks - - - if (config.type !== "Window") { - this.log("[ERROR] Window Service for non 'Window' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] Window Service without 'name' property called"); - return undefined; - } - var myService = new Service.Window(config.name,config.name); - - if (config.CurrentPosition) { - this.log("["+ this.name +"]:Window CurrentPosition characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition); - } - if (config.TargetPosition) { - this.log("["+ this.name +"]:Window TargetPosition characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition); - } - if (config.PositionState) { - this.log("["+ this.name +"]:Window PositionState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.PositionState, "Int", config.PositionState); - } - return myService; - }, - getWindowCoveringService: function(config) { - /** - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.HoldPosition); - this.addOptionalCharacteristic(Characteristic.TargetHorizontalTiltAngle); - this.addOptionalCharacteristic(Characteristic.TargetVerticalTiltAngle); - this.addOptionalCharacteristic(Characteristic.CurrentHorizontalTiltAngle); - this.addOptionalCharacteristic(Characteristic.CurrentVerticalTiltAngle); - this.addOptionalCharacteristic(Characteristic.ObstructionDetected); - */ - // some sanity checks - if (config.type !== "WindowCovering") { - this.log("[ERROR] WindowCovering Service for non 'WindowCovering' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] WindowCovering Service without 'name' property called"); - return undefined; - } - - var myService = new Service.WindowCovering(config.name,config.name); - if (config.CurrentPosition) { - this.log("["+ this.name +"]:WindowCovering CurrentPosition characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition); - } - if (config.TargetPosition) { - this.log("["+ this.name +"]:WindowCovering TargetPosition characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition); - } - if (config.PositionState) { - this.log("["+ this.name +"]:WindowCovering PositionState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.PositionState, "Float", config.PositionState); - } - return myService; - }, - - - - -/* assemble the device ***************************************************************************************************/ - 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 accessoryServices = []; - - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "Opensource Community") - .setCharacteristic(Characteristic.Model, "KNX Universal Device") - .setCharacteristic(Characteristic.SerialNumber, "Version 1.1.4"); - - accessoryServices.push(informationService); - - //iterate(this.config); - - if (!this.config.services){ - this.log("No services found in accessory?!") - } - var currServices = this.config.services; - this.log("Preparing Services: " + currServices.length) - // go through the config thing and look for services - for (var int = 0; int < currServices.length; int++) { - var configService = currServices[int]; - // services need to have type and name properties - if (!configService.type && !configService.name) { - this.log("[ERROR] must specify 'type' and 'name' properties for each service in config.json. KNX platform section fault "); - throw new Error("Must specify 'type' and 'name' properties for each service in config.json"); - } - this.log("Preparing Service: " + int + " of type "+configService.type) - switch (configService.type) { - case "ContactSensor": - accessoryServices.push(this.getContactSenserService(configService)); - break; - case "GarageDoorOpener": - accessoryServices.push(this.getGarageDoorOpenerService(configService)); - break; - case "Lightbulb": - accessoryServices.push(this.getLightbulbService(configService)); - break; - case "LightSensor": - accessoryServices.push(this.getLightSensorService(configService)); - break; - case "LockMechanism": - accessoryServices.push(this.getLockMechanismService(configService)); - break; - case "MotionSensor": - accessoryServices.push(this.getMotionSensorService(configService)); - break; - case "Switch": - accessoryServices.push(this.getSwitchService(configService)); - break; - case "TemperatureSensor": - accessoryServices.push(this.getTemperatureSensorService(configService)); - break; - case "Thermostat": - accessoryServices.push(this.getThermostatService(configService)); - break; - case "Window": - accessoryServices.push(this.getWindowService(configService)); - break; - case "WindowCovering": - accessoryServices.push(this.getWindowCoveringService(configService)); - break; - default: - this.log("[ERROR] unknown 'type' property of ["+configService.type+"] for service ["+ configService.name + "] in config.json. KNX platform section fault "); - throw new Error("[ERROR] unknown 'type' property of ["+configService.type+"] for service '"+ configService.name + "' in config.json. KNX platform section fault "); - } - } - // start listening for events on the bus (if not started yet - will prevent itself) - knxd_startMonitor({ host: this.knxd_ip, port: this.knxd_port }); - return accessoryServices; - } -}; diff --git a/accessories/mpdclient.js b/accessories/mpdclient.js deleted file mode 100644 index ac6bf5d..0000000 --- a/accessories/mpdclient.js +++ /dev/null @@ -1,89 +0,0 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); -var komponist = require('komponist') - -module.exports = { - accessory: MpdClient -} - -function MpdClient(log, config) { - this.log = log; - this.host = config["host"] || 'localhost'; - this.port = config["port"] || 6600; -} - -MpdClient.prototype = { - - setPowerState: function(powerOn, callback) { - - var log = this.log; - - komponist.createConnection(this.port, this.host, function(error, client) { - - if (error) { - return callback(error); - } - - if (powerOn) { - client.play(function(error) { - log("start playing"); - client.destroy(); - callback(error); - }); - } else { - client.stop(function(error) { - log("stop playing"); - client.destroy(); - callback(error); - }); - } - - }); - }, - - getPowerState: function(callback) { - - komponist.createConnection(this.port, this.host, function(error, client) { - - if (error) { - return callback(error); - } - - client.status(function(error, status) { - - client.destroy(); - - if (status['state'] == 'play') { - callback(error, 1); - } else { - callback(error, 0); - } - }); - - }); - }, - - identify: function(callback) { - this.log("Identify requested!"); - callback(); // success - }, - - getServices: function() { - - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "MPD") - .setCharacteristic(Characteristic.Model, "MPD Client") - .setCharacteristic(Characteristic.SerialNumber, "81536334"); - - var switchService = new Service.Switch(); - - switchService.getCharacteristic(Characteristic.On) - .on('get', this.getPowerState.bind(this)) - .on('set', this.setPowerState.bind(this)); - - return [informationService, switchService]; - } -}; diff --git a/app.js b/app.js deleted file mode 100644 index 20279fa..0000000 --- a/app.js +++ /dev/null @@ -1,223 +0,0 @@ -var fs = require('fs'); -var path = require('path'); -var storage = require('node-persist'); -var hap = require("hap-nodejs"); -var uuid = require("hap-nodejs").uuid; -var Bridge = require("hap-nodejs").Bridge; -var Accessory = require("hap-nodejs").Accessory; -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var accessoryLoader = require("hap-nodejs").AccessoryLoader; -var once = require("hap-nodejs/lib/util/once").once; - -console.log("Starting HomeBridge server..."); - -console.log("_____________________________________________________________________"); -console.log("IMPORTANT: Homebridge is in the middle of some big changes."); -console.log(" Read more about it here:"); -console.log(" https://github.com/nfarina/homebridge/wiki/Migration-Guide"); -console.log("_____________________________________________________________________"); -console.log(""); - -// Look for the configuration file -var configPath = path.join(__dirname, "config.json"); - -// Complain and exit if it doesn't exist yet -if (!fs.existsSync(configPath)) { - console.log("Couldn't find a config.json file in the same directory as app.js. Look at config-sample.json for examples of how to format your config.js and add your home accessories."); - process.exit(1); -} - -// Initialize HAP-NodeJS -hap.init(); - -// Load up the configuration file -var config; -try { - config = JSON.parse(fs.readFileSync(configPath)); -} -catch (err) { - console.log("There was a problem reading your config.json file."); - console.log("Please try pasting your config.json file here to validate it: http://jsonlint.com"); - console.log(""); - throw err; -} - -// pull out our custom Bridge settings from config.json, if any -var bridgeConfig = config.bridge || {}; - -// Start by creating our Bridge which will host all loaded Accessories -var bridge = new Bridge(bridgeConfig.name || 'Homebridge', uuid.generate("HomeBridge")); - -// keep track of async calls we're waiting for callbacks on before we can start up -// this is hacky but this is all going away once we build proper plugin support -var asyncCalls = 0; -var asyncWait = false; - -function startup() { - asyncWait = true; - if (config.platforms) loadPlatforms(); - if (config.accessories) loadAccessories(); - asyncWait = false; - - // publish now unless we're waiting on anyone - if (asyncCalls == 0) - publish(); -} - -function loadAccessories() { - - // Instantiate all accessories in the config - console.log("Loading " + config.accessories.length + " accessories..."); - - for (var i=0; i", - "username": "your-liftmaster-username", - "password" : "your-liftmaster-password" - }, - { - "accessory": "Lockitron", - "name": "Front Door", - "description": "This shim supports Lockitron locks. It uses the Lockitron cloud API, so the Lockitron must be 'awake' for locking and unlocking to actually happen. You can wake up Lockitron after issuing an lock/unlock command by knocking on the door.", - "lock_id": "your-lock-id", - "api_token" : "your-lockitron-api-access-token" - }, - { - "accessory": "Carwings", - "name": "Leaf", - "description": "This shim supports controlling climate control on Nissan cars with Carwings. Note that Carwings is super slow and it may take up to 5 minutes for your command to be processed by the Carwings system.", - "username": "your-carwings-username", - "password" : "your-carwings-password" - }, - { - "accessory": "iControl", - "name": "Xfinity Home", - "description": "This shim supports iControl-based security systems like Xfinity Home.", - "system": "XFINITY_HOME", - "email": "your-comcast-email", - "password": "your-comcast-password", - "pin": "your-security-system-pin-code" - }, - { - "accessory": "HomeMatic", - "name": "Light", - "description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)", - "ccu_id": "The XMP-API id of your HomeMatic device", - "ccu_ip": "The IP-Adress of your HomeMatic CCU device" - }, - { - "accessory": "HomeMaticWindow", - "name": "Contact", - "description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)", - "ccu_id": "The XMP-API id of your HomeMatic device (type HM-Sec-RHS)", - "ccu_ip": "The IP-Adress of your HomeMatic CCU device" - }, - { - "accessory": "HomeMaticThermo", - "name": "Contact", - "description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)", - "ccu_id_TargetTemp": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", - "ccu_id_CurrentTemp": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", - "ccu_id_ControlMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", - "ccu_id_ManuMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", - "ccu_id_AutoMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", - "ccu_ip": "The IP-Adress of your HomeMatic CCU device" - }, - { - "accessory": "X10", - "name": "Lamp", - "ip_address": "localhost:3000", - "device_id": "E1", - "protocol": "pl", - "can_dim": true - }, - { - "accessory": "Http", - "name": "Kitchen Lamp", - "on_url": "https://192.168.1.22:3030/devices/23222/on", - "on_body": "{\"state\":\"On\"}", - "off_url": "https://192.168.1.22:3030/devices/23222/off", - "off_body": "{\"state\":\"Off\"}", - "brightness_url": "https://192.168.1.22:3030/devices/23222/brightness/%b", - "username": "", - "password": "", - "http_method": "POST", - "service": "Switch", - "brightnessHandling": "no" - }, - { - "accessory": "HttpHygrometer", - "name": "Kitchen", - "url": "http://host/URL", - "http_method": "GET" - }, - { - "accessory": "HttpThermometer", - "name": "Garage", - "url": "http://home/URL", - "http_method": "GET" - }, - { - "accessory": "ELKM1", - "name": "Security System", - "description": "Allows basic control of Elk M1 security system. You can use 1 of 3 arm modes: Away, Stay, Night. If you need to access all 3, create 3 accessories with different names.", - "zone": "1", - "host": "192.168.1.10", - "port": "2101", - "pin": "1234", - "arm": "Away" - }, - { - "accessory": "AD2USB", - "name": "Alarm", - "description": "Arm, disarm, and status monitoring of the default partition for Honeywell/Ademco alarm systems. Requires network configured AD2USB interface", - "host": "192.168.1.200", // IP address of the SER2SOCK service - "port" : 4999, // Port the SER2SOCK process is running on - "pin": "1234" // PIN used for arming / disarming - }, - { - "accessory": "Tesla", - "name": "Tesla", - "description": "This shim supports controlling climate control on the Tesla Model S.", - "username": "tesla_email", - "password" : "tesla_password" - }, - { - "accessory": "Hyperion", - "name": "TV Backlight", - "description": "Control the Hyperion TV backlight server. https://github.com/tvdzwan/hyperion", - "host": "localhost", - "port": "19444" - }, - { - "accessory": "mpdclient", - "name" : "mpd", - "host" : "localhost", - "port" : 6600, - "description": "Allows some control of an MPD server" - }, - { - "accessory": "FileSensor", - "name": "File Time Motion Sensor", - "path": "/tmp/CameraDump/", - "window_seconds": 5, - "sensor_type": "m", - "inverse": false - }, - { - "accessory": "GenericRS232Device", - "name": "Projector", - "description": "Make sure you set a 'Siri-Name' for your iOS-Device (example: 'Home Cinema') otherwise it might not work.", - "id": "TYDYMU044UVNP", - "baudrate": 9600, - "device": "/dev/tty.usbserial", - "manufacturer": "Acer", - "model_name": "H6510BD", - "on_command": "* 0 IR 001\r", - "off_command": "* 0 IR 002\r" + "platform" : "PhilipsHue", + "name" : "Hue", } ] } diff --git a/example-plugins/homebridge-lockitron/README.md b/example-plugins/homebridge-lockitron/README.md new file mode 100644 index 0000000..4a900da --- /dev/null +++ b/example-plugins/homebridge-lockitron/README.md @@ -0,0 +1,3 @@ +This is an example plugin for homebridge. It is a fully-working implementation of a Lockitron door lock accessory. + +Remember to run `npm install` in this directory in order to install the dependencies needed by this plugin. If a user is installing your plugin from npm, this will be done automatically for them. \ No newline at end of file diff --git a/accessories/Lockitron.js b/example-plugins/homebridge-lockitron/index.js similarity index 90% rename from accessories/Lockitron.js rename to example-plugins/homebridge-lockitron/index.js index ed95b7e..1d5bfd0 100644 --- a/accessories/Lockitron.js +++ b/example-plugins/homebridge-lockitron/index.js @@ -1,9 +1,11 @@ -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); +var Service, Characteristic; -module.exports = { - accessory: LockitronAccessory +module.exports = function(homebridge) { + Service = homebridge.hap.Service; + Characteristic = homebridge.hap.Characteristic; + + homebridge.registerAccessory("homebridge-lockitron", "Lockitron", LockitronAccessory); } function LockitronAccessory(log, config) { @@ -73,7 +75,7 @@ LockitronAccessory.prototype.setState = function(state, callback) { callback(err || new Error("Error setting lock state.")); } }.bind(this)); -}, +} LockitronAccessory.prototype.getServices = function() { return [this.service]; diff --git a/example-plugins/homebridge-lockitron/package.json b/example-plugins/homebridge-lockitron/package.json new file mode 100644 index 0000000..f33cd09 --- /dev/null +++ b/example-plugins/homebridge-lockitron/package.json @@ -0,0 +1,16 @@ +{ + "name": "homebridge-lockitron", + "version": "0.0.1", + "description": "Lockitron plugin for homebridge: https://github.com/nfarina/homebridge", + "license": "ISC", + "keywords": [ + "homebridge-plugin" + ], + "engines": { + "node": ">=0.12.0", + "homebridge": ">=0.2.0" + }, + "dependencies": { + "request": "^2.65.0" + } +} diff --git a/lib/api.js b/lib/api.js new file mode 100644 index 0000000..f760917 --- /dev/null +++ b/lib/api.js @@ -0,0 +1,105 @@ +var hap = require("hap-nodejs"); +var hapLegacyTypes = require("hap-nodejs/accessories/types.js"); +var log = require("./logger")._system; + +// The official homebridge API is the object we feed the plugin's exported initializer function. + +module.exports = { + API: API +} + +function API() { + this._accessories = {}; // this._accessories[pluginName.accessoryName] = accessory constructor + this._platforms = {}; // this._platforms[pluginName.platformName] = platform constructor + + // expose HAP-NodeJS in its entirely for plugins to use instead of making Plugins + // require() it as a dependency - it's a heavy dependency so we don't want it in + // every single plugin. + this.hap = hap; + + // we also need to "bolt on" the legacy "types" constants for older accessories/platforms + // still using the "object literal" style JSON. + this.hapLegacyTypes = hapLegacyTypes; +} + +API.prototype.accessory = function(name) { + + // if you passed the "short form" name like "Lockitron" instead of "homebridge-lockitron.Lockitron", + // see if it matches exactly one accessory. + if (name.indexOf('.') == -1) { + var found = []; + for (var fullName in this._accessories) { + if (fullName.split(".")[1] == name) + found.push(fullName); + } + + if (found.length == 1) { + return this._accessories[found[0]]; + } + else if (found.length > 1) { + throw new Error("The requested accessory '" + name + "' has been registered multiple times. Please be more specific by writing one of: " + found.join(", ")); + } + else { + throw new Error("The requested accessory '" + name + "' was not registered by any plugin."); + } + } + else { + + if (!this._accessories[name]) + throw new Error("The requested accessory '" + name + "' was not registered by any plugin."); + + return this._accessories[name]; + } +} + +API.prototype.registerAccessory = function(pluginName, accessoryName, constructor) { + var fullName = pluginName + "." + accessoryName; + + if (this._accessories[fullName]) + throw new Error("Attempting to register an accessory '" + fullName + "' which has already been registered!"); + + log.info("Registering accessory '%s'", fullName); + + this._accessories[fullName] = constructor; +} + +API.prototype.platform = function(name) { + + // if you passed the "short form" name like "Lockitron" instead of "homebridge-lockitron.Lockitron", + // see if it matches exactly one platform. + if (name.indexOf('.') == -1) { + var found = []; + for (var fullName in this._platforms) { + if (fullName.split(".")[1] == name) + found.push(fullName); + } + + if (found.length == 1) { + return this._platforms[found[0]]; + } + else if (found.length > 1) { + throw new Error("The requested platform '" + name + "' has been registered multiple times. Please be more specific by writing one of: " + found.join(", ")); + } + else { + throw new Error("The requested platform '" + name + "' was not registered by any plugin."); + } + } + else { + + if (!this._platforms[name]) + throw new Error("The requested platform '" + name + "' was not registered by any plugin."); + + return this._platforms[name]; + } +} + +API.prototype.registerPlatform = function(pluginName, platformName, constructor) { + var fullName = pluginName + "." + platformName; + + if (this._platforms[fullName]) + throw new Error("Attempting to register a platform '" + fullName + "' which has already been registered!"); + + log.info("Registering platform '%s'", fullName); + + this._platforms[fullName] = constructor; +} \ No newline at end of file diff --git a/lib/cli.js b/lib/cli.js new file mode 100644 index 0000000..cbdeb06 --- /dev/null +++ b/lib/cli.js @@ -0,0 +1,23 @@ +var program = require('commander'); +var hap = require("hap-nodejs"); +var version = require('./version'); +var Server = require('./server').Server; +var Plugin = require('./plugin').Plugin; +var User = require('./user').User; +var log = require("./logger")._system; + +'use strict'; + +module.exports = function() { + + program + .version(version) + .option('-P, --plugin-path [path]', 'look for plugins installed at [path] as well as node_modules', function(p) { Plugin.addPluginPath(p); }) + .option('-D, --debug', 'turn on debug level logging', function() { logger.setDebugEnabled(true) }) + .parse(process.argv); + + // Initialize HAP-NodeJS with a custom persist directory + hap.init(User.persistPath()); + + new Server().run(); +} diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 0000000..9e8c4ca --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,88 @@ +var chalk = require('chalk'); +var util = require('util'); + +'use strict'; + +module.exports = { + Logger: Logger, + setDebugEnabled: setDebugEnabled, + _system: new Logger() // system logger, for internal use only +} + +var DEBUG_ENABLED = false; + +// Turns on debug level logging +function setDebugEnabled(enabled) { + DEBUG_ENABLED = enabled; +} + +// global cache of logger instances by plugin name +var loggerCache = {}; + +/** + * Logger class + */ + +function Logger(prefix) { + this.prefix = prefix; +} + +Logger.prototype.debug = function(msg) { + if (DEBUG_ENABLED) + this.log.apply(this, ['debug'].concat(Array.prototype.slice.call(arguments))); +} + +Logger.prototype.info = function(msg) { + this.log.apply(this, ['info'].concat(Array.prototype.slice.call(arguments))); +} + +Logger.prototype.warn = function(msg) { + this.log.apply(this, ['warn'].concat(Array.prototype.slice.call(arguments))); +} + +Logger.prototype.error = function(msg) { + this.log.apply(this, ['error'].concat(Array.prototype.slice.call(arguments))); +} + +Logger.prototype.log = function(level, msg) { + + msg = util.format.apply(util, Array.prototype.slice.call(arguments, 1)); + func = console.log; + + if (level == 'debug') { + msg = chalk.gray(msg); + } + else if (level == 'warn') { + msg = chalk.yellow(msg); + func = console.error; + } + else if (level == 'error') { + msg = chalk.bold.red(msg); + func = console.error; + } + + // prepend prefix if applicable + if (this.prefix) + msg = chalk.cyan("[" + this.prefix + "]") + " " + msg; + + func(msg); +} + +Logger.withPrefix = function(prefix) { + + if (!loggerCache[prefix]) { + // create a class-like logger thing that acts as a function as well + // as an instance of Logger. + var logger = new Logger(prefix); + var log = logger.info.bind(logger); + log.debug = logger.debug; + log.info = logger.info; + log.warn = logger.warn; + log.error = logger.error; + log.log = logger.log; + log.prefix = logger.prefix; + loggerCache[prefix] = log; + } + + return loggerCache[prefix]; +} diff --git a/lib/plugin.js b/lib/plugin.js new file mode 100644 index 0000000..262523c --- /dev/null +++ b/lib/plugin.js @@ -0,0 +1,183 @@ +var path = require('path'); +var fs = require('fs'); +var semver = require('semver'); +var User = require('./user').User; +var version = require('./version'); + +'use strict'; + +module.exports = { + Plugin: Plugin +} + +/** + * Homebridge Plugin. + * + * Allows for discovering and loading installed Homebridge plugins. + */ + +function Plugin(pluginPath) { + this.pluginPath = pluginPath; // like "/usr/local/lib/node_modules/homebridge-lockitron" + this.initializer; // exported function from the plugin that initializes it +} + +Plugin.prototype.name = function() { + return path.basename(this.pluginPath); +} + +Plugin.prototype.load = function(options) { + options = options || {}; + + // does this plugin exist at all? + if (!fs.existsSync(this.pluginPath)) { + throw new Error("Plugin " + this.pluginPath + " was not found. Make sure the module '" + this.pluginPath + "' is installed."); + } + + // attempt to load package.json + var pjson = Plugin.loadPackageJSON(this.pluginPath); + + // make sure the name is prefixed with 'homebridge-' + if (!pjson.name || pjson.name.indexOf('homebridge-') != 0) { + throw new Error("Plugin " + this.pluginPath + " does not have a package name that begins with 'homebridge-'."); + } + + // very temporary fix for first wave of plugins + if (pjson.peerDepdendencies && (!pjson.engines || !pjson.engines.homebridge)) { + var engines = pjson.engines || {} + engines.homebridge = pjson.peerDepdendencies.homebridge; + pjson.engines = engines; + } + + // pluck out the HomeBridge version requirement + if (!pjson.engines || !pjson.engines.homebridge) { + throw new Error("Plugin " + this.pluginPath + " does not contain the 'homebridge' package in 'engines'."); + } + + var versionRequired = pjson.engines.homebridge; + + // make sure the version is satisfied by the currently running version of HomeBridge + if (!semver.satisfies(version, versionRequired)) { + throw new Error("Plugin " + this.pluginPath + " requires a HomeBridge version of " + versionRequired + " which does not satisfy the current HomeBridge version of " + version + ". You may need to upgrade your installation of HomeBridge."); + } + + // figure out the main module - index.js unless otherwise specified + var main = pjson.main || "./index.js"; + + var mainPath = path.join(this.pluginPath, main); + + // try to require() it and grab the exported initialization hook + this.initializer = require(mainPath); +} + +Plugin.loadPackageJSON = function(pluginPath) { + // check for a package.json + var pjsonPath = path.join(pluginPath, "package.json"); + var pjson = null; + + if (!fs.existsSync(pjsonPath)) { + throw new Error("Plugin " + pluginPath + " does not contain a package.json."); + } + + try { + // attempt to parse package.json + pjson = JSON.parse(fs.readFileSync(pjsonPath)); + } + catch (err) { + throw new Error("Plugin " + pluginPath + " contains an invalid package.json. Error: " + err); + } + + // verify that it's tagged with the correct keyword + if (!pjson.keywords || pjson.keywords.indexOf("homebridge-plugin") == -1) { + throw new Error("Plugin " + pluginPath + " package.json does not contain the keyword 'homebridge-plugin'."); + } + + return pjson; +} + +Plugin.getDefaultPaths = function() { + var win32 = process.platform === 'win32'; + var paths = []; + + // add the paths used by require() + paths = paths.concat(require.main.paths); + + // THIS SECTION FROM: https://github.com/yeoman/environment/blob/master/lib/resolver.js + + // Adding global npm directories + // We tried using npm to get the global modules path, but it haven't work out + // because of bugs in the parseable implementation of `ls` command and mostly + // performance issues. So, we go with our best bet for now. + if (process.env.NODE_PATH) { + paths = process.env.NODE_PATH.split(path.delimiter) + .filter(function(p) { return !!p; }) // trim out empty values + .concat(paths); + } else { + // Default paths for each system + if (win32) { + paths.push(path.join(process.env.APPDATA, 'npm/node_modules')); + } else { + paths.push('/usr/local/lib/node_modules'); + paths.push('/usr/lib/node_modules'); + } + } + + return paths; +} + +// All search paths we will use to discover installed plugins +Plugin.paths = Plugin.getDefaultPaths(); + +Plugin.addPluginPath = function(pluginPath) { + Plugin.paths.unshift(path.resolve(process.cwd(), pluginPath)); +} + +// Gets all plugins installed on the local system +Plugin.installed = function() { + + var plugins = []; + var pluginsByName = {}; // don't add duplicate plugins + + // search for plugins among all known paths, in order + for (var index in Plugin.paths) { + var requirePath = Plugin.paths[index]; + + // just because this path is in require.main.paths doesn't mean it necessarily exists! + if (!fs.existsSync(requirePath)) + continue; + + var names = fs.readdirSync(requirePath); + + // does this path point inside a single plugin and not a directory containing plugins? + if (fs.existsSync(path.join(requirePath, "package.json"))) + names = [""]; + + // read through each directory in this node_modules folder + for (var index2 in names) { + var name = names[index2]; + + // reconstruct full path + var pluginPath = path.join(requirePath, name); + + // we only care about directories + if (!fs.statSync(pluginPath).isDirectory()) continue; + + // does this module contain a package.json? + try { + // throws an Error if this isn't a homebridge plugin + Plugin.loadPackageJSON(pluginPath); + } + catch (err) { + // swallow error and skip this module + continue; + } + + // add it to the return list + if (!pluginsByName[name]) { + pluginsByName[name] = true; + plugins.push(new Plugin(pluginPath)); + } + } + } + + return plugins; +} diff --git a/lib/server.js b/lib/server.js new file mode 100644 index 0000000..a9838d7 --- /dev/null +++ b/lib/server.js @@ -0,0 +1,281 @@ +var path = require('path'); +var fs = require('fs'); +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 once = require("hap-nodejs/lib/util/once").once; +var Plugin = require('./plugin').Plugin; +var User = require('./user').User; +var API = require('./api').API; +var log = require("./logger")._system; +var Logger = require('./logger').Logger; + +'use strict'; + +module.exports = { + Server: Server +} + +function Server() { + this._api = new API(); // object we feed to Plugins + this._plugins = this._loadPlugins(); // plugins[name] = Plugin instance + this._config = this._loadConfig(); + this._bridge = this._createBridge(); +} + +Server.prototype.run = function() { + + // keep track of async calls we're waiting for callbacks on before we can start up + this._asyncCalls = 0; + this._asyncWait = true; + + if (this._config.platforms) this._loadPlatforms(); + if (this._config.accessories) this._loadAccessories(); + + this._asyncWait = false; + + // publish now unless we're waiting on anyone + if (this._asyncCalls == 0) + this._publish(); +} + +Server.prototype._publish = function() { + // pull out our custom Bridge settings from config.json, if any + var bridgeConfig = this._config.bridge || {}; + + this._printPin(bridgeConfig.pin); + this._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 + }); + + log.info("Homebridge is running on port %s.", bridgeConfig.port || 51826); +} + +Server.prototype._loadPlugins = function(accessories, platforms) { + + var plugins = {}; + var foundOnePlugin = false; + + // load and validate plugins - check for valid package.json, etc. + Plugin.installed().forEach(function(plugin) { + + // attempt to load it + try { + plugin.load(); + } + catch (err) { + log.error("====================") + log.error("ERROR LOADING PLUGIN " + plugin.name() + ":") + log.error(err); + log.error("====================") + plugin.loadError = err; + } + + if (!plugin.loadError) { + + // add it to our dict for easy lookup later + plugins[plugin.name()] = plugin; + + log.info("Loaded plugin: " + plugin.name()); + + // call the plugin's initializer and pass it our API instance + plugin.initializer(this._api); + + log.info("---"); + foundOnePlugin = true; + } + + }.bind(this)); + + // Complain if you don't have any plugins. + if (!foundOnePlugin) { + log.warn("No plugins found. See the README for information on installing plugins.") + } + + return plugins; +} + +Server.prototype._loadConfig = function() { + + // Look for the configuration file + var configPath = User.configPath(); + + // Complain and exit if it doesn't exist yet + if (!fs.existsSync(configPath)) { + log.error("Couldn't find a config.json file at '"+configPath+"'. Look at config-sample.json for examples of how to format your config.js and add your home accessories."); + process.exit(1); + } + + // Load up the configuration file + var config; + try { + config = JSON.parse(fs.readFileSync(configPath)); + } + catch (err) { + log.error("There was a problem reading your config.json file."); + log.error("Please try pasting your config.json file here to validate it: http://jsonlint.com"); + log.error(""); + throw err; + } + + var accessoryCount = (config.accessories && config.accessories.length) || 0; + var platformCount = (config.platforms && config.platforms.length) || 0; + log.info("Loaded config.json with %s accessories and %s platforms.", accessoryCount, platformCount); + + log.info("---"); + + return config; +} + +Server.prototype._createBridge = function() { + // pull out our custom Bridge settings from config.json, if any + var bridgeConfig = this._config.bridge || {}; + + // Create our Bridge which will host all loaded Accessories + return new Bridge(bridgeConfig.name || 'Homebridge', uuid.generate("HomeBridge")); +} + +Server.prototype._loadAccessories = function() { + + // Instantiate all accessories in the config + log.info("Loading " + this._config.accessories.length + " accessories..."); + + for (var i=0; i=0.12.0" + }, + "preferGlobal": true, "dependencies": { - "ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local", - "async": "^1.4.2", - "carwingsjs": "0.0.x", - "chokidar": "^1.0.5", - "color": "0.10.x", - "debug": "^2.2.0", - "eibd": "^0.3.1", - "elkington": "kevinohara80/elkington", - "hap-nodejs": "^0.0.3", - "harmonyhubjs-client": "^1.1.6", - "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", - "homematic-xmlrpc": "git+https://github.com/hobbyquaker/homematic-xmlrpc", - "isy-js": "", - "komponist": "0.1.0", - "lifx": "git+https://github.com/magicmonkey/lifxjs.git", - "lifx-api": "^1.0.1", - "mdns": "^2.2.4", - "netatmo": "git+https://github.com/patricks/netatmo.git", - "node-cache": "3.0.0", - "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/kraigm/unofficial_nodejs_nest.git#3cbd337adc32fab3b481659b38d86f9fcd6a9c02", - "wemo": "0.2.x", - "wink-js": "0.0.5", - "xml2js": "0.4.x", - "xmldoc": "0.1.x", - "yamaha-nodejs": "0.4.x", - "serialport": "2.0.x" + "chalk": "^1.1.1", + "commander": "2.8.1", + "hap-nodejs": "0.0.2", + "semver": "5.0.3" } } diff --git a/platforms/Domoticz.js b/platforms/Domoticz.js deleted file mode 100644 index 948167d..0000000 --- a/platforms/Domoticz.js +++ /dev/null @@ -1,378 +0,0 @@ -// Domoticz Platform Shim for HomeBridge -// Written by Joep Verhaeg (http://www.joepverhaeg.nl) -// -// Revisions: -// -// 12 June 2015 [GizMoCuz] -// - Added support for RGB lights -// - Added support for Scenes -// - Sorting device names -// -// 22 July 2015 [lukeredpath] -// - Added SSL and basic auth support -// -// 26 August 2015 [EddyK69] -// - Added parameter in config.json: 'loadscenes' for enabling/disabling loading scenes -// - Fixed issue with dimmer-range; was 0-100, should be 0-16 -// -// 27 August 2015 [EddyK69] -// - Fixed issue that 'on/off'-type lights showed as dimmers in HomeKit. Checking now on SwitchType instead of HaveDimmer -// - Fixed issue that 'on-off'-type lights would not react on Siri 'Switch on/off light'; On/Off types are now handled as Lights instead of Switches -// (Cannot determine if 'on/off'-type device is a Light or a Switch :( ) -// -// 14 September 2015 [lukeredpath] -// - Fixed incorrect dimmer range for LightwaveRF lights (0-32 required, MaxDimLevel should be honored) -// -// -// Domoticz JSON API required -// https://www.domoticz.com/wiki/Domoticz_API/JSON_URL's#Lights_and_switches -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "Domoticz", -// "name": "Domoticz", -// "server": "127.0.0.1", -// "port": "8080", -// "roomid": 123, (0=no roomplan) -// "loadscenes": 1 (0=disable scenes) -// } -// ], -// -// If your server uses HTTPS, you can specify "ssl": true in your config. If -// your server uses a self-signed certificate, you'll need to run the following -// before starting the server or you will get an error: -// -// export NODE_TLS_REJECT_UNAUTHORIZED=0 -// -// For basic auth support, specify the "user" and "password" in your config. -// -// When you attempt to add a device, it will ask for a "PIN code". -// The default code for all HomeBridge accessories is 031-45-154. -// -var types = require("hap-nodejs/accessories/types.js"); -var request = require("request"); - -function DomoticzPlatform(log, config){ - this.log = log; - this.user = config["user"]; - this.password = config["password"]; - this.server = config["server"]; - this.port = config["port"]; - this.protocol = config["ssl"] ? "https" : "http"; - this.roomid = 0; - if (typeof config["roomid"] != 'undefined') { - this.roomid = config["roomid"]; - } - this.loadscenes = 1; - if (typeof config["loadscenes"] != 'undefined') { - this.loadscenes = config["loadscenes"]; - } -} - -function sortByKey(array, key) { - return array.sort(function(a, b) { - var x = a[key]; var y = b[key]; - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - }); -} - -DomoticzPlatform.prototype = { - urlForQuery: function(query) { - var serverString = this.server; - if (this.user && this.password) { - serverString = this.user + ":" + this.password + "@" + serverString; - } - return this.protocol + "://" + serverString + ":" + this.port + "/json.htm?" + query; - }, - - accessories: function(callback) { - this.log("Fetching Domoticz lights and switches..."); - var that = this; - var foundAccessories = []; - - // mechanism to ensure callback is only executed once all requests complete - var asyncCalls = 0; - function callbackLater() { if (--asyncCalls == 0) callback(foundAccessories); } - - if (this.roomid == 0) { - //Get Lights - asyncCalls++; - request.get({ - url: this.urlForQuery("type=devices&filter=light&used=true&order=Name"), - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - if (json['result'] != undefined) { - var sArray=sortByKey(json['result'],"Name"); - sArray.map(function(s) { - var havedimmer = (s.SwitchType == 'Dimmer') - accessory = new DomoticzAccessory(that.log, that, false, s.idx, s.Name, havedimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW")); - foundAccessories.push(accessory); - }) - } - callbackLater(); - } else { - that.log("There was a problem connecting to Domoticz. (" + err + ")"); - } - }); - } - else { - //Get all devices specified in the room - asyncCalls++; - request.get({ - url: this.urlForQuery("type=devices&plan=" + this.roomid), - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - if (json['result'] != undefined) { - var sArray=sortByKey(json['result'],"Name"); - sArray.map(function(s) { - //only accept switches for now - if (typeof s.SwitchType != 'undefined') { - var havedimmer = (s.SwitchType == 'Dimmer') - accessory = new DomoticzAccessory(that.log, that, false, s.idx, s.Name, havedimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW")); - foundAccessories.push(accessory); - } - }) - } - callbackLater(); - } else { - that.log("There was a problem connecting to Domoticz."); - } - }); - } - //Get Scenes - if (this.loadscenes == 1) { - asyncCalls++; - request.get({ - url: this.urlForQuery("type=scenes"), - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - if (json['result'] != undefined) { - var sArray=sortByKey(json['result'],"Name"); - sArray.map(function(s) { - accessory = new DomoticzAccessory(that.log, that, true, s.idx, s.Name, false, 0, false); - foundAccessories.push(accessory); - }) - } - callbackLater(); - } else { - that.log("There was a problem connecting to Domoticz."); - } - }); - } - } -} - -function DomoticzAccessory(log, platform, IsScene, idx, name, HaveDimmer, MaxDimLevel, HaveRGB) { - // device info - this.IsScene = IsScene; - this.idx = idx; - this.name = name; - this.HaveDimmer = HaveDimmer; - this.MaxDimLevel = MaxDimLevel; - this.HaveRGB = HaveRGB; - this.log = log; - this.platform = platform; -} - -DomoticzAccessory.prototype = { - command: function(c,value) { - this.log(this.name + " sending command " + c + " with value " + value); - if (this.IsScene == false) { - //Lights - if (c == "On" || c == "Off") { - url = this.platform.urlForQuery("type=command¶m=switchlight&idx=" + this.idx + "&switchcmd=" + c + "&level=0"); - } - else if (c == "setHue") { - url = this.platform.urlForQuery("type=command¶m=setcolbrightnessvalue&idx=" + this.idx + "&hue=" + value + "&brightness=100" + "&iswhite=false"); - } - else if (c == "setLevel") { - value = this.dimmerLevelForValue(value) - url = this.platform.urlForQuery("type=command¶m=switchlight&idx=" + this.idx + "&switchcmd=Set%20Level&level=" + value); - } - else if (value != undefined) { - this.log(this.name + " Unhandled Light command! cmd=" + c + ", value=" + value); - } - } - else { - //Scenes - if (c == "On" || c == "Off") { - url = this.platform.urlForQuery("type=command¶m=switchscene&idx=" + this.idx + "&switchcmd=" + c); - } - else if (value != undefined) { - this.log(this.name + " Unhandled Scene command! cmd=" + c + ", value=" + value); - } - } - - var that = this; - request.put({ url: url }, function(err, response) { - if (err) { - that.log("There was a problem sending command " + c + " to" + that.name); - that.log(url); - } else { - that.log(that.name + " sent command " + c + " (value: " + value + ")"); - } - }) - }, - - // translates the HomeKit dim level as a percentage to whatever scale the device requires - dimmerLevelForValue: function(value) { - if (this.MaxDimLevel == 100) { - return value; - } - return Math.round((value / 100.0) * this.MaxDimLevel) - }, - - informationCharacteristics: function() { - return [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Domoticz", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Rev-1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - } - ] - }, - - controlCharacteristics: function(that) { - cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }] - - if (this.idx != undefined) { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { - if (value == 0) { - that.command("Off") - } else { - that.command("On") - } - }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - }) - } - - if (this.HaveDimmer == true) { - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { that.command("setLevel", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: this.MaxDimLevel, - designedMinStep: 1, - unit: "%" - }) - } - if (this.HaveRGB == true) { - cTypes.push({ - cType: types.HUE_CTYPE, - onUpdate: function(value) { that.command("setHue", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Hue of Light", - designedMinValue: 0, - designedMaxValue: 360, - designedMinStep: 1, - unit: "arcdegrees" - }) - } - - return cTypes - }, - - sType: function() { - //if (this.HaveDimmer == true) { - return types.LIGHTBULB_STYPE - //} else { - // return types.SWITCH_STYPE - //} - }, - - getServices: function() { - var that = this; - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), - }, - { - sType: this.sType(), - characteristics: this.controlCharacteristics(that) - }]; - this.log("Loaded services for " + this.name) - return services; - } -}; - -module.exports.accessory = DomoticzAccessory; -module.exports.platform = DomoticzPlatform; diff --git a/platforms/FHEM.js b/platforms/FHEM.js deleted file mode 100644 index 08f337d..0000000 --- a/platforms/FHEM.js +++ /dev/null @@ -1,2159 +0,0 @@ -// FHEM Platform Shim for HomeBridge -// current version on https://github.com/justme-1968/homebridge -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// 'platform': "FHEM", -// 'name': "FHEM", -// 'server': "127.0.0.1", -// 'port': 8083, -// 'ssl': true, -// 'auth': {'user': "fhem", 'pass': "fhempassword"}, -// 'filter': "room=xyz" -// } -// ], - -var Service; -try { - Service = require("hap-nodejs").Service; -} catch(err) { - Service = require("HAP-NodeJS").Service; -} - -var Characteristic; -try { - Characteristic = require("hap-nodejs").Characteristic; -} catch(err) { - Characteristic = require("HAP-NodeJS").Characteristic; -} - -var util = require('util'); - - -// subscriptions to fhem longpoll evens -var FHEM_subscriptions = {}; -function -FHEM_subscribe(characteristic, inform_id, accessory) { - FHEM_subscriptions[inform_id] = { 'characteristic': characteristic, 'accessory': accessory }; -} - -function -FHEM_isPublished(device) { - var keys = Object.keys(FHEM_subscriptions); - for( var i = 0; i < keys.length; i++ ) { - var key = keys[i]; - - var subscription = FHEM_subscriptions[key]; - var accessory = subscription.accessory; - - if( accessory.device === device ) - return true; - }; - - return false; -} - -// cached readings from longpoll & query -var FHEM_cached = {}; -//var FHEM_internal = {}; -function -FHEM_update(inform_id, value, no_update) { - var subscription = FHEM_subscriptions[inform_id]; - if( subscription != undefined ) { - if( value == undefined - || FHEM_cached[inform_id] === value ) - return; - - FHEM_cached[inform_id] = value; - //FHEM_cached[inform_id] = { 'value': value, 'timestamp': Date.now() }; - var date = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); - console.log(" " + date + " caching: " + inform_id + ": " + value + " as " + typeof(value) ); - - if( !no_update && subscription.characteristic ) - subscription.characteristic.setValue(value, undefined, 'fromFHEM'); - } -} - - -var FHEM_lastEventTime = {}; -var FHEM_longpoll_running = {}; -//FIXME: add filter -function FHEM_startLongpoll(connection) { - if( FHEM_longpoll_running[connection.base_url] ) - return; - FHEM_longpoll_running[connection.base_url] = true; - - if( connection.disconnects == undefined ) - connection.disconnects = 0; - - var filter = ".*"; - var since = "null"; - if( FHEM_lastEventTime[connection.base_url] ) - since = FHEM_lastEventTime[connection.base_url]/1000; - var query = "/fhem.pl?XHR=1"+ - "&inform=type=status;filter="+filter+";since="+since+";fmt=JSON"+ - "×tamp="+Date.now() - - var url = encodeURI( connection.base_url + query ); - console.log( 'starting longpoll: ' + url ); - - var FHEM_longpollOffset = 0; - var input = ""; - connection.request.get( { url: url } ).on( 'data', function(data) { -//console.log( 'data: '+ data ); - if( !data ) - return; - - input += data; - var lastEventTime = Date.now(); - for(;;) { - var nOff = input.indexOf('\n', FHEM_longpollOffset); - if(nOff < 0) - break; - var l = input.substr(FHEM_longpollOffset, nOff-FHEM_longpollOffset); - FHEM_longpollOffset = nOff+1; -//console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); - if(!l.length) - continue; - - var d; - if( l.substr(0,1) == '[' ) - d = JSON.parse(l); - else - d = l.split("<<", 3); - - //console.log(d); - - if(d.length != 3) - continue; - if(d[0].match(/-ts$/)) - continue; - -//console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); - - var subscription = FHEM_subscriptions[d[0]]; - if( subscription != undefined ) { -//console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); - FHEM_lastEventTime[connection.base_url] = lastEventTime; - var accessory = subscription.accessory; - - var value = d[1]; - if( value.match( /^set-/ ) ) - continue; - - var match = d[0].match(/([^-]*)-(.*)/); - var device = match[1]; - var reading = match[2]; - if( reading == undefined ) - continue; - - if( reading == 'state') { - if( accessory.mappings.window ) { - var level = 50; - if( match = value.match(/^(\d+)/ ) ) - level = parseInt( match[1] ); - else if( value == 'locked' ) - level = 0; - - FHEM_update( accessory.mappings.window.informId, level ); - continue; - - } else if( accessory.mappings.lock ) { - var lock = Characteristic.LockCurrentState.UNSECURED; - if( value.match( /^locked/ ) ) - lock = Characteristic.LockCurrentState.SECURED; - - if( value.match( /uncertain/ ) ) - level = Characteristic.LockCurrentState.UNKNOWN; - - FHEM_update( accessory.mappings.lock.informId, lock ); - continue; - - } else if( match = value.match(/dim(\d+)%/ ) ) { - var pct = parseInt( match[1] ); - - FHEM_update( device+'-pct', pct ); - } - - } else if( reading == 'activity') { - - FHEM_update( device+'-'+reading, value, true ); - - Object.keys(FHEM_subscriptions).forEach(function(key) { - var parts = key.split( '-', 3 ); - if( parts[0] != '#' + device ) - return; - if( parts[1] != reading ) - return; - - var subscription = FHEM_subscriptions[key]; - var accessory = subscription.accessory; - - var activity = parts[2]; - - subscription.characteristic.setValue(value==activity?1:0, undefined, 'fromFHEM'); - } ); - - continue; - - } else if(accessory.mappings.rgb && reading == accessory.mappings.rgb.reading) { - var hsv = FHEM_rgb2hsv(value); - var hue = parseInt( hsv[0] * 360 ); - var sat = parseInt( hsv[1] * 100 ); - var bri = parseInt( hsv[2] * 100 ); - - //FHEM_update( device+'-'+reading, value, false ); - FHEM_update( device+'-hue', hue ); - FHEM_update( device+'-sat', sat ); - FHEM_update( device+'-bri', bri ); - continue; - - } else if(accessory.mappings.colormode) { - //FIXME: add colormode ct - if( reading == 'xy') { - var xy = value.split(','); - var rgb = FHEM_xyY2rgb(xy[0], xy[1] , 1); - var hsv = FHEM_rgb2hsv(rgb); - var hue = parseInt( hsv[0] * 360 ); - var sat = parseInt( hsv[1] * 100 ); - var bri = parseInt( hsv[2] * 100 ); - - FHEM_update( device+'-hue', hue ); - FHEM_update( device+'-sat', sat ); - FHEM_update( device+'-bri', bri ); - } - - FHEM_update( device+'-'+reading, value, false ); - continue; - - } - - value = accessory.reading2homekit(reading, value); - FHEM_update( device+'-'+reading, value ); - - } else { - } - - } - - input = input.substr(FHEM_longpollOffset); - FHEM_longpollOffset = 0; - - connection.disconnects = 0; - - } ).on( 'end', function() { - FHEM_longpoll_running[connection.base_url] = false; - - connection.disconnects++; - var timeout = 500 * connection.disconnects - 300; - if( timeout > 30000 ) timeout = 30000; - - console.log( "longpoll ended, reconnect in: " + timeout + "msec" ); - setTimeout( function(){FHEM_startLongpoll(connection)}, timeout ); - - } ).on( 'error', function(err) { - FHEM_longpoll_running[connection.base_url] = false; - - connection.disconnects++; - var timeout = 5000 * connection.disconnects; - if( timeout > 30000 ) timeout = 30000; - - console.log( "longpoll error: " + err + ", retry in: " + timeout + "msec" ); - setTimeout( function(){FHEM_startLongpoll(connection)}, timeout ); - - } ); -} - - -function -FHEMPlatform(log, config) { - this.log = log; - this.server = config['server']; - this.port = config['port']; - this.filter = config['filter']; - - var base_url; - if( config['ssl'] ) - base_url = 'https://'; - else - base_url = 'http://'; - base_url += this.server + ':' + this.port; - - var request = require('request'); - var auth = config['auth']; - if( auth ) { - if( auth.sendImmediately == undefined ) - auth.sendImmediately = false; - - request = request.defaults( { 'auth': auth, 'rejectUnauthorized': false } ); - } - - this.connection = { 'base_url': base_url, 'request': request }; - - FHEM_startLongpoll( this.connection ); -} - -function -FHEM_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 -FHEM_rgb2hex(r,g,b) { - if( g == undefined ) - return Number(0x1000000 + r[0]*0x10000 + r[1]*0x100 + r[2]).toString(16).substring(1); - - return Number(0x1000000 + r*0x10000 + g*0x100 + b).toString(16).substring(1); -} - -function -FHEM_hsv2rgb(h,s,v) { - var r = 0.0; - var g = 0.0; - var b = 0.0; - - if( s == 0 ) { - r = v; - g = v; - b = v; - - } else { - var i = Math.floor( h * 6.0 ); - var f = ( h * 6.0 ) - i; - var p = v * ( 1.0 - s ); - var q = v * ( 1.0 - s * f ); - var t = v * ( 1.0 - s * ( 1.0 - f ) ); - i = i % 6; - - if( i == 0 ) { - r = v; - g = t; - b = p; - } else if( i == 1 ) { - r = q; - g = v; - b = p; - } else if( i == 2 ) { - r = p; - g = v; - b = t; - } else if( i == 3 ) { - r = p; - g = q; - b = v; - } else if( i == 4 ) { - r = t; - g = p; - b = v; - } else if( i == 5 ) { - r = v; - g = p; - b = q; - } - } - - return FHEM_rgb2hex( Math.round(r*255),Math.round(g*255),Math.round(b*255) ); -} -function -FHEM_ct2rgb(ct) -{ - // calculation from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code - // adjusted by 1000K - var temp = (1000000/ct)/100 + 10; - - var r = 0; - var g = 0; - var b = 0; - - r = 255; - if( temp > 66 ) - r = 329.698727446 * Math.pow(temp - 60, -0.1332047592); - if( r < 0 ) - r = 0; - if( r > 255 ) - r = 255; - - if( temp <= 66 ) - g = 99.4708025861 * Math.log(temp) - 161.1195681661; - else - g = 288.1221695283 * Math.pow(temp - 60, -0.0755148492); - if( g < 0 ) - g = 0; - if( g > 255 ); - g = 255; - - b = 255; - if( temp <= 19 ) - b = 0; - if( temp < 66 ) - b = 138.5177312231 * log(temp-10) - 305.0447927307; - if( b < 0 ) - b = 0; - if( b > 255 ) - b = 255; - - return FHEM_rgb2hex( Math.round(r*255),Math.round(g*255),Math.round(b*255) ); -} - -function -FHEM_xyY2rgb(x,y,Y) -{ - // calculation from http://www.brucelindbloom.com/index.html - - var r = 0; - var g = 0; - var b = 0; - - if( y > 0 ) { - var X = x * Y / y; - var Z = (1 - x - y)*Y / y; - - if( X > 1 - || Y > 1 - || Z > 1 ) { - var f = Math.max(X,Y,Z); - X /= f; - Y /= f; - Z /= f; - } - - r = 0.7982 * X + 0.3389 * Y - 0.1371 * Z; - g = -0.5918 * X + 1.5512 * Y + 0.0406 * Z; - b = 0.0008 * X + 0.0239 * Y + 0.9753 * Z; - - if( r > 1 - || g > 1 - || b > 1 ) { - var f = Math.max(r,g,b); - r /= f; - g /= f; - b /= f; - } - - r *= 255; - g *= 255; - b *= 255; - } - - return FHEM_rgb2hex( Math.round(r*255),Math.round(g*255),Math.round(b*255) ); -} - - -function -FHEM_rgb2hsv(r,g,b){ - if( r == undefined ) - return; - - if( g == undefined ) { - var str = r; - r = parseInt( str.substr(0,2), 16 ); - g = parseInt( str.substr(2,2), 16 ); - b = parseInt( str.substr(4,2), 16 ); - } - - var M = Math.max( r, g, b ); - var m = Math.min( r, g, b ); - var c = M - m; - - var h, s, v; - if( c == 0 ) { - h = 0; - } else if( M == r ) { - h = ( 60 * ( ( g - b ) / c ) % 360 ) / 360; - } else if( M == g ) { - h = ( 60 * ( ( b - r ) / c ) + 120 ) / 360; - } else if( M == b ) { - h = ( 60 * ( ( r - g ) / c ) + 240 ) / 360; - } - - if( M == 0 ) { - s = 0; - } else { - s = c / M; - } - - v = M/255; - - return [h,s,v]; -} - -function -FHEM_execute(log,connection,cmd,callback) { - var url = encodeURI( connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); - log( ' executing: ' + url ); - - connection.request - .get( { url: url, gzip: true }, - function(err, response, result) { - if( !err && response.statusCode == 200 ) { - result = result.replace(/[\r\n]/g, ""); - if( callback ) - callback( result ); - - } else { - log("There was a problem connecting to FHEM ("+ url +")."); - if( response ) - log( " " + response.statusCode + ": " + response.statusMessage ); - - } - - } ) - .on( 'error', function(err) { log("There was a problem connecting to FHEM ("+ url +"):"+ err); } ); -} - -FHEMPlatform.prototype = { - execute: function(cmd,callback) {FHEM_execute(this.log, this.connection, cmd, callback)}, - - checkAndSetGenericDeviceType: function() { - this.log("Checking genericDeviceType..."); - - var cmd = '{AttrVal("global","userattr","")}'; - - this.execute( cmd, - function(result) { - //if( result == undefined ) - //result = ""; - - if( !result.match(/(^| )genericDeviceType\b/) ) { - //FIXME: use addToAttrList - var cmd = 'attr global userattr ' + result + ' genericDeviceType:ignore,switch,outlet,light,blind,thermostat,garage,window,lock'; - this.execute( cmd, - function(result) { -console.log( result ); - console.log( 'genericDeviceType attribute was not known. please restart homebridge.' ); - process.exit(0); - } ); - } - }.bind(this) ); - - }, - - accessories: function(callback) { - //this.checkAndSetGenericDeviceType(); - - this.log("Fetching FHEM switchable devices..."); - - var foundAccessories = []; - - // mechanism to ensure callback is only executed once all requests complete - var asyncCalls = 0; - function callbackLater() { if (--asyncCalls == 0) callback(foundAccessories); } - - var cmd = 'jsonlist2'; - if( this.filter ) - cmd += " " + this.filter; - var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); - this.log( 'fetching: ' + url ); - - - asyncCalls++; - - this.connection.request.get( { url: url, json: true, gzip: true }, - function(err, response, json) { - if( !err && response.statusCode == 200 ) { - this.log( 'got: ' + json['totalResultsReturned'] + ' results' ); -//this.log("got json: " + util.inspect(json) ); - if( json['totalResultsReturned'] ) { - var sArray=FHEM_sortByKey(json['Results'],"Name"); - sArray.map(function(s) { - - var accessory; - if( FHEM_isPublished(s.Internals.NAME) ) - this.log( s.Internals.NAME + ' is already published'); - - else if( s.Attributes.disable == 1 ) { - this.log( s.Internals.NAME + ' is disabled'); - - } else if( s.Internals.TYPE == 'structure' ) { - this.log( 'ignoring structure ' + s.Internals.NAME ); - - } else if( s.Attributes.genericDisplayType - || s.Attributes.genericDeviceType ) { - accessory = new FHEMAccessory(this.log, this.connection, s); - - } else if( s.PossibleSets.match(/(^| )on\b/) - && s.PossibleSets.match(/(^| )off\b/) ) { - accessory = new FHEMAccessory(this.log, this.connection, s); - - } else if( s.Attributes.subType == 'thermostat' - || s.Attributes.subType == 'blindActuator' - || s.Attributes.subType == 'threeStateSensor' ) { - accessory = new FHEMAccessory(this.log, this.connection, s); - - } else if( s.Attributes.model == 'HM-SEC-WIN' ) { - accessory = new FHEMAccessory(this.log, this.connection, s); - - } else if( s.Attributes.model && s.Attributes.model.match(/^HM-SEC-KEY/) ) { - accessory = new FHEMAccessory(this.log, this.connection, s); - - } else if( s.Internals.TYPE == 'PRESENCE' - || s.Internals.TYPE == 'ROOMMATE' ) { - accessory = new FHEMAccessory(this.log, this.connection, s); - - } else if( s.Internals.TYPE == 'SONOSPLAYER' ) { - accessory = new FHEMAccessory(this.log, this.connection, s); - - } else if( s.Readings.temperature ) { - accessory = new FHEMAccessory(this.log, this.connection, s); - - } else if( s.Readings.humidity ) { - accessory = new FHEMAccessory(this.log, this.connection, s); - - } else if( s.Readings.voc ) { - accessory = new FHEMAccessory(this.log, this.connection, s); - - } else if( s.Internals.TYPE == 'harmony' ) { - accessory = new FHEMAccessory(this.log, this.connection, s); - - } else { - this.log( 'ignoring ' + s.Internals.NAME + ' (' + s.Internals.TYPE + ')' ); - - } - - if( accessory && Object.getOwnPropertyNames(accessory).length ) - foundAccessories.push(accessory); - - }.bind(this) ); - } - - callback(foundAccessories); - //callbackLater(); - - } else { - this.log("There was a problem connecting to FHEM (1)."); - if( response ) - this.log( " " + response.statusCode + ": " + response.statusMessage ); - - } - - }.bind(this) ); - } -} - -function -FHEMAccessory(log, connection, s) { -//log( 'sets: ' + s.PossibleSets ); -//log("got json: " + util.inspect(s) ); -//log("got json: " + util.inspect(s.Internals) ); - - if( !(this instanceof FHEMAccessory) ) - return new FHEMAccessory(log, connection, s); - - if( s.Attributes.disable == 1 ) { - log( s.Internals.NAME + ' is disabled'); - return null; - - } else if( s.Internals.TYPE == 'structure' ) { - log( 'ignoring ' + s.Internals.NAME + ' (' + s.Internals.TYPE + ')' ); - return null; - - } - - var genericType = s.Attributes.genericDeviceType; - if( !genericType ) - genericType = s.Attributes.genericDisplayType; - - if( genericType == 'ignore' ) { - log( 'ignoring ' + s.Internals.NAME ); - return null; - } - - - this.mappings = {}; - - var match; - if( match = s.PossibleSets.match(/(^| )pct\b/) ) { - this.mappings.pct = { reading: 'pct', cmd: 'pct' }; - } else if( match = s.PossibleSets.match(/(^| )dim\d+%/) ) { - s.hasDim = true; - s.pctMax = 100; - } - if( match = s.PossibleSets.match(/(^| )hue[^\b\s]*(,(\d+)?)+\b/) ) { - s.isLight = true; - var max = 360; - if( match[3] != undefined ) - max = match[3]; - this.mappings.hue = { reading: 'hue', cmd: 'hue', min: 0, max: max }; - } - if( match = s.PossibleSets.match(/(^| )sat[^\b\s]*(,(\d+)?)+\b/) ) { - s.isLight = true; - var max = 100; - if( match[3] != undefined ) - max = match[3]; - this.mappings.sat = { reading: 'sat', cmd: 'sat', min: 0, max: max }; - } - - if( s.Readings.colormode ) - this.mappings.colormode = { reading: 'colormode' }; - if( s.Readings.xy ) - this.mappings.xy = { reading: 'xy' }; - //FIXME: add ct/colortemperature - - if( s.PossibleSets.match(/(^| )rgb\b/) ) { - s.isLight = true; - this.mappings.rgb = { reading: 'rgb', cmd: 'rgb' }; - if( s.Internals.TYPE == 'SWAP_0000002200000003' ) - this.mappings.rgb = { reading: '0B-RGBlevel', cmd: 'rgb' }; - } else if( s.PossibleSets.match(/(^| )RGB\b/) ) { - s.isLight = true; - this.mappings.rgb = { reading: 'RGB', cmd: 'RGB' }; - } - - if( s.Readings['measured-temp'] ) - this.mappings.temperature = { reading: 'measured-temp' }; - else if( s.Readings.temperature ) - this.mappings.temperature = { reading: 'temperature' }; - - if( s.Readings.volume ) - this.mappings.volume = { reading: 'volume', cmd: 'volume' }; - else if( s.Readings.Volume ) { - this.mappings.volume = { reading: 'Volume', cmd: 'Volume', nocache: true }; - if( s.Attributes.generateVolumeEvent == 1 ) - delete this.mappings.volume.nocache; - } - - if( s.Readings.humidity ) - this.mappings.humidity = { reading: 'humidity' }; - - if( s.Readings.luminosity ) - this.mappings.light = { reading: 'luminosity' }; - - if( s.Readings.voc ) - this.mappings.airquality = { reading: 'voc' }; - - if( s.Readings.motor ) - this.mappings.motor = { reading: 'motor' }; - - if( s.Readings.battery ) - this.mappings.battery = { reading: 'battery' }; - - if( s.Readings.direction ) - this.mappings.direction = { reading: 'direction' }; - - if( s.Readings['D-firmware'] ) - this.mappings.firmware = { reading: 'D-firmware' }; - - - if( genericType == 'switch' ) - s.isSwitch = true; - - else if( genericType == 'outlet' ) - s.isOutlet = true; - - else if( genericType == 'garage' ) - this.mappings.garage = { cmdOpen: 'on', cmdClose: 'off' }; - - else if( genericType == 'light' ) - s.isLight = true; - - else if( genericType == 'blind' - || s.Attributes.subType == 'blindActuator' ) { - delete this.mappings.pct; - if( s.PossibleSets.match(/[\^ ]position\b/) ) - this.mappings.blind = { reading: 'position', cmd: 'position' }; - else - this.mappings.blind = { reading: 'pct', cmd: 'pct' }; - - } else if( genericType == 'window' - || s.Attributes.model == 'HM-SEC-WIN' ) - this.mappings.window = { reading: 'level', cmd: 'level' }; - - else if( genericType == 'lock' - || ( s.Attributes.model && s.Attributes.model.match(/^HM-SEC-KEY/ ) ) ) { - this.mappings.lock = { reading: 'lock', cmdLock: 'lock', cmdUnlock: 'unlock', cmdOpen: 'open' }; - if( s.Internals.TYPE == 'dummy' ) - this.mappings.lock = { reading: 'lock', cmdLock: 'lock locked', cmdUnlock: 'lock unlocked', cmdOpen: 'open' }; - - } else if( genericType == 'thermostat' - || s.Attributes.subType == 'thermostat' ) - s.isThermostat = true; - - else if( s.Internals.TYPE == 'CUL_FHTTK' ) - this.mappings.contact = { reading: 'Window' }; - - else if( s.Internals.TYPE == 'MAX' - && s.Internals.type == 'ShutterContact' ) - this.mappings.contact = { reading: 'state' }; - - else if( s.Attributes.subType == 'threeStateSensor' ) - this.mappings.contact = { reading: 'contact' }; - - else if( s.Internals.TYPE == 'PRESENCE' ) - this.mappings.occupancy = { reading: 'state' }; - - else if( s.Internals.TYPE == 'ROOMMATE' ) - this.mappings.occupancy = { reading: 'presence' }; - - else if( s.Attributes.model == 'fs20di' ) - s.isLight = true; - - //if( s.PossibleSets.match(/(^| )desired-temp\b/) ) { - if( match = s.PossibleSets.match(/(^| )desired-temp(:[^\d]*([^\$ ]*))?/) ) { - this.mappings.thermostat = { reading: 'desired-temp', cmd: 'desired-temp' }; - - if( s.Readings.controlMode ) - this.mappings.thermostat_mode = { reading: 'controlMode', cmd: 'controlMode' }; - - if( match[3] ) { - var values = match[3].split(','); - this.mappings.thermostat.min = parseFloat(values[0]); - this.mappings.thermostat.max = parseFloat(values[values.length-1]); - this.mappings.thermostat.step = values[1] - values[0]; - } - - //} else if( s.PossibleSets.match(/(^| )desiredTemperature\b/) ) { - } else if( match = s.PossibleSets.match(/(^| )desiredTemperature(:[^\d]*([^\$ ]*))?/) ) { - this.mappings.thermostat = { reading: 'desiredTemperature', cmd: 'desiredTemperature' }; - if( s.Readings.mode ) - this.mappings.thermostat_mode = { reading: 'mode', cmd: 'desiredTemperature' }; - - if( match[3] ) { - var values = match[3].split(','); - this.mappings.thermostat.min = values[0]; - this.mappings.thermostat.max = values[values.length-2]; - this.mappings.thermostat.step = values[1] - values[0]; - } - - } else if( s.isThermostat ) { - s.isThermostat = false; - delete this.mappings.thermostat; - log( s.Internals.NAME + ' is NOT a thermostat. set for target temperature missing' ); - - } - - if( s.Internals.TYPE == 'SONOSPLAYER' ) //FIXME: use sets [Pp]lay/[Pp]ause/[Ss]top - this.mappings.onOff = { reading: 'transportState', cmdOn: 'play', cmdOff: 'pause' }; - - else if( s.Internals.TYPE == 'harmony' ) { - if( s.Internals.id != undefined ) { - if( s.Attributes.genericDeviceType ) - this.mappings.onOff = { reading: 'power', cmdOn: 'on', cmdOff: 'off' }; - else - return null; - - } else - this.mappings.onOff = { reading: 'activity', cmdOn: 'activity', cmdOff: 'off' }; - - } else if( s.PossibleSets.match(/(^| )on\b/) - && s.PossibleSets.match(/(^| )off\b/) ) { - this.mappings.onOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; - if( !s.Readings.state ) - delete this.mappings.onOff.reading; - } - - var event_map = s.Attributes.eventMap; - if( event_map ) { - var parts = event_map.split( ' ' ); - for( var p = 0; p < parts.length; p++ ) { - var map = parts[p].split( ':' ); - if( map[1] == 'on' - || map[1] == 'off' ) { - if( !this.event_map ) - this.event_map = {} - this.event_map[map[0]] = map[1]; - } - } - } - - if( this.mappings.door ) - log( s.Internals.NAME + ' is door' ); - else if( this.mappings.garage ) - log( s.Internals.NAME + ' is garage' ); - else if( this.mappings.lock ) - log( s.Internals.NAME + ' is lock ['+ this.mappings.lock.reading +']' ); - else if( this.mappings.window ) - log( s.Internals.NAME + ' is window' ); - else if( this.mappings.blind ) - log( s.Internals.NAME + ' is blind ['+ this.mappings.blind.reading +']' ); - else if( this.mappings.thermostat ) - log( s.Internals.NAME + ' is thermostat ['+ this.mappings.thermostat.reading + ';' + this.mappings.thermostat.min + '-' + this.mappings.thermostat.max + ':' + this.mappings.thermostat.step +']' ); - else if( this.mappings.contact ) - log( s.Internals.NAME + ' is contact sensor [' + this.mappings.contact.reading +']' ); - else if( this.mappings.occupancy ) - log( s.Internals.NAME + ' is occupancy sensor' ); - else if( this.mappings.rgb ) - log( s.Internals.NAME + ' has RGB [' + this.mappings.rgb.reading +']'); - else if( this.mappings.pct ) - log( s.Internals.NAME + ' is dimable ['+ this.mappings.pct.reading +']' ); - else if( s.hasDim ) - log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); - else if( s.isLight ) - log( s.Internals.NAME + ' is light' ); - else if( s.isOutlet ) - log( s.Internals.NAME + ' is outlet' ); - else if( this.mappings.onOff || s.isSwitch ) - log( s.Internals.NAME + ' is switchable' ); - else if( !this.mappings ) - return {}; - - - if( this.mappings.onOff ) - log( s.Internals.NAME + ' has onOff [' + this.mappings.onOff.reading + ';' + this.mappings.onOff.cmdOn +',' + this.mappings.onOff.cmdOff + ']' ); - if( this.mappings.hue ) - log( s.Internals.NAME + ' has hue [' + this.mappings.hue.reading + ';0-' + this.mappings.hue.max +']' ); - if( this.mappings.sat ) - log( s.Internals.NAME + ' has sat [' + this.mappings.sat.reading + ';0-' + this.mappings.sat.max +']' ); - if( this.mappings.colormode ) - log( s.Internals.NAME + ' has colormode [' + this.mappings.colormode.reading +']' ); - if( this.mappings.xy ) - log( s.Internals.NAME + ' has xy [' + this.mappings.xy.reading +']' ); - if( this.mappings.thermostat_mode ) - log( s.Internals.NAME + ' has thermostat mode ['+ this.mappings.thermostat_mode.reading + ';' + this.mappings.thermostat_mode.cmd +']' ); - if( this.mappings.temperature ) - log( s.Internals.NAME + ' has temperature ['+ this.mappings.temperature.reading +']' ); - if( this.mappings.humidity ) - log( s.Internals.NAME + ' has humidity ['+ this.mappings.humidity.reading +']' ); - if( this.mappings.light ) - log( s.Internals.NAME + ' has light ['+ this.mappings.light.reading +']' ); - if( this.mappings.airquality ) - log( s.Internals.NAME + ' has voc ['+ this.mappings.airquality.reading +']' ); - if( this.mappings.motor ) - log( s.Internals.NAME + ' has motor ['+ this.mappings.motor.reading +']' ); - if( this.mappings.battery ) - log( s.Internals.NAME + ' has battery ['+ this.mappings.battery.reading +']' ); - if( this.mappings.direction ) - log( s.Internals.NAME + ' has direction ['+ this.mappings.direction.reading +']' ); - if( this.mappings.firmware ) - log( s.Internals.NAME + ' has firmware ['+ this.mappings.firmware.reading +']' ); - if( this.mappings.volume ) - log( s.Internals.NAME + ' has volume ['+ this.mappings.volume.reading + ':' + (this.mappings.volume.nocache ? 'not cached' : 'cached' ) +']' ); - -//log( util.inspect(s) ); - - // device info - this.name = s.Internals.NAME; - this.alias = s.Attributes.alias ? s.Attributes.alias : s.Internals.NAME; - this.device = s.Internals.NAME; - this.type = s.Internals.TYPE; - this.model = s.Readings.model ? s.Readings.model.Value - : (s.Attributes.model ? s.Attributes.model - : ( s.Internals.model ? s.Internals.model : '' ) ); - this.PossibleSets = s.PossibleSets; - - if( this.type == 'CUL_HM' ) { - this.serial = this.type + '.' + s.Internals.DEF; - if( s.Attributes.serialNr ) - this.serial = s.Attributes.serialNr; - else if( s.Readings['D-serialNr'] && s.Readings['D-serialNr'].Value ) - this.serial = s.Readings['D-serialNr'].Value; - } else if( this.type == 'CUL_WS' ) - this.serial = this.type + '.' + s.Internals.DEF; - else if( this.type == 'FS20' ) - this.serial = this.type + '.' + s.Internals.DEF; - else if( this.type == 'IT' ) - this.serial = this.type + '.' + s.Internals.DEF; - else if( this.type == 'HUEDevice' ) - this.serial = s.Internals.uniqueid; - else if( this.type == 'SONOSPLAYER' ) - this.serial = s.Internals.UDN; - else if( this.type == 'EnOcean' ) - this.serial = this.type + '.' + s.Internals.DEF; - - this.uuid_base = this.serial; - - this.hasDim = s.hasDim; - this.pctMax = s.pctMax; - - this.isLight = s.isLight; - this.isSwitch = s.isSwitch; - this.isOutlet = s.isOutlet; - -//log( util.inspect(s.Readings) ); - - if( this.mappings.blind || this.mappings.door || this.mappings.garage || this.mappings.window || this.mappings.thermostat ) - delete this.mappings.onOff; - - Object.keys(this.mappings).forEach(function(key) { - var reading = this.mappings[key].reading; - if( s.Readings[reading] && s.Readings[reading].Value ) { - var value = s.Readings[reading].Value; - value = this.reading2homekit(reading, value); - - if( value != undefined ) { - var inform_id = this.device +'-'+ reading; - this.mappings[key].informId = inform_id; - - if( !this.mappings[key].nocache ) - FHEM_cached[inform_id] = value; - } - } - }.bind(this) ); - - this.log = log; - this.connection = connection; -} - -FHEM_dim_values = [ 'dim06%', 'dim12%', 'dim18%', 'dim25%', 'dim31%', 'dim37%', 'dim43%', 'dim50%', 'dim56%', 'dim62%', 'dim68%', 'dim75%', 'dim81%', 'dim87%', 'dim93%' ]; - -FHEMAccessory.prototype = { - reading2homekit: function(reading,value) { - if( value == undefined ) - return undefined; - - if( reading == 'hue' ) { - value = Math.round(value * 360 / (this.mappings.hue ? this.mappings.hue.max : 360) ); - - } else if( reading == 'sat' ) { - value = Math.round(value * 100 / (this.mappings.sat ? this.mappings.sat.max : 100) ); - - } else if( reading == 'pct' ) { - value = parseInt( value ); - - } else if( reading == 'position' ) { - value = parseInt( value ); - - } else if(reading == 'motor') { - if( value.match(/^up/)) - value = Characteristic.PositionState.INCREASING; - else if( value.match(/^down/)) - value = Characteristic.PositionState.DECREASING; - else - value = Characteristic.PositionState.STOPPED; - - } else if(reading == 'controlMode') { - if( value.match(/^auto/)) - value = Characteristic.TargetHeatingCoolingState.AUTO; - else if( value.match(/^manu/)) - value = Characteristic.TargetHeatingCoolingState.HEAT; - else - value = Characteristic.TargetHeatingCoolingState.OFF; - - } else if(reading == 'mode') { - if( value.match(/^auto/)) - value = Characteristic.TargetHeatingCoolingState.AUTO; - else - value = Characteristic.TargetHeatingCoolingState.HEAT; - - } else if(reading == 'direction') { - if( value.match(/^opening/)) - value = PositionState.INCREASING; - else if( value.match(/^closing/)) - value = Characteristic.PositionState.DECREASING; - else - value = Characteristic.PositionState.STOPPED; - - } else if( reading == 'transportState' ) { - if( value == 'PLAYING' ) - value = 1; - else - value = 0; - - } else if( reading == 'volume' - || reading == 'Volume' ) { - value = parseInt( value ); - - } else if( reading == 'contact' ) { - if( value.match( /^closed/ ) ) - value = Characteristic.ContactSensorState.CONTACT_DETECTED; - else - value = Characteristic.ContactSensorState.CONTACT_NOT_DETECTED; - - } else if( reading == 'Window' ) { - if( value.match( /^Closed/ ) ) - value = Characteristic.ContactSensorState.CONTACT_DETECTED; - else - value = Characteristic.ContactSensorState.CONTACT_NOT_DETECTED; - - } else if( reading == 'lock' ) { - if( value.match( /uncertain/ ) ) - value = Characteristic.LockCurrentState.UNKNOWN; - else if( value.match( /^locked/ ) ) - value = Characteristic.LockCurrentState.SECURED; - else - value = Characteristic.LockCurrentState.UNSECURED; - - } else if( reading == 'temperature' - || reading == 'measured-temp' - || reading == 'desired-temp' - || reading == 'desiredTemperature' ) { - value = parseFloat( value ); - - if( this.mappings.thermostat - && reading == this.mappings.thermostat.reading ) { - if( value < this.mappings.thermostat.min ) - value = this.mappings.thermostat.min; - else if( value > this.mappings.thermostat.max ) - value = this.mappings.thermostat.min; - - value = Math.round(value / this.mappings.thermostat.step) * this.mappings.thermostat.step; - } - - } else if( reading == 'humidity' ) { - value = parseInt( value ); - - } else if( reading == 'luminosity' ) { - value = parseFloat( value ) / 0.265; - - } else if( reading == 'voc' ) { - value = parseInt( value ); - if( value > 1500 ) - Characteristic.AirQuality.POOR; - else if( value > 1000 ) - Characteristic.AirQuality.INFERIOR; - else if( value > 800 ) - Characteristic.AirQuality.FAIR; - else if( value > 600 ) - Characteristic.AirQuality.GOOD; - else if( value > 0 ) - Characteristic.AirQuality.EXCELLENT; - else - Characteristic.AirQuality.UNKNOWN; - - } else if( reading == 'battery' ) { - if( value == 'ok' ) - value = Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - else - value = Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW; - - } else if( reading == 'presence' ) { - if( value == 'present' ) - value = Characteristic.OccupancyDetected.OCCUPANCY_DETECTED; - else - value = Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED; - - } else if( reading == 'state' ) { - if( value.match(/^set-/ ) ) - return undefined; - - if( this.event_map != undefined ) { - var mapped = this.event_map[value]; - if( mapped != undefined ) - value = mapped; - } - - if( value == 'off' ) - value = 0; - else if( value == 'opened' ) - value = Characteristic.ContactSensorState.CONTACT_NOT_DETECTED; - else if( value == 'closed' ) - value = Characteristic.ContactSensorState.CONTACT_DETECTED; - else if( value == 'present' ) - value = Characteristic.OccupancyDetected.OCCUPANCY_DETECTED; - else if( value == 'absent' ) - value = Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED; - else if( value == 'locked' ) - value = Characteristic.LockCurrentState.SECURED; - else if( value == 'unlocked' ) - value = Characteristic.LockCurrentState.UNSECURED; - else if( value == '000000' ) - value = 0; - else if( value.match( /^[A-D]0$/ ) ) //FIXME: not necessary any more. handled by event_map now. - value = 0; - else - value = 1; - - } - - return(value); - }, - - delayed: function(c,value,delay) { - var timer = this.delayed[c]; - if( timer ) { - //this.log(this.name + " removing old command " + c); - clearTimeout( timer ); - } - - this.log(this.name + " delaying command " + c + " with value " + value); - this.delayed[c] = setTimeout( function(){clearTimeout(this.delayed[c]); this.command(c,value);}.bind(this), - delay?delay:1000 ); - }, - - command: function(c,value) { - this.log(this.name + " sending command " + c + " with value " + value); - if( c == 'identify' ) { - if( this.type == 'HUEDevice' ) - cmd = "set " + this.device + "alert select"; - else - cmd = "set " + this.device + " toggle; sleep 1; set "+ this.device + " toggle"; - - } else if( c == 'set' ) { - cmd = "set " + this.device + " " + value; - - } else if( c == 'volume' ) { - cmd = "set " + this.device + " volume " + value; - - } else if( c == 'pct' ) { - cmd = "set " + this.device + " pct " + value; - - } else if( c == 'dim' ) { - //if( value < 3 ) - // cmd = "set " + this.device + " off"; - //else - if( value > 97 ) - cmd = "set " + this.device + " on"; - else - cmd = "set " + this.device + " " + FHEM_dim_values[Math.round(value/6.25)]; - - } else if( c == 'H-rgb' || c == 'S-rgb' || c == 'B-rgb' ) { - var h = FHEM_cached[this.device + '-hue' ] / 360; - var s = FHEM_cached[this.device + '-sat' ] / 100; - var v = FHEM_cached[this.device + '-bri' ] / 100; - //this.log( this.name + ' cached : [' + h + ',' + s + ',' + v + ']' ); - if( h == undefined ) h = 0.0; - if( s == undefined ) s = 1.0; - if( v == undefined ) v = 1.0; - //this.log( this.name + ' old : [' + h + ',' + s + ',' + v + ']' ); - - if( c == 'H-rgb' ) { - FHEM_update(this.device + '-hue', value, false ); - h = value / 360; - } else if( c == 'S-rgb' ) { - FHEM_update(this.device + '-sat', value, false ); - s = value / 100; - } else if( c == 'B-rgb' ) { - FHEM_update(this.device + '-bri', value, false ); - v = value / 100; - } - //this.log( this.name + ' new : [' + h + ',' + s + ',' + v + ']' ); - - value = FHEM_hsv2rgb( h, s, v ); - //this.log( this.name + ' rgb : [' + value + ']' ); - cmd = "set " + this.device + " " + this.mappings.rgb.cmd + " " + value; - - } else if( c == 'hue' ) { - value = Math.round(value * this.mappings.hue.max / 360); - cmd = "set " + this.device + " hue " + value; - - } else if( c == 'sat' ) { - value = value / 100 * this.mappings.sat.max; - cmd = "set " + this.device + " sat " + value; - - } else if( c == 'targetTemperature' ) { - cmd = "set " + this.device + " " + this.mappings.thermostat.cmd + " " + value; - - } else if( c == 'targetMode' ) { - var set = this.mappings.thermostat_mode.cmd; - if( value == Characteristic.TargetHeatingCoolingState.OFF ) { - value = 'off' - if( this.mappings.thermostat_mode.cmd == 'controlMode' ) - set = 'desired-temp'; - - } else if( value == Characteristic.TargetHeatingCoolingState.AUTO ) { - value = 'auto' - - }else { - if( this.mappings.thermostat_mode == 'controlMode' ) - value = 'manu'; - else { - value = FHEM_cached[this.mappings.thermostat.informId]; - set = 'desired-temp'; - } - - } - cmd = "set " + this.device + " " + set + " " + value; - - } else if( c == 'targetPosition' ) { - if( this.mappings.window ) { - if( value == 0 ) - value = 'lock'; - - cmd = "set " + this.device + " " + this.mappings.window.cmd + " " + value; - - } else if( this.mappings.blind ) - cmd = "set " + this.device + " " + this.mappings.blind.cmd + " " + value; - - else - this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); - - } else { - this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); - return; - - } - - this.execute(cmd); - }, - - execute: function(cmd,callback) {FHEM_execute(this.log, this.connection, cmd, callback)}, - - query: function(reading, callback) { - if( reading == undefined ) { - if( callback != undefined ) - callback( 1 ); - return; - } - - this.log("query: " + this.name + "-" + reading); - - var result = FHEM_cached[this.device + '-' + reading]; - if( result != undefined ) { - this.log(" cached: " + result); - if( callback != undefined ) - callback( undefined, result ); - return result; - - } else - this.log(" not cached" ); - - var query_reading = reading; - if( reading == 'hue' && !this.mappings.hue && this.mappings.rgb ) { - query_reading = this.mappings.rgb.reading; - - } else if( reading == 'sat' && !this.mappings.sat && this.mappings.rgb ) { - query_reading = this.mappings.rgb.reading; - - } else if( reading == 'bri' && !this.mappings.pct && this.mappings.rgb ) { - query_reading = this.mappings.rgb.reading; - - } else if( reading == 'pct' && !this.mappings.pct && this.hasDim ) { - query_reading = 'state'; - - } else if( reading == 'level' && this.mappings.window ) { - query_reading = 'state'; - - } else if( reading == 'lock' && this.mappings.lock ) { - //query_reading = 'state'; - - } - - var cmd = '{ReadingsVal("'+this.device+'","'+query_reading+'","")}'; - - this.execute( cmd, - function(result) { - value = result.replace(/[\r\n]/g, ""); - this.log(" value: " + value); - - if( value == undefined ) - return value; - - if( reading != query_reading ) { - if( reading == 'pct' - && query_reading == 'state') { - - if( match = value.match(/dim(\d+)%/ ) ) - value = parseInt( match[1] ); - else if( value == 'off' ) - value = 0; - else - value = 100; - - } else if( reading == 'level' - && query_reading == 'state') { - - if( match = value.match(/^(\d+)/ ) ) - value = parseInt( match[1] ); - else if( value == 'locked' ) - value = 0; - else - value = 50; - - } else if( reading == 'lock' - && query_reading == 'state') { - - if( value.match( /uncertain/ ) ) - value = Characteristic.LockCurrentState.UNKNOWN; - else if( value.match( /^locked/ ) ) - value = Characteristic.LockCurrentState.SECURED; - else - value = Characteristic.LockCurrentState.UNSECURED; - - } else if(reading == 'hue' && query_reading == this.mappings.rgb) { - //FHEM_update( this.device+'-'+query_reading, value ); - - value = parseInt( FHEM_rgb2hsv(value)[0] * 360 ); - - } else if(reading == 'sat' && query_reading == this.mappings.rgb) { - //FHEM_update( this.device+'-'+query_reading, value ); - - value = parseInt( FHEM_rgb2hsv(value)[1] * 100 ); - - } else if(reading == 'bri' && query_reading == this.mappings.rgb) { - //FHEM_update( this.device+'-'+query_reading, value ); - - value = parseInt( FHEM_rgb2hsv(value)[2] * 100 ); - - } - } else { - value = this.reading2homekit(reading, value); - } - - this.log(" mapped: " + value); - FHEM_update( this.device + '-' + reading, value, true ); - - if( callback != undefined ) { - if( value == undefined ) - callback(1); - else - callback(undefined, value); - } - - return value ; - - }.bind(this) ); - }, - - createDeviceService: function(subtype) { - //var name = this.alias + ' (' + this.name + ')'; - var name = this.alias; - if( subtype ) - //name = subtype + ' (' + this.name + ')'; - name = subtype + ' (' + this.alias + ')'; - - if( this.isSwitch ) { - this.log(" switch service for " + this.name) - return new Service.Switch(name); - } else if( this.isOutlet ) { - this.log(" outlet service for " + this.name) - return new Service.Outlet(name); - } else if( this.mappings.garage ) { - this.log(" garage door opener service for " + this.name) - return new Service.GarageDoorOpener(name); - } else if( this.mappings.lock ) { - this.log(" lock mechanism service for " + this.name) - return new Service.LockMechanism(name); - } else if( this.mappings.window ) { - this.log(" window service for " + this.name) - return new Service.Window(name); - } else if( this.mappings.blind ) { - this.log(" window covering service for " + this.name) - return new Service.WindowCovering(name); - } else if( this.mappings.thermostat ) { - this.log(" thermostat service for " + this.name) - return new Service.Thermostat(name); - } else if( this.mappings.contact ) { - this.log(" contact sensor service for " + this.name) - return new Service.ContactSensor(name); - } else if( this.mappings.occupancy ) { - this.log(" occupancy sensor service for " + this.name) - return new Service.OccupancySensor(name); - } else if( this.isLight || this.mappings.pct || this.mappings.hue || this.mappings.rgb ) { - this.log(" lightbulb service for " + this.name) - return new Service.Lightbulb(name); - } else if( this.mappings.temperature ) { - this.log(" temperature sensor service for " + this.name) - return new Service.TemperatureSensor(name); - } else if( this.mappings.humidity ) { - this.log(" humidity sensor service for " + this.name) - return new Service.HumiditySensor(name); - } else if( this.mappings.light ) { - this.log(" light sensor service for " + this.name) - return new Service.LightSensor(name); - } else if( this.mappings.airquality ) { - this.log(" air quality sensor service for " + this.name) - return new Service.AirQualitySensor(name); - } - - this.log(" switch service for " + this.name + ' (' + subtype + ')' ) - return new Service.Switch(name, subtype); - }, - - identify: function(callback) { - this.log('['+this.name+'] identify requested!'); - if( match = this.PossibleSets.match(/(^| )toggle\b/) ) { - this.command( 'identify' ); - } - callback(); - }, - - getServices: function() { - var services = []; - - this.log("creating services for " + this.name) - - this.log(" information service for " + this.name) - var informationService = new Service.AccessoryInformation(); - services.push( informationService ); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "FHEM:"+this.type) - .setCharacteristic(Characteristic.Model, "FHEM:"+ (this.model ? this.model : '') ) - .setCharacteristic(Characteristic.SerialNumber, this.serial ? this.serial : ''); - - - if( this.mappings.firmware ) { - this.log(" firmware revision characteristic for " + this.name) - - var characteristic = informationService.getCharacteristic(Characteristic.FirmwareRevision) - || informationService.addCharacteristic(Characteristic.FirmwareRevision); - - FHEM_subscribe(characteristic, this.mappings.firmware.informId, this); - - characteristic.value = FHEM_cached[this.mappings.firmware.informId]; - - characteristic - .on('get', function(callback) { - if( this.mappings.firmware ) - this.query(this.mappings.firmware.reading, callback); - }.bind(this) ); - } - - - // FIXME: allow multiple switch characteristics also for other types. check if this.mappings.onOff an array. - if( this.type == 'harmony' - && this.mappings.onOff.reading == 'activity' ) { - - FHEM_subscribe(undefined, this.mappings.onOff.informId, this); - - var match; - if( match = this.PossibleSets.match(/(^| )activity:([^\s]*)/) ) { - var activities = match[2].split(','); - for( var i = 0; i < activities.length; i++ ) { - var activity = activities[i]; - - var controlService = this.createDeviceService(activity); - services.push( controlService ); - - this.log(" on characteristic for " + this.name + ' ' + activity); - - var characteristic = controlService.getCharacteristic(Characteristic.On); - - FHEM_subscribe(characteristic, '#' + this.device + '-' + this.mappings.onOff.reading + '-' + activity, this); - - characteristic.displayName = activity; - characteristic.value = (FHEM_cached[this.mappings.onOff.informId]==activity?1:0); - - characteristic - .on('set', function(activity, value, callback, context) { - if( context !== 'fromFHEM' ) - this.command( 'set', value == 0 ? this.mappings.onOff.cmdOff : this.mappings.onOff.cmdOn + ' ' + activity ); - callback(); - }.bind(this, activity) ) - .on('get', function(activity, callback) { - var result = this.query(this.mappings.onOff.reading); - callback( undefined, result==activity?1:0 ); - }.bind(this, activity) ); - } - } - - return services; - } - - if( this.mappings.xy - && this.mappings.colormode ) { - FHEM_subscribe(undefined, this.mappings.xy.informId, this); - FHEM_subscribe(undefined, this.mappings.colormode.informId, this); - - - //FIXME: add colormode ct - if( FHEM_cached[this.mappings.colormode.informId] == 'xy' ) { - var value = FHEM_cached[this.mappings.xy.informId]; - var xy = value.split(','); - var rgb = FHEM_xyY2rgb(xy[0], xy[1] , 1); - var hsv = FHEM_rgb2hsv(rgb); - var hue = parseInt( hsv[0] * 360 ); - var sat = parseInt( hsv[1] * 100 ); - var bri = parseInt( hsv[2] * 100 ); - - //FHEM_update( device+'-'+reading, value, false ); - FHEM_update( this.device+'-hue', hue ); - FHEM_update( this.device+'-sat', sat ); - FHEM_update( this.device+'-bri', bri ); - } - } - - var controlService = this.createDeviceService(); - services.push( controlService ); - - if( this.mappings.onOff ) { - this.log(" on characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.On); - - FHEM_subscribe(characteristic, this.mappings.onOff.informId, this); - - if( FHEM_cached[this.mappings.onOff.informId] != undefined ) - characteristic.value = FHEM_cached[this.mappings.onOff.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.command( 'set', value == 0 ? this.mappings.onOff.cmdOff : this.mappings.onOff.cmdOn ); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query(this.mappings.onOff.reading, callback); - }.bind(this) ); - } - - if( this.mappings.pct ) { - this.log(" brightness characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Brightness); - - FHEM_subscribe(characteristic, this.mappings.pct.informId, this); - if( FHEM_cached[this.mappings.pct.informId] != undefined ) - characteristic.value = FHEM_cached[this.mappings.pct.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.command('pct', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query(this.mappings.pct.reading, callback); - }.bind(this) ); - - } else if( this.hasDim ) { - this.log(" fake brightness characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Brightness); - - FHEM_subscribe(characteristic, this.name+'-pct', this); - characteristic.value = 0; - characteristic.maximumValue = this.pctMax; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.delayed('dim', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query('pct', callback); - }.bind(this) ); - } - - if( this.mappings.hue ) { - this.log(" hue characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Hue); - - FHEM_subscribe(characteristic, this.mappings.hue.informId, this); - if( FHEM_cached[this.mappings.hue.informId] != undefined ) - characteristic.value = FHEM_cached[this.mappings.hue.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.command('hue', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query(this.mappings.hue.reading, callback); - }.bind(this) ); - - } else if( this.mappings.rgb ) { - this.log(" fake hue characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Hue); - - FHEM_subscribe(characteristic, this.name+'-hue', this); - FHEM_subscribe(characteristic, this.mappings.rgb.informId, this); - characteristic.value = 0; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.command('H-rgb', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query('hue', callback); - }.bind(this) ); - - if( !this.mappings.sat ) { - this.log(" fake saturation characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Saturation); - - FHEM_subscribe(characteristic, this.name+'-sat', this); - characteristic.value = 100; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.command('S-rgb', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query('sat', callback); - }.bind(this) ); - } - - if( !this.mappings.pct ) { - this.log(" fake brightness characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Brightness); - - FHEM_subscribe(characteristic, this.name+'-bri', this); - characteristic.value = 0; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.command('B-rgb', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query('bri', callback); - }.bind(this) ); - } - } - - if( this.mappings.sat ) { - this.log(" saturation characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.Saturation); - - FHEM_subscribe(characteristic, this.mappings.sat.informId, this); - if( FHEM_cached[this.mappings.sat.informId] != undefined ) - characteristic.value = FHEM_cached[this.mappings.sat.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.command('sat', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query(this.mappings.sat.reading, callback); - }.bind(this) ); - } - - if( this.mappings.volume ) { - this.log(" custom volume characteristic for " + this.name) - - var characteristic = new Characteristic('Volume', '00000027-0000-1000-8000-0026BB765291'); // FIXME!!! - controlService.addCharacteristic(characteristic); - - if( !this.mappings.volume.nocache ) { - FHEM_subscribe(characteristic, this.mappings.volume.informId, this); - characteristic.value = FHEM_cached[this.mappings.volume.informId]; - } else { - characteristic.value = 10; - } - - characteristic.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] - }); - - characteristic.readable = true; - characteristic.writable = true; - characteristic.supportsEventNotification = true; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.delayed('volume', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query(this.mappings.volume.reading, callback); - }.bind(this) ); - } - - if( this.mappings.blind ) { - this.log(" current position characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.CurrentPosition); - - var step = 1; - FHEM_subscribe(characteristic, this.mappings.blind.informId, this); - characteristic.value = Math.round(FHEM_cached[this.mappings.blind.informId] / step) * step; - - characteristic - .on('get', function(callback) { - this.query(this.mappings.blind.reading, callback); - }.bind(this) ); - - - this.log(" target position characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.TargetPosition); - characteristic.setProps( { - minStep: step, - } ); - - characteristic.value = FHEM_cached[this.mappings.blind.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.delayed('targetPosition', value, 1500); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query(this.mappings.blind.reading, callback); - }.bind(this) ); - - - this.log(" position state characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.PositionState); - - if( this.mappings.motor ) - FHEM_subscribe(characteristic, this.mappings.motor.informId, this); - characteristic.value = this.mappings.motor?FHEM_cached[this.mappings.motor.informId]:Characteristic.PositionState.STOPPED; - - characteristic - .on('get', function(callback) { - if( this.mappings.motor ) - this.query(this.mappings.motor.reading, callback); - }.bind(this) ); - } - - if( this.mappings.window ) { - this.log(" current position characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.CurrentPosition); - - FHEM_subscribe(characteristic, this.name+'-state', this); - FHEM_subscribe(characteristic, this.mappings.window.informId, this); - characteristic.value = FHEM_cached[this.mappings.window.informId]; - - characteristic - .on('get', function(callback) { - this.query(this.mappings.window.reading, callback); - }.bind(this) ); - - - this.log(" target position characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.TargetPosition); - - characteristic.value = FHEM_cached[this.mappings.window.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.delayed('targetPosition', value, 1500); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query(this.mappings.window.reading, callback); - }.bind(this) ); - - - this.log(" position state characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.PositionState); - - if( this.mappings.direction ) - FHEM_subscribe(characteristic, this.mappings.direction.informId, this); - characteristic.value = this.mappings.direction?FHEM_cached[this.mappings.direction.informId]:Characteristic.PositionState.STOPPED; - - characteristic - .on('get', function(callback) { - if( this.mappings.direction ) - this.query(this.mappings.direction.reading, callback); - }.bind(this) ); - } - - if( this.mappings.lock ) { - this.log(" lock current state characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.LockCurrentState); - - //FHEM_subscribe(characteristic, this.name+'-state', this); - FHEM_subscribe(characteristic, this.mappings.lock.informId, this); - characteristic.value = FHEM_cached[this.mappings.lock.informId]; - - characteristic - .on('get', function(callback) { - this.query(this.mappings.lock.reading, callback); - }.bind(this) ); - - this.log(" lock target state characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.LockTargetState); - - characteristic.value = FHEM_cached[this.mappings.lock.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.command( 'set', value == Characteristic.LockTargetState.UNSECURED ? this.mappings.lock.cmdUnlock : this.mappings.lock.cmdLock ); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query(this.mappings.lock.reading, callback); - }.bind(this) ); - - if( this.mappings.lock.cmdOpen ) { - this.log(" target door state characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.TargetDoorState); - - characteristic.value = Characteristic.TargetDoorState.CLOSED; - - characteristic - .on('set', function(characteristic,value, callback, context) { - if( context !== 'fromFHEM' ) { - this.command( 'set', this.mappings.lock.cmdOpen ); - setTimeout( function(){characteristic.setValue(Characteristic.TargetDoorState.CLOSED, undefined, 'fromFHEM');}, 500 ); - } - if( callback ) callback(); - }.bind(this,characteristic) ) - .on('get', function(callback) { - callback(undefined,Characteristic.TargetDoorState.CLOSED); - }.bind(this) ); - } - } - - if( this.mappings.garage ) { - this.log(" current door state characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.CurrentDoorState); - - characteristic.value = Characteristic.CurrentDoorState.STOPPED; - - characteristic - .on('get', function(callback) { - callback(undefined, Characteristic.CurrentDoorState.STOPPED); - }.bind(this) ); - - - this.log(" target door state characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.TargetDoorState); - - characteristic.value = 1; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.command( 'set', value == 0 ? this.mappings.garage.cmdOpen : this.mappings.garage.cmdClose ); - callback(); - }.bind(this) ) - .on('get', function(callback) { - callback(undefined,0); - }.bind(this) ); - - - if( 0 ) { - this.log(" obstruction detected characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.ObstructionDetected); - - //FHEM_subscribe(characteristic, this.mappings.direction.informId, this); - characteristic.value = 0; - - characteristic - .on('get', function(callback) { - callback(undefined,1); - }.bind(this) ); - } - } - - if( this.mappings.temperature ) { - this.log(" temperature characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.CurrentTemperature) - || controlService.addCharacteristic(Characteristic.CurrentTemperature); - - FHEM_subscribe(characteristic, this.mappings.temperature.informId, this); - characteristic.value = FHEM_cached[this.mappings.temperature.informId]; - - characteristic - .on('get', function(callback) { - this.query(this.mappings.temperature.reading, callback); - }.bind(this) ); - } - - if( this.mappings.humidity ) { - this.log(" humidity characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.CurrentRelativeHumidity) - || controlService.addCharacteristic(Characteristic.CurrentRelativeHumidity); - - FHEM_subscribe(characteristic, this.mappings.humidity.informId, this); - characteristic.value = FHEM_cached[this.mappings.humidity.informId]; - - characteristic - .on('get', function(callback) { - this.query(this.mappings.humidity.reading, callback); - }.bind(this) ); - } - - if( this.mappings.light ) { - this.log(" light characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.CurrentAmbientLightLevel) - || controlService.addCharacteristic(Characteristic.CurrentAmbientLightLevel); - - FHEM_subscribe(characteristic, this.mappings.light.informId, this); - characteristic.value = FHEM_cached[this.mappings.light.informId]; - - characteristic - .on('get', function(callback) { - this.query(this.mappings.light.reading, callback); - }.bind(this) ); - } - - if( this.mappings.airquality ) { - this.log(" air quality characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.AirQuality) - || controlService.addCharacteristic(Characteristic.AirQuality); - - FHEM_subscribe(characteristic, this.mappings.airquality.informId, this); - characteristic.value = FHEM_cached[this.mappings.airquality.informId]; - - characteristic - .on('get', function(callback) { - this.query(this.mappings.airquality.reading, callback); - }.bind(this) ); - } - - if( this.mappings.battery ) { - this.log(" battery status characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.StatusLowBattery) - || controlService.addCharacteristic(Characteristic.StatusLowBattery); - - FHEM_subscribe(characteristic, this.mappings.battery.informId, this); - characteristic.value = FHEM_cached[this.mappings.battery.informId]; - - characteristic - .on('get', function(callback) { - this.query(this.mappings.battery.reading, callback); - }.bind(this) ); - } - - - if( this.mappings.thermostat ) { - this.log(" target temperature characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.TargetTemperature); - - FHEM_subscribe(characteristic, this.mappings.thermostat.informId, this); - characteristic.value = FHEM_cached[this.mappings.thermostat.informId]; - - characteristic.setProps( { - maxValue: this.mappings.thermostat.max, - minValue: this.mappings.thermostat.min, - minStep: this.mappings.thermostat.step, - } ); - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.delayed('targetTemperature', value, 1500); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query(this.mappings.thermostat.reading, callback); - }.bind(this) ); - - if( this.mappings.thermostat_modex ) { - this.log(" current mode characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.CurrentHeatingCoolingState); - - FHEM_subscribe(characteristic, this.mappings.thermostat_mode.informId, this); - characteristic.value = FHEM_cached[this.mappings.thermostat_mode.informId]; - - characteristic - .on('get', function(callback) { - this.query(this.mappings.thermostat_mode.reading, callback); - }.bind(this) ); - } - - if( this.mappings.thermostat_modex ) { - this.log(" target mode characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.TargetHeatingCoolingState); - - FHEM_subscribe(characteristic, this.mappings.thermostat_mode.informId, this); - characteristic.value = FHEM_cached[this.mappings.thermostat_mode.informId]; - - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFHEM' ) - this.command('targetMode', value); - callback(); - }.bind(this) ) - .on('get', function(callback) { - this.query(this.mappings.thermostat_mode.reading, callback); - }.bind(this) ); - } - } - - if( this.mappings.contact ) { - this.log(" contact sensor characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.ContactSensorState); - - FHEM_subscribe(characteristic, this.mappings.contact.informId, this); - characteristic.value = FHEM_cached[this.mappings.contact.informId]; - - characteristic - .on('get', function(callback) { - this.query(this.mappings.contact.reading, callback); - }.bind(this) ); - - if( 1 ) { - this.log(" current door state characteristic for " + this.name) - - var characteristic = controlService.addCharacteristic(Characteristic.CurrentDoorState); - - characteristic.value = FHEM_cached[this.mappings.contact.informId]==Characteristic.ContactSensorState.CONTACT_DETECTED?Characteristic.CurrentDoorState.CLOSED:Characteristic.CurrentDoorState.OPEN; - - characteristic - .on('get', function(callback) { - callback(undefined, FHEM_cached[this.mappings.contact.informId]==Characteristic.ContactSensorState.CONTACT_DETECTED?Characteristic.CurrentDoorState.CLOSED:Characteristic.CurrentDoorState.OPEN); - }.bind(this) ); - } - } - - if( this.mappings.occupancy ) { - this.log(" occupancy detected characteristic for " + this.name) - - var characteristic = controlService.getCharacteristic(Characteristic.OccupancyDetected); - - FHEM_subscribe(characteristic, this.mappings.occupancy.informId, this); - characteristic.value = FHEM_cached[this.mappings.occupancy.informId]; - - characteristic - .on('get', function(callback) { - this.query(this.mappings.occupancy.reading, callback); - }.bind(this) ); - } - - return services; - } - -}; - -//module.exports.accessory = FHEMAccessory; -module.exports.platform = FHEMPlatform; - - - -//http server for debugging -var http = require('http'); - -const FHEMdebug_PORT=8081; - -function FHEMdebug_handleRequest(request, response){ - //console.log( request ); - - if( request.url == "/cached" ) { - response.write( "home

" ); - if( FHEM_lastEventTime ) - var keys = Object.keys(FHEM_lastEventTime); - for( var i = 0; i < keys.length; i++ ) - response.write( "FHEM_lastEventTime " + keys[i] + ": "+ new Date(FHEM_lastEventTime[keys[i]]) +"
" ); - response.write( "
" ); - response.end( "cached: " + util.inspect(FHEM_cached).replace(/\n/g, '
') ); - - } else if( request.url == "/subscriptions" ) { - response.write( "home

" ); - response.end( "subscriptions: " + util.inspect(FHEM_subscriptions, {depth: 4}).replace(/\n/g, '
') ); - - } else - response.end( "cached
subscriptions" ); -} - -var FHEMdebug_server = http.createServer( FHEMdebug_handleRequest ); - -FHEMdebug_server.on('error', function (e) { - console.log("Server error: " + e); -}); - -//Lets start our server -FHEMdebug_server.listen(FHEMdebug_PORT, function(){ - console.log("Server listening on: http://:%s", FHEMdebug_PORT); -}); - diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js deleted file mode 100644 index 52b7490..0000000 --- a/platforms/FibaroHC2.js +++ /dev/null @@ -1,287 +0,0 @@ -// Fibaro Home Center 2 Platform Shim for HomeBridge -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "FibaroHC2", -// "name": "FibaroHC2", -// "host": "PUT IP ADDRESS OF YOUR HC2 HERE", -// "username": "PUT USERNAME OF YOUR HC2 HERE", -// "password": "PUT PASSWORD OF YOUR HC2 HERE" -// } -// ], -// -// When you attempt to add a device, it will ask for a "PIN code". -// The default code for all HomeBridge accessories is 031-45-154. - -var types = require("hap-nodejs/accessories/types.js"); -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); - -function FibaroHC2Platform(log, config){ - this.log = log; - this.host = config["host"]; - this.username = config["username"]; - this.password = config["password"]; - this.auth = "Basic " + new Buffer(this.username + ":" + this.password).toString("base64"); - this.url = "http://"+this.host+"/api/devices"; - - startPollingUpdate( this ); -} - -FibaroHC2Platform.prototype = { - accessories: function(callback) { - this.log("Fetching Fibaro Home Center devices..."); - - var that = this; - var foundAccessories = []; - - request.get({ - url: this.url, - headers : { - "Authorization" : this.auth - }, - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - if (json != undefined) { - json.map(function(s) { - that.log("Found: " + s.type); - if (s.visible == true) { - var accessory = null; - if (s.type == "com.fibaro.multilevelSwitch") - accessory = new FibaroBridgedAccessory([{controlService: new Service.Lightbulb(s.name), characteristics: [Characteristic.On, Characteristic.Brightness]}]); - else if (s.type == "com.fibaro.FGRM222" || s.type == "com.fibaro.FGR221") - accessory = new FibaroBridgedAccessory([{controlService: new Service.WindowCovering(s.name), characteristics: [Characteristic.CurrentPosition, Characteristic.TargetPosition, Characteristic.PositionState]}]); - else if (s.type == "com.fibaro.binarySwitch" || s.type == "com.fibaro.developer.bxs.virtualBinarySwitch") - accessory = new FibaroBridgedAccessory([{controlService: new Service.Switch(s.name), characteristics: [Characteristic.On]}]); - else if (s.type == "com.fibaro.FGMS001" || s.type == "com.fibaro.motionSensor") - accessory = new FibaroBridgedAccessory([{controlService: new Service.MotionSensor(s.name), characteristics: [Characteristic.MotionDetected]}]); - else if (s.type == "com.fibaro.temperatureSensor") - accessory = new FibaroBridgedAccessory([{controlService: new Service.TemperatureSensor(s.name), characteristics: [Characteristic.CurrentTemperature]}]); - else if (s.type == "com.fibaro.doorSensor") - accessory = new FibaroBridgedAccessory([{controlService: new Service.ContactSensor(s.name), characteristics: [Characteristic.ContactSensorState]}]); - else if (s.type == "com.fibaro.lightSensor") - accessory = new FibaroBridgedAccessory([{controlService: new Service.LightSensor(s.name), characteristics: [Characteristic.CurrentAmbientLightLevel]}]); - else if (s.type == "com.fibaro.FGWP101") - accessory = new FibaroBridgedAccessory([{ controlService: new Service.Outlet(s.name), characteristics: [Characteristic.On, Characteristic.OutletInUse]}]); - else if (s.type == "virtual_device" && s.name.charAt(0) != "_") { - var services = []; - for (var r = 0; r < s.properties.rows.length; r++) { - if (s.properties.rows[r].type == "button") { - for (var e = 0; e < s.properties.rows[r].elements.length; e++) { - var service = { - controlService: new Service.Switch(s.properties.rows[r].elements[e].caption), - characteristics: [Characteristic.On] - }; - service.controlService.subtype = s.properties.rows[r].elements[e].id; - services.push(service); - } - } - } - accessory = new FibaroBridgedAccessory(services); - } - 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 = ""; - 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, service, 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; - if (service.controlService.subtype != null) { - subscribeUpdate(characteristic, homebridgeAccessory, onOff); - } - if (!readOnly) { - characteristic - .on('set', function(value, callback, context) { - if( context !== 'fromFibaro' && context !== 'fromSetValue') { - if (service.controlService.subtype != null) { - homebridgeAccessory.platform.command("pressButton", service.controlService.subtype, homebridgeAccessory); - // In order to behave like a push button reset the status to off - setTimeout( function(){ - characteristic.setValue(false, undefined, 'fromSetValue'); - }, 100 ); - } else 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) { - if (service.controlService.subtype != null) { - // a push button is normally off - callback(undefined, false); - } else { - homebridgeAccessory.platform.getAccessoryValue(callback, onOff, homebridgeAccessory, powerValue); - } - }.bind(this) ); - }, - getServices: function(homebridgeAccessory) { - var services = []; - var informationService = homebridgeAccessory.platform.getInformationService(homebridgeAccessory); - services.push(informationService); - for (var s = 0; s < homebridgeAccessory.services.length; s++) { - var service = homebridgeAccessory.services[s]; - for (var i=0; i < service.characteristics.length; i++) { - var characteristic = service.controlService.getCharacteristic(service.characteristics[i]); - if (characteristic == undefined) - characteristic = service.controlService.addCharacteristic(service.characteristics[i]); - homebridgeAccessory.platform.bindCharacteristicEvents(characteristic, service, homebridgeAccessory); - } - services.push(service.controlService); - } - return services; - } -} - -function FibaroBridgedAccessory(services) { - this.services = services; -} - - -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 (var i=0;i 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; diff --git a/platforms/HomeAssistant.js b/platforms/HomeAssistant.js deleted file mode 100644 index 1bfdc6c..0000000 --- a/platforms/HomeAssistant.js +++ /dev/null @@ -1,542 +0,0 @@ -// Home Assistant -// -// Current Support: lights -// -// This is a shim to publish lights maintained by Home Assistant. -// Home Assistant is an open-source home automation platform. -// URL: http://home-assistant.io -// GitHub: https://github.com/balloob/home-assistant -// -// HA accessories supported: Lights, Switches, Media Players, Scenes. -// -// Optional Devices - Edit the supported_types key in the config to pick which -// of the 4 types you would like to expose to HomeKit from -// Home Assistant. light, switch, media_player, scene. -// -// -// Scene Support -// -// You can optionally import your Home Assistant scenes. These will appear to -// HomeKit as switches. You can simply say "turn on party time". In some cases -// scenes names are already rerved in HomeKit...like "Good Morning" and -// "Good Night". You will be able to just say "Good Morning" or "Good Night" to -// have these triggered. -// -// You might want to play with the wording to figure out what ends up working well -// for your scene names. It's also important to not populate any actual HomeKit -// scenes with the same names, as Siri will pick these instead of your Home -// Assistant scenes. -// -// -// -// Media Player Support -// -// Media players on your Home Assistant will be added to your HomeKit as a light. -// While this seems like a hack at first, it's actually quite useful. You can -// turn them on, off, and set their volume (as a function of brightness). -// -// There are some rules to know about how on/off treats your media player. If -// your media player supports play/pause, then turning them on and off via -// HomeKit will play and pause them. If they do not support play/pause but then -// support on/off they will be turned on and then off. -// -// HomeKit does not have a characteristic of Volume or a Speaker type. So we are -// using the light device type here. So to turn your speaker up and down, you -// will need to use the same language you use to set the brighness of a light. -// You can play around with language to see what fits best. -// -// -// -// Examples -// -// Dim the Kitchen Speaker to 40% - sets volume to 40% -// Dim the the Kitchen Speaker 10% - lowers the volume by 10% -// Set the brightness of the Kitchen Speaker to 40% -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "HomeAssistant", -// "name": "HomeAssistant", -// "host": "http://192.168.1.50:8123", -// "password": "xxx", -// "supported_types": ["light", "switch", "media_player", "scene"] -// } -// ] -// -// When you attempt to add a device, it will ask for a "PIN code". -// The default code for all HomeBridge accessories is 031-45-154. - -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var url = require('url') -var request = require("request"); - -var communicationError = new Error('Can not communicate with Home Assistant.') - -function HomeAssistantPlatform(log, config){ - - // auth info - this.host = config["host"]; - this.password = config["password"]; - this.supportedTypes = config["supported_types"]; - - this.log = log; -} - -HomeAssistantPlatform.prototype = { - _request: function(method, path, options, callback) { - var self = this - var requestURL = this.host + '/api' + path - options = options || {} - options.query = options.query || {} - - var reqOpts = { - url: url.parse(requestURL), - method: method || 'GET', - qs: options.query, - body: JSON.stringify(options.body), - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'x-ha-access': this.password - } - } - - request(reqOpts, function onResponse(error, response, body) { - if (error) { - callback(error, response) - return - } - - if (response.statusCode === 401) { - callback(new Error('You are not authenticated'), response) - return - } - - json = JSON.parse(body) - callback(error, response, json) - }) - - }, - fetchState: function(entity_id, callback){ - this._request('GET', '/states/' + entity_id, {}, function(error, response, data){ - if (error) { - callback(null) - }else{ - callback(data) - } - }) - }, - callService: function(domain, service, service_data, callback){ - var options = {} - options.body = service_data - - this._request('POST', '/services/' + domain + '/' + service, options, function(error, response, data){ - if (error) { - callback(null) - }else{ - callback(data) - } - }) - }, - accessories: function(callback) { - this.log("Fetching HomeAssistant devices."); - - var that = this; - var foundAccessories = []; - - this._request('GET', '/states', {}, function(error, response, data){ - - for (var i = 0; i < data.length; i++) { - entity = data[i] - entity_type = entity.entity_id.split('.')[0] - - // ignore devices that are not in the list of supported types - if (that.supportedTypes.indexOf(entity_type) == -1) { - continue; - } - - // ignore hidden devices - if (entity.attributes && entity.attributes.hidden) { - continue; - } - - var accessory = null - - if (entity_type == 'light') { - accessory = new HomeAssistantLight(that.log, entity, that) - }else if (entity_type == 'switch'){ - console.log(JSON.stringify(entity)) - console.log(""); - console.log(""); - accessory = new HomeAssistantSwitch(that.log, entity, that) - }else if (entity_type == 'scene'){ - accessory = new HomeAssistantSwitch(that.log, entity, that, 'scene') - }else if (entity_type == 'media_player' && entity.attributes && entity.attributes.supported_media_commands){ - accessory = new HomeAssistantMediaPlayer(that.log, entity, that) - } - - if (accessory) { - foundAccessories.push(accessory) - } - } - - callback(foundAccessories) - }) - - } -} - -function HomeAssistantLight(log, data, client) { - // device info - this.domain = "light" - this.data = data - this.entity_id = data.entity_id - if (data.attributes && data.attributes.friendly_name) { - this.name = data.attributes.friendly_name - }else{ - this.name = data.entity_id.split('.').pop().replace(/_/g, ' ') - } - - this.client = client - this.log = log; -} - -HomeAssistantLight.prototype = { - getPowerState: function(callback){ - this.log("fetching power state for: " + this.name); - - this.client.fetchState(this.entity_id, function(data){ - if (data) { - powerState = data.state == 'on' - callback(null, powerState) - }else{ - callback(communicationError) - } - }.bind(this)) - }, - getBrightness: function(callback){ - this.log("fetching brightness for: " + this.name); - - this.client.fetchState(this.entity_id, function(data){ - if (data && data.attributes) { - brightness = ((data.attributes.brightness || 0) / 255)*100 - callback(null, brightness) - }else{ - callback(communicationError) - } - }.bind(this)) - }, - setPowerState: function(powerOn, callback) { - var that = this; - var service_data = {} - service_data.entity_id = this.entity_id - - if (powerOn) { - this.log("Setting power state on the '"+this.name+"' to on"); - - this.client.callService(this.domain, 'turn_on', service_data, function(data){ - if (data) { - that.log("Successfully set power state on the '"+that.name+"' to on"); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - }else{ - this.log("Setting power state on the '"+this.name+"' to off"); - - this.client.callService(this.domain, 'turn_off', service_data, function(data){ - if (data) { - that.log("Successfully set power state on the '"+that.name+"' to off"); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - } - }, - setBrightness: function(level, callback) { - var that = this; - var service_data = {} - service_data.entity_id = this.entity_id - - service_data.brightness = 255*(level/100.0) - - this.log("Setting brightness on the '"+this.name+"' to " + level); - - this.client.callService(this.domain, 'turn_on', service_data, function(data){ - if (data) { - that.log("Successfully set brightness on the '"+that.name+"' to " + level); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - }, - getServices: function() { - var lightbulbService = new Service.Lightbulb(); - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "Home Assistant") - .setCharacteristic(Characteristic.Model, "Light") - .setCharacteristic(Characteristic.SerialNumber, "xxx"); - - lightbulbService - .getCharacteristic(Characteristic.On) - .on('get', this.getPowerState.bind(this)) - .on('set', this.setPowerState.bind(this)); - - lightbulbService - .addCharacteristic(Characteristic.Brightness) - .on('get', this.getBrightness.bind(this)) - .on('set', this.setBrightness.bind(this)); - - return [informationService, lightbulbService]; - } - -} - -function HomeAssistantMediaPlayer(log, data, client) { - var SUPPORT_PAUSE = 1 - var SUPPORT_SEEK = 2 - var SUPPORT_VOLUME_SET = 4 - var SUPPORT_VOLUME_MUTE = 8 - var SUPPORT_PREVIOUS_TRACK = 16 - var SUPPORT_NEXT_TRACK = 32 - var SUPPORT_YOUTUBE = 64 - var SUPPORT_TURN_ON = 128 - var SUPPORT_TURN_OFF = 256 - - // device info - this.domain = "media_player" - this.data = data - this.entity_id = data.entity_id - this.supportsVolume = false - this.supportedMediaCommands = data.attributes.supported_media_commands - - if (data.attributes && data.attributes.friendly_name) { - this.name = data.attributes.friendly_name - }else{ - this.name = data.entity_id.split('.').pop().replace(/_/g, ' ') - } - - if ((this.supportedMediaCommands | SUPPORT_PAUSE) == this.supportedMediaCommands) { - this.onState = "playing" - this.offState = "paused" - this.onService = "media_play" - this.offService = "media_pause" - }else if ((this.supportedMediaCommands | SUPPORT_TURN_ON) == this.supportedMediaCommands && (this.supportedMediaCommands | SUPPORT_TURN_OFF) == this.supportedMediaCommands) { - this.onState = "on" - this.offState = "off" - this.onService = "turn_on" - this.offService = "turn_off" - } - - if ((this.supportedMediaCommands | SUPPORT_VOLUME_SET) == this.supportedMediaCommands) { - this.supportsVolume = true - } - - this.client = client - this.log = log; -} - -HomeAssistantMediaPlayer.prototype = { - getPowerState: function(callback){ - this.log("fetching power state for: " + this.name); - - this.client.fetchState(this.entity_id, function(data){ - if (data) { - powerState = data.state == this.onState - callback(null, powerState) - }else{ - callback(communicationError) - } - }.bind(this)) - }, - getVolume: function(callback){ - this.log("fetching volume for: " + this.name); - that = this - this.client.fetchState(this.entity_id, function(data){ - if (data && data.attributes) { - that.log(JSON.stringify(data.attributes)) - level = data.attributes.volume_level ? data.attributes.volume_level*100 : 0 - callback(null, level) - }else{ - callback(communicationError) - } - }.bind(this)) - }, - setPowerState: function(powerOn, callback) { - var that = this; - var service_data = {} - service_data.entity_id = this.entity_id - - if (powerOn) { - this.log("Setting power state on the '"+this.name+"' to on"); - - this.client.callService(this.domain, this.onService, service_data, function(data){ - if (data) { - that.log("Successfully set power state on the '"+that.name+"' to on"); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - }else{ - this.log("Setting power state on the '"+this.name+"' to off"); - - this.client.callService(this.domain, this.offService, service_data, function(data){ - if (data) { - that.log("Successfully set power state on the '"+that.name+"' to off"); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - } - }, - setVolume: function(level, callback) { - var that = this; - var service_data = {} - service_data.entity_id = this.entity_id - - service_data.volume_level = level/100.0 - - this.log("Setting volume on the '"+this.name+"' to " + level); - - this.client.callService(this.domain, 'volume_set', service_data, function(data){ - if (data) { - that.log("Successfully set volume on the '"+that.name+"' to " + level); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - }, - getServices: function() { - var lightbulbService = new Service.Lightbulb(); - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "Home Assistant") - .setCharacteristic(Characteristic.Model, "Media Player") - .setCharacteristic(Characteristic.SerialNumber, "xxx"); - - lightbulbService - .getCharacteristic(Characteristic.On) - .on('get', this.getPowerState.bind(this)) - .on('set', this.setPowerState.bind(this)); - - - if (this.supportsVolume) { - lightbulbService - .addCharacteristic(Characteristic.Brightness) - .on('get', this.getVolume.bind(this)) - .on('set', this.setVolume.bind(this)); - } - - return [informationService, lightbulbService]; - } - -} - - -function HomeAssistantSwitch(log, data, client, type) { - // device info - this.domain = type || "switch" - this.data = data - this.entity_id = data.entity_id - if (data.attributes && data.attributes.friendly_name) { - this.name = data.attributes.friendly_name - }else{ - this.name = data.entity_id.split('.').pop().replace(/_/g, ' ') - } - - this.client = client - this.log = log; -} - -HomeAssistantSwitch.prototype = { - getPowerState: function(callback){ - this.log("fetching power state for: " + this.name); - - this.client.fetchState(this.entity_id, function(data){ - if (data) { - powerState = data.state == 'on' - callback(null, powerState) - }else{ - callback(communicationError) - } - }.bind(this)) - }, - setPowerState: function(powerOn, callback) { - var that = this; - var service_data = {} - service_data.entity_id = this.entity_id - - if (powerOn) { - this.log("Setting power state on the '"+this.name+"' to on"); - - this.client.callService(this.domain, 'turn_on', service_data, function(data){ - if (data) { - that.log("Successfully set power state on the '"+that.name+"' to on"); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - }else{ - this.log("Setting power state on the '"+this.name+"' to off"); - - this.client.callService(this.domain, 'turn_off', service_data, function(data){ - if (data) { - that.log("Successfully set power state on the '"+that.name+"' to off"); - callback() - }else{ - callback(communicationError) - } - }.bind(this)) - } - }, - getServices: function() { - var switchService = new Service.Switch(); - var informationService = new Service.AccessoryInformation(); - var model; - - switch (this.domain) { - case "scene": - model = "Scene" - break; - default: - model = "Switch" - } - - informationService - .setCharacteristic(Characteristic.Manufacturer, "Home Assistant") - .setCharacteristic(Characteristic.Model, model) - .setCharacteristic(Characteristic.SerialNumber, "xxx"); - - if (this.domain == 'switch') { - switchService - .getCharacteristic(Characteristic.On) - .on('get', this.getPowerState.bind(this)) - .on('set', this.setPowerState.bind(this)); - - }else{ - switchService - .getCharacteristic(Characteristic.On) - .on('set', this.setPowerState.bind(this)); - } - - return [informationService, switchService]; - } - -} - -module.exports.accessory = HomeAssistantLight; -module.exports.accessory = HomeAssistantMediaPlayer; -module.exports.accessory = HomeAssistantSwitch; -module.exports.platform = HomeAssistantPlatform; diff --git a/platforms/HomeMatic.js b/platforms/HomeMatic.js deleted file mode 100644 index 6f3a6aa..0000000 --- a/platforms/HomeMatic.js +++ /dev/null @@ -1,434 +0,0 @@ -"use strict"; -// -// Homematic Platform Shim for HomeBridge -// -// to add the homematic platform add this to config.json. Example: -// "platforms": [ -// { -// "platform": "HomeMatic", -// "name": "HomeMatic", -// "filter_device":[], -// "filter_channel":["BidCos-RF.KEQXXXXXXX:4", "BidCos-RF.LEQXXXXXXX:2"], -// "outlets":[ "BidCos-RF.KEQXXXXXXX:4","BidCos-RF.IEQXXXXXXX:1"] -// -// } -// -// V0.1 - 2015/10/29 -// - initial version -// - reintegrated Homematic Platform fork from https://github.com/thkl/homebridge/tree/xmlrpc -// 2015/10/30 thkl -// - added Rotary Sensors ; fixed thermostat - -// ], - - - -var types = require("hap-nodejs/accessories/types.js"); -var xmlrpc = require("homematic-xmlrpc"); - -var request = require("request"); -var http = require("http"); -var path = require("path"); - -var HomeMaticGenericChannel = require(path.resolve(__dirname, "HomematicChannel.js")); - - - -function RegaRequest(log, ccuip) { - this.log = log; - this.ccuIP = ccuip; -} - -RegaRequest.prototype = { - - script: function(script, callback) { - - var post_options = { - host: this.ccuIP, - port: "80", - path: "/tclrega.exe", - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - "Content-Length": script.length - } - }; - - var post_req = http.request(post_options, function(res) { - var data = ""; - res.setEncoding("binary"); - res.on("data", function(chunk) { - data += chunk.toString(); - }); - res.on("end", function() { - var pos = data.lastIndexOf(""); - var response = (data.substring(0, pos)); - callback(response); - }); - }); - - post_req.on("error", function(e) { - callback("{}"); - }); - - post_req.write(script); - post_req.end(); - - - }, - - getValue: function(channel, datapoint, callback) { - var that = this; - - var script = "var d = dom.GetObject(\"" + channel + "." + datapoint + "\");if (d){Write(d.State());}"; - //that.log("Rega Request " + script); - this.script(script, function(data) { - that.log("Rega Response" + data); - if (data !== undefined) { - callback(parseFloat(data)); - } - }); - }, - - setValue: function(channel, datapoint, value) { - - var script = "var d = dom.GetObject(\"" + channel + "." + datapoint + "\");if (d){d.State(\"" + value + "\");}"; - //this.log("Rega Request " + script); - this.script(script, function(data) { - - }); - } - -}; - -function HomematicRPC(log, ccuip, platform) { - this.log = log; - this.ccuip = ccuip; - this.platform = platform; - this.server; - this.client; - this.stopping = false; - this.localIP; -} - -HomematicRPC.prototype = { - - - init: function() { - var that = this; - - var ip = this.getIPAddress(); - if (ip == "0.0.0.0") { - that.log("Can not fetch IP"); - return; - } - - this.localIP = ip; - this.log("Local IP: " + this.localIP); - - this.server = xmlrpc.createServer({ - host: this.localIP, - port: 9090 - }); - - this.server.on("NotFound", function(method, params) { - that.log("Method " + method + " does not exist"); - }); - - this.server.on("system.listMethods", function(err, params, callback) { - that.log("Method call params for 'system.listMethods': " + params); - callback(null, ["system.listMethods", "system.multicall"]); - }); - - - this.server.on("system.multicall", function(err, params, callback) { - params.map(function(events) { - try { - events.map(function(event) { - if ((event["methodName"] == "event") && (event["params"] !== undefined)) { - var params = event["params"]; - var channel = "BidCos-RF." + params[1]; - var datapoint = params[2]; - var value = params[3]; - that.platform.foundAccessories.map(function(accessory) { - if (accessory.adress == channel) { - accessory.event(datapoint, value); - } - }); - } - }); - } catch (err) {} - }); - callback(null); - }); - - this.log("XML-RPC server listening on port 9090"); - this.connect(); - - - process.on("SIGINT", function() { - if (that.stopping) { - return; - } - that.stopping = true; - that.stop(); - }); - - process.on("SIGTERM", function() { - if (that.stopping) { - return; - } - that.stopping = true; - that.stop(); - }); - - }, - - getIPAddress: function() { - var interfaces = require("os").networkInterfaces(); - for (var devName in interfaces) { - var iface = interfaces[devName]; - for (var i = 0; i < iface.length; i++) { - var alias = iface[i]; - if (alias.family === "IPv4" && alias.address !== "127.0.0.1" && !alias.internal) - return alias.address; - } - } - return "0.0.0.0"; - }, - - getValue: function(channel, datapoint, callback) { - - var that = this; - if (this.client === undefined) { - that.log("Returning cause client is invalid"); - return; - } - if (channel.indexOf("BidCos-RF.") > -1)  { - channel = channel.substr(10); - this.client.methodCall("getValue", [channel, datapoint], function(error, value) { - callback(value); - }); - return; - } - }, - - setValue: function(channel, datapoint, value) { - - var that = this; - - if (this.client === undefined) return; - - if (channel.indexOf("BidCos-RF.") > -1)  { - channel = channel.substr(10); - } - - this.client.methodCall("setValue", [channel, datapoint, value], function(error, value) { - - }); - }, - - connect: function() { - var that = this; - this.log("Creating Local HTTP Client for CCU RPC Events"); - this.client = xmlrpc.createClient({ - host: this.ccuip, - port: 2001, - path: "/" - }); - this.log("CCU RPC Init Call on port 2001"); - this.client.methodCall("init", ["http://" + this.localIP + ":9090", "homebridge"], function(error, value) { - that.log("CCU Response ...."); - }); - }, - - - stop: function() { - this.log("Removing Event Server"); - this.client.methodCall("init", ["http://" + this.localIP + ":9090"], function(error, value) { - - }); - setTimeout(process.exit(0), 1000); - } - -}; - - -function HomeMaticPlatform(log, config) { - this.log = log; - this.ccuIP = config["ccu_ip"]; - this.filter_device = config["filter_device"]; - this.filter_channel = config["filter_channel"]; - this.outlets = config["outlets"]; - - this.sendQueue = []; - this.timer = 0; - - this.foundAccessories = []; - this.adressesToQuery = []; - - this.xmlrpc = new HomematicRPC(this.log, this.ccuIP, this); - this.xmlrpc.init(); -} - -HomeMaticPlatform.prototype = { - - - - accessories: function(callback) { - this.log("Fetching Homematic devices..."); - var that = this; - that.foundAccessories = []; - - var script = "string sDeviceId;string sChannelId;boolean df = true;Write(\'{\"devices\":[\');foreach(sDeviceId, root.Devices().EnumIDs()){object oDevice = dom.GetObject(sDeviceId);if(oDevice){var oInterface = dom.GetObject(oDevice.Interface());if(df) {df = false;} else { Write(\',\');}Write(\'{\');Write(\'\"id\": \"\' # sDeviceId # \'\",\');Write(\'\"name\": \"\' # oDevice.Name() # \'\",\');Write(\'\"address\": \"\' # oDevice.Address() # \'\",\');Write(\'\"channels\": [\');boolean bcf = true;foreach(sChannelId, oDevice.Channels().EnumIDs()){object oChannel = dom.GetObject(sChannelId);if(bcf) {bcf = false;} else {Write(\',\');}Write(\'{\');Write(\'\"cId\": \' # sChannelId # \',\');Write(\'\"name\": \"\' # oChannel.Name() # \'\",\');if(oInterface){Write(\'\"address\": \"\' # oInterface.Name() #\'.'\ # oChannel.Address() # \'\",\');}Write(\'\"type\": \"\' # oChannel.HssType() # \'\"\');Write(\'}\');}Write(\']}\');}}Write(\']}\');"; - - var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) { - var json = JSON.parse(data); - if (json["devices"] !== undefined) { - json["devices"].map(function(device) { - var isFiltered = false; - - if ((that.filter_device !== undefined) && (that.filter_device.indexOf(device.address) > -1)) { - isFiltered = true; - } else { - isFiltered = false; - } - // that.log('device address:', device.address); - - if ((device["channels"] !== undefined) && (!isFiltered)) { - - device["channels"].map(function(ch) { - var isChannelFiltered = false; - - if ((that.filter_channel !== undefined) && (that.filter_channel.indexOf(ch.address) > -1)) { - isChannelFiltered = true; - } else { - isChannelFiltered = false; - } - // that.log('name', ch.name, ' -> address:', ch.address); - if ((ch.address !== undefined) && (!isChannelFiltered)) { - - - // Switch found - // Check if marked as Outlet - var special = (that.outlets.indexOf(ch.address) > -1) ? "OUTLET" : undefined; - var accessory = new HomeMaticGenericChannel(that.log, that, ch.id, ch.name, ch.type, ch.address, special); - if (accessory.sType()!=undefined) { - // support exists for this channel - that.foundAccessories.push(accessory); - } - - } else { - that.log(device.name + " has no address"); - } - - }); - } else { - that.log(device.name + " has no channels or is filtered"); - } - - }); - - /* - var accessory = new HomeMaticGenericChannel(that.log, that, "1234" , "DummyKM" , "SMOKE_DETECTOR" , "1234"); - that.foundAccessories.push(accessory); - - accessory = new HomeMaticGenericChannel(that.log, that, "5678" , "DummyBLIND" , "BLIND" , "5678"); - that.foundAccessories.push(accessory); - - */ - callback(that.foundAccessories); - } else { - callback(that.foundAccessories); - } - }); - - }, - - setValue: function(channel, datapoint, value) { - if (channel.indexOf("BidCos-RF.") > -1)  { - this.xmlrpc.setValue(channel, datapoint, value); - return; - } - - if (channel.indexOf("VirtualDevices.") > -1)  { - var rega = new RegaRequest(this.log, this.ccuIP); - rega.setValue(channel, datapoint, value); - return; - } - - }, - - - setRegaValue: function(channel, datapoint, value) { - var rega = new RegaRequest(this.log, this.ccuIP); - rega.setValue(channel, datapoint, value); - return; - }, - - getValue: function(channel, datapoint, callback) { - - if (channel.indexOf("BidCos-RF.") > -1)  { - this.xmlrpc.getValue(channel, datapoint, callback); - return; - } - - if (channel.indexOf("VirtualDevices.") > -1)  { - var rega = new RegaRequest(this.log, this.ccuIP); - rega.getValue(channel, datapoint, callback); - return; - } - - }, - - prepareRequest: function(accessory, script) { - var that = this; - this.sendQueue.push(script); - that.delayed(100); - }, - - sendPreparedRequests: function() { - var that = this; - var script = "var d;"; - this.sendQueue.map(function(command) { - script = script + command; - }); - this.sendQueue = []; - //this.log('RegaSend: ' + script); - var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) {}); - }, - - sendRequest: function(accessory, script, callback) { - - var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) { - if (data !== undefined) { - try { - var json = JSON.parse(data); - callback(json); - } catch (err) { - callback(undefined); - } - return; - } - }); - }, - - delayed: function(delay) { - var timer = this.delayed[delay]; - if (timer) { - this.log("removing old command"); - clearTimeout(timer); - } - - var that = this; - this.delayed[delay] = setTimeout(function() { - clearTimeout(that.delayed[delay]); - that.sendPreparedRequests(); - }, delay ? delay : 100); - this.log("New Timer was set"); - } -}; - - - -module.exports.platform = HomeMaticPlatform; \ No newline at end of file diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js deleted file mode 100644 index b4fa36d..0000000 --- a/platforms/HomeSeer.js +++ /dev/null @@ -1,1209 +0,0 @@ -'use strict'; - -// -// HomeSeer Platform Shim for HomeBridge by Jean-Michel Joudrier - (stipus at stipus dot com) -// V0.1 - 2015/10/07 -// - Initial version -// V0.2 - 2015/10/10 -// - Occupancy sensor fix -// V0.3 - 2015/10/11 -// - Added TemperatureUnit=F|C option to temperature sensors -// - Added negative temperature support to temperature sensors -// V0.4 - 2015/10/12 -// - Added thermostat support -// V0.5 - 2015/10/12 -// - Added Humidity sensor support -// V0.6 - 2015/10/12 -// - Added Battery support -// - Added low battery support for all sensors -// - Added HomeSeer event support (using HomeKit switches...) -// V0.7 - 2015/10/13 -// - You can add multiple HomeKit devices for the same HomeSeer device reference -// - Added CarbonMonoxide sensor -// - Added CarbonDioxide sensor -// - Added onValues option to all binary sensors -// V0.8 - 2015/10/14 -// - Added uuid_base parameter to all accessories -// V0.9 - 2015/10/16 -// - Smoke sensor battery fix -// - Added offEventGroup && offEventName to events (turn on launches one HS event. turn off can launch another HS event) -// - Added GarageDoorOpener support -// - Added Lock support -// V0.10 - 2015/10/29 -// - Added Security System support -// - Added Window support -// - Added Window Covering support -// - Added obstruction support to doors, windows, and windowCoverings -// -// -// Remember to add platform to config.json. -// -// You can get HomeSeer Device References by clicking a HomeSeer device name, then -// choosing the Advanced Tab. -// -// The uuid_base parameter is valid for all events and accessories. -// If you set this parameter to some unique identifier, the HomeKit accessory ID will be based on uuid_base instead of the accessory name. -// It is then easier to change the accessory name without messing the HomeKit database. -// -// -// Example: -// "platforms": [ -// { -// "platform": "HomeSeer", // Required -// "name": "HomeSeer", // Required -// "host": "http://192.168.3.4:81", // Required - If you did setup HomeSeer authentication, use "http://user:password@ip_address:port" -// -// "events":[ // Optional - List of Events - Currently they are imported into HomeKit as switches -// { -// "eventGroup":"My Group", // Required - The HomeSeer event group -// "eventName":"My On Event", // Required - The HomeSeer event name -// "offEventGroup":"My Group", // Optional - The HomeSeer event group for turn-off -// "offEventName":"My Off Event", // Optional - The HomeSeer event name for turn-off -// "name":"Test", // Optional - HomeSeer event name is the default -// "uuid_base":"SomeUniqueId" // Optional - HomeKit identifier will be derived from this parameter instead of the name -// } -// ], -// -// "accessories":[ // Required - List of Accessories -// { -// "ref":8, // Required - HomeSeer Device Reference (To get it, select the HS Device - then Advanced Tab) -// "type":"Lightbulb", // Optional - Lightbulb is the default -// "name":"My Light", // Optional - HomeSeer device name is the default -// "offValue":"0", // Optional - 0 is the default -// "onValue":"100", // Optional - 100 is the default -// "can_dim":true, // Optional - true is the default - false for a non dimmable lightbulb -// "uuid_base":"SomeUniqueId2" // Optional - HomeKit identifier will be derived from this parameter instead of the name. You SHOULD add this parameter to all accessories ! -// }, -// { -// "ref":9 // This is a dimmable Lightbulb by default -// }, -// { -// "ref":58, // This is a controllable outlet -// "type":"Outlet" -// }, -// { -// "ref":111, // Required - HomeSeer Device Reference for your sensor -// "type":"TemperatureSensor", // Required for a temperature sensor -// "temperatureUnit":"F", // Optional - C is the default -// "name":"Bedroom temp", // Optional - HomeSeer device name is the default -// "batteryRef":112, // Optional - HomeSeer device reference for the sensor battery level -// "batteryThreshold":15 // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 -// }, -// { -// "ref":34, // Required - HomeSeer Device Reference for your sensor -// "type":"SmokeSensor", // Required for a smoke sensor -// "name":"Kichen smoke detector", // Optional - HomeSeer device name is the default -// "batteryRef":35, // Optional - HomeSeer device reference for the sensor battery level -// "batteryThreshold":15, // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 -// "onValues":[1,1.255] // Optional - List of all HomeSeer values triggering a "ON" sensor state - Default is any value different than 0 -// }, -// { -// "ref":34, // Required - HomeSeer Device Reference for your sensor (Here it's the same device as the SmokeSensor above) -// "type":"CarbonMonoxideSensor", // Required for a carbon monoxide sensor -// "name":"Kichen CO detector", // Optional - HomeSeer device name is the default -// "batteryRef":35, // Optional - HomeSeer device reference for the sensor battery level -// "batteryThreshold":15, // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 -// "onValues":[2,2.255] // Optional - List of all HomeSeer values triggering a "ON" sensor state - Default is any value different than 0 -// }, -// { -// "ref":113, // Required - HomeSeer Device Reference of the Current Temperature Device -// "type":"Thermostat", // Required for a Thermostat -// "name":"Température Salon", // Optional - HomeSeer device name is the default -// "temperatureUnit":"C", // Optional - F for Fahrenheit, C for Celsius, C is the default -// "setPointRef":167, // Required - HomeSeer device reference for your thermostat Set Point. -// "setPointReadOnly":true, // Optional - Set to false if your SetPoint is read/write. true is the default -// "stateRef":166, // Required - HomeSeer device reference for your thermostat current state -// "stateOffValues":[0,4,5], // Required - List of the HomeSeer device values for a HomeKit state=OFF -// "stateHeatValues":[1], // Required - List of the HomeSeer device values for a HomeKit state=HEAT -// "stateCoolValues":[2], // Required - List of the HomeSeer device values for a HomeKit state=COOL -// "stateAutoValues":[3], // Required - List of the HomeSeer device values for a HomeKit state=AUTO -// "controlRef":168, // Required - HomeSeer device reference for your thermostat mode control (It can be the same as stateRef for some thermostats) -// "controlOffValue":0, // Required - HomeSeer device control value for OFF -// "controlHeatValue":1, // Required - HomeSeer device control value for HEAT -// "controlCoolValue":2, // Required - HomeSeer device control value for COOL -// "controlAutoValue":3, // Required - HomeSeer device control value for AUTO -// "coolingThresholdRef":169, // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat cooling threshold -// "heatingThresholdRef":170 // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat heating threshold -// }, -// { -// "ref":200, // Required - HomeSeer Device Reference of a garage door opener -// "type":"GarageDoorOpener", // Required for a Garage Door Opener -// "name":"Garage Door", // Optional - HomeSeer device name is the default -// "stateRef":201, // Required - HomeSeer device reference for your garage door opener current state (can be the same as ref) -// "stateOpenValues":[0], // Required - List of the HomeSeer device values for a HomeKit state=OPEN -// "stateClosedValues":[1], // Required - List of the HomeSeer device values for a HomeKit state=CLOSED -// "stateOpeningValues":[2], // Optional - List of the HomeSeer device values for a HomeKit state=OPENING -// "stateClosingValues":[3], // Optional - List of the HomeSeer device values for a HomeKit state=CLOSING -// "stateStoppedValues":[4], // Optional - List of the HomeSeer device values for a HomeKit state=STOPPED -// "controlRef":201, // Required - HomeSeer device reference for your garage door opener control (can be the same as ref and stateRef) -// "controlOpenValue":0, // Required - HomeSeer device control value for OPEN -// "controlCloseValue":1, // Required - HomeSeer device control value for CLOSE -// "obstructionRef":201, // Optional - HomeSeer device reference for your garage door opener obstruction state (can be the same as ref) -// "obstructionValues":[5], // Optional - List of the HomeSeer device values for a HomeKit obstruction state=OBSTRUCTION -// "lockRef":202, // Optional - HomeSeer device reference for your garage door lock (can be the same as ref) -// "lockUnsecuredValues":[0], // Optional - List of the HomeSeer device values for a HomeKit lock state=UNSECURED -// "lockSecuredValues":[1], // Optional - List of the HomeSeer device values for a HomeKit lock state=SECURED -// "lockJammedValues":[2], // Optional - List of the HomeSeer device values for a HomeKit lock state=JAMMED -// "unlockValue":0, // Optional - HomeSeer device control value to unlock the garage door opener -// "lockValue":1 // Optional - HomeSeer device control value to lock the garage door opener -// }, -// { -// "ref":210, // Required - HomeSeer Device Reference of a Lock -// "type":"Lock", // Required for a Lock -// "name":"Main Door Lock", // Optional - HomeSeer device name is the default -// "lockUnsecuredValues":[0], // Required - List of the HomeSeer device values for a HomeKit lock state=UNSECURED -// "lockSecuredValues":[1], // Required - List of the HomeSeer device values for a HomeKit lock state=SECURED -// "lockJammedValues":[2], // Optional - List of the HomeSeer device values for a HomeKit lock state=JAMMED -// "unlockValue":0, // Required - HomeSeer device control value to unlock -// "lockValue":1 // Required - HomeSeer device control value to lock -// }, -// { -// "ref":230, // Required - HomeSeer Device Reference of a Security System -// "type":"SecuritySystem", // Required for a security system -// "name":"Home alarm", // Optional - HomeSeer device name is the default -// "armedStayValues":[0], // Optional - List of the HomeSeer device values for a HomeKit security state=ARMED-STAY -// "armedAwayValues":[1], // Optional - List of the HomeSeer device values for a HomeKit security state=ARMED-AWAY -// "armedNightValues":[2], // Optional - List of the HomeSeer device values for a HomeKit security state=ARMED-NIGHT -// "disarmedValues":[3], // Optional - List of the HomeSeer device values for a HomeKit security state=DISARMED -// "alarmValues":[4], // Optional - List of the HomeSeer device values for a HomeKit security state=ALARM -// "armStayValue":0, // Required - HomeSeer device control value to arm in stay mode. If you don't have this mode, select any value that arms your system -// "armAwayValue":1, // Required - HomeSeer device control value to arm in away mode. If you don't have this mode, select any value that arms your system -// "armNightValue":2, // Required - HomeSeer device control value to arm in night mode. If you don't have this mode, select any value that arms your system -// "disarmValue":3 // Required - HomeSeer device control value to disarm security system -// }, -// { -// "ref":115, // Required - HomeSeer Device Reference for a device holding battery level (0-100) -// "type":"Battery", // Required for a Battery -// "name":"Roomba battery", // Optional - HomeSeer device name is the default -// "batteryThreshold":15 // Optional - If the level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 -// }, -// { -// "ref":240, // Required - HomeSeer Device Reference for a door - HomeSeer values must go from 0 (closed) to 100 (open) -// "type":"Door", // Required for a Door -// "name":"Main door", // Optional - HomeSeer device name is the default -// "obstructionRef":241, // Optional - HomeSeer device reference for your door obstruction state (can be the same as ref) -// "obstructionValues":[1] // Optional - List of the HomeSeer device values for a HomeKit obstruction state=OBSTRUCTION -// } -// ] -// } -// ], -// -// -// SUPORTED TYPES: -// - Lightbulb (can_dim, onValue, offValue options) -// - Fan (onValue, offValue options) -// - Switch (onValue, offValue options) -// - Outlet (onValue, offValue options) -// - Thermostat (temperatureUnit, setPoint, state, control options) -// - TemperatureSensor (temperatureUnit=C|F) -// - HumiditySensor (HomeSeer device value in % - batteryRef, batteryThreshold options) -// - LightSensor (HomeSeer device value in Lux - batteryRef, batteryThreshold options) -// - ContactSensor (onValues, batteryRef, batteryThreshold options) -// - MotionSensor (onValues, batteryRef, batteryThreshold options) -// - LeakSensor (onValues, batteryRef, batteryThreshold options) -// - OccupancySensor (onValues, batteryRef, batteryThreshold options) -// - SmokeSensor (onValues, batteryRef, batteryThreshold options) -// - CarbonMonoxideSensor (onValues, batteryRef, batteryThreshold options) -// - CarbonDioxideSensor (onValues, batteryRef, batteryThreshold options) -// - Battery (batteryThreshold option) -// - GarageDoorOpener (state, control, obstruction, lock options) -// - Lock (unsecured, secured, jammed options) -// - SecuritySystem (arm, disarm options) -// - Door (obstruction option) -// - Window (obstruction option) -// - WindowCovering (obstruction option) - -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var request = require("request"); - - -function httpRequest(url, method, callback) { - request({ - url: url, - method: method - }, - function (error, response, body) { - callback(error, response, body) - }) -} - - - -function HomeSeerPlatform(log, config){ - this.log = log; - this.config = config; -} - -HomeSeerPlatform.prototype = { - accessories: function(callback) { - var that = this; - var foundAccessories = []; - - if( this.config.events ) { - this.log("Creating HomeSeer events."); - for( var i=0; i" + newValue); - that.eventupdate = true; - that.cache(dp,newValue); - that.eventupdate = false; - }); - }, - - - event:function(dp,newValue) { - - if (dp=="LEVEL") { - newValue = newValue*100; - } - - this.eventupdate = true; - this.cache(dp,newValue); - this.eventupdate = false; - }, - - cache:function(dp,value) { - var that = this; - - - // Check custom Mapping from HM to HomeKit - var map = this.datapointMappings[dp]; - if (map != undefined) { - if (map[value]!=undefined) { - value = map[value]; - } - } - - if (that.currentStateCharacteristic[dp]!=undefined) { - that.currentStateCharacteristic[dp].updateValue(value, null); - } - this.state[dp] = value; - }, - - - delayed: function(mode, dp,value,delay) { - - if (this.eventupdate==true) { - return; - } - - var timer = this.delayed[delay]; - if( timer ) { - clearTimeout( timer ); - } - - this.log(this.name + " delaying command "+mode + " " + dp +" with value " + value); - var that = this; - this.delayed[delay] = setTimeout( function(){clearTimeout(that.delayed[delay]);that.command(mode,dp,value)}, delay?delay:100 ); - }, - - command: function(mode,dp,value,callback) { - - if (this.eventupdate==true) { - return; - } - var that = this; - - if (mode == "set") { - this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); - that.platform.setValue(that.adress,dp,value); - } - - if (mode == "setrega") { - this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); - that.platform.setRegaValue(that.adress,dp,value); - } - - }, - - informationCharacteristics: function() { - return [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "EQ-3", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.type, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.adress , - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - } - ] - }, - - controlCharacteristics: function(that) { - - cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }] - - - if (this.type=="SWITCH") { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { - that.command("set","STATE" , (value==1)?true:false) - }, - - onRead: function(callback) { - that.query("STATE",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["STATE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("STATE"); - }, - - perms: ["pw","pr","ev"], - format: "bool", - initialValue: that.dpvalue("STATE",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - }); - - if (this.special=="OUTLET") { - cTypes.push({ - cType: types.OUTLET_IN_USE_CTYPE, - - onRead: function(callback) { - callback(true); - }, - perms: ["pr","ev"], - format: "bool", - initialValue: true, - supportEvents: false, - supportBonjour: false, - manfDescription: "Is Outlet in Use", - designedMaxLength: 1 - }) - } - } - - - if (this.type=="KEYMATIC") { - cTypes.push( - { - cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE, - - onRead: function(callback) { - that.query("STATE",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["STATE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("STATE"); - }, - - perms: ["pr","ev"], - format: "bool", - initialValue: that.dpvalue("STATE",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current State of your Lock", - designedMaxLength: 1 - }, - { - cType: types.TARGET_LOCK_MECHANISM_STATE_CTYPE, - - onUpdate: function(value) { - that.command("set","STATE",(value==1)?"true":"false") - }, - - onRead: function(callback) { - that.query("STATE",callback); - }, - - onRegister: function(characteristic) { - that.addValueMapping("STATE","1",0); - that.addValueMapping("STATE","0",1); - that.currentStateCharacteristic["STATE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("STATE"); - }, - - - perms: ["pw","pr","ev"], - format: "bool", - initialValue: that.dpvalue("STATE",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Target State of your Lock", - designedMaxLength: 1 - } - - , - { - cType: types.TARGET_DOORSTATE_CTYPE, - - onUpdate: function(value) { - that.command("set","OPEN" , "true") - }, - - onRead: function(callback) { - callback(1); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["OPEN"] = characteristic; - characteristic.eventEnabled = true; - }, - - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 1, - supportEvents: false, - supportBonjour: false, - manfDescription: "Open the Lock", - designedMaxLength: 1 - } - ); - - - } - - - - if (this.type=="DIMMER") { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { - that.command("set","LEVEL" , (value==true) ? "1" : "0") - }, - - onRead: function(callback) { - that.query("LEVEL",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["LEVEL"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("LEVEL"); - }, - - perms: ["pw","pr","ev"], - format: "bool", - initialValue: (that.dpvalue("LEVEL")>0,0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - }, - { - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { - that.delayed("set","LEVEL" , String(value/100),100); - }, - - onRead: function(callback) { - that.query("LEVEL",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["LEVEL"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("LEVEL"); - }, - - - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.dpvalue("LEVEL",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }); - } - - - - if (this.type=="BLIND") { - cTypes.push( - { - cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, - - onRead: function(callback) { - that.query("LEVEL",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["LEVEL"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("LEVEL"); - }, - - perms: ["pr","ev"], - format: "int", - initialValue: that.dpvalue("LEVEL",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Blind Position", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }, - - { - cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, - - onUpdate: function(value) { - that.delayed("set","LEVEL" , String(value/100),100); - }, - - - onRead: function(callback) { - that.query("LEVEL",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["LEVEL"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("LEVEL"); - }, - - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.dpvalue("LEVEL",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Blind Position", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }, - { - cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, - - onRead: function(callback) { - that.query("DIRECTION",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["DIRECTION"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("DIRECTION"); - }, - - perms: ["pr","ev"], - format: "int", - initialValue: that.dpvalue("DIRECTION",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Operating State ", - designedMinValue: 0, - designedMaxValue: 2, - designedMinStep: 1 - } - - ); - } - - // Simple Contact (Magnet) - - if (this.type=="SHUTTER_CONTACT") { - cTypes.push( - { - cType: types.CONTACT_SENSOR_STATE_CTYPE, - - onRead: function(callback) { - that.query("STATE",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["STATE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("STATE"); - }, - - perms: ["pr","ev"], - format: "bool", - initialValue: that.dpvalue("STATE",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current State" - }); - } - - // Rotary Handle - if (this.type=="ROTARY_HANDLE_SENSOR") { - cTypes.push( - { - cType: types.CONTACT_SENSOR_STATE_CTYPE, - - onRead: function(callback) { - that.query("STATE",callback); - }, - - onRegister: function(characteristic) { - that.addValueMapping("STATE","2",1); - that.currentStateCharacteristic["STATE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("STATE"); - }, - - perms: ["pr","ev"], - format: "bool", - initialValue: that.dpvalue("STATE",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current State" - }); - } - - - // Motion Detector - - if (this.type=="MOTION_DETECTOR") { - cTypes.push( - { - cType: types.MOTION_DETECTED_CTYPE, - - onRead: function(callback) { - that.query("MOTION",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["MOTION"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("MOTION"); - }, - - perms: ["pr","ev"], - format: "bool", - initialValue: that.dpvalue("MOTION",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Motion State" - }); - } - - // Smoke Detector - if (this.type=="SMOKE_DETECTOR") { - cTypes.push( - { - cType: "00000076-0000-1000-8000-0026BB765291", - - onRead: function(callback) { - that.query("STATE",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["STATE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("STATE"); - }, - - perms: ["pr","ev"], - format: "bool", - initialValue: that.dpvalue("STATE",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Smoke detected" - }); - } - - // Heating Device - - if ((this.type=="CLIMATECONTROL_RT_TRANSCEIVER") || (this.type=="THERMALCONTROL_TRANSMIT")) { - - cTypes.push({ - 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.CURRENTHEATINGCOOLING_CTYPE,onUpdate: null, - perms: ["pr"],format: "int",initialValue: 1,supportEvents: false, - supportBonjour: false,manfDescription: "Current Mode",designedMaxLength: 1,designedMinValue: 1,designedMaxValue: 1,designedMinStep: 1 - }, - - { - cType: types.TARGETHEATINGCOOLING_CTYPE,onUpdate: null,perms: ["pw","pr"], - format: "int",initialValue: 1,supportEvents: false,supportBonjour: false,manfDescription: "Target Mode", - designedMinValue: 1,designedMaxValue: 1,designedMinStep: 1 - }, - - { - cType: types.CURRENT_TEMPERATURE_CTYPE, - onUpdate: null, - - onRead: function(callback) { - that.query("ACTUAL_TEMPERATURE",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["ACTUAL_TEMPERATURE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("ACTUAL_TEMPERATURE"); - }, - perms: ["pw","pr","ev"], perms: ["pr"],format: "double", - initialValue: that.dpvalue("ACTUAL_TEMPERATURE",20), - supportEvents: false,supportBonjour: false,manfDescription: "Current Temperature",unit: "celsius" - }, - - { - cType: types.TARGET_TEMPERATURE_CTYPE, - onUpdate: function(value) { - if (that.state["CONTROL_MODE"]!=1) { - that.delayed("setrega", "MANU_MODE",value,500); - that.state["CONTROL_MODE"]=1; // set to Manual Mode - } else { - that.delayed("setrega", "SET_TEMPERATURE", value,500); - } - }, - onRead: function(callback) { - that.query("SET_TEMPERATURE",callback); - that.query("CONTROL_MODE",undefined); - - }, - onRegister: function(characteristic) { - that.currentStateCharacteristic["SET_TEMPERATURE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("SET_TEMPERATURE"); - that.remoteGetValue("CONTROL_MODE"); - }, - perms: ["pw","pr","ev"],format: "double", - initialValue: that.dpvalue("SET_TEMPERATURE",16), - supportEvents: false,supportBonjour: false, manfDescription: "Target Temperature", - designedMinValue: 16,designedMaxValue: 38,designedMinStep: 1,unit: "celsius" - }, - - { - cType: types.TEMPERATURE_UNITS_CTYPE,onRead: null, - perms: ["pr"],format: "int",initialValue: 0,supportEvents: false, - supportBonjour: false,manfDescription: "Current Temperature Unit",unit: "celsius" - } - - ); - } - - - return cTypes - }, - - sType: function() { - - if (this.type=="SWITCH") { - - if (this.special=="OUTLET") { - return types.OUTLET_STYPE; - } else { - return types.LIGHTBULB_STYPE; - } - } - - if (this.type=="DIMMER") { - return types.LIGHTBULB_STYPE; - } - - if (this.type=="BLIND") { - return types.WINDOW_COVERING_STYPE; - } - - if ((this.type=="CLIMATECONTROL_RT_TRANSCEIVER") || (this.type=="THERMALCONTROL_TRANSMIT")) { - return types.THERMOSTAT_STYPE; - } - - if ((this.type=="SHUTTER_CONTACT") || (this.type=="ROTARY_HANDLE_SENSOR")) { - return types.CONTACT_SENSOR_STYPE; - } - - if (this.type=="MOTION_DETECTOR") { - return types.MOTION_SENSOR_STYPE - } - - - if (this.type=="KEYMATIC") { - return types.LOCK_MECHANISM_STYPE - } - - if (this.type=="SMOKE_DETECTOR") { - return "00000087-0000-1000-8000-0026BB765291"; - } - - - }, - - getServices: function() { - var that = this; - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), - }, - { - sType: this.sType(), - characteristics: this.controlCharacteristics(that) - }]; - this.log("Loaded services for " + this.name) - return services; - } -}; - - -module.exports = HomeMaticGenericChannel; diff --git a/platforms/ISY.js b/platforms/ISY.js deleted file mode 100644 index 53c4980..0000000 --- a/platforms/ISY.js +++ /dev/null @@ -1,385 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var xml2js = require('xml2js'); -var request = require('request'); -var util = require('util'); - -var parser = new xml2js.Parser(); - - -var power_state_ctype = { - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { return; }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 -}; - -function ISYURL(user, pass, host, port, path) { - return util.format("http://%s:%s@%s:%d%s", user, pass, host, port, encodeURI(path)); -} - -function ISYPlatform(log, config) { - this.host = config["host"]; - this.port = config["port"]; - this.user = config["username"]; - this.pass = config["password"]; - - this.log = log; -} - -ISYPlatform.prototype = { - accessories: function(callback) { - this.log("Fetching ISY Devices."); - - var that = this; - var url = ISYURL(this.user, this.pass, this.host, this.port, "/rest/nodes"); - - var options = { - url: url, - method: 'GET' - }; - - var foundAccessories = []; - - request(options, function(error, response, body) { - if (error) - { - console.trace("Requesting ISY devices."); - that.log(error); - return error; - } - - parser.parseString(body, function(err, result) { - result.nodes.node.forEach(function(obj) { - var enabled = obj.enabled[0] == 'true'; - - if (enabled) - { - var device = new ISYAccessory( - that.log, - that.host, - that.port, - that.user, - that.pass, - obj.name[0], - obj.address[0], - obj.property[0].$.uom - ); - - foundAccessories.push(device); - } - }); - }); - - callback(foundAccessories.sort(function (a,b) { - return (a.name > b.name) - (a.name < b.name); - })); - }); - } -} - -function ISYAccessory(log, host, port, user, pass, name, address, uom) { - this.log = log; - this.host = host; - this.port = port; - this.user = user; - this.pass = pass; - this.name = name; - this.address = address; - this.uom = uom; -} - -ISYAccessory.prototype = { - query: function() { - var path = util.format("/rest/status/%s", encodeURI(this.address)); - var url = ISYURL(this.user, this.pass, this.host, this.port, path); - - var options = { url: url, method: 'GET' }; - request(options, function(error, response, body) { - if (error) - { - console.trace("Requesting Device Status."); - that.log(error); - return error; - } - - parser.parseString(body, function(err, result) { - var value = result.properties.property[0].$.value; - return value; - }); - - }); - }, - - command: function(c, value) { - this.log(this.name + " sending command " + c + " with value " + value); - - switch (c) - { - case 'On': - path = "/rest/nodes/" + this.address + "/cmd/DFON"; - break; - case 'Off': - path = "/rest/nodes/" + this.address + "/cmd/DFOF"; - break; - case 'Low': - path = "/rest/nodes/" + this.address + "/cmd/DON/85"; - break; - case 'Medium': - path = "/rest/nodes/" + this.address + "/cmd/DON/128"; - break; - case 'High': - path = "/rest/nodes/" + this.address + "/cmd/DON/255"; - break; - case 'setLevel': - if (value > 0) - { - path = "/rest/nodes/" + this.address + "/cmd/DON/" + Math.floor(255 * (value / 100)); - } - break; - default: - this.log("Unimplemented command sent to " + this.name + " Command " + c); - break; - } - - if (path) - { - var url = ISYURL(this.user, this.pass, this.host, this.port, path); - var options = { - url: url, - method: 'GET' - }; - - var that = this; - request(options, function(error, response, body) { - if (error) - { - console.trace("Sending Command."); - that.log(error); - return error; - } - that.log("Sent command " + path + " to " + that.name); - }); - } - }, - - informationCharacteristics: function() { - return [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "SmartHome", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Rev-1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.address, - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - } - ] - }, - - controlCharacteristics: function(that) { - cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }] - - if (this.uom == "%/on/off") { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.command("Off") - } else { - that.command("On") - } - }, - onRead: function() { - return this.query(); - } - }); - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%", - onUpdate: function(value) { - that.command("setLevel", value); - }, - onRead: function() { - var val = this.query(); - that.log("Query: " + val); - return val; - } - }); - } - else if (this.uom == "off/low/med/high") - { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.command("Off") - } else { - that.command("On") - } - }, - onRead: function() { - return this.query(); - } - }); - cTypes.push({ - cType: types.ROTATION_SPEED_CTYPE, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the speed of the fan", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.command("Off"); - } else if (value > 0 && value < 40) { - that.command("Low"); - } else if (value > 40 && value < 75) { - that.command("Medium"); - } else { - that.command("High"); - } - }, - onRead: function() { - return this.query(); - } - }); - } - else if (this.uom == "on/off") - { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.command("Off") - } else { - that.command("On") - } - }, - onRead: function() { - return this.query(); - } - }); - } - - return cTypes; - }, - - sType: function() { - if (this.uom == "%/on/off") { - return types.LIGHTBULB_STYPE; - } else if (this.uom == "on/off") { - return types.SWITCH_STYPE; - } else if (this.uom == "off/low/med/high") { - return types.FAN_STYPE; - } - - return types.SWITCH_STYPE; - }, - - getServices: function() { - var that = this; - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), - }, - { - sType: this.sType(), - characteristics: this.controlCharacteristics(that) - }]; - - //that.log("Loaded services for " + that.name); - return services; - } -}; - -module.exports.accessory = ISYAccessory; -module.exports.platform = ISYPlatform; diff --git a/platforms/Indigo.js b/platforms/Indigo.js deleted file mode 100644 index 4886f29..0000000 --- a/platforms/Indigo.js +++ /dev/null @@ -1,552 +0,0 @@ -// Indigo Platform Shim for HomeBridge -// Written by Mike Riccio (https://github.com/webdeck) -// Based on many of the other HomeBridge plartform modules -// See http://www.indigodomo.com/ for more info on Indigo -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "Indigo", // required -// "name": "Indigo", // required -// "host": "127.0.0.1", // required -// "port": "8176", // required -// "username": "username", // optional -// "password": "password" // optional -// } -// ], -// -// When you attempt to add a device, it will ask for a "PIN code". -// The default code for all HomeBridge accessories is 031-45-154. -// - -var types = require("hap-nodejs/accessories/types.js"); -var Characteristic = require("hap-nodejs").Characteristic; -var request = require('request'); -var async = require('async'); - - -function IndigoPlatform(log, config) { - this.log = log; - - this.baseURL = "http://" + config["host"] + ":" + config["port"]; - - if (config["username"] && config["password"]) { - this.auth = { - 'user': config["username"], - 'pass': config["password"], - 'sendImmediately': false - }; - } -} - -IndigoPlatform.prototype = { - accessories: function(callback) { - var that = this; - this.log("Discovering Indigo Devices."); - - var options = { - url: this.baseURL + "/devices.json/", - method: 'GET' - }; - if (this.auth) { - options['auth'] = this.auth; - } - this.foundAccessories = []; - this.callback = callback; - - request(options, function(error, response, body) { - if (error) { - console.trace("Requesting Indigo devices."); - that.log(error); - return error; - } - - // Cheesy hack because response may have an extra comma at the start of the array, which is invalid - var firstComma = body.indexOf(","); - if (firstComma < 10) { - body = "[" + body.substr(firstComma + 1); - } - - var json = JSON.parse(body); - async.eachSeries(json, function(item, asyncCallback) { - var deviceURL = that.baseURL + item.restURL; - var deviceOptions = { - url: deviceURL, - method: 'GET' - }; - if (that.auth) { - deviceOptions['auth'] = that.auth; - } - - request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { - if (deviceError) { - console.trace("Requesting Indigo device info: " + deviceURL + "\nError: " + deviceError + "\nResponse: " + deviceBody); - } - else { - try { - var deviceJson = JSON.parse(deviceBody); - that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); - that.foundAccessories.push( - new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); - } - catch (e) { - that.log("Error parsing Indigo device info: " + deviceURL + "\nException: " + e + "\nResponse: " + deviceBody); - } - } - asyncCallback(); - }); - }, function(asyncError) { - // This will be called after all the requests complete - if (asyncError) { - console.trace("Requesting Indigo device info."); - that.log(asyncError); - } - - that.callback(that.foundAccessories.sort(function (a,b) { - return (a.name > b.name) - (a.name < b.name); - })); - }); - }); - } -} - - -function IndigoAccessory(log, auth, deviceURL, json) { - this.log = log; - this.auth = auth; - this.deviceURL = deviceURL; - - for (var prop in json) { - if (json.hasOwnProperty(prop)) { - this[prop] = json[prop]; - } - } -} - -IndigoAccessory.prototype = { - getStatus: function(callback) { - var that = this; - - var options = { - url: this.deviceURL, - method: 'GET' - }; - if (this.auth) { - options['auth'] = this.auth; - } - - request(options, function(error, response, body) { - if (error) { - console.trace("Requesting Device Status."); - that.log(error); - } - else { - that.log("getStatus of " + that.name + ": " + body); - try { - var json = JSON.parse(body); - callback(json); - } - catch (e) { - console.trace("Requesting Device Status."); - that.log("Exception: " + e + "\nResponse: " + body); - } - } - }); - }, - - updateStatus: function(params) { - var that = this; - var options = { - url: this.deviceURL + "?" + params, - method: 'PUT' - }; - if (this.auth) { - options['auth'] = this.auth; - } - - this.log("updateStatus of " + that.name + ": " + params); - request(options, function(error, response, body) { - if (error) { - console.trace("Updating Device Status."); - that.log(error); - return error; - } - }); - }, - - query: function(prop, callback) { - this.getStatus(function(json) { - callback(json[prop]); - }); - }, - - turnOn: function() { - if (this.typeSupportsOnOff) { - this.updateStatus("isOn=1"); - } - }, - - turnOff: function() { - if (this.typeSupportsOnOff) { - this.updateStatus("isOn=0"); - } - }, - - setBrightness: function(brightness) { - if (this.typeSupportsDim && brightness >= 0 && brightness <= 100) { - this.updateStatus("brightness=" + brightness); - } - }, - - setSpeedIndex: function(speedIndex) { - if (this.typeSupportsSpeedControl && speedIndex >= 0 && speedIndex <= 3) { - this.updateStatus("speedIndex=" + speedIndex); - } - }, - - getCurrentHeatingCooling: function(callback) { - this.getStatus(function(json) { - var mode = 0; - if (json["hvacOperatonModeIsHeat"]) { - mode = 1; - } - else if (json["hvacOperationModeIsCool"]) { - mode = 2; - } - else if (json["hvacOperationModeIsAuto"]) { - mode = 3; - } - callback(mode); - }); - }, - - setTargetHeatingCooling: function(mode) { - if (mode == 0) { - param = "Off"; - } - else if (mode == 1) { - param = "Heat"; - } - else if (mode == 2) { - param = "Cool"; - } - else if (mode == 3) { - param = "Auto"; - } - - if (param) { - this.updateStatus("hvacOperationModeIs" + param + "=true"); - } - }, - - // Note: HomeKit wants all temperature values to be in celsius - getCurrentTemperature: function(callback) { - this.query("displayRawState", function(temperature) { - callback((temperature - 32.0) * 5.0 / 9.0); - }); - }, - - getTargetTemperature: function(callback) { - this.getStatus(function(json) { - var temperature; - if (json["hvacOperatonModeIsHeat"]) { - temperature = json["setpointHeat"]; - } - else if (json["hvacOperationModeIsCool"]) { - temperature = json["setpointCool"]; - } - else { - temperature = (json["setpointHeat"] + json["setpointCool"]) / 2.0; - } - callback((temperature - 32.0) * 5.0 / 9.0); - }); - }, - - setTargetTemperature: function(temperature) { - var that = this; - var t = (temperature * 9.0 / 5.0) + 32.0; - this.getStatus(function(json) { - if (json["hvacOperatonModeIsHeat"]) { - that.updateStatus("setpointHeat=" + t); - } - else if (json["hvacOperationModeIsCool"]) { - that.updateStatus("setpointCool=" + t); - } - else { - var cool = t + 5; - var heat = t - 5; - that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat); - } - }); - }, - - informationCharacteristics: function() { - return [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: [Characteristic.Perms.READ], - format: Characteristic.Formats.STRING, - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: [Characteristic.Perms.READ], - format: Characteristic.Formats.STRING, - initialValue: "Indigo", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: [Characteristic.Perms.READ], - format: Characteristic.Formats.STRING, - initialValue: this.type, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: [Characteristic.Perms.READ], - format: Characteristic.Formats.STRING, - initialValue: this.addressStr, - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: [Characteristic.Perms.WRITE], - format: Characteristic.Formats.BOOL, - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - } - ] - }, - - controlCharacteristics: function(that) { - var hasAType = false; - - var cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: [Characteristic.Perms.READ], - format: Characteristic.Formats.STRING, - initialValue: that.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - }]; - - if (that.typeSupportsDim) { - hasAType = true; - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - initialValue: that.brightness, - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: Characteristic.Units.PERCENTAGE, - onUpdate: function(value) { - that.setBrightness(value); - }, - onRead: function(callback) { - that.query("brightness", callback); - } - }); - } - - if (that.typeSupportsSpeedControl) { - hasAType = true; - cTypes.push({ - cType: types.ROTATION_SPEED_CTYPE, - perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the speed of the fan", - designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, - onUpdate: function(value) { - that.setSpeedIndex(value); - }, - onRead: function(callback) { - that.query("speedIndex", callback); - } - }); - } - - if (that.typeSupportsHVAC) { - hasAType = true; - cTypes.push({ - cType: types.CURRENTHEATINGCOOLING_CTYPE, - perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Mode", - designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, - onUpdate: null, - onRead: function(callback) { - that.getCurrentHeatingCooling(callback); - } - }); - - cTypes.push({ - cType: types.TARGETHEATINGCOOLING_CTYPE, - perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Mode", - designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, - onUpdate: function(value) { - that.setTargetHeatingCooling(value); - }, - onRead: function(callback) { - that.getCurrentHeatingCooling(callback); - } - }); - - cTypes.push({ - cType: types.CURRENT_TEMPERATURE_CTYPE, - perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - designedMinValue: 16, - designedMaxValue: 38, - designedMinStep: 1, - initialValue: 20, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Temperature", - unit: Characteristic.Units.FAHRENHEIT, - onUpdate: null, - onRead: function(callback) { - that.getCurrentTemperature(callback); - } - }); - - cTypes.push({ - cType: types.TARGET_TEMPERATURE_CTYPE, - perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - designedMinValue: 16, - designedMaxValue: 38, - designedMinStep: 1, - initialValue: 20, - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Temperature", - unit: Characteristic.Units.FAHRENHEIT, - onUpdate: function(value) { - that.setTargetTemperature(value); - }, - onRead: function(callback) { - that.getTargetTemperature(callback); - } - }); - - cTypes.push({ - cType: types.TEMPERATURE_UNITS_CTYPE, - perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - initialValue: 1, - supportEvents: false, - supportBonjour: false, - manfDescription: "Unit", - onUpdate: null, - onRead: function(callback) { - callback(1); - } - }); - } - - if (that.typeSupportsOnOff || !hasAType) { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.BOOL, - initialValue: (that.isOn) ? 1 : 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.turnOff(); - } else { - that.turnOn(); - } - }, - onRead: function(callback) { - that.query("isOn", function(isOn) { - callback((isOn) ? 1 : 0); - }); - } - }); - } - - return cTypes; - }, - - sType: function() { - if (this.typeSupportsHVAC) { - return types.THERMOSTAT_STYPE; - } else if (this.typeSupportsDim) { - return types.LIGHTBULB_STYPE; - } else if (this.typeSupportsSpeedControl) { - return types.FAN_STYPE; - } else if (this.typeSupportsOnOff) { - return types.SWITCH_STYPE; - } - - return types.SWITCH_STYPE; - }, - - getServices: function() { - var that = this; - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: that.informationCharacteristics(), - }, - { - sType: that.sType(), - characteristics: that.controlCharacteristics(that) - }]; - - that.log("Loaded services for " + that.name); - return services; - } -}; - -module.exports.accessory = IndigoAccessory; -module.exports.platform = IndigoPlatform; diff --git a/platforms/KNX-sample-config.json b/platforms/KNX-sample-config.json deleted file mode 100644 index eda2fe3..0000000 --- a/platforms/KNX-sample-config.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "bridge": { - "name": "Homebridge", - "username": "CC:22:3D:E3:CE:30", - "port": 51826, - "pin": "031-45-154" - }, - "description": "This is an example configuration file for KNX platform shim", - "hint": "Always paste into jsonlint.com validation page before starting your homebridge, saves a lot of frustration", - "hint2": "Replace all group addresses by current addresses of your installation, these are arbitrary examples!", - "hint3": "For valid services and their characteristics have a look at the KNX.md file in folder platforms!", - "platforms": [ - { - "platform": "KNX", - "name": "KNX", - "knxd_ip": "192.168.178.205", - "knxd_port": 6720, - "accessories": [ - { - "accessory_type": "knxdevice", - "description": "Only generic type knxdevice is supported, all previous knx types have been merged into that.", - "name": "Living Room North Lamp", - "services": [ - { - "type": "Lightbulb", - "description": "iOS8 Lightbulb type, supports On (Switch) and Brightness", - "name": "Living Room North Lamp", - "On": { - "Set": "1/1/6", - "Listen": [ - "1/1/63" - ] - }, - "Brightness": { - "Set": "1/1/62", - "Listen": [ - "1/1/64" - ] - } - } - ], - "services-description": "Services is an array, you CAN have multiple service types in one accessory, though it is not fully supported in many iOS HK apps, such as EVE and myTouchHome" - }, - { - "accessory_type": "knxdevice", - "name": "Office Temperature", - "description": "iOS8.4.1 TemperatureSensor type, supports CurrentTemperature", - "services": [ - { - "type": "TemperatureSensor", - "name": "Raumtemperatur", - "CurrentTemperature": { - "Listen": "3/3/44" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "name": "Office Window Lock", - "services": [ - { - "type": "LockMechanism", - "description": "iOS8 Lock mechanism, Supports LockCurrentState, LockTargetState, append R to the addresses if LOCKED is 1", - "name": "Office Window Lock", - "LockCurrentState": { - "Listen": "5/3/15R" - }, - "LockTargetState": { - "Listen": "5/3/16R" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "description": "sample device with multiple services. Multiple services of different types are widely supported", - "name": "Office", - "services": [ - { - "type": "Lightbulb", - "name": "Office Lamp", - "On": { - "Set": "1/3/5" - } - }, - { - "type": "Thermostat", - "description": "iOS8 Thermostat type, supports CurrentTemperature, TargetTemperature, CurrentHeatingCoolingState ", - "name": "Raumtemperatur", - "CurrentTemperature": { - "Listen": "3/3/44" - }, - "TargetTemperature": { - "Set": "3/3/94" - }, - "CurrentHeatingCoolingState": { - "Listen": "3/3/64" - } - }, - { - "type": "WindowCovering", - "description": "iOS9 Window covering (blinds etc) type, still WIP", - "name": "Blinds", - "TargetPosition": { - "Set": "1/2/3", - "Listen": "1/2/4" - }, - "CurrentPosition": { - "Set": "1/3/1", - "Listen": "1/3/2" - }, - "PositionState": { - "Listen": "2/7/1" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "description": "sample contact sensor device", - "name": "Office Contact", - "services": [ - { - "type": "ContactSensor", - "name": "Office Door", - "ContactSensorState": { - "Listen": "5/3/5" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "description": "sample garage door opener", - "name": "Office Garage", - "services": [ - { - "type": "GarageDoorOpener", - "name": "Office Garage Opener", - "CurrentDoorState": { - "Listen": "5/4/5" - }, - "TargetDoorState": { - "Listen": "5/4/6" - } - } - ] - } - ] - } - ], - "accessories": [ - - ] -} \ No newline at end of file diff --git a/platforms/KNX.js b/platforms/KNX.js deleted file mode 100644 index 1051fb9..0000000 --- a/platforms/KNX.js +++ /dev/null @@ -1,205 +0,0 @@ -/** Sample platform outline - * based on Sonos platform - */ -'use strict'; -var types = require("hap-nodejs/accessories/types.js"); - -var knxd = require('eibd'); - -function KNXPlatform(log, config){ - this.log = log; - this.config = config; - - - - // initiate connection to bus for listening ==> done with first shim - -} - -KNXPlatform.prototype = { - accessories: function(callback) { - this.log("Fetching KNX devices."); - var that = this; - - - // iterate through all devices the platform my offer - // for each device, create an accessory - - // read accessories from file !!!!! - var foundAccessories = this.config.accessories; - - - //create array of accessories - var myAccessories = []; - - for (var int = 0; int < foundAccessories.length; int++) { - this.log("parsing acc " + int + " of " + foundAccessories.length); - // instantiate and push to array - switch (foundAccessories[int].accessory_type) { - case "knxdevice": - this.log("push new universal device "+foundAccessories[int].name); - // push knxd connection setting to each device from platform - foundAccessories[int].knxd_ip = this.config.knxd_ip; - foundAccessories[int].knxd_port = this.config.knxd_port; - var accConstructor = require('./../accessories/knxdevice.js'); - var acc = new accConstructor.accessory(this.log,foundAccessories[int]); - this.log("created "+acc.name+" universal accessory"); - myAccessories.push(acc); - break; - default: - // do something else - this.log("unkown accessory type found"); - } - - } - // if done, return the array to callback function - this.log("returning "+myAccessories.length+" accessories"); - callback(myAccessories); - } -}; - - -/** - * The buscallbacks module is to expose a simple function to listen on the bus and register callbacks for value changes - * of registered addresses. - * - * Usage: - * You can start the monitoring process at any time - startMonitor({host: name-ip, port: port-num }); - - * You can add addresses to the subscriptions using - -registerGA(groupAddress, callback) - - * groupAddress has to be an groupAddress in common knx notation string '1/2/3' - * the callback has to be a - * var f = function(value) { handle value update;} - * so you can do a - * registerGA('1/2/3', function(value){ - * console.log('1/2/3 got a hit with '+value); - * }); - * but of course it is meant to be used programmatically, not literally, otherwise it has no advantage - * - * You can also use arrays of addresses if your callback is supposed to listen to many addresses: - -registerGA(groupAddresses[], callback) - - * as in - * registerGA(['1/2/3','1/0/0'], function(value){ - * console.log('1/2/3 or 1/0/0 got a hit with '+value); - * }); - * if you are having central addresses like "all lights off" or additional response objects - * - * - * callbacks can have a signature of - * function(value, src, dest, type) but do not have to support these parameters (order matters) - * src = physical address such as '1.1.20' - * dest = groupAddress hit (you subscribed to that address, remember?), as '1/2/3' - * type = Data point type, as 'DPT1' - * - * - */ - - - -//array of registered addresses and their callbacks -var subscriptions = []; -//check variable to avoid running two listeners -var running; - -function groupsocketlisten(opts, callback) { - var conn = knxd.Connection(); - conn.socketRemote(opts, function() { - conn.openGroupSocket(0, callback); - }); -} - - -var registerSingleGA = function registerSingleGA (groupAddress, callback, reverse) { - subscriptions.push({address: groupAddress, callback: callback, reverse:reverse }); -}; - -/* - * public busMonitor.startMonitor() - * starts listening for telegrams on KNX bus - * - */ -var startMonitor = function startMonitor(opts) { // using { host: name-ip, port: port-num } options object - if (!running) { - running = true; - } else { - console.log("<< knxd socket listener already running >>"); - return null; - } - console.log(">>> knxd groupsocketlisten starting <<<"); - groupsocketlisten(opts, function(parser) { - //console.log("knxfunctions.read: in callback parser"); - parser.on('write', function(src, dest, type, val){ - // search the registered group addresses - //console.log('recv: Write from '+src+' to '+dest+': '+val+' ['+type+'], listeners:' + subscriptions.length); - for (var i = 0; i < subscriptions.length; i++) { - // iterate through all registered addresses - if (subscriptions[i].address === dest) { - // found one, notify - console.log('HIT: Write from '+src+' to '+dest+': '+val+' ['+type+']'); - subscriptions[i].callback(val, src, dest, type, subscriptions[i].reverse); - } - } - }); - - parser.on('response', function(src, dest, type, val) { - // search the registered group addresses -// console.log('recv: resp from '+src+' to '+dest+': '+val+' ['+type+']'); - for (var i = 0; i < subscriptions.length; i++) { - // iterate through all registered addresses - if (subscriptions[i].address === dest) { - // found one, notify -// console.log('HIT: Response from '+src+' to '+dest+': '+val+' ['+type+']'); - subscriptions[i].callback(val, src, dest, type, subscriptions[i].reverse); - } - } - - }); - - //dont care about reads here -// parser.on('read', function(src, dest) { -// console.log('Read from '+src+' to '+dest); -// }); - //console.log("knxfunctions.read: in callback parser at end"); - }); // groupsocketlisten parser -}; //startMonitor - - -/* - * public registerGA(groupAdresses[], callback(value)) - * parameters - * callback: function(value, src, dest, type) called when a value is sent on the bus - * groupAddresses: (Array of) string(s) for group addresses - * - * - * - */ -var registerGA = function (groupAddresses, callback) { - // check if the groupAddresses is an array - if (groupAddresses.constructor.toString().indexOf("Array") > -1) { - // handle multiple addresses - for (var i = 0; i < groupAddresses.length; i++) { - if (groupAddresses[i] && groupAddresses[i].match(/(\d*\/\d*\/\d*)/)) { // do not bind empty addresses or invalid addresses - // clean the addresses - registerSingleGA (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0], callback,groupAddresses[i].match(/\d*\/\d*\/\d*(R)/) ? true:false ); - } - } - } else { - // it's only one - if (groupAddresses.match(/(\d*\/\d*\/\d*)/)) { - registerSingleGA (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0], callback, groupAddresses.match(/\d*\/\d*\/\d*(R)/) ? true:false); - } - } -// console.log("listeners now: " + subscriptions.length); -}; - - - -module.exports.platform = KNXPlatform; -module.exports.registerGA = registerGA; -module.exports.startMonitor = startMonitor; \ No newline at end of file diff --git a/platforms/KNX.md b/platforms/KNX.md deleted file mode 100644 index ffb7301..0000000 --- a/platforms/KNX.md +++ /dev/null @@ -1,213 +0,0 @@ -# Syntax of the config.json -In the platforms section, you can insert a KNX type platform. -You need to configure all devices directly in the config.json. -````json - "platforms": [ - { - "platform": "KNX", - "name": "KNX", - "knxd_ip": "192.168.178.205", - "knxd_port": 6720, - "accessories": [ - { - "accessory_type": "knxdevice", - "name": "Living Room North Lamp", - "services": [ - { - "type": "Lightbulb", - "description": "iOS8 Lightbulb type, supports On (Switch) and Brightness", - "name": "Living Room North Lamp", - "On": { - "Set": "1/1/6", - "Listen": ["1/1/63"] - }, - "Brightness": { - "Set": "1/1/62", - "Listen": ["1/1/64"] - } - } - ] - } - ] - } -```` -In the accessories section (the array within the brackets [ ]) you can insert as many objects as you like in the following form -````json - { - "accessory_type": "knxdevice", - "name": "Here goes your display name, this will be shown in HomeKit apps", - "services": [ - { - } - ] - } -```` -You have to add services in the following syntax: -````json - { - "type": "SERVICENAME", - "description": "This is just for you to remember things", - "name": "beer tap thermostat", - "CHARACTERISTIC1": { - "Set": "1/1/6", - "Listen": [ - "1/1/63" - ] - }, - "CHARACTERISTIC2": { - "Set": "1/1/62", - "Listen": [ - "1/1/64" - ] - } - } -```` -`CHARACTERISTICx` are properties that are dependent on the service type, so they are listed below. - -Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group address, to which changes are sent if the service supports changing values. Changes on the bus are listened to, too. -`"Listen":["1/2/3","1/2/4","1/2/5"]` is an array of addresses that are listened to additionally. To these addresses never values get written, but the on startup the service will issue *KNX read requests* to ALL addresses listed in `Set:` and in `Listen:` - - -For two characteristics there are additional minValue and maxValue attributes. These are CurrentTemperature and TargetTemperature, and are used in TemperatureSensor and Thermostat. - -So the charcteristic section may look like: - - ````json - { - "type": "Thermostat", - "description": "Sample thermostat", - "name": "We need a name for each service, though it usually shows only if multiple services are present in one accessory", - "CurrentTemperature": { - "Set": "1/1/6", - "Listen": [ - "1/1/63" - ], - "minValue": -18, - "maxValue": 30 - }, - "TargetTemperature": { - "Set": "1/1/62", - "Listen": [ - "1/1/64" - ], - "minValue": -4, - "maxValue": 12 - } - } -```` - - -## reversal of values for characteristics -In general, all DPT1 types can be reversed. If you need a 1 for "contact" of a contact senser, you can append an "R" to the group address. -Likewise, all percentages of DPT5 can be reversed, if you need a 100% (=255) for window closed, append an "R" to the group address. Do not forget the listening addresses! - ````json - { - "type": "ContactSensor", - "description": "Sample ContactSensor with 1 as contact (0 is Apple's default)", - "name": "WindowContact1", - "ContactSensorState": { - "Listen": [ - "1/1/100R" - ] - } - } -```` -# Supported Services and their characteristics -## ContactSensor -- ContactSensorState: DPT 1.002, 0 as contact -- ~~ContactSensorStateContact1: DPT 1.002, 1 as contact~~ - -- StatusActive: DPT 1.011, 1 as true -- StatusFault: DPT 1.011, 1 as true -- StatusTampered: DPT 1.011, 1 as true -- StatusLowBattery: DPT 1.011, 1 as true - -## GarageDoorOpener -- CurrentDoorState: DPT5 integer value in range 0..4 - // Characteristic.CurrentDoorState.OPEN = 0; - // Characteristic.CurrentDoorState.CLOSED = 1; - // Characteristic.CurrentDoorState.OPENING = 2; - // Characteristic.CurrentDoorState.CLOSING = 3; - // Characteristic.CurrentDoorState.STOPPED = 4; - -- TargetDoorState: DPT5 integer value in range 0..1 - // Characteristic.TargetDoorState.OPEN = 0; - // Characteristic.TargetDoorState.CLOSED = 1; - -- ObstructionDetected: DPT1, 1 as true - -- LockCurrentState: DPT5 integer value in range 0..3 - // Characteristic.LockCurrentState.UNSECURED = 0; - // Characteristic.LockCurrentState.SECURED = 1; - // Characteristic.LockCurrentState.JAMMED = 2; - // Characteristic.LockCurrentState.UNKNOWN = 3; - -- LockTargetState: DPT5 integer value in range 0..1 - // Characteristic.LockTargetState.UNSECURED = 0; - // Characteristic.LockTargetState.SECURED = 1; - - - -## Lightbulb - - On: DPT 1.001, 1 as on, 0 as off - - Brightness: DPT5.001 percentage, 100% (=255) the brightest - -## LightSensor -- CurrentAmbientLightLevel: DPT 9.004, 0 to 100000 Lux - -## LockMechanism (This is poorly mapped!) -- LockCurrentState: DPT 1, 1 as secured -- ~~LockCurrentStateSecured0: DPT 1, 0 as secured~~ -- LockTargetState: DPT 1, 1 as secured -- ~~LockTargetStateSecured0: DPT 1, 0 as secured~~ - -*ToDo here: correction of mappings, HomeKit reqires lock states UNSECURED=0, SECURED=1, JAMMED = 2, UNKNOWN=3* - -## MotionSensor -- MotionDetected: DPT 1.002, 1 as motion detected - -- StatusActive: DPT 1.011, 1 as true -- StatusFault: DPT 1.011, 1 as true -- StatusTampered: DPT 1.011, 1 as true -- StatusLowBattery: DPT 1.011, 1 as true - -## Outlet - - On: DPT 1.001, 1 as on, 0 as off - - OutletInUse: DPT 1.011, 1 as on, 0 as off - -## Switch - - On: DPT 1.001, 1 as on, 0 as off - -## TemperatureSensor -- CurrentTemperature: DPT9.001 in °C [listen only] - -## Thermostat -- CurrentTemperature: DPT9.001 in °C [listen only], -40 to 80°C if not overriden as shown above -- TargetTemperature: DPT9.001, values 0..40°C only, all others are ignored -- CurrentHeatingCoolingState: DPT20.102 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only] -- TargetHeatingCoolingState: DPT20.102 HVAC, as above - -## Window -- CurrentPosition: DPT5.001 percentage -- TargetPosition: DPT5.001 percentage -- PositionState: DPT5.005 value [listen only: 0 Increasing, 1 Decreasing, 2 Stopped] - -## WindowCovering -- CurrentPosition: DPT5 percentage -- TargetPosition: DPT5 percentage -- PositionState: DPT5 value [listen only: 0 Closing, 1 Opening, 2 Stopped] - -### not yet supported -- HoldPosition -- TargetHorizontalTiltAngle -- TargetVerticalTiltAngle -- CurrentHorizontalTiltAngle -- CurrentVerticalTiltAngle -- ObstructionDetected - - - - -# DISCLAIMER -**This is work in progress!** - diff --git a/platforms/LIFx.js b/platforms/LIFx.js deleted file mode 100644 index 89de156..0000000 --- a/platforms/LIFx.js +++ /dev/null @@ -1,302 +0,0 @@ -'use strict'; - -// LiFX Platform Shim for HomeBridge -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "LIFx", // required -// "name": "LIFx", // required -// "access_token": "access token", // required -// "use_lan": "true" // optional set to "true" (gets and sets over the lan) or "get" (gets only over the lan) -// } -// ], -// -// When you attempt to add a device, it will ask for a "PIN code". -// The default code for all HomeBridge accessories is 031-45-154. -// - -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var lifxRemoteObj = require('lifx-api'); -var lifx_remote; - -var lifxLanObj; -var lifx_lan; -var use_lan; - -function LIFxPlatform(log, config){ - // auth info - this.access_token = config["access_token"]; - - lifx_remote = new lifxRemoteObj(this.access_token); - - // use remote or lan api ? - use_lan = config["use_lan"] || false; - - if (use_lan != false) { - lifxLanObj = require('lifx'); - lifx_lan = lifxLanObj.init(); - } - - this.log = log; -} - -LIFxPlatform.prototype = { - accessories: function(callback) { - this.log("Fetching LIFx devices."); - - var that = this; - var foundAccessories = []; - - lifx_remote.listLights("all", function(body) { - var bulbs = JSON.parse(body); - - for(var i = 0; i < bulbs.length; i ++) { - var accessory = new LIFxBulbAccessory(that.log, bulbs[i]); - foundAccessories.push(accessory); - } - callback(foundAccessories) - }); - } -} - -function LIFxBulbAccessory(log, bulb) { - // device info - this.name = bulb.label; - this.model = bulb.product_name; - this.deviceId = bulb.id; - this.serial = bulb.uuid; - this.capabilities = bulb.capabilities; - this.log = log; -} - -LIFxBulbAccessory.prototype = { - getLan: function(type, callback){ - var that = this; - - if (!lifx_lan.bulbs[this.deviceId]) { - callback(new Error("Device not found"), false); - return; - } - - lifx_lan.requestStatus(); - lifx_lan.on('bulbstate', function(bulb) { - if (callback == null) { - return; - } - - if (bulb.addr.toString('hex') == that.deviceId) { - switch(type) { - case "power": - callback(null, bulb.state.power > 0); - break; - case "brightness": - callback(null, Math.round(bulb.state.brightness * 100 / 65535)); - break; - case "hue": - callback(null, Math.round(bulb.state.hue * 360 / 65535)); - break; - case "saturation": - callback(null, Math.round(bulb.state.saturation * 100 / 65535)); - break; - } - - callback = null - } - }); - }, - getRemote: function(type, callback){ - var that = this; - - lifx_remote.listLights("id:"+ that.deviceId, function(body) { - var bulb = JSON.parse(body); - - if (bulb.connected != true) { - callback(new Error("Device not found"), false); - return; - } - - switch(type) { - case "power": - callback(null, bulb.power == "on" ? 1 : 0); - break; - case "brightness": - callback(null, Math.round(bulb.brightness * 100)); - break; - case "hue": - callback(null, bulb.color.hue); - break; - case "saturation": - callback(null, Math.round(bulb.color.saturation * 100)); - break; - } - }); - }, - identify: function(callback) { - lifx_remote.breatheEffect("id:"+ this.deviceId, 'green', null, 1, 3, false, true, 0.5, function (body) { - callback(); - }); - }, - setLanColor: function(type, value, callback){ - var bulb = lifx_lan.bulbs[this.deviceId]; - - if (!bulb) { - callback(new Error("Device not found"), false); - return; - } - - var state = { - hue: bulb.state.hue, - saturation: bulb.state.saturation, - brightness: bulb.state.brightness, - kelvin: bulb.state.kelvin - }; - - var scale = type == "hue" ? 360 : 100; - - state[type] = Math.round(value * 65535 / scale) & 0xffff; - lifx_lan.lightsColour(state.hue, state.saturation, state.brightness, state.kelvin, 0, bulb); - - callback(null); - }, - setLanPower: function(state, callback){ - var bulb = lifx_lan.bulbs[this.deviceId]; - - if (!bulb) { - callback(new Error("Device not found"), false); - return; - } - - if (state) { - lifx_lan.lightsOn(bulb); - } - else { - lifx_lan.lightsOff(bulb); - } - - callback(null); - }, - setRemoteColor: function(type, value, callback){ - var color; - - switch(type) { - case "brightness": - color = "brightness:" + (value / 100); - break; - case "hue": - color = "hue:" + value; - break; - case "saturation": - color = "saturation:" + (value / 100); - break; - } - - lifx_remote.setColor("id:"+ this.deviceId, color, 0, null, function (body) { - callback(); - }); - }, - setRemotePower: function(state, callback){ - var that = this; - - lifx_remote.setPower("id:"+ that.deviceId, (state == 1 ? "on" : "off"), 0, function (body) { - callback(); - }); - }, - getServices: function() { - var that = this; - var services = [] - var service = new Service.Lightbulb(this.name); - - switch(use_lan) { - case true: - case "true": - // gets and sets over the lan api - service - .getCharacteristic(Characteristic.On) - .on('get', function(callback) { that.getLan("power", callback);}) - .on('set', function(value, callback) {that.setLanPower(value, callback);}); - - service - .addCharacteristic(Characteristic.Brightness) - .on('get', function(callback) { that.getLan("brightness", callback);}) - .on('set', function(value, callback) { that.setLanColor("brightness", value, callback);}); - - if (this.capabilities.has_color == true) { - service - .addCharacteristic(Characteristic.Hue) - .on('get', function(callback) { that.getLan("hue", callback);}) - .on('set', function(value, callback) { that.setLanColor("hue", value, callback);}); - - service - .addCharacteristic(Characteristic.Saturation) - .on('get', function(callback) { that.getLan("saturation", callback);}) - .on('set', function(value, callback) { that.setLanColor("saturation", value, callback);}); - } - break; - case "get": - // gets over the lan api, sets over the remote api - service - .getCharacteristic(Characteristic.On) - .on('get', function(callback) { that.getLan("power", callback);}) - .on('set', function(value, callback) {that.setRemotePower(value, callback);}); - - service - .addCharacteristic(Characteristic.Brightness) - .on('get', function(callback) { that.getLan("brightness", callback);}) - .on('set', function(value, callback) { that.setRemoteColor("brightness", value, callback);}); - - if (this.capabilities.has_color == true) { - service - .addCharacteristic(Characteristic.Hue) - .on('get', function(callback) { that.getLan("hue", callback);}) - .on('set', function(value, callback) { that.setRemoteColor("hue", value, callback);}); - - service - .addCharacteristic(Characteristic.Saturation) - .on('get', function(callback) { that.getLan("saturation", callback);}) - .on('set', function(value, callback) { that.setRemoteColor("saturation", value, callback);}); - } - break; - default: - // gets and sets over the remote api - service - .getCharacteristic(Characteristic.On) - .on('get', function(callback) { that.getRemote("power", callback);}) - .on('set', function(value, callback) {that.setRemotePower(value, callback);}); - - service - .addCharacteristic(Characteristic.Brightness) - .on('get', function(callback) { that.getRemote("brightness", callback);}) - .on('set', function(value, callback) { that.setRemoteColor("brightness", value, callback);}); - - if (this.capabilities.has_color == true) { - service - .addCharacteristic(Characteristic.Hue) - .on('get', function(callback) { that.getRemote("hue", callback);}) - .on('set', function(value, callback) { that.setRemoteColor("hue", value, callback);}); - - service - .addCharacteristic(Characteristic.Saturation) - .on('get', function(callback) { that.getRemote("saturation", callback);}) - .on('set', function(value, callback) { that.setRemoteColor("saturation", value, callback);}); - } - } - - services.push(service); - - service = new Service.AccessoryInformation(); - - service - .setCharacteristic(Characteristic.Manufacturer, "LIFX") - .setCharacteristic(Characteristic.Model, this.model) - .setCharacteristic(Characteristic.SerialNumber, this.serial); - - services.push(service); - - return services; - } -} - -module.exports.accessory = LIFxBulbAccessory; -module.exports.platform = LIFxPlatform; diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js deleted file mode 100644 index c1ee15a..0000000 --- a/platforms/LogitechHarmony.js +++ /dev/null @@ -1,267 +0,0 @@ -'use strict'; - -// Logitech Harmony Remote Platform Shim for HomeBridge -// Based on the Domoticz Platform Shim for HomeBridge by Joep Verhaeg (http://www.joepverhaeg.nl) -// Wriiten by John Wells (https://github.com/madmod) -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "LogitechHarmony", -// "name": "Logitech Harmony" -// } -// ], -// -// When you attempt to add a device, it will ask for a "PIN code". -// The default code for all HomeBridge accessories is 031-45-154. -// - - -var types = require('hap-nodejs/accessories/types.js'); - -var harmonyDiscover = require('harmonyhubjs-discover'); -var harmony = require('harmonyhubjs-client'); - -var _harmonyHubPort = 61991; - -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var Accessory = require("hap-nodejs").Accessory; -var uuid = require("hap-nodejs").uuid; -var inherits = require('util').inherits; -var queue = require('queue'); - - -function sortByKey (array, key) { - return array.sort(function(a, b) { - var x = a[key]; var y = b[key]; - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - }); -}; - - -function LogitechHarmonyPlatform (log, config) { - this.log = log; - this.ip_address = config['ip_address']; -}; - - -LogitechHarmonyPlatform.prototype = { - - accessories: function (callback) { - var plat = this; - var foundAccessories = []; - var activityAccessories = []; - var hub = null; - var hubIP = null; - var hubQueue = queue(); - hubQueue.concurrency = 1; - - // Get the first hub - locateHub(function (err, client, clientIP) { - if (err) throw err; - - plat.log("Fetching Logitech Harmony devices and activites..."); - - hub = client; - hubIP = clientIP; - //getDevices(hub); - getActivities(); - }); - - // Find one Harmony remote hub (only support one for now) - function locateHub(callback) { - // Use the ip address in configuration if available - if (plat.ip_address) { - console.log("Using Logitech Harmony hub ip address from configuration"); - - return createClient(plat.ip_address, callback) - } - - plat.log("Searching for Logitech Harmony remote hubs..."); - - // Discover the harmony hub with bonjour - var discover = new harmonyDiscover(_harmonyHubPort); - - // TODO: Support update event with some way to add accessories - // TODO: Have some kind of timeout with an error message. Right now this searches forever until it finds one hub. - discover.on('online', function (hubInfo) { - plat.log("Found Logitech Harmony remote hub: " + hubInfo.ip); - - // Stop looking for hubs once we find the first one - // TODO: Support multiple hubs - discover.stop(); - - createClient(hubInfo.ip, callback); - }); - - // Start looking for hubs - discover.start(); - } - - // Connect to a Harmony hub - function createClient(ipAddress, callback) { - plat.log("Connecting to Logitech Harmony remote hub..."); - harmony(ipAddress) - .then(function (client) { - plat.log("Connected to Logitech Harmony remote hub"); - callback(null, client, ipAddress); - }); - } - - // Get Harmony Activities - function getActivities() { - plat.log("Fetching Logitech Harmony activities..."); - - hub.getActivities() - .then(function (activities) { - plat.log("Found activities: \n" + activities.map(function (a) { return "\t" + a.label; }).join("\n")); - - hub.getCurrentActivity().then(function (currentActivity) { - var actAccessories = []; - var sArray = sortByKey(activities, "label"); - sArray.map(function(s) { - var accessory = createActivityAccessory(s); - if (accessory.id > 0) { - accessory.updateActivityState(currentActivity); - actAccessories.push(accessory); - foundAccessories.push(accessory); - } - }); - activityAccessories = actAccessories; - keepAliveRefreshLoop(); - callback(foundAccessories); - }).catch(function (err) { - plat.log('Unable to get current activity with error', err); - throw err; - }); - }); - } - - function createActivityAccessory(activity) { - var accessory = new LogitechHarmonyActivityAccessory(plat.log, activity, changeCurrentActivity.bind(plat), -1); - return accessory; - } - - var isChangingActivity = false; - function changeCurrentActivity(nextActivity, callback) { - if (!nextActivity) { - nextActivity = -1; - } - plat.log('Queue activity to ' + nextActivity); - executeOnHub(function(h, cb) { - plat.log('Set activity to ' + nextActivity); - h.startActivity(nextActivity) - .then(function () { - cb(); - isChangingActivity = false; - plat.log('Finished setting activity to ' + nextActivity); - updateCurrentActivity(nextActivity); - if (callback) callback(null, nextActivity); - }) - .catch(function (err) { - cb(); - isChangingActivity = false; - plat.log('Failed setting activity to ' + nextActivity + ' with error ' + err); - if (callback) callback(err); - }); - }, function(){ - callback(Error("Set activity failed too many times")); - }); - } - - function updateCurrentActivity(currentActivity) { - var actAccessories = activityAccessories; - if (actAccessories instanceof Array) { - actAccessories.map(function(a) { a.updateActivityState(currentActivity); }); - } - } - - // prevent connection from closing - function keepAliveRefreshLoop() { - setTimeout(function() { - setInterval(function() { - executeOnHub(function(h, cb) { - plat.log("Refresh Status"); - h.getCurrentActivity() - .then(function(currentActivity){ - cb(); - updateCurrentActivity(currentActivity); - }) - .catch(cb); - }); - }, 20000); - }, 5000); - } - - function executeOnHub(func, funcMaxTimeout) - { - if (!func) return; - hubQueue.push(function(cb) { - var tout = setTimeout(function(){ - plat.log("Reconnecting to Hub " + hubIP); - createClient(hubIP, function(err, newHub){ - if (err) throw err; - hub = newHub; - if (funcMaxTimeout) { - funcMaxTimeout(); - } - cb(); - }); - }, 30000); - func(hub, function(){ - clearTimeout(tout); - cb(); - }); - }); - if (!hubQueue.running){ - hubQueue.start(); - } - } - } -}; - -function LogitechHarmonyActivityAccessory (log, details, changeCurrentActivity) { - this.log = log; - this.id = details.id; - this.name = details.label; - this.isOn = false; - this.changeCurrentActivity = changeCurrentActivity; - Accessory.call(this, this.name, uuid.generate(this.id)); - var self = this; - - this.getService(Service.AccessoryInformation) - .setCharacteristic(Characteristic.Manufacturer, "Logitech") - .setCharacteristic(Characteristic.Model, "Harmony") - // TODO: Add hub unique id to this for people with multiple hubs so that it is really a guid. - .setCharacteristic(Characteristic.SerialNumber, this.id); - - this.addService(Service.Switch) - .getCharacteristic(Characteristic.On) - .on('get', function(callback) { - // Refreshed automatically by platform - callback(null, self.isOn); - }) - .on('set', this.setPowerState.bind(this)); - -} -inherits(LogitechHarmonyActivityAccessory, Accessory); -LogitechHarmonyActivityAccessory.prototype.parent = Accessory.prototype; -LogitechHarmonyActivityAccessory.prototype.getServices = function() { - return this.services; -}; - -LogitechHarmonyActivityAccessory.prototype.updateActivityState = function (currentActivity) { - this.isOn = (currentActivity === this.id); - // Force get to trigger 'change' if needed - this.getService(Service.Switch) - .getCharacteristic(Characteristic.On) - .getValue(); -}; - -LogitechHarmonyActivityAccessory.prototype.setPowerState = function (state, callback) { - this.changeCurrentActivity(state ? this.id : null, callback); -}; - -module.exports.platform = LogitechHarmonyPlatform; - diff --git a/platforms/MiLight.js b/platforms/MiLight.js deleted file mode 100644 index 0325352..0000000 --- a/platforms/MiLight.js +++ /dev/null @@ -1,242 +0,0 @@ -/* - -MiLight platform shim for Homebridge -Written by Sam Edwards (https://samedwards.ca/) - -Uses the node-milight-promise library (https://github.com/mwittig/node-milight-promise) which features some code from -applamp.nl (http://www.applamp.nl/service/applamp-api/) and uses other details from (http://www.limitlessled.com/dev/) - -Configure in config.json as follows: - -"platforms": [ - { - "platform":"MiLight", - "name":"MiLight", - "ip_address": "255.255.255.255", - "port": 8899, - "type": "rgbw", - "delay": 30, - "repeat": 3, - "zones":["Kitchen Lamp","Bedroom Lamp","Living Room Lamp","Hallway Lamp"] - } -] - -Where the parameters are: - *platform (required): This must be "MiLight", and refers to the name of the accessory as exported from this file - *name (optional): The display name used for logging output by Homebridge. Best to set to "MiLight" - *ip_address (optional): The IP address of the WiFi Bridge. Default to the broadcast address of 255.255.255.255 if not specified - *port (optional): Port of the WiFi bridge. Defaults to 8899 if not specified - *type (optional): One of either "rgbw", "rgb", or "white", depending on the type of bulb being controlled. This applies to all zones. Defaults to rgbw. - *delay (optional): Delay between commands sent over UDP. Default 30ms. May cause delays when sending a lot of commands. Try decreasing to improve. - *repeat (optional): Number of times to repeat the UDP command for better reliability. Default 3 - *zones (required): An array of the names of the zones, in order, 1-4. Use null if a zone is skipped. RGB lamps can only have a single zone. - -Tips and Tricks: - *Setting the brightness of an rgbw or a white bulb will set it to "night mode", which is dimmer than the lowest brightness setting - *White and rgb bulbs don't support absolute brightness setting, so we just send a brightness up/brightness down command depending - if we got a percentage above/below 50% respectively - *The only exception to the above is that white bulbs support a "maximum brightness" command, so we send that when we get 100% - *Implemented warmer/cooler for white lamps in a similar way to brightnes, except this time above/below 180 degrees on the colour wheel - *I welcome feedback on a better way to work the brightness/hue for white and rgb bulbs - -Troubleshooting: -The node-milight-promise library provides additional debugging output when the MILIGHT_DEBUG environmental variable is set - -TODO: - *Possibly build in some sort of state logging and persistance so that we can answswer HomeKit status queries to the best of our ability - -*/ - -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var Milight = require('node-milight-promise').MilightController; -var commands = require('node-milight-promise').commands; - -module.exports = { - accessory: MiLightAccessory, - platform: MiLightPlatform -} - -function MiLightPlatform(log, config) { - this.log = log; - - this.config = config; -} - -MiLightPlatform.prototype = { - accessories: function(callback) { - var zones = []; - - // Various error checking - if (this.config.zones) { - var zoneLength = this.config.zones.length; - } else { - this.log("ERROR: Could not read zones from configuration."); - return; - } - - if (!this.config["type"]) { - this.log("INFO: Type not specified, defaulting to rgbw"); - this.config["type"] = "rgbw"; - } - - if (zoneLength == 0) { - this.log("ERROR: No zones found in configuration."); - return; - } else if (this.config["type"] == "rgb" && zoneLength > 1) { - this.log("WARNING: RGB lamps only have a single zone. Only the first defined zone will be used."); - zoneLength = 1; - } else if (zoneLength > 4) { - this.log("WARNING: Only a maximum of 4 zones are supported per bridge. Only recognizing the first 4 zones."); - zoneLength = 4; - } - - // Create lamp accessories for all of the defined zones - for (var i=0; i < zoneLength; i++) { - if (!!this.config.zones[i]) { - this.config["name"] = this.config.zones[i]; - this.config["zone"] = i+1; - lamp = new MiLightAccessory(this.log, this.config); - zones.push(lamp); - } - } - if (zones.length > 0) { - callback(zones); - } else { - this.log("ERROR: Unable to find any valid zones"); - return; - } - } -} - -function MiLightAccessory(log, config) { - this.log = log; - - // config info - this.ip_address = config["ip_address"]; - this.port = config["port"]; - this.name = config["name"]; - this.zone = config["zone"]; - this.type = config["type"]; - this.delay = config["delay"]; - this.repeat = config["repeat"]; - - this.light = new Milight({ - ip: this.ip_address, - port: this.port, - delayBetweenCommands: this.delay, - commandRepeat: this.repeat - }); - -} -MiLightAccessory.prototype = { - - setPowerState: function(powerOn, callback) { - if (powerOn) { - this.log("["+this.name+"] Setting power state to on"); - this.light.sendCommands(commands[this.type].on(this.zone)); - } else { - this.log("["+this.name+"] Setting power state to off"); - this.light.sendCommands(commands[this.type].off(this.zone)); - } - callback(); - }, - - setBrightness: function(level, callback) { - if (level == 0) { - // If brightness is set to 0, turn off the lamp - this.log("["+this.name+"] Setting brightness to 0 (off)"); - this.light.sendCommands(commands[this.type].off(this.zone)); - } else if (level <= 2 && (this.type == "rgbw" || this.type == "white")) { - // If setting brightness to 2 or lower, instead set night mode for lamps that support it - this.log("["+this.name+"] Setting night mode", level); - - this.light.sendCommands(commands[this.type].off(this.zone)); - // Ensure we're pausing for 100ms between these commands as per the spec - this.light.pause(100); - this.light.sendCommands(commands[this.type].nightMode(this.zone)); - - } else { - this.log("["+this.name+"] Setting brightness to %s", level); - - // Send on command to ensure we're addressing the right bulb - this.light.sendCommands(commands[this.type].on(this.zone)); - - // If this is an rgbw lamp, set the absolute brightness specified - if (this.type == "rgbw") { - this.light.sendCommands(commands.rgbw.brightness(level)); - } else { - // If this is an rgb or a white lamp, they only support brightness up and down. - // Set brightness up when value is >50 and down otherwise. Not sure how well this works real-world. - if (level >= 50) { - if (this.type == "white" && level == 100) { - // But the white lamps do have a "maximum brightness" command - this.light.sendCommands(commands.white.maxBright(this.zone)); - } else { - this.light.sendCommands(commands[this.type].brightUp()); - } - } else { - this.light.sendCommands(commands[this.type].brightDown()); - } - } - } - callback(); - }, - - setHue: function(value, callback) { - this.log("["+this.name+"] Setting hue to %s", value); - - var hue = Array(value, 0, 0); - - // Send on command to ensure we're addressing the right bulb - this.light.sendCommands(commands[this.type].on(this.zone)); - - if (this.type == "rgbw") { - if (value == 0) { - this.light.sendCommands(commands.rgbw.whiteMode(this.zone)); - } else { - this.light.sendCommands(commands.rgbw.hue(commands.rgbw.hsvToMilightColor(hue))); - } - } else if (this.type == "rgb") { - this.light.sendCommands(commands.rgb.hue(commands.rgbw.hsvToMilightColor(hue))); - } else if (this.type == "white") { - // Again, white lamps don't support setting an absolue colour temp, so trying to do warmer/cooler step at a time based on colour - if (value >= 180) { - this.light.sendCommands(commands.white.cooler()); - } else { - this.light.sendCommands(commands.white.warmer()); - } - } - callback(); - }, - - identify: function(callback) { - this.log("["+this.name+"] Identify requested!"); - callback(); // success - }, - - getServices: function() { - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "MiLight") - .setCharacteristic(Characteristic.Model, this.type) - .setCharacteristic(Characteristic.SerialNumber, "MILIGHT12345"); - - var lightbulbService = new Service.Lightbulb(); - - lightbulbService - .getCharacteristic(Characteristic.On) - .on('set', this.setPowerState.bind(this)); - - lightbulbService - .addCharacteristic(new Characteristic.Brightness()) - .on('set', this.setBrightness.bind(this)); - - lightbulbService - .addCharacteristic(new Characteristic.Hue()) - .on('set', this.setHue.bind(this)); - - return [informationService, lightbulbService]; - } -}; diff --git a/platforms/Nest.js b/platforms/Nest.js deleted file mode 100644 index 6f90e7c..0000000 --- a/platforms/Nest.js +++ /dev/null @@ -1,289 +0,0 @@ -var nest = require('unofficial-nest-api'); -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; - -function NestPlatform(log, config){ - // auth info - this.username = config["username"]; - this.password = config["password"]; - - this.log = log; - this.accessoryLookup = { }; -} - -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 initialData = data.shared[deviceId]; - var name = initialData.name; - var accessory = new NestThermostatAccessory(that.log, name, device, deviceId, initialData); - that.accessoryLookup[deviceId] = accessory; - foundAccessories.push(accessory); - } - } - } - function subscribe() { - nest.subscribe(subscribeDone, ['device','shared']); - } - - function subscribeDone(deviceId, data, type) { - // data if set, is also stored here: nest.lastStatus.shared[thermostatID] - if (deviceId && that.accessoryLookup[deviceId]) { - that.log('Update to Device: ' + deviceId + " type: " + type); - var accessory = that.accessoryLookup[deviceId]; - if (accessory) { - switch (type) { - case 'shared': - accessory.updateData(data); - break; - case 'device': - accessory.device = data; - accessory.updateData(); - break; - } - } - - } - setTimeout(subscribe, 2000); - } - - subscribe(); - callback(foundAccessories) - }); - } - }); - } -} - -function NestThermostatAccessory(log, name, device, deviceId, initialData) { - // device info - this.name = name || ("Nest" + device.serial_number); - this.deviceId = deviceId; - this.log = log; - this.device = device; - - var id = uuid.generate('nest.thermostat.' + deviceId); - Accessory.call(this, this.name, id); - this.uuid_base = id; - - this.currentData = initialData; - - this.getService(Service.AccessoryInformation) - .setCharacteristic(Characteristic.Manufacturer, "Nest") - .setCharacteristic(Characteristic.Model, device.model_version) - .setCharacteristic(Characteristic.SerialNumber, device.serial_number); - - this.addService(Service.Thermostat, name); - - this.getService(Service.Thermostat) - .getCharacteristic(Characteristic.TemperatureDisplayUnits) - .on('get', function(callback) { - var units = this.getTemperatureUnits(); - var unitsName = units == Characteristic.TemperatureDisplayUnits.FAHRENHEIT ? "Fahrenheit" : "Celsius"; - this.log("Tempature unit for " + this.name + " is: " + unitsName); - if (callback) callback(null, units); - }.bind(this)); - - this.getService(Service.Thermostat) - .getCharacteristic(Characteristic.CurrentTemperature) - .on('get', function(callback) { - var curTemp = this.getCurrentTemperature(); - this.log("Current temperature for " + this.name + " is: " + curTemp); - if (callback) callback(null, curTemp); - }.bind(this)); - - this.getService(Service.Thermostat) - .getCharacteristic(Characteristic.CurrentHeatingCoolingState) - .on('get', function(callback) { - var curHeatingCooling = this.getCurrentHeatingCooling(); - this.log("Current heating for " + this.name + " is: " + curHeatingCooling); - if (callback) callback(null, curHeatingCooling); - }.bind(this)); - - this.getService(Service.Thermostat) - .getCharacteristic(Characteristic.CurrentRelativeHumidity) - .on('get', function(callback) { - var curHumidity = this.getCurrentRelativeHumidity(); - this.log("Current humidity for " + this.name + " is: " + curHumidity); - if (callback) callback(null, curHumidity); - }.bind(this)); - - this.getService(Service.Thermostat) - .getCharacteristic(Characteristic.TargetTemperature) - .on('get', function(callback) { - var targetTemp = this.getTargetTemperature(); - this.log("Target temperature for " + this.name + " is: " + targetTemp); - if (callback) callback(null, targetTemp); - }.bind(this)) - .on('set', this.setTargetTemperature.bind(this)); - - this.getService(Service.Thermostat) - .getCharacteristic(Characteristic.TargetHeatingCoolingState) - .on('get', function(callback) { - var targetHeatingCooling = this.getTargetHeatingCooling(); - this.log("Target heating for " + this.name + " is: " + targetHeatingCooling); - if (callback) callback(null, targetHeatingCooling); - }.bind(this)) - .on('set', this.setTargetHeatingCooling.bind(this)); - - this.updateData(initialData); -} -inherits(NestThermostatAccessory, Accessory); -NestThermostatAccessory.prototype.parent = Accessory.prototype; - -NestThermostatAccessory.prototype.getServices = function() { - return this.services; -}; - -NestThermostatAccessory.prototype.updateData = function(data) { - if (data != undefined) { - this.currentData = data; - } - var thermostat = this.getService(Service.Thermostat); - thermostat.getCharacteristic(Characteristic.TemperatureDisplayUnits).getValue(); - thermostat.getCharacteristic(Characteristic.CurrentTemperature).getValue(); - thermostat.getCharacteristic(Characteristic.CurrentHeatingCoolingState).getValue(); - thermostat.getCharacteristic(Characteristic.CurrentRelativeHumidity).getValue(); - thermostat.getCharacteristic(Characteristic.TargetHeatingCoolingState).getValue(); - thermostat.getCharacteristic(Characteristic.TargetTemperature).getValue(); -}; - -NestThermostatAccessory.prototype.getCurrentHeatingCooling = function(){ - var current = this.getCurrentTemperature(); - var state = this.getTargetHeatingCooling(); - - var isRange = state == (Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL); - var high = isRange ? this.currentData.target_temperature_high : this.currentData.target_temperature; - var low = isRange ? this.currentData.target_temperature_low : this.currentData.target_temperature; - - // Add threshold - var threshold = .2; - high += threshold; - low -= threshold; - - if ((state & Characteristic.CurrentHeatingCoolingState.COOL) && this.currentData.can_cool && high < current) { - return Characteristic.CurrentHeatingCoolingState.COOL; - } - if ((state & Characteristic.CurrentHeatingCoolingState.HEAT) && this.currentData.can_heat && low > current) { - return Characteristic.CurrentHeatingCoolingState.HEAT; - } - return Characteristic.CurrentHeatingCoolingState.OFF; -}; - -NestThermostatAccessory.prototype.getTargetHeatingCooling = function(){ - switch(this.currentData.target_temperature_type) { - case "off": - return Characteristic.CurrentHeatingCoolingState.OFF; - case "heat": - return Characteristic.CurrentHeatingCoolingState.HEAT; - case "cool": - return Characteristic.CurrentHeatingCoolingState.COOL; - case "range": - return Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL; - default: - return Characteristic.CurrentHeatingCoolingState.OFF; - } -}; - -NestThermostatAccessory.prototype.getCurrentTemperature = function(){ - return this.currentData.current_temperature; -}; - -NestThermostatAccessory.prototype.getCurrentRelativeHumidity = function(){ - return this.device.current_humidity; -}; - -NestThermostatAccessory.prototype.getTargetTemperature = function() { - switch (this.getTargetHeatingCooling()) { - case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: - // Choose closest target as single target - var high = this.currentData.target_temperature_high; - var low = this.currentData.target_temperature_low; - var cur = this.currentData.current_temperature; - return Math.abs(high - cur) < Math.abs(cur - low) ? high : low; - default: - return this.currentData.target_temperature; - } -}; - -NestThermostatAccessory.prototype.getTemperatureUnits = function() { - switch(this.device.temperature_scale) { - case "F": - return Characteristic.TemperatureDisplayUnits.FAHRENHEIT; - case "C": - return Characteristic.TemperatureDisplayUnits.CELSIUS; - default: - return Characteristic.TemperatureDisplayUnits.CELSIUS; - } -}; - -NestThermostatAccessory.prototype.setTargetHeatingCooling = function(targetHeatingCooling, callback){ - var targetTemperatureType = null; - - switch(targetHeatingCooling) { - case Characteristic.CurrentHeatingCoolingState.HEAT: - targetTemperatureType = 'heat'; - break; - case Characteristic.CurrentHeatingCoolingState.COOL: - targetTemperatureType = 'cool'; - break; - case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: - targetTemperatureType = 'range'; - break; - default: - targetTemperatureType = 'off'; - break; - } - - this.log("Setting target heating cooling for " + this.name + " to: " + targetTemperatureType); - nest.setTargetTemperatureType(this.deviceId, targetTemperatureType); - - if (callback) callback(null, targetTemperatureType); -}; - -NestThermostatAccessory.prototype.setTargetTemperature = function(targetTemperature, callback){ - - switch (this.getTargetHeatingCooling()) { - case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: - // Choose closest target as single target - var high = this.currentData.target_temperature_high; - var low = this.currentData.target_temperature_low; - var cur = this.currentData.current_temperature; - var isHighTemp = Math.abs(high - cur) < Math.abs(cur - low); - if (isHighTemp) { - high = targetTemperature; - } else { - low = targetTemperature; - } - this.log("Setting " + (isHighTemp ? "high" : "low") + " target temperature for " + this.name + " to: " + targetTemperature); - nest.setTemperatureRange(this.deviceId, low, high); - break; - default: - this.log("Setting target temperature for " + this.name + " to: " + targetTemperature); - nest.setTemperature(this.deviceId, targetTemperature); - break; - } - - if (callback) callback(null, targetTemperature); -}; - -module.exports.accessory = NestThermostatAccessory; -module.exports.platform = NestPlatform; diff --git a/platforms/Netatmo.js b/platforms/Netatmo.js deleted file mode 100644 index ca10f10..0000000 --- a/platforms/Netatmo.js +++ /dev/null @@ -1,383 +0,0 @@ -'use strict'; - -// Netatmo weatherstation for HomeBridge -// Wriiten by planetk (https://github.com/planetk) -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "Netatmo", -// "name": "Netatmo Weather", -// "ttl": 10, -// "auth": { -// "client_id": "", -// "client_secret": "", -// "username": "", -// "password": "" -// } -// } -// ], -// -// The default code for all HomeBridge accessories is 031-45-154. - -var DEFAULT_CACHE_TTL = 10; // 10 seconds caching - use config["ttl"] to override - -// CUSTOM SERVICE AND CHARACTERISTIC IDS -var ATMOSPHERIC_PRESSURE_STYPE_ID = "B77831FD-D66A-46A4-B66D-FD7EE8DFE3CE"; -var ATMOSPHERIC_PRESSURE_CTYPE_ID = "28FDA6BC-9C2A-4DEA-AAFD-B49DB6D155AB"; -var NOISE_LEVEL_STYPE_ID = "8C85FD40-EB20-45EE-86C5-BCADC773E580"; -var NOISE_LEVEL_CTYPE_ID = "2CD7B6FD-419A-4740-8995-E3BFE43735AB"; - -var Service; -try { - Service = require("hap-nodejs").Service; -} catch(err) { - Service = require("HAP-NodeJS").Service; -} - -var Characteristic; -try { - Characteristic = require("hap-nodejs").Characteristic; -} catch(err) { - Characteristic = require("HAP-NodeJS").Characteristic; -} - -var netatmo = require("netatmo"); -var NodeCache = require("node-cache"); -var inherits = require('util').inherits; - -Characteristic.AtmosphericPressureLevel = function() { - Characteristic.call(this, 'Atmospheric Pressure', ATMOSPHERIC_PRESSURE_CTYPE_ID); - this.setProps({ - format: Characteristic.Formats.UINT8, - unit: "mbar", - minValue: 800, - maxValue: 1200, - minStep: 1, - perms: [ - Characteristic.Perms.READ, - Characteristic.Perms.NOTIFY - ] - }); - this.value = this.getDefaultValue(); -}; -inherits(Characteristic.AtmosphericPressureLevel, Characteristic); -Characteristic.NoiseLevel = function() { - Characteristic.call(this, 'Noise Level', NOISE_LEVEL_CTYPE_ID); - this.setProps({ - format: Characteristic.Formats.UINT8, - unit: "dB", - minValue: 0, - maxValue: 200, - minStep: 1, - perms: [ - Characteristic.Perms.READ, - Characteristic.Perms.NOTIFY - ] - }); - this.value = this.getDefaultValue(); -}; -inherits(Characteristic.NoiseLevel, Characteristic); - -Service.AtmosphericPressureSensor = function(displayName, subtype) { - Service.call(this, displayName, ATMOSPHERIC_PRESSURE_STYPE_ID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.AtmosphericPressureLevel); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.Name); -}; -inherits(Service.AtmosphericPressureSensor, Service); - -Service.NoiseLevelSensor = function(displayName, subtype) { - Service.call(this, displayName, NOISE_LEVEL_STYPE_ID, subtype); - - // Required Characteristics - this.addCharacteristic(Characteristic.NoiseLevel); - - // Optional Characteristics - this.addOptionalCharacteristic(Characteristic.StatusActive); - this.addOptionalCharacteristic(Characteristic.StatusFault); - this.addOptionalCharacteristic(Characteristic.StatusLowBattery); - this.addOptionalCharacteristic(Characteristic.StatusTampered); - this.addOptionalCharacteristic(Characteristic.Name); -}; -inherits(Service.NoiseLevelSensor, Service); - -function NetAtmoRepository(log, api, ttl) { - this.api = api; - this.log = log; - this.cache = new NodeCache( { stdTTL: ttl } ); -} - -NetAtmoRepository.prototype = { - refresh: function(callback) { - var datasource = { - modules: {} - }; - var that = this; - that.api.getStationsData(function(err, devices) { - - // querying for the device infos and the main module - for (var device of devices) { - device.module_name = device.station_name + " " + device.module_name - - that.log("refreshing device " + device._id + " (" + device.module_name + ")"); - datasource.modules[device._id] = device; - - // querying for the extra modules - for (var module of device.modules) { - module.module_name = device.station_name + " " + module.module_name - - that.log("refreshing device " + module._id + " (" + module.module_name + ")"); - datasource.modules[module._id] = module; - } - } - - that.cache.set( "datasource", datasource ); - callback(datasource); - }); - }, - load: function(callback) { - var that = this; - this.cache.get( "datasource", function(err, datasource) { - if(!err) { - if (datasource == undefined) { - that.refresh(callback); - } else { - callback(datasource) - } - } - }); - } -} - -function NetatmoPlatform(log, config) { - this.log = log; - var api = new netatmo(config["auth"]); - var ttl = typeof config["ttl"] !== 'undefined' ? config["ttl"] : DEFAULT_CACHE_TTL; - this.repository = new NetAtmoRepository(this.log, api, ttl); - api.on("error", function(error) { - this.log('ERROR - Netatmo: ' + error); - }); - api.on("warning", function(error) { - this.log('WARN - Netatmo: ' + error); - }); -} - -NetatmoPlatform.prototype = { - accessories: function(callback) { - - var that = this; - var foundAccessories = []; - - this.repository.load(function(datasource) { - for (var id in datasource.modules) { - var device = datasource.modules[id]; - var accessory = new NetatmoAccessory(that.log, that.repository, device); - foundAccessories.push(accessory); - } - callback(foundAccessories); - }); - } -} - -function NetatmoAccessory(log, repository, device) { - this.log = log; - this.repository = repository; - this.deviceId = device._id; - this.name = device.module_name - this.serial = device._id; - this.firmware = device.firmware; - this.model = device.type; - this.serviceTypes = device.data_type; - if (device.battery_vp) { - this.serviceTypes.push("Battery"); - } -} - -NetatmoAccessory.prototype = { - - getData: function(callback) { - var that = this; - this.repository.load(function(datasource) { - callback(datasource.modules[that.deviceId]); - }); - }, - - identify: function(callback) { - this.log("Identify requested!"); - callback(); // success - }, - - currentTemperature: function (callback) { - this.getData(function(deviceData) { -/* - if (error) { - callback(error); - } else { -*/ - callback(null, deviceData.dashboard_data.Temperature); - }.bind(this)); - }, - - currentRelativeHumidity: function(callback) { - this.getData(function(deviceData) { - callback(null, deviceData.dashboard_data.Humidity); - }.bind(this)); - }, - - carbonDioxideDetected: function(callback) { - var that = this; - that.log ("getting CO2" + Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL); - - this.getData(function(deviceData) { - var result = (deviceData.dashboard_data.CO2 > 1000 ? Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL : Characteristic.CarbonDioxideDetected.CO2_LEVELS_NORMAL); - callback(null, result); - }.bind(this)); - }, - - carbonDioxideLevel: function(callback) { - this.getData(function(deviceData) { - callback(null, deviceData.dashboard_data.CO2); - }.bind(this)); - }, - - airQuality: function(callback) { - this.getData(function(deviceData) { - var level = deviceData.dashboard_data.CO2; - var quality = Characteristic.AirQuality.UNKNOWN; - if (level > 2000) quality = Characteristic.AirQuality.POOR; - else if (level > 1500) quality = Characteristic.AirQuality.INFERIOR; - else if (level > 1000) quality = Characteristic.AirQuality.FAIR; - else if (level > 500) quality = Characteristic.AirQuality.GOOD; - else if (level > 250) quality = Characteristic.AirQuality.EXCELLENT; - callback(null, quality); - }.bind(this)); - }, - - batteryLevel: function(callback) { - this.getData(function(deviceData) { - var charge = deviceData.battery_vp; - var level = charge < 3000 ? 0 : (charge - 3000)/30; - callback(null, level); - }.bind(this)); - }, - - statusLowBattery: function(callback) { - this.getData(function(deviceData) { - var charge = deviceData.battery_vp; - var level = charge < 4600 ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - callback(null, level); - }.bind(this)); - }, - - atmosphericPressure: function(callback) { - this.getData(function(deviceData) { - callback(null, deviceData.dashboard_data.Pressure); - }.bind(this)); - }, - - noiseLevel: function(callback) { - this.getData(function(deviceData) { - callback(null, deviceData.dashboard_data.Noise); - }.bind(this)); - }, - - getServices: function() { - var that = this; - var services = []; - - this.log("creating services for " + this.name) - - // INFORMATION /////////////////////////////////////////////////// - - var informationService = new Service.AccessoryInformation(); - var firmwareCharacteristic = informationService.getCharacteristic(Characteristic.FirmwareRevision) - || informationService.addCharacteristic(Characteristic.FirmwareRevision); - services.push( informationService ); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "Netatmo") - .setCharacteristic(Characteristic.Model, this.model) - .setCharacteristic(Characteristic.Name, this.name) - .setCharacteristic(Characteristic.SerialNumber, this.serial) - .setCharacteristic(Characteristic.FirmwareRevision, this.firmware); - - // TEMPERATURE ////////////////////////////////////////////////// - if (this.serviceTypes.indexOf("Temperature") > -1) { - var temperatureSensor = new Service.TemperatureSensor(this.name + " Temperature"); - services.push( temperatureSensor ); - temperatureSensor.getCharacteristic(Characteristic.CurrentTemperature) - .on('get', this.currentTemperature.bind(this)); - } - - // HUMIDITY //////////////////////////////////////////////////// - if (this.serviceTypes.indexOf("Humidity") > -1) { - var humiditySensor = new Service.HumiditySensor(this.name + " Humidity"); - services.push( humiditySensor ); - humiditySensor.getCharacteristic(Characteristic.CurrentRelativeHumidity) - .on('get', this.currentRelativeHumidity.bind(this)); - } - - - // CO2 SENSOR ///////////////////////////////////////////////// - if (this.serviceTypes.indexOf("CO2") > -1) { - var carbonDioxideSensor = new Service.CarbonDioxideSensor(this.name + " Carbon Dioxide"); - var carbonDioxideLevelCharacteristic = carbonDioxideSensor.getCharacteristic(Characteristic.CarbonDioxideLevel) - || carbonDioxideSensor.addCharacteristic(Characteristic.CarbonDioxideLevel); - - services.push( carbonDioxideSensor ); - carbonDioxideSensor.getCharacteristic(Characteristic.CarbonDioxideDetected) - .on('get', this.carbonDioxideDetected.bind(this)); - carbonDioxideLevelCharacteristic - .on('get', this.carbonDioxideLevel.bind(this)); - - var airQualitySensor = new Service.AirQualitySensor(this.name + " Air Quality"); - services.push( airQualitySensor ); - airQualitySensor.getCharacteristic(Characteristic.AirQuality) - .on('get', this.airQuality.bind(this)); - } - - // BATTERY SERVICE //////////////////////////////////////////// - - if (this.serviceTypes.indexOf("Battery") > -1) { - var batteryService = new Service.BatteryService(this.name + " Battery Level"); - services.push( batteryService ); - batteryService.getCharacteristic(Characteristic.BatteryLevel) - .on('get', this.batteryLevel.bind(this)); - batteryService.getCharacteristic(Characteristic.StatusLowBattery) - .on('get', this.statusLowBattery.bind(this)); - } - - // ATMOSPHERIC PRESSURE ///////////////////////////////////////////////////// - - if (this.serviceTypes.indexOf("Pressure") > -1) { - var atmosphericPressureSensor = new Service.AtmosphericPressureSensor(this.name + " Atmospheric Pressure"); - services.push( atmosphericPressureSensor ); - atmosphericPressureSensor.getCharacteristic(Characteristic.AtmosphericPressureLevel) - .on('get', this.atmosphericPressure.bind(this)); - } - - // NOISE LEVEL ////////////////////////////////////////////////////////////// - - if (this.serviceTypes.indexOf("Noise") > -1) { - var noiseLevelSensor = new Service.NoiseLevelSensor(this.name + " Noise Level"); - services.push( noiseLevelSensor ); - noiseLevelSensor.getCharacteristic(Characteristic.NoiseLevel) - .on('get', this.noiseLevel.bind(this)); - } - - // TODO: Check Elgato Eve Characteristics (map min, max, time series, etc.)! - - return services; - } -}; - -module.exports.accessory = NetatmoAccessory; -module.exports.platform = NetatmoPlatform; diff --git a/platforms/Openhab.js b/platforms/Openhab.js deleted file mode 100644 index 4c0e6bd..0000000 --- a/platforms/Openhab.js +++ /dev/null @@ -1,573 +0,0 @@ -// OpenHAB 1 Platform Shim for HomeBridge -// Written by Tommaso Marchionni -// Based on many of the other HomeBridge platform modules -// -// Revisions: -// -// 17 October 2015 [tommasomarchionni] -// - Initial release -// -// 25 October 2015 [tommasomarchionni] -// - Added WS listener and new OOP structure -// -// 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" -// } -// -// Rollershutter is tested with this binding in OpenHAB: -// command=SWITCH_MULTILEVEL,invert_percent=true,invert_state=false" -// 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. -// - -//////// LIBS ///////// - -var WebSocket = require('ws'); -var request = require("request"); -var Service = require("hap-nodejs/lib/Service.js").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var currentModule = this; -var util = require('core-util-is'); -util.inherits = require('inherits'); - -//////// PLATFORM ///////// - -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 = { - accessories: function(callback) { - var that = this; - this.log("Platform - Fetching OpenHAB devices."); - var itemFactory = new ItemFactory(this); - url = itemFactory.sitemapUrl(); - this.log("Platform - Connecting to " + url); - request.get({ - url: url, - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - callback(itemFactory.parseSitemap(json)); - } else { - that.log("Platform - There was a problem connecting to OpenHAB."); - } - }); - } -}; - -//////// END PLATFORM ///////// - -///////// ACCESSORY ///////// - -function OpenhabAccessory(widget,platform) {} - -///////// ABSTRACT ITEM ///////// - -function AbstractItem(widget,platform){ - - AbstractItem.super_.call(this,widget,platform); - - this.widget = widget; - this.label = widget.label; - this.name = widget.item.name; - this.url = widget.item.link; - this.state = widget.item.state; - this.platform = platform; - this.log = platform.log; - - this.setInitialState = false; - this.setFromOpenHAB = false; - this.informationService = undefined; - this.otherService = undefined; - this.listener = undefined; - this.ws = undefined; -}; - -util.inherits(AbstractItem, OpenhabAccessory); - -AbstractItem.prototype.getInformationServices = function() { - informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "OpenHAB") - .setCharacteristic(Characteristic.Model, this.constructor.name) - .setCharacteristic(Characteristic.SerialNumber, "N/A") - .setCharacteristic(Characteristic.Name, this.name); - return informationService; -} - -AbstractItem.prototype.checkListener = function() { - - if (typeof this.listener == 'undefined' || typeof this.ws == 'undefined') { - this.ws = undefined; - this.listener = new WSListener(this, this.updateCharacteristics.bind(this)); - this.listener.startListener(); - } -}; - -///////// END ABSTRACT ITEM ///////// - -///////// SWITCH ITEM ///////// - -function SwitchItem(widget,platform){ - SwitchItem.super_.call(this, widget,platform); -}; - -util.inherits(SwitchItem, AbstractItem); - -SwitchItem.prototype.getServices = function() { - - this.checkListener(); - this.setInitialState = true; - this.informationService = this.getInformationServices(); - - this.otherService = new Service.Lightbulb(); - this.otherService.getCharacteristic(Characteristic.On) - .on('set', this.setItem.bind(this)) - .on('get', this.getItemPowerState.bind(this)) - .setValue(this.state === 'ON'); - - return [this.informationService, this.otherService]; -}; - -SwitchItem.prototype.updateCharacteristics = function(message) { - - this.setFromOpenHAB = true; - this.otherService - .getCharacteristic(Characteristic.On) - .setValue(message === 'ON' ? true : false, - function() { - this.setFromOpenHAB = false; - }.bind(this) - ); -}; - -SwitchItem.prototype.getItemPowerState = function(callback) { - - var self = this; - this.checkListener(); - - this.log("iOS - request power state from " + this.name); - request(this.url + '/state?type=json', function (error, response, body) { - if (!error && response.statusCode == 200) { - self.log("OpenHAB HTTP - response from " + self.name + ": " + body); - callback(undefined,body == "ON" ? true : false); - } else { - self.log("OpenHAB HTTP - error from " + self.name + ": " + error); - } - }) -}; - -SwitchItem.prototype.setItem = function(value, callback) { - - var self = this; - this.checkListener(); - - if (this.setInitialState) { - this.setInitialState = false; - callback(); - return; - } - - if (this.setFromOpenHAB) { - callback(); - return; - } - - this.log("iOS - send message to " + this.name + ": " + value); - var command = value ? 'ON' : 'OFF'; - request.post( - this.url, - { body: command }, - function (error, response, body) { - if (!error && response.statusCode == 201) { - self.log("OpenHAB HTTP - response from " + self.name + ": " + body); - } else { - self.log("OpenHAB HTTP - error from " + self.name + ": " + error); - } - callback(); - } - ); -}; - -///////// END SWITCH ITEM ///////// - -///////// DIMMER ITEM ///////// - -function DimmerItem(widget,platform){ - DimmerItem.super_.call(this, widget,platform); -}; - -util.inherits(DimmerItem, AbstractItem); - -DimmerItem.prototype.getServices = function() { - - this.checkListener(); - this.setInitialState = true; - - this.informationService = this.getInformationServices(); - - this.otherService = new Service.Lightbulb(); - this.otherService.getCharacteristic(Characteristic.On) - .on('set', this.setItem.bind(this)) - .on('get', this.getItemPowerState.bind(this)) - .setValue(+this.state > 0); - - this.setInitialState = true; - - this.otherService.addCharacteristic(Characteristic.Brightness) - .on('set', this.setItem.bind(this)) - .on('get', this.getItemBrightnessState.bind(this)) - .setValue(+this.state); - - return [this.informationService, this.otherService]; -}; - -DimmerItem.prototype.updateCharacteristics = function(message) { - - this.setFromOpenHAB = true; - var brightness = +message; - var steps = 2; - if (brightness >= 0) { - this.otherService.getCharacteristic(Characteristic.Brightness) - .setValue(brightness, - function() { - steps--; - if (!steps) { - this.setFromOpenHAB = false; - } - }.bind(this)); - this.otherService.getCharacteristic(Characteristic.On) - .setValue(brightness > 0 ? true : false, - function() { - steps--; - if (!steps) { - this.setFromOpenHAB = false; - } - }.bind(this)); - } -} - -DimmerItem.prototype.getItemPowerState = function(callback) { - - var self = this; - this.checkListener(); - - this.log("iOS - request power state from " + this.name); - request(this.url + '/state?type=json', function (error, response, body) { - if (!error && response.statusCode == 200) { - self.log("OpenHAB HTTP - response from " + self.name + ": " + body); - callback(undefined,+body > 0 ? true : false); - } else { - self.log("OpenHAB HTTP - error from " + self.name + ": " + error); - } - }) -}; - -DimmerItem.prototype.setItem = function(value, callback) { - - var self = this; - this.checkListener(); - - if (this.setInitialState) { - this.setInitialState = false; - callback(); - return; - } - - if (this.setFromOpenHAB) { - callback(); - return; - } - - this.log("iOS - send message to " + this.name + ": " + value); - var command = 0; - if (typeof value === 'boolean') { - command = value ? '100' : '0'; - } else { - command = "" + value; - } - request.post( - this.url, - { - body: command, - headers: {'Content-Type': 'text/plain'} - }, - function (error, response, body) { - if (!error && response.statusCode == 201) { - self.log("OpenHAB HTTP - response from " + self.name + ": " + body); - } else { - self.log("OpenHAB HTTP - error from " + self.name + ": " + error); - } - callback(); - } - ); -}; - -DimmerItem.prototype.getItemBrightnessState = function(callback) { - - var self = this; - - this.log("iOS - request brightness state from " + this.name); - request(this.url + '/state?type=json', function (error, response, body) { - if (!error && response.statusCode == 200) { - self.log("OpenHAB HTTP - response from " + self.name + ": " + body); - callback(undefined,+body); - } else { - self.log("OpenHAB HTTP - error from " + self.name + ": " + error); - } - }) -}; - -///////// END DIMMER ITEM ///////// - -///////// ROLLERSHUTTER ITEM ///////// - -function RollershutterItem(widget,platform){ - RollershutterItem.super_.call(this, widget,platform); - this.positionState = Characteristic.PositionState.STOPPED; - this.currentPosition = 100; - this.targetPosition = 100; - this.startedPosition = 100; -}; - -util.inherits(RollershutterItem, AbstractItem); - -RollershutterItem.prototype.getServices = function() { - - this.checkListener(); - - this.informationService = this.getInformationServices(); - - this.otherService = new Service.WindowCovering(); - - this.otherService.getCharacteristic(Characteristic.CurrentPosition) - .on('get', this.getItemCurrentPosition.bind(this)) - .setValue(this.currentPosition); - - this.setInitialState = true; - - this.otherService.getCharacteristic(Characteristic.TargetPosition) - .on('set', this.setItem.bind(this)) - .on('get', this.getItemTargetPosition.bind(this)) - .setValue(this.currentPosition); - - this.otherService.getCharacteristic(Characteristic.PositionState) - .on('get', this.getItemPositionState.bind(this)) - .setValue(this.positionState); - - return [this.informationService, this.otherService]; -}; - - - -RollershutterItem.prototype.updateCharacteristics = function(message) { - - console.log(message); - console.log(this.targetPosition); - - - - if (parseInt(message) == this.targetPosition) { - var ps = Characteristic.PositionState.STOPPED; - var cs = parseInt(message); - } else if (parseInt(message) > this.targetPosition){ - var ps = Characteristic.PositionState.INCREASING; - var cs = this.startedPosition; - } else { - var ps = Characteristic.PositionState.DECREASING; - var cs = this.startedPosition; - } - - this.otherService - .getCharacteristic(Characteristic.PositionState) - .setValue(ps); - - this.otherService - .getCharacteristic(Characteristic.CurrentPosition) - .setValue(parseInt(cs)); - this.currentPosition = parseInt(cs); -}; - -RollershutterItem.prototype.setItem = function(value, callback) { - - var self = this; - this.checkListener(); - - if (this.setInitialState) { - this.setInitialState = false; - callback(); - return; - } - - this.startedPosition = this.currentPosition; - - this.log("iOS - send message to " + this.name + ": " + value); - - var command = 0; - if (typeof value === 'boolean') { - command = value ? '100' : '0'; - } else { - command = "" + value; - } - request.post( - this.url, - { - body: command, - headers: {'Content-Type': 'text/plain'} - }, - function (error, response, body) { - if (!error && response.statusCode == 201) { - self.log("OpenHAB HTTP - response from " + self.name + ": " + body); - self.targetPosition = parseInt(value); - } else { - self.log("OpenHAB HTTP - error from " + self.name + ": " + error); - } - callback(); - } - ); -}; - -RollershutterItem.prototype.getItemPositionState = function(callback) { - this.log("iOS - request position state from " + this.name); - this.log("Platform - response from " + this.name + ": " + this.positionState); - callback(undefined,this.positionState); -}; - -RollershutterItem.prototype.getItemTargetPosition = function(callback) { - this.log("iOS - get target position state from " + this.name); - this.log("Platform - response from " + this.name + ": " + this.targetPosition); - callback(undefined,this.targetPosition); -} - -RollershutterItem.prototype.getItemCurrentPosition = function(callback) { - var self = this; - this.log("iOS - request current position state from " + this.name); - - request(this.url + '/state?type=json', function (error, response, body) { - if (!error && response.statusCode == 200) { - - self.log("OpenHAB HTTP - response from " + self.name + ": " +body); - self.currentPosition = parseInt(body); - callback(undefined,parseInt(body)); - - } else { - self.log("OpenHAB HTTP - error from " + self.name + ": " + error); - } - }) -}; - -///////// END ROLLERSHUTTER ITEM ///////// - -///////// ITEM UTILITY ///////// - -function ItemFactory(openhabPlatform){ - this.platform = openhabPlatform; - this.log = this.platform.log; -} - -ItemFactory.prototype = { - sitemapUrl: function() { - var serverString = this.platform.server; - //TODO da verificare - if (this.platform.user && this.platform.password) { - serverString = this.platform.user + ":" + this.platform.password + "@" + serverString; - } - - return this.platform.protocol + "://" + serverString + ":" + this.platform.port + "/rest/sitemaps/" + this.platform.sitemap + "?type=json"; - }, - - parseSitemap: function(jsonSitemap) { - var widgets = [].concat(jsonSitemap.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("Platform - The widget '" + widget.label + "' is not an item."); - continue; - } - - if (currentModule[widget.item.type] != undefined) { - var accessory = new currentModule[widget.item.type](widget,this.platform); - } else { - this.log("Platform - The widget '" + widget.label + "' of type "+widget.item.type+" is an item not handled."); - continue; - } - - this.log("Platform - Accessory Found: " + widget.label); - result.push(accessory); - } - return result; - } - -}; - -///////// END ITEM UTILITY ///////// - -///////// WS LISTENER ///////// - -function WSListener(item, callback){ - this.item = item; - this.callback = callback; -} - -WSListener.prototype = { - startListener: function() { - var self = this; - - if (typeof this.item.ws == 'undefined') { - this.item.ws = new WebSocket(this.item.url.replace('http:', 'ws:') + '/state?type=json'); - } - - this.item.ws.on('open', function() { - self.item.log("OpenHAB WS - new connection for "+self.item.name); - }); - - this.item.ws.on('message', function(message) { - self.item.log("OpenHAB WS - message from " +self.item.name+": "+ message); - self.callback(message); - }); - - this.item.ws.on('close', function close() { - self.item.log("OpenHAB WS - closed connection for "+self.item.name); - self.item.listener = undefined; - self.item.ws = undefined; - }); - } - -}; - -///////// END WS LISTENER ///////// - -///////// SUPPORTED ITEMS ///////// -module.exports.SwitchItem = SwitchItem; -module.exports.DimmerItem = DimmerItem; -module.exports.RollershutterItem = RollershutterItem; -///////// END SUPPORTED ITEMS ///////// - -module.exports.accessory = OpenhabAccessory; -module.exports.platform = OpenhabPlatform; diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js deleted file mode 100644 index 7ba1f0e..0000000 --- a/platforms/PhilipsHue.js +++ /dev/null @@ -1,338 +0,0 @@ -// Philips Hue Platform Shim for HomeBridge -// -// Remember to add platform to config.json. Example: -// "platforms": [ -// { -// "platform": "PhilipsHue", -// "name": "Philips Hue", -// "ip_address": "127.0.0.1", -// "username": "252deadbeef0bf3f34c7ecb810e832f" -// } -// ], -// -// If you do not know the IP address of your Hue Bridge, simply leave it blank and your Bridge -// will be discovered automatically. -// -// If you do not have a "username" for your Hue API already, simply leave the field blank and -// you will be prompted to press the link button on your Hue Bridge before running HomeBridge. -// A username will be created for you and printed out, then the server will exit so you may -// enter it in your config.json. -// -// -// When you attempt to add a device, it will ask for a "PIN code". -// The default code for all HomeBridge accessories is 031-45-154. -// - -/* jslint node: true */ -/* globals require: false */ -/* globals config: false */ - -"use strict"; - -var hue = require("node-hue-api"), - HueApi = hue.HueApi, - lightState = hue.lightState; - -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; - - -function PhilipsHuePlatform(log, config) { - this.log = log; - this.ip_address = config["ip_address"]; - this.username = config["username"]; -} - -function PhilipsHueAccessory(log, device, api) { - this.id = device.id; - this.name = device.name; - this.model = device.modelid; - this.device = device; - this.api = api; - this.log = log; -} - -// Get the ip address of the first available bridge with meethue.com or a network scan. -var locateBridge = function (callback) { - var that = this; - - // Report the results of the scan to the user - var getIp = function (err, bridges) { - if (!bridges || bridges.length === 0) { - that.log("No Philips Hue bridges found."); - callback(err || new Error("No bridges found")); - return; - } - - if (bridges.length > 1) { - that.log("Warning: Multiple Philips Hue bridges detected. The first bridge will be used automatically. To use a different bridge, set the `ip_address` manually in the configuration."); - } - - that.log( - "Philips Hue bridges found:\n" + - (bridges.map(function (bridge) { - // Bridge name is only returned from meethue.com so use id instead if it isn't there - return "\t" + bridge.ipaddress + ' - ' + (bridge.name || bridge.id); - })).join("\n") - ); - - callback(null, bridges[0].ipaddress); - }; - - // Try to discover the bridge ip using meethue.com - that.log("Attempting to discover Philips Hue bridge with meethue.com..."); - hue.nupnpSearch(function (locateError, bridges) { - if (locateError) { - that.log("Philips Hue bridge discovery with meethue.com failed. Register your bridge with the meethue.com for more reliable discovery."); - - that.log("Attempting to discover Philips Hue bridge with network scan..."); - - // Timeout after one minute - hue.upnpSearch(60000) - .then(function (bridges) { - that.log("Scan complete"); - getIp(null, bridges); - }) - .fail(function (scanError) { - that.log("Philips Hue bridge discovery with network scan failed. Check your network connection or set ip_address manually in configuration."); - getIp(new Error("Scan failed: " + scanError.message)); - }).done(); - } else { - getIp(null, bridges); - } - }); -}; - -PhilipsHuePlatform.prototype = { - - accessories: function(callback) { - this.log("Fetching Philips Hue lights..."); - var that = this; - var getLights = function () { - var api = new HueApi(that.ip_address, that.username); - - // Connect to the API - // Get a dump of all lights, so as not to hit rate limiting for installations with larger amounts of bulbs - - api.fullState(function(err, response) { - if (err) throw err; - - var foundAccessories = []; - for (var deviceId in response.lights) { - var device = response.lights[deviceId]; - device.id = deviceId; - var accessory = new PhilipsHueAccessory(that.log, device, api); - foundAccessories.push(accessory); - } - callback(foundAccessories); - - }); - }; - - // Create a new user if needed - function checkUsername() { - if (!that.username) { - var api = new HueApi(that.ip_address); - api.createUser(that.ip_address, null, null, function(err, user) { - - // try and help explain this particular error - if (err && err.message == "link button not pressed") - throw "Please press the link button on your Philips Hue bridge, then start the HomeBridge server within 30 seconds."; - - if (err) throw err; - - throw "Created a new username " + JSON.stringify(user) + " for your Philips Hue. Please add it to your config.json then start the HomeBridge server again: "; - }); - } - else { - getLights(); - } - } - - // Discover the bridge if needed - if (!this.ip_address) { - locateBridge.call(this, function (err, ip_address) { - if (err) throw err; - - // TODO: Find a way to persist this - that.ip_address = ip_address; - that.log("Save the Philips Hue bridge ip address "+ ip_address +" to your config to skip discovery."); - checkUsername(); - }); - } else { - checkUsername(); - } - } -}; - -PhilipsHueAccessory.prototype = { - // Convert 0-65535 to 0-360 - hueToArcDegrees: function(value) { - value = value/65535; - value = value*360; - value = Math.round(value); - return value; - }, - // Convert 0-360 to 0-65535 - arcDegreesToHue: function(value) { - value = value/360; - value = value*65535; - value = Math.round(value); - return value; - }, - // Convert 0-255 to 0-100 - bitsToPercentage: function(value) { - value = value/255; - value = value*100; - value = Math.round(value); - return value; - }, - extractValue: function(characteristic, status) { - switch(characteristic.toLowerCase()) { - case 'power': - return status.state.reachable && status.state.on ? 1 : 0; - case 'hue': - return this.hueToArcDegrees(status.state.hue); - case 'brightness': - return this.bitsToPercentage(status.state.bri); - case 'saturation': - return this.bitsToPercentage(status.state.sat); - default: - return null; - } - }, - // Create and set a light state - executeChange: function(characteristic, value, callback) { - var state = lightState.create(); - switch(characteristic.toLowerCase()) { - case 'identify': - state.alert('select'); - break; - case 'power': - if (value) { - state.on(); - } - else { - state.off(); - } - break; - case 'hue': - state.hue(this.arcDegreesToHue(value)); - break; - case 'brightness': - state.brightness(value); - break; - case 'saturation': - state.saturation(value); - break; - } - this.api.setLightState(this.id, state, function(err, lights) { - if (callback == null) { - return; - } - if (!err) { - if (callback) callback(); // Success - callback = null; - this.log("Set " + this.device.name + ", characteristic: " + characteristic + ", value: " + value + "."); - } - else { - if (err.code == "ECONNRESET") { - setTimeout(function() { - this.executeChange(characteristic, value, callback); - }.bind(this), 300); - } else { - this.log(err); - callback(new Error(err)); - } - } - }.bind(this)); - }, - // Read light state - // TODO: implement clever polling/update and caching - // maybe a better NodeJS hue API exists for this - getState: function(characteristic, callback) { - this.api.lightStatus(this.id, function(err, status) { - if (callback == null) { - return; - } - - if (err) { - if (err.code == "ECONNRESET") { - setTimeout(function() { - this.getState(characteristic, callback); - }.bind(this), 300); - } else { - this.log(err); - callback(new Error(err)); - } - } - - else { - var newValue = this.extractValue(characteristic, status); - if (newValue != undefined) { - callback(null, newValue); - } else { - // this.log("Device " + that.device.name + " does not support reading characteristic " + characteristic); - // callback(Error("Device " + that.device.name + " does not support reading characteristic " + characteristic) ); - } - - callback = null; - - //this.log("Get " + that.device.name + ", characteristic: " + characteristic + ", value: " + value + "."); - } - }.bind(this)); - }, - - // Respond to identify request - identify: function(callback) { - this.executeChange("identify", true, callback); - }, - - // Get Services - getServices: function() { - var that = this; - - // Use HomeKit types defined in HAP node JS - var lightbulbService = new Service.Lightbulb(this.name); - - // Basic light controls, common to Hue and Hue lux - lightbulbService - .getCharacteristic(Characteristic.On) - .on('get', function(callback) { that.getState("power", callback);}) - .on('set', function(value, callback) { that.executeChange("power", value, callback);}) - .value = this.extractValue("power", this.device); - - lightbulbService - .addCharacteristic(Characteristic.Brightness) - .on('get', function(callback) { that.getState("brightness", callback);}) - .on('set', function(value, callback) { that.executeChange("brightness", value, callback);}) - .value = this.extractValue("brightness", this.device); - - // Handle the Hue/Hue Lux divergence - if (this.device.state.hasOwnProperty('hue') && this.device.state.hasOwnProperty('sat')) { - lightbulbService - .addCharacteristic(Characteristic.Hue) - .on('get', function(callback) { that.getState("hue", callback);}) - .on('set', function(value, callback) { that.executeChange("hue", value, callback);}) - .value = this.extractValue("hue", this.device); - - lightbulbService - .addCharacteristic(Characteristic.Saturation) - .on('get', function(callback) { that.getState("saturation", callback);}) - .on('set', function(value, callback) { that.executeChange("saturation", value, callback);}) - .value = this.extractValue("saturation", this.device); - } - - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "Philips") - .setCharacteristic(Characteristic.Model, this.model) - .setCharacteristic(Characteristic.SerialNumber, this.device.uniqueid) - .addCharacteristic(Characteristic.FirmwareRevision, this.device.swversion); - - return [informationService, lightbulbService]; - } -}; - -module.exports.platform = PhilipsHuePlatform; diff --git a/platforms/SmartThings.js b/platforms/SmartThings.js deleted file mode 100644 index 470ee55..0000000 --- a/platforms/SmartThings.js +++ /dev/null @@ -1,242 +0,0 @@ -// SmartThings JSON API SmartApp required -// https://github.com/jnewland/SmartThings/blob/master/JSON.groovy -// -var types = require("hap-nodejs/accessories/types.js"); -var request = require("request"); - -function SmartThingsPlatform(log, config){ - this.log = log; - this.app_id = config["app_id"]; - this.access_token = config["access_token"]; -} - -SmartThingsPlatform.prototype = { - accessories: function(callback) { - this.log("Fetching SmartThings devices..."); - - var that = this; - var foundAccessories = []; - - request.get({ - url: "https://graph.api.smartthings.com/api/smartapps/installations/"+this.app_id+"/devices?access_token="+this.access_token, - json: true - }, function(err, response, json) { - if (!err && response.statusCode == 200) { - if (json['switches'] != undefined) { - json['switches'].map(function(s) { - accessory = new SmartThingsAccessory(that.log, s.name, s.commands); - foundAccessories.push(accessory); - }) - } - if (json['hues'] != undefined) { - json['hues'].map(function(s) { - accessory = new SmartThingsAccessory(that.log, s.name, s.commands); - foundAccessories.push(accessory); - }) - } - callback(foundAccessories); - } else { - that.log("There was a problem authenticating with SmartThings."); - } - }); - - } -} - -function SmartThingsAccessory(log, name, commands) { - // device info - this.name = name; - this.commands = commands; - this.log = log; -} - -SmartThingsAccessory.prototype = { - - command: function(c,value) { - this.log(this.name + " sending command " + c); - var url = this.commands[c]; - if (value != undefined) { - url = this.commands[c] + "&value="+value - } - - var that = this; - request.put({ - url: url - }, function(err, response) { - if (err) { - that.log("There was a problem sending command " + c + " to" + that.name); - that.log(url); - } else { - that.log(that.name + " sent command " + c); - } - }) - }, - - informationCharacteristics: function() { - return [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "SmartThings", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Rev-1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - } - ] - }, - - controlCharacteristics: function(that) { - cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }] - - if (this.commands['on'] != undefined) { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { - if (value == 0) { - that.command("off") - } else { - that.command("on") - } - }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - }) - } - - if (this.commands['on'] != undefined) { - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { that.command("setLevel", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }) - } - - if (this.commands['setHue'] != undefined) { - cTypes.push({ - cType: types.HUE_CTYPE, - onUpdate: function(value) { that.command("setHue", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Hue of Light", - designedMinValue: 0, - designedMaxValue: 360, - designedMinStep: 1, - unit: "arcdegrees" - }) - } - - if (this.commands['setSaturation'] != undefined) { - cTypes.push({ - cType: types.SATURATION_CTYPE, - onUpdate: function(value) { that.command("setSaturation", value); }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }) - } - - return cTypes - }, - - sType: function() { - if (this.commands['setLevel'] != undefined) { - return types.LIGHTBULB_STYPE - } else { - return types.SWITCH_STYPE - } - }, - - getServices: function() { - var that = this; - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), - }, - { - sType: this.sType(), - characteristics: this.controlCharacteristics(that) - }]; - this.log("Loaded services for " + this.name) - return services; - } -}; - -module.exports.accessory = SmartThingsAccessory; -module.exports.platform = SmartThingsPlatform; diff --git a/platforms/Sonos.js b/platforms/Sonos.js deleted file mode 100644 index 8e7581d..0000000 --- a/platforms/Sonos.js +++ /dev/null @@ -1,188 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var sonos = require('sonos'); - -function SonosPlatform(log, config){ - this.log = log; - this.config = config; - this.name = config["name"]; - this.playVolume = config["play_volume"]; - // timeout for device discovery - this.discoveryTimeout = (config.deviceDiscoveryTimeout || 10)*1000; // assume 10sec as a default -} - -SonosPlatform.prototype = { - accessories: function(callback) { - this.log("Fetching Sonos devices."); - var that = this; - - // track found devices so we don't add duplicates - var roomNamesFound = {}; - - // collector array for the devices from callbacks - var devicesFound = []; - // tell the sonos callbacks if timeout already occured - var timeout = false; - - // the timeout event will push the accessories back - setTimeout(function(){ - timeout=true; - callback(devicesFound); - }, this.discoveryTimeout); - - - sonos.search(function (device) { - that.log("Found device at " + device.host); - - device.deviceDescription(function (err, description) { - if (description["zoneType"] != '11' && description["zoneType"] != '8' && description["zoneType"] != '4') { // 8 is the Sonos SUB, 4 is the Sonos Bridge - var roomName = description["roomName"]; - - if (!roomNamesFound[roomName]) { - roomNamesFound[roomName] = true; - that.log("Found playable device - " + roomName); - if (timeout) { - that.log("Ignored: Discovered after timeout (Set deviceDiscoveryTimeout parameter in Sonos section of config.json)"); - } - // device is an instance of sonos.Sonos - var accessory = new SonosAccessory(that.log, that.config, device, description); - // add it to the collector array - devicesFound.push(accessory); - } - else { - that.log("Ignoring playable device with duplicate room name - " + roomName); - } - } - }); - }); - } -}; - -function SonosAccessory(log, config, device, description) { - this.log = log; - this.config = config; - this.device = device; - this.description = description; - - this.name = this.description["roomName"] + " " + this.config["name"]; - this.serviceName = this.description["roomName"] + " Speakers"; - this.playVolume = this.config["play_volume"]; -} - -SonosAccessory.prototype = { - - setPlaying: function(playing) { - - if (!this.device) { - this.log("No device found (yet?)"); - return; - } - - var that = this; - - if (playing) { - this.device.play(function(err, success) { - that.log("Playback attempt with success: " + success); - }); - - if (this.playVolume) { - this.device.setVolume(this.playVolume, function(err, success) { - if (!err) { - that.log("Set volume to " + that.playVolume); - } - else { - that.log("Problem setting volume: " + err); - } - }); - } - } - else { - this.device.stop(function(err, success) { - that.log("Stop attempt with success: " + success); - }); - } - }, - - getServices: function() { - var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Sonos", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Rev-1", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }] - },{ - sType: types.SWITCH_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.serviceName, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { that.setPlaying(value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the playback state of the sonos", - designedMaxLength: 1 - }] - }]; - } -}; - -module.exports.accessory = SonosAccessory; -module.exports.platform = SonosPlatform; diff --git a/platforms/Telldus.js b/platforms/Telldus.js deleted file mode 100644 index c192a31..0000000 --- a/platforms/Telldus.js +++ /dev/null @@ -1,265 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var telldus = require('telldus'); - -function TelldusPlatform(log, config) { - var that = this; - that.log = log; -} - -TelldusPlatform.prototype = { - - accessories: function(callback) { - var that = this; - - that.log("Fetching devices..."); - - var devices = telldus.getDevicesSync(); - - that.log("Found " + devices.length + " devices..."); - - var foundAccessories = []; - - // Clean non device - for (var i = 0; i < devices.length; i++) { - if (devices[i].type != 'DEVICE') { - devices.splice(i, 1); - } - } - - for (var i = 0; i < devices.length; i++) { - if (devices[i].type === 'DEVICE') { - TelldusAccessory.create(that.log, devices[i], function(err, accessory) { - if (!!err) that.log("Couldn't load device info"); - foundAccessories.push(accessory); - if (foundAccessories.length >= devices.length) { - callback(foundAccessories); - } - }); - } - } - } -}; - -var TelldusAccessory = function TelldusAccessory(log, device) { - - this.log = log; - - var m = device.model.split(':'); - - this.dimTimeout = false; - - // Set accessory info - this.device = device; - this.id = device.id; - this.name = device.name; - this.manufacturer = "Telldus"; // NOTE: Change this later - this.model = device.model; - this.status = device.status; - switch (device.status.name) { - case 'OFF': - this.state = 0; - this.stateValue = 0; - break; - case 'ON': - this.state = 2; - this.stateValue = 1; - break; - case 'DIM': - this.state = 16; - this.stateValue = device.status.level; - break; - } -}; - -TelldusAccessory.create = function (log, device, callback) { - - callback(null, new TelldusAccessory(log, device)); - -}; - -TelldusAccessory.prototype = { - - dimmerValue: function() { - - if (this.state === 1) { - return 100; - } - - if (this.state === 16 && this.stateValue != "unde") { - return parseInt(this.stateValue * 100 / 255); - } - - return 0; - }, - - informationCharacteristics: function() { - var that = this; - - informationCharacteristics = [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.manufacturer, - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.model, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: function () { - telldus.turnOff(that.id, function(err){ - if (!!err) that.log("Error: " + err.message); - telldus.turnOn(that.id, function(err){ - if (!!err) that.log("Error: " + err.message); - telldus.turnOff(that.id, function(err){ - if (!!err) that.log("Error: " + err.message); - telldus.turnOn(that.id, function(err){ - if (!!err) that.log("Error: " + err.message); - }); - }); - }); - }); - }, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - } - ]; - return informationCharacteristics; - }, - - controlCharacteristics: function() { - var that = this; - - cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }] - - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { - if (value) { - telldus.turnOn(that.id, function(err){ - if (!!err) { - that.log("Error: " + err.message) - } else { - that.log(that.name + " - Updated power state: ON"); - } - }); - } else { - telldus.turnOff(that.id, function(err){ - if (!!err) { - that.log("Error: " + err.message) - } else { - that.log(that.name + " - Updated power state: OFF"); - } - }); - } - }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: (that.state != 2 && (that.state === 16 && that.stateValue != 0)) ? 1 : 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - }) - - if (that.model === "selflearning-dimmer") { - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function (value) { - if (that.dimTimeout) { - clearTimeout(that.dimTimeout); - } - - that.dimTimeout = setTimeout(function(){ - telldus.dim(that.id, (255 * (value / 100)), function(err, result){ - if (!!err) { - that.log("Error: " + err.message); - } else { - that.log(that.name + " - Updated brightness: " + value); - } - }); - that.dimTimeout = false; - }, 250); - }, - perms: ["pw", "pr", "ev"], - format: "int", - initialValue: that.dimmerValue(), - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }) - } - - return cTypes - }, - - getServices: function() { - - var services = [ - { - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics() - }, - { - sType: types.LIGHTBULB_STYPE, - characteristics: this.controlCharacteristics() - } - ]; - - return services; - } -}; - -module.exports.platform = TelldusPlatform; -module.exports.accessory = TelldusAccessory; diff --git a/platforms/TelldusLive.js b/platforms/TelldusLive.js deleted file mode 100644 index fd479d9..0000000 --- a/platforms/TelldusLive.js +++ /dev/null @@ -1,265 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var TellduAPI = require("telldus-live"); - -function TelldusLivePlatform(log, config) { - var that = this; - that.log = log; - - that.isLoggedIn = false; - - // Login to Telldus Live! - that.cloud = new TellduAPI.TelldusAPI({publicKey: config["public_key"], privateKey: config["private_key"]}) - .login(config["token"], config["token_secret"], function(err, user) { - if (!!err) that.log("Login error: " + err.message); - that.log("User logged in: " + user.firstname + " " + user.lastname + ", " + user.email); - that.isLoggedIn = true; - } - ); -} - -TelldusLivePlatform.prototype = { - - accessories: function(callback) { - var that = this; - - that.log("Fetching devices..."); - - that.cloud.getDevices(function(err, devices) { - - if (!!err) return that.log('getDevices: ' + err.message); - - var foundAccessories = []; - - // Clean non device - for (var i = 0; i < devices.length; i++) { - if (devices[i].type != 'device') { - devices.splice(i, 1); - } - } - - for (var i = 0; i < devices.length; i++) { - if (devices[i].type === 'device') { - TelldusLiveAccessory.create(that.log, devices[i], that.cloud, function(err, accessory) { - if (!!err) that.log("Couldn't load device info"); - foundAccessories.push(accessory); - if (foundAccessories.length >= devices.length) { - callback(foundAccessories); - } - }); - } - } - - }); - } -}; - -var TelldusLiveAccessory = function TelldusLiveAccessory(log, cloud, device) { - - this.log = log; - this.cloud = cloud; - - var m = device.model ? device.model.split(':') : ['unknown', 'unknown'] ; - - // Set accessory info - this.device = device; - this.id = device.id; - this.name = device.name; - this.manufacturer = m[1]; - this.model = m[0]; - this.state = device.state; - this.stateValue = device.stateValue; - this.status = device.status; -}; - -TelldusLiveAccessory.create = function (log, device, cloud, callback) { - - cloud.getDeviceInfo(device, function(err, device) { - - if (!!err) that.log("Couldn't load device info"); - - callback(err, new TelldusLiveAccessory(log, cloud, device)); - }); -}; - -TelldusLiveAccessory.prototype = { - - dimmerValue: function() { - - if (this.state === 1) { - return 100; - } - - if (this.state === 16 && this.stateValue != "unde") { - return parseInt(this.stateValue * 100 / 255); - } - - return 0; - }, - - informationCharacteristics: function() { - var that = this; - - informationCharacteristics = [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.manufacturer, - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.model, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: function () { - that.cloud.onOffDevice(that.device, true, function(err, result) { - if (!!err) that.log("Error: " + err.message); - that.cloud.onOffDevice(that.device, false, function(err, result) { - if (!!err) that.log("Error: " + err.message); - that.cloud.onOffDevice(that.device, true, function(err, result) { - if (!!err) that.log("Error: " + err.message); - that.cloud.onOffDevice(that.device, false, function(err, result) { - if (!!err) that.log("Error: " + err.message); - that.cloud.onOffDevice(that.device, true, function(err, result) { - if (!!err) that.log("Error: " + err.message); - }) - }) - }) - }) - }) - }, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - } - ]; - return informationCharacteristics; - }, - - controlCharacteristics: function() { - var that = this; - - cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }] - - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { - if (value == 1) { - that.cloud.onOffDevice(that.device, value, function(err, result) { - if (!!err) { - that.log("Error: " + err.message) - } else { - that.log(that.name + " - Updated power state: " + (value === true ? 'ON' : 'OFF')); - } - }); - } else { - that.cloud.onOffDevice(that.device, value, function(err, result) { - if (!!err) { - that.log("Error: " + err.message) - } else { - that.log(that.name + " - Updated power state: " + (value === true ? 'ON' : 'OFF')); - } - }); - } - }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: (that.state != 2 && (that.state === 16 && that.stateValue != "0")) ? 1 : 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - }) - - if (that.model === "selflearning-dimmer") { - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function (value) { - that.cloud.dimDevice(that.device, (255 * (value / 100)), function (err, result) { - if (!!err) { - that.log("Error: " + err.message); - } else { - that.log(that.name + " - Updated brightness: " + value); - } - }); - }, - perms: ["pw", "pr", "ev"], - format: "int", - initialValue: that.dimmerValue(), - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }) - } - - return cTypes - }, - - getServices: function() { - - var services = [ - { - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics() - }, - { - sType: types.LIGHTBULB_STYPE, - characteristics: this.controlCharacteristics() - } - ]; - - return services; - } -}; - -module.exports.platform = TelldusLivePlatform; -module.exports.accessory = TelldusLiveAccessory; \ No newline at end of file diff --git a/platforms/Wink.js b/platforms/Wink.js deleted file mode 100644 index 12ff7a8..0000000 --- a/platforms/Wink.js +++ /dev/null @@ -1,350 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var wink = require('wink-js'); -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; - -process.env.WINK_NO_CACHE = true; - -var model = { - light_bulbs: require('wink-js/lib/model/light'), - refreshUntil: function(that, maxTimes, predicate, callback, interval, incrementInterval) { - if (!interval) { - interval = 500; - } - if (!incrementInterval) { - incrementInterval = 500; - } - setTimeout(function() { - that.reloadData(function() { - if (predicate == undefined || predicate(that.device) == true) { - if (callback) callback(true); - } else if (maxTimes > 0) { - maxTimes = maxTimes - 1; - interval += incrementInterval; - model.refreshUntil(that, maxTimes, predicate, callback, interval, incrementInterval); - } else { - if (callback) callback(false); - } - }); - }, interval); - } -}; - -function WinkPlatform(log, config){ - - // auth info - this.client_id = config["client_id"]; - this.client_secret = config["client_secret"]; - - this.username = config["username"]; - this.password = config["password"]; - - this.log = log; - this.deviceLookup = {}; -} - -WinkPlatform.prototype = { - reloadData: function(callback) { - this.log("Refreshing Wink Data"); - var that = this; - wink.user().devices(function(devices) { - for (var i=0; i 0) { - return res.errors[0]; - } else if (res.data) { - this.device = res.data; - this.loadData(); - } -}; - -WinkAccessory.prototype.reloadData = function(callback){ - var that = this; - this.control.get(function(res) { - callback(that.handleResponse(res)); - }); -}; - - -/* - * Light Accessory - */ - -function WinkLightAccessory(log, device) { - // construct base - WinkAccessory.call(this, log, device, 'light_bulb', device.light_bulb_id); - - // accessor - var that = this; - - that.device = device; - that.deviceControl = model.light_bulbs(device, wink); - - this - .addService(Service.Lightbulb) - .getCharacteristic(Characteristic.On) - .on('get', function(callback) { - var powerState = that.device.desired_state.powered; - that.log("power state for " + that.name + " is: " + powerState); - callback(null, powerState != undefined ? powerState : false); - }) - .on('set', function(powerOn, callback) { - if (powerOn) { - that.log("Setting power state on the '"+that.name+"' to on"); - that.deviceControl.power.on(function(response) { - if (response === undefined) { - that.log("Error setting power state on the '"+that.name+"'"); - callback(Error("Error setting power state on the '"+that.name+"'")); - } else { - that.log("Successfully set power state on the '"+that.name+"' to on"); - callback(null, powerOn); - } - }); - }else{ - that.log("Setting power state on the '"+that.name+"' to off"); - that.deviceControl.power.off(function(response) { - if (response === undefined) { - that.log("Error setting power state on the '"+that.name+"'"); - callback(Error("Error setting power state on the '"+that.name+"'")); - } else { - that.log("Successfully set power state on the '"+that.name+"' to off"); - callback(null, powerOn); - } - }); - } - }); - - this - .getService(Service.Lightbulb) - .getCharacteristic(Characteristic.Brightness) - .on('get', function(callback) { - var level = that.device.desired_state.brightness * 100; - that.log("brightness level for " + that.name + " is: " + level); - callback(null, level); - }) - .on('set', function(level, callback) { - that.log("Setting brightness on the '"+this.name+"' to " + level); - that.deviceControl.brightness(level, function(response) { - if (response === undefined) { - that.log("Error setting brightness on the '"+that.name+"'"); - callback(Error("Error setting brightness on the '"+that.name+"'")); - } else { - that.log("Successfully set brightness on the '"+that.name+"' to " + level); - callback(null, level); - } - }); - }); - - WinkLightAccessory.prototype.loadData.call(this); -} - -inherits(WinkLightAccessory, WinkAccessory); -WinkLightAccessory.prototype.parent = WinkAccessory.prototype; - -WinkLightAccessory.prototype.loadData = function() { - this.parent.loadData.call(this); - this.getService(Service.Lightbulb) - .getCharacteristic(Characteristic.On) - .getValue(); - this.getService(Service.Lightbulb) - .getCharacteristic(Characteristic.Brightness) - .getValue(); -}; - - -/* - * Lock Accessory - */ - -function WinkLockAccessory(log, device) { - // construct base - WinkAccessory.call(this, log, device, 'lock', device.lock_id); - - // accessor - var that = this; - - this - .addService(Service.LockMechanism) - .getCharacteristic(Characteristic.LockTargetState) - .on('get', function(callback) { - callback(null, that.isLockTarget()); - }) - .on('set', function(value, callback) { - var locked = that.fromLockState(value); - - if (locked == undefined) { - callback(Error("Unsupported")); - return; - } - - that.log("Changing target lock state of " + that.name + " to " + (locked ? "locked" : "unlocked")); - - var update = function(retry) { - that.control.update({ "desired_state": { "locked": locked } }, function(res) { - var err = that.handleResponse(res); - if (!err) { - model.refreshUntil(that, 5, - function() { return that.isLocked() == that.isLockTarget(); }, - function(completed) { - if (completed) { - that.log("Successfully changed lock status to " + (that.isLocked() ? "locked" : "unlocked")); - } else if (retry) { - that.log("Unable to determine if update was successful. Retrying update."); - retry(); - } else { - that.log("Unable to determine if update was successful."); - } - }); - } - if (callback) - { - callback(err); - callback = null; - } - }); - }; - update(update); - }); - - WinkLockAccessory.prototype.loadData.call(this); -} - -inherits(WinkLockAccessory, WinkAccessory); -WinkLockAccessory.prototype.parent = WinkAccessory.prototype; - -WinkLockAccessory.prototype.loadData = function() { - this.parent.loadData.call(this); - this.getService(Service.LockMechanism) - .setCharacteristic(Characteristic.LockCurrentState, this.isLocked()); - this.getService(Service.LockMechanism) - .getCharacteristic(Characteristic.LockTargetState) - .getValue(); -}; - -WinkLockAccessory.prototype.toLockState= function(isLocked) { - return isLocked ? Characteristic.LockCurrentState.SECURED : Characteristic.LockCurrentState.UNSECURED; -}; - -WinkLockAccessory.prototype.fromLockState= function(lockState) { - switch (lockState) { - case Characteristic.LockCurrentState.SECURED: - return true; - case Characteristic.LockCurrentState.UNSECURED: - return false; - default: - return undefined; - } -}; - -WinkLockAccessory.prototype.isLockTarget= function() { - return this.toLockState(this.device.desired_state.locked); -}; - -WinkLockAccessory.prototype.isLocked= function() { - return this.toLockState(this.device.last_reading.locked); -}; - -module.exports.accessory = WinkAccessory; -module.exports.lockAccessory = WinkLockAccessory; -module.exports.lightAccessory = WinkLightAccessory; -module.exports.platform = WinkPlatform; diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js deleted file mode 100644 index 8d79058..0000000 --- a/platforms/YamahaAVR.js +++ /dev/null @@ -1,238 +0,0 @@ -var types = require("hap-nodejs/accessories/types.js"); -var inherits = require('util').inherits; -var debug = require('debug')('YamahaAVR'); -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var Yamaha = require('yamaha-nodejs'); -var Q = require('q'); -var mdns = require('mdns'); -//workaround for raspberry pi -var sequence = [ - mdns.rst.DNSServiceResolve(), - 'DNSServiceGetAddrInfo' in mdns.dns_sd ? mdns.rst.DNSServiceGetAddrInfo() : mdns.rst.getaddrinfo({families:[4]}), - mdns.rst.makeAddressesUnique() -]; - -function YamahaAVRPlatform(log, config){ - this.log = log; - this.config = config; - this.playVolume = config["play_volume"]; - this.minVolume = config["min_volume"] || -50.0; - this.maxVolume = config["max_volume"] || -20.0; - this.gapVolume = this.maxVolume - this.minVolume; - this.setMainInputTo = config["setMainInputTo"]; - this.expectedDevices = config["expected_devices"] || 100; - this.discoveryTimeout = config["discovery_timeout"] || 30; - this.manualAddresses = config["manual_addresses"] || {}; - this.browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence}); -} - -// Custom Characteristics and service... - -YamahaAVRPlatform.AudioVolume = function() { - Characteristic.call(this, 'Audio Volume', '00001001-0000-1000-8000-135D67EC4377'); - this.setProps({ - format: Characteristic.Formats.UINT8, - unit: Characteristic.Units.PERCENTAGE, - maxValue: 100, - minValue: 0, - minStep: 1, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; -inherits(YamahaAVRPlatform.AudioVolume, Characteristic); - -YamahaAVRPlatform.Muting = function() { - Characteristic.call(this, 'Muting', '00001002-0000-1000-8000-135D67EC4377'); - this.setProps({ - format: Characteristic.Formats.UINT8, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); -}; -inherits(YamahaAVRPlatform.Muting, Characteristic); - -YamahaAVRPlatform.AudioDeviceService = function(displayName, subtype) { - Service.call(this, displayName, '00000001-0000-1000-8000-135D67EC4377', subtype); - - // Required Characteristics - this.addCharacteristic(YamahaAVRPlatform.AudioVolume); - - // Optional Characteristics - this.addOptionalCharacteristic(YamahaAVRPlatform.Muting); -}; -inherits(YamahaAVRPlatform.AudioDeviceService, Service); - - -YamahaAVRPlatform.prototype = { - accessories: function(callback) { - this.log("Getting Yamaha AVR devices."); - var that = this; - - var browser = this.browser; - browser.stop(); - browser.removeAllListeners('serviceUp'); // cleanup listeners - var accessories = []; - var timer, timeElapsed = 0, checkCyclePeriod = 5000; - - // Hmm... seems we need to prevent double-listing via manual and Bonjour... - var sysIds = {}; - - var setupFromService = function(service){ - var name = service.name; - //console.log('Found HTTP service "' + name + '"'); - // We can't tell just from mdns if this is an AVR... - if (service.port != 80) return; // yamaha-nodejs assumes this, so finding one on another port wouldn't do any good anyway. - var yamaha = new Yamaha(service.host); - yamaha.getSystemConfig().then( - function(sysConfig){ - var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0]; - var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]; - if(sysIds[sysId]){ - this.log("WARN: Got multiple systems with ID " + sysId + "! Omitting duplicate!"); - return; - } - sysIds[sysId] = true; - this.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\""); - var accessory = new YamahaAVRAccessory(this.log, this.config, name, yamaha, sysConfig); - accessories.push(accessory); - if(accessories.length >= this.expectedDevices) - timeoutFunction(); // We're done, call the timeout function now. - }.bind(this) - ); - }.bind(this); - - // process manually specified devices... - for(var key in this.manualAddresses){ - if(!this.manualAddresses.hasOwnProperty(key)) continue; - setupFromService({ - name: key, - host: this.manualAddresses[key], - port: 80 - }); - } - - browser.on('serviceUp', setupFromService); - browser.start(); - - // The callback can only be called once...so we'll have to find as many as we can - // in a fixed time and then call them in. - var timeoutFunction = function(){ - if(accessories.length >= that.expectedDevices){ - clearTimeout(timer); - } else { - timeElapsed += checkCyclePeriod; - if(timeElapsed > that.discoveryTimeout * 1000){ - that.log("Waited " + that.discoveryTimeout + " seconds, stopping discovery."); - } else { - timer = setTimeout(timeoutFunction, checkCyclePeriod); - return; - } - } - browser.stop(); - browser.removeAllListeners('serviceUp'); - that.log("Discovery finished, found " + accessories.length + " Yamaha AVR devices."); - callback(accessories); - }; - timer = setTimeout(timeoutFunction, checkCyclePeriod); - } -}; - -function YamahaAVRAccessory(log, config, name, yamaha, sysConfig) { - this.log = log; - this.config = config; - this.yamaha = yamaha; - this.sysConfig = sysConfig; - - this.nameSuffix = config["name_suffix"] || " Speakers"; - this.name = name; - this.serviceName = name + this.nameSuffix; - this.setMainInputTo = config["setMainInputTo"]; - this.playVolume = this.config["play_volume"]; - this.minVolume = config["min_volume"] || -50.0; - this.maxVolume = config["max_volume"] || -20.0; - this.gapVolume = this.maxVolume - this.minVolume; -} - -YamahaAVRAccessory.prototype = { - - setPlaying: function(playing) { - var that = this; - var yamaha = this.yamaha; - - if (playing) { - - return yamaha.powerOn().then(function(){ - if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10); - else return Q(); - }).then(function(){ - if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo); - else return Q(); - }).then(function(){ - if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver( - 'Play' - ); - 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; diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js deleted file mode 100644 index 2d8adac..0000000 --- a/platforms/ZWayServer.js +++ /dev/null @@ -1,1012 +0,0 @@ -var debug = require('debug')('ZWayServer'); -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var types = require("hap-nodejs/accessories/types.js"); -var request = require("request"); -var tough = require('tough-cookie'); -var Q = require("q"); - -function ZWayServerPlatform(log, config){ - this.log = log; - this.url = config["url"]; - this.login = config["login"]; - this.password = config["password"]; - this.opt_in = config["opt_in"]; - this.name_overrides = config["name_overrides"]; - this.batteryLow = config["battery_low_level"] || 15; - this.pollInterval = config["poll_interval"] || 2; - this.splitServices= config["split_services"] || false; - this.lastUpdate = 0; - this.cxVDevMap = {}; - this.vDevStore = {}; - this.sessionId = ""; - this.jar = request.jar(new tough.CookieJar()); -} - -ZWayServerPlatform.getVDevTypeKey = function(vdev){ - return vdev.deviceType + (vdev.metrics && vdev.metrics.probeTitle ? "." + vdev.metrics.probeTitle : "") -} - -ZWayServerPlatform.prototype = { - - zwayRequest: function(opts){ - var that = this; - var deferred = Q.defer(); - - opts.jar = true;//this.jar; - opts.json = true; - opts.headers = { - "Cookie": "ZWAYSession=" + this.sessionId - }; - - request(opts, function(error, response, body){ - if(response.statusCode == 401){ - debug("Authenticating..."); - request({ - method: "POST", - url: that.url + 'ZAutomation/api/v1/login', - body: { //JSON.stringify({ - "form": true, - "login": that.login, - "password": that.password, - "keepme": false, - "default_ui": 1 - }, - headers: { - "Accept": "application/json", - "Content-Type": "application/json" - }, - json: true, - jar: true//that.jar - }, function(error, response, body){ - if(response.statusCode == 200){ - that.sessionId = body.data.sid; - opts.headers["Cookie"] = "ZWAYSession=" + that.sessionId; - debug("Authenticated. Resubmitting original request..."); - request(opts, function(error, response, body){ - if(response.statusCode == 200){ - deferred.resolve(body); - } else { - deferred.reject(response); - } - }); - } else { - deferred.reject(response); - } - }); - } else if(response.statusCode == 200) { - deferred.resolve(body); - } else { - deferred.reject(response); - } - }); - return deferred.promise; - } - , - getTagValue: function(vdev, tagStem){ - if(!(vdev.tags && vdev.tags.length > 0)) return false; - var tagStem = "Homebridge." + tagStem; - if(vdev.tags.indexOf(tagStem) >= 0) return true; - var tags = vdev.tags, l = tags.length, tag; - for(var i = 0; i < l; i++){ - tag = tags[i]; - if(tag.indexOf(tagStem + ":") === 0){ - return tag.substr(tagStem.length + 1); - } - } - return false; - } - , - accessories: function(callback) { - debug("Fetching Z-Way devices..."); - - //TODO: Unify this with getVDevServices, so there's only one place with mapping between service and vDev type. - //Note: Order matters! - var primaryDeviceClasses = [ - "thermostat", - "switchMultilevel", - "switchBinary", - "sensorBinary.Door/Window", - "sensorMultilevel.Temperature" - ]; - - var that = this; - var foundAccessories = []; - - this.zwayRequest({ - method: "GET", - url: this.url + 'ZAutomation/api/v1/devices' - }).then(function(result){ - this.lastUpdate = result.data.updateTime; - - var devices = result.data.devices; - var groupedDevices = {}; - for(var i = 0; i < devices.length; i++){ - var vdev = devices[i]; - if(this.getTagValue("Skip")) { debug("Tag says skip!"); continue; } - if(this.opt_in && !this.getTagValue(vdev, "Include")) continue; - - var gdid = this.getTagValue(vdev, "Accessory.Id"); - if(!gdid){ - gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); - } - - var gd = groupedDevices[gdid] || (groupedDevices[gdid] = { devices: [], types: {}, extras: {}, primary: undefined, cxmap: {} }); - - gd.devices.push(vdev); - var vdevIndex = gd.devices.length - 1; - - var tk = ZWayServerPlatform.getVDevTypeKey(vdev); - - // If this is explicitly set as primary, set it now... - if(this.getTagValue(vdev, "IsPrimary")){ - // everybody out of the way! Can't be in "extras" if you're the primary... - if(gd.types[tk] !== undefined){ - gd.extras[tk] = gd.extras[tk] || []; - gd.extras[tk].push(gd.types[tk]); - delete gd.types[tk]; // clear the way for this one to be set here below... - } - gd.primary = vdevIndex; - //gd.types[tk] = gd.primary; - } - - if(gd.types[tk] === undefined){ - gd.types[tk] = vdevIndex; - } else { - gd.extras[tk] = gd.extras[tk] || []; - gd.extras[tk].push(vdevIndex); - } - if(tk !== vdev.deviceType) gd.types[vdev.deviceType] = vdevIndex; // also include the deviceType only as a possibility - - // Create a map entry when Homebridge.Characteristic.Type is set... - var ctype = this.getTagValue(vdev, "Characteristic.Type"); - if(ctype && Characteristic[ctype]){ - var cx = new Characteristic[ctype](); - gd.cxmap[cx.UUID] = vdevIndex; - } - } - - for(var gdid in groupedDevices) { - if(!groupedDevices.hasOwnProperty(gdid)) continue; - - // Debug/log... - debug('Got grouped device ' + gdid + ' consiting of devices:'); - var gd = groupedDevices[gdid]; - for(var j = 0; j < gd.devices.length; j++){ - debug(gd.devices[j].id + " - " + gd.devices[j].deviceType + (gd.devices[j].metrics && gd.devices[j].metrics.probeTitle ? "." + gd.devices[j].metrics.probeTitle : "")); - } - - var accessory = null; - if(gd.primary !== undefined){ - var pd = gd.devices[gd.primary]; - var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id; - accessory = new ZWayServerAccessory(name, gd, that); - } - else for(var ti = 0; ti < primaryDeviceClasses.length; ti++){ - if(gd.types[primaryDeviceClasses[ti]] !== undefined){ - gd.primary = gd.types[primaryDeviceClasses[ti]]; - var pd = gd.devices[gd.primary]; - var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id; - //debug("Using primary device with type " + primaryDeviceClasses[ti] + ", " + name + " (" + pd.id + ") as primary."); - accessory = new ZWayServerAccessory(name, gd, that); - break; - } - } - - if(!accessory) - debug("WARN: Didn't find suitable device class!"); - else - foundAccessories.push(accessory); - - } - callback(foundAccessories); - - // Start the polling process... - this.pollingTimer = setTimeout(this.pollUpdate.bind(this), this.pollInterval*1000); - - }.bind(this)); - - } - , - - pollUpdate: function(){ - //debug("Polling for updates since " + this.lastUpdate + "..."); - return this.zwayRequest({ - method: "GET", - url: this.url + 'ZAutomation/api/v1/devices', - qs: {since: this.lastUpdate} - }).then(function(result){ - this.lastUpdate = result.data.updateTime; - if(result.data && result.data.devices && result.data.devices.length){ - var updates = result.data.devices; - debug("Got " + updates.length + " updates."); - for(var i = 0; i < updates.length; i++){ - var upd = updates[i]; - if(this.cxVDevMap[upd.id]){ - var vdev = this.vDevStore[upd.id]; - vdev.metrics.level = upd.metrics.level; - if(upd.metrics.color){ - vdev.metrics.r = upd.metrics.r; - vdev.metrics.g = upd.metrics.g; - vdev.metrics.b = upd.metrics.b; - } - vdev.updateTime = upd.updateTime; - var cxs = this.cxVDevMap[upd.id]; - for(var j = 0; j < cxs.length; j++){ - var cx = cxs[j]; - if(typeof cx.zway_getValueFromVDev !== "function") continue; - var oldValue = cx.value; - var newValue = cx.zway_getValueFromVDev(vdev); - if(oldValue !== newValue){ - cx.value = newValue; - cx.emit('change', { oldValue:oldValue, newValue:cx.value, context:null }); - debug("Updated characteristic " + cx.displayName + " on " + vdev.metrics.title); - } - } - } - } - } - - // setup next poll... - this.pollingTimer = setTimeout(this.pollUpdate.bind(this), this.pollInterval*1000); - }.bind(this)); - } - -} - -function ZWayServerAccessory(name, devDesc, platform) { - // device info - this.name = name; - this.devDesc = devDesc; - this.platform = platform; - this.log = platform.log; -} - - -ZWayServerAccessory.prototype = { - - getVDev: function(vdev){ - return this.platform.zwayRequest({ - method: "GET", - url: this.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id - })//.then(function()); - } - , - command: function(vdev, command, value) { - return this.platform.zwayRequest({ - method: "GET", - url: this.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id + '/command/' + command, - qs: (value === undefined ? undefined : value) - }); - }, - - rgb2hsv: function(obj) { - // RGB: 0-255; H: 0-360, S,V: 0-100 - var r = obj.r/255, g = obj.g/255, b = obj.b/255; - var max, min, d, h, s, v; - - min = Math.min(r, Math.min(g, b)); - max = Math.max(r, Math.max(g, b)); - - if (min === max) { - // shade of gray - return {h: 0, s: 0, v: r * 100}; - } - - var d = (r === min) ? g - b : ((b === min) ? r - g : b - r); - h = (r === min) ? 3 : ((b === min) ? 1 : 5); - h = 60 * (h - d/(max - min)); - s = (max - min) / max; - v = max; - return {"h": h, "s": s * 100, "v": v * 100}; - } - , - hsv2rgb: function(obj) { - // H: 0-360; S,V: 0-100; RGB: 0-255 - var r, g, b; - var sfrac = obj.s / 100; - var vfrac = obj.v / 100; - - if(sfrac === 0){ - var vbyte = Math.round(vfrac*255); - return { r: vbyte, g: vbyte, b: vbyte }; - } - - var hdb60 = (obj.h % 360) / 60; - var sector = Math.floor(hdb60); - var fpart = hdb60 - sector; - var c = vfrac * (1 - sfrac); - var x1 = vfrac * (1 - sfrac * fpart); - var x2 = vfrac * (1 - sfrac * (1 - fpart)); - switch(sector){ - case 0: - r = vfrac; g = x2; b = c; break; - case 1: - r = x1; g = vfrac; b = c; break; - case 2: - r = c; g = vfrac; b = x2; break; - case 3: - r = c; g = x1; b = vfrac; break; - case 4: - r = x2; g = c; b = vfrac; break; - case 5: - default: - r = vfrac; g = c; b = x1; break; - } - - return { "r": Math.round(255 * r), "g": Math.round(255 * g), "b": Math.round(255 * b) }; - } - , - getVDevServices: function(vdev){ - var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev); - var services = [], service; - switch (typeKey) { - case "thermostat": - services.push(new Service.Thermostat(vdev.metrics.title, vdev.id)); - break; - case "switchBinary": - services.push(new Service.Switch(vdev.metrics.title, vdev.id)); - break; - case "switchRGBW": - case "switchMultilevel": - if(this.platform.getTagValue(vdev, "Service.Type") === "Switch"){ - services.push(new Service.Switch(vdev.metrics.title, vdev.id)); - } else { - services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); - } - break; - case "sensorBinary.Door/Window": - var stype = this.platform.getTagValue(vdev, "Service.Type"); - if(stype === "ContactSensor"){ - services.push(new Service.ContactSensor(vdev.metrics.title, vdev.id)); - } else if(stype === "GarageDoorOpener"){ - services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); - } else if(stype === "Window"){ - services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); - } else { - services.push(new Service.Door(vdev.metrics.title, vdev.id)); - } - break; - case "sensorMultilevel.Temperature": - services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id)); - break; - case "battery.Battery": - services.push(new Service.BatteryService(vdev.metrics.title, vdev.id)); - break; - case "sensorMultilevel.Luminiscence": - services.push(new Service.LightSensor(vdev.metrics.title, vdev.id)); - break; - case "sensorBinary": - var stype = this.platform.getTagValue(vdev, "Service.Type"); - if(stype === "MotionSensor"){ - services.push(new Service.MotionSensor(vdev.metrics.title, vdev.id)); - } else { - services.push(new Service.ContactSensor(vdev.metrics.title, vdev.id)); - } - } - - var validServices =[]; - for(var i = 0; i < services.length; i++){ - if(this.configureService(services[i], vdev)) - validServices.push(services[i]); - } - - return validServices; - } - , - uuidToTypeKeyMap: null - , - extraCharacteristicsMap: { - "battery.Battery": [Characteristic.BatteryLevel, Characteristic.StatusLowBattery], - "sensorMultilevel.Temperature": [Characteristic.CurrentTemperature, Characteristic.TemperatureDisplayUnits], - "sensorMultilevel.Luminiscence": [Characteristic.CurrentAmbientLightLevel] - } - , - getVDevForCharacteristic: function(cx, vdevPreferred){ - - // If we know which vdev should be used for this Characteristic, we're done! - if(this.devDesc.cxmap[cx.UUID] !== undefined){ - return this.devDesc.devices[this.devDesc.cxmap[cx.UUID]]; - } - - var map = this.uuidToTypeKeyMap; - if(!map){ - this.uuidToTypeKeyMap = map = {}; - map[(new Characteristic.On).UUID] = ["switchBinary","switchMultilevel"]; - map[(new Characteristic.Brightness).UUID] = ["switchMultilevel"]; - map[(new Characteristic.Hue).UUID] = ["switchRGBW"]; - map[(new Characteristic.Saturation).UUID] = ["switchRGBW"]; - map[(new Characteristic.CurrentTemperature).UUID] = ["sensorMultilevel.Temperature","thermostat"]; - map[(new Characteristic.TargetTemperature).UUID] = ["thermostat"]; - map[(new Characteristic.TemperatureDisplayUnits).UUID] = ["sensorMultilevel.Temperature","thermostat"]; //TODO: Always a fixed result - map[(new Characteristic.CurrentHeatingCoolingState).UUID] = ["thermostat"]; //TODO: Always a fixed result - map[(new Characteristic.TargetHeatingCoolingState).UUID] = ["thermostat"]; //TODO: Always a fixed result - map[(new Characteristic.CurrentDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; - map[(new Characteristic.TargetDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result - map[(new Characteristic.ContactSensorState).UUID] = ["sensorBinary"]; - map[(new Characteristic.CurrentPosition).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; - map[(new Characteristic.TargetPosition).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; - map[(new Characteristic.PositionState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; - map[(new Characteristic.ObstructionDetected).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result - map[(new Characteristic.BatteryLevel).UUID] = ["battery.Battery"]; - map[(new Characteristic.StatusLowBattery).UUID] = ["battery.Battery"]; - map[(new Characteristic.ChargingState).UUID] = ["battery.Battery"]; //TODO: Always a fixed result - map[(new Characteristic.CurrentAmbientLightLevel).UUID] = ["sensorMultilevel.Luminiscence"]; - } - - if(cx instanceof Characteristic.Name) return vdevPreferred; - - // Special case!: If cx is a CurrentTemperature, ignore the preferred device...we want the sensor if available! - if(cx instanceof Characteristic.CurrentTemperature) vdevPreferred = null; - // - - var typekeys = map[cx.UUID]; - if(typekeys === undefined) return null; - - if(vdevPreferred && typekeys.indexOf(ZWayServerPlatform.getVDevTypeKey(vdevPreferred)) >= 0){ - return vdevPreferred; - } - - var candidates = this.devDesc.devices; - for(var i = 0; i < typekeys.length; i++){ - for(var j = 0; j < candidates.length; j++){ - if(ZWayServerPlatform.getVDevTypeKey(candidates[j]) === typekeys[i]) return candidates[j]; - } - } - - return null; - } - , - configureCharacteristic: function(cx, vdev, service){ - var accessory = this; - - // Add this combination to the maps... - if(!this.platform.cxVDevMap[vdev.id]) this.platform.cxVDevMap[vdev.id] = []; - this.platform.cxVDevMap[vdev.id].push(cx); - if(!this.platform.vDevStore[vdev.id]) this.platform.vDevStore[vdev.id] = vdev; - - if(cx instanceof Characteristic.Name){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.title; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, accessory.name); - }); - return cx; - } - - // We don't want to override "Name"'s name...so we just move this below that block. - var descOverride = this.platform.getTagValue(vdev, "Characteristic.Description"); - if(descOverride){ - cx.displayName = descOverride; - } - - if(cx instanceof Characteristic.On){ - cx.zway_getValueFromVDev = function(vdev){ - var val = false; - if(vdev.metrics.level === "on"){ - val = true; - } else if(vdev.metrics.level <= 5) { - val = false; - } else if (vdev.metrics.level > 5) { - val = true; - } - return val; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - cx.on('set', function(powerOn, callback){ - this.command(vdev, powerOn ? "on" : "off").then(function(result){ - callback(); - }); - }.bind(this)); - cx.on('change', function(ev){ - debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); - }); - return cx; - } - - if(cx instanceof Characteristic.Brightness){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - cx.on('set', function(level, callback){ - this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){ - callback(); - }); - }.bind(this)); - return cx; - } - - if(cx instanceof Characteristic.Hue){ - cx.zway_getValueFromVDev = function(vdev){ - debug("Derived value " + accessory.rgb2hsv(vdev.metrics.color).h + " for hue."); - return accessory.rgb2hsv(vdev.metrics.color).h; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - cx.on('set', function(hue, callback){ - var scx = service.getCharacteristic(Characteristic.Saturation); - var vcx = service.getCharacteristic(Characteristic.Brightness); - if(!scx || !vcx){ - debug("Hue without Saturation and Brightness is not supported! Cannot set value!") - callback(true, cx.value); - } - var rgb = this.hsv2rgb({ h: hue, s: scx.value, v: vcx.value }); - this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){ - callback(); - }); - }.bind(this)); - - return cx; - } - - if(cx instanceof Characteristic.Saturation){ - cx.zway_getValueFromVDev = function(vdev){ - debug("Derived value " + accessory.rgb2hsv(vdev.metrics.color).s + " for saturation."); - return accessory.rgb2hsv(vdev.metrics.color).s; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - cx.on('set', function(saturation, callback){ - var hcx = service.getCharacteristic(Characteristic.Hue); - var vcx = service.getCharacteristic(Characteristic.Brightness); - if(!hcx || !vcx){ - debug("Saturation without Hue and Brightness is not supported! Cannot set value!") - callback(true, cx.value); - } - var rgb = this.hsv2rgb({ h: hcx.value, s: saturation, v: vcx.value }); - this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){ - callback(); - }); - }.bind(this)); - - return cx; - } - - if(cx instanceof Characteristic.CurrentTemperature){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - cx.setProps({ - minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : -40, - maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 999 - }); - return cx; - } - - if(cx instanceof Characteristic.TargetTemperature){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - cx.on('set', function(level, callback){ - this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){ - //debug("Got value: " + result.data.metrics.level + ", for " + vdev.metrics.title + "."); - callback(); - }); - }.bind(this)); - cx.setProps({ - minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5, - maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40 - }); - return cx; - } - - if(cx instanceof Characteristic.TemperatureDisplayUnits){ - //TODO: Always in °C for now. - cx.zway_getValueFromVDev = function(vdev){ - return Characteristic.TemperatureDisplayUnits.CELSIUS; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, Characteristic.TemperatureDisplayUnits.CELSIUS); - }); - cx.setProps({ - perms: [Characteristic.Perms.READ] - }); - return cx; - } - - if(cx instanceof Characteristic.CurrentHeatingCoolingState){ - //TODO: Always HEAT for now, we don't have an example to work with that supports another function. - cx.zway_getValueFromVDev = function(vdev){ - return Characteristic.CurrentHeatingCoolingState.HEAT; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, Characteristic.CurrentHeatingCoolingState.HEAT); - }); - return cx; - } - - if(cx instanceof Characteristic.TargetHeatingCoolingState){ - //TODO: Always HEAT for now, we don't have an example to work with that supports another function. - cx.zway_getValueFromVDev = function(vdev){ - return Characteristic.TargetHeatingCoolingState.HEAT; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, Characteristic.TargetHeatingCoolingState.HEAT); - }); - // Hmm... apparently if this is not setable, we can't add a thermostat change to a scene. So, make it writable but a no-op. - cx.on('set', function(newValue, callback){ - debug("WARN: Set of TargetHeatingCoolingState not yet implemented, resetting to HEAT!") - callback(undefined, Characteristic.TargetHeatingCoolingState.HEAT); - }.bind(this)); - return cx; - } - - if(cx instanceof Characteristic.CurrentDoorState){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level === "off" ? Characteristic.CurrentDoorState.CLOSED : Characteristic.CurrentDoorState.OPEN; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - cx.on('change', function(ev){ - debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); - }); - } - - if(cx instanceof Characteristic.TargetDoorState){ - //TODO: We only support this for Door sensors now, so it's a fixed value. - cx.zway_getValueFromVDev = function(vdev){ - return Characteristic.TargetDoorState.CLOSED; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, Characteristic.TargetDoorState.CLOSED); - }); - cx.setProps({ - perms: [Characteristic.Perms.READ] - }); - } - - if(cx instanceof Characteristic.ObstructionDetected){ - //TODO: We only support this for Door sensors now, so it's a fixed value. - cx.zway_getValueFromVDev = function(vdev){ - return false; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, false); - }); - } - - if(cx instanceof Characteristic.BatteryLevel){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - } - - if(cx instanceof Characteristic.StatusLowBattery){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level <= accessory.platform.batteryLow ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - } - - if(cx instanceof Characteristic.ChargingState){ - //TODO: No known chargeable devices(?), so always return false. - cx.zway_getValueFromVDev = function(vdev){ - return Characteristic.ChargingState.NOT_CHARGING; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, Characteristic.ChargingState.NOT_CHARGING); - }); - } - - if(cx instanceof Characteristic.CurrentAmbientLightLevel){ - cx.zway_getValueFromVDev = function(vdev){ - if(vdev.metrics.scaleTitle === "%"){ - // Completely unscientific guess, based on test-fit data and Wikipedia real-world lux values. - // This will probably change! - var lux = 0.0005 * (vdev.metrics.level^3.6); - // Bounds checking now done upstream! - //if(lux < cx.minimumValue) return cx.minimumValue; if(lux > cx.maximumValue) return cx.maximumValue; - return lux; - } else { - return vdev.metrics.level; - } - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - cx.on('change', function(ev){ - debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); - }); - return cx; - } - - if(cx instanceof Characteristic.MotionDetected){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level === "off" ? false : true; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - cx.on('change', function(ev){ - debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); - }); - return cx; - } - - if(cx instanceof Characteristic.StatusTampered){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level === "off" ? Characteristic.StatusTampered.NOT_TAMPERED : Characteristic.StatusTampered.TAMPERED; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - cx.on('change', function(ev){ - debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); - }); - return cx; - } - - if(cx instanceof Characteristic.ContactSensorState){ - cx.zway_getValueFromVDev = function(vdev){ - var boolval = vdev.metrics.level === "off" ? false : true; - boolval = accessory.platform.getTagValue(vdev, "ContactSensorState.Invert") ? !boolval : boolval; - return boolval ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - cx.on('change', function(ev){ - debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); - }); - return cx; - } - - if(cx instanceof Characteristic.CurrentPosition){ - cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level === "off" ? 0 : 100 ; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - this.getVDev(vdev).then(function(result){ - debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); - callback(false, cx.zway_getValueFromVDev(result.data)); - }); - }.bind(this)); - cx.on('change', function(ev){ - debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); - }); - return cx; - } - - if(cx instanceof Characteristic.TargetPosition){ - //TODO: Currently only Door sensors, so always return 0. - cx.zway_getValueFromVDev = function(vdev){ - return 0; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, cx.zway_getValueFromVDev(vdev)); - }); - } - - if(cx instanceof Characteristic.PositionState){ - //TODO: Currently only Door sensors, so always return STOPPED. - cx.zway_getValueFromVDev = function(vdev){ - return Characteristic.PositionState.STOPPED; - }; - cx.value = cx.zway_getValueFromVDev(vdev); - cx.on('get', function(callback, context){ - debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, cx.zway_getValueFromVDev(vdev)); - }); - } - - } - , - configureService: function(service, vdev){ - var success = true; - for(var i = 0; i < service.characteristics.length; i++){ - var cx = service.characteristics[i]; - var vdev = this.getVDevForCharacteristic(cx, vdev); - if(!vdev){ - success = false; - debug("ERROR! Failed to configure required characteristic \"" + service.characteristics[i].displayName + "\"!"); - } - cx = this.configureCharacteristic(cx, vdev, service); - } - for(var i = 0; i < service.optionalCharacteristics.length; i++){ - var cx = service.optionalCharacteristics[i]; - var vdev = this.getVDevForCharacteristic(cx, vdev); - if(!vdev) continue; - - //NOTE: Questionable logic, but if the vdev has already been used for the same - // characteristic type elsewhere, lets not duplicate it just for the sake of an - // optional characteristic. This eliminates the problem with RGB+W+W bulbs - // having the HSV controls shown again, but might have unintended consequences... - var othercx, othercxs = this.platform.cxVDevMap[vdev.id]; - if(othercxs) for(var j = 0; j < othercxs.length; j++) if(othercxs[j].UUID === cx.UUID) othercx = othercxs[j]; - if(othercx) - continue; - - cx = this.configureCharacteristic(cx, vdev, service); - try { - if(cx) service.addCharacteristic(cx); - } - catch (ex) { - debug('Adding Characteristic "' + cx.displayName + '" failed with message "' + ex.message + '". This may be expected.'); - } - } - return success; - } - , - getServices: function() { - var that = this; - - var vdevPrimary = this.devDesc.devices[this.devDesc.primary]; - var accId = this.platform.getTagValue(vdevPrimary, "Accessory.Id"); - if(!accId){ - accId = "VDev-" + vdevPrimary.h; //FIXME: Is this valid? - } - - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Name, this.name) - .setCharacteristic(Characteristic.Manufacturer, "Z-Wave.me") - .setCharacteristic(Characteristic.Model, "Virtual Device (VDev version 1)") - .setCharacteristic(Characteristic.SerialNumber, accId); - - var services = [informationService]; - - services = services.concat(this.getVDevServices(vdevPrimary)); - - // Any extra switchMultilevels? Could be a RGBW+W bulb, add them as additional services... - if(this.devDesc.extras["switchMultilevel"]) for(var i = 0; i < this.devDesc.extras["switchMultilevel"].length; i++){ - var xvdev = this.devDesc.devices[this.devDesc.extras["switchMultilevel"][i]]; - var xservice = this.getVDevServices(xvdev); - services = services.concat(xservice); - } - - if(this.platform.splitServices){ - if(this.devDesc.types["battery.Battery"]){ - services = services.concat(this.getVDevServices(this.devDesc.devices[this.devDesc.types["battery.Battery"]])); - } - - // Odds and ends...if there are sensors that haven't been used, add services for them... - - var tempSensor = this.devDesc.types["sensorMultilevel.Temperature"] !== undefined ? this.devDesc.devices[this.devDesc.types["sensorMultilevel.Temperature"]] : false; - if(tempSensor && !this.platform.cxVDevMap[tempSensor.id]){ - services = services.concat(this.getVDevServices(tempSensor)); - } - - var lightSensor = this.devDesc.types["sensorMultilevel.Luminiscence"] !== undefined ? this.devDesc.devices[this.devDesc.types["sensorMultilevel.Luminiscence"]] : false; - if(lightSensor && !this.platform.cxVDevMap[lightSensor.id]){ - services = services.concat(this.getVDevServices(lightSensor)); - } - } else { - // Everything outside the primary service gets added as optional characteristics... - var service = services[1]; - var existingCxUUIDs = {}; - for(var i = 0; i < service.characteristics.length; i++) existingCxUUIDs[service.characteristics[i].UUID] = true; - - for(var i = 0; i < this.devDesc.devices.length; i++){ - var vdev = this.devDesc.devices[i]; - if(this.platform.cxVDevMap[vdev.id]) continue; // Don't double-use anything - var extraCxClasses = this.extraCharacteristicsMap[ZWayServerPlatform.getVDevTypeKey(vdev)]; - var extraCxs = []; - if(!extraCxClasses || extraCxClasses.length === 0) continue; - for(var j = 0; j < extraCxClasses.length; j++){ - var cx = new extraCxClasses[j](); - if(existingCxUUIDs[cx.UUID]) continue; // Don't have two of the same Characteristic type in one service! - var vdev2 = this.getVDevForCharacteristic(cx, vdev); // Just in case...will probably return vdev. - if(!vdev2){ - // Uh oh... one of the extraCxClasses can't be configured! Abort all extras for this vdev! - extraCxs = []; // to wipe out any already setup cxs. - break; - } - this.configureCharacteristic(cx, vdev2, service); - extraCxs.push(cx); - } - for(var j = 0; j < extraCxs.length; j++) - service.addCharacteristic(extraCxs[j]); - } - } - - debug("Loaded services for " + this.name); - return services; - } -}; - -module.exports.accessory = ZWayServerAccessory; -module.exports.platform = ZWayServerPlatform; diff --git a/platforms/isy-js.js b/platforms/isy-js.js deleted file mode 100644 index 172bd32..0000000 --- a/platforms/isy-js.js +++ /dev/null @@ -1,763 +0,0 @@ -/* - ISY-JS - - ISY-99 REST / WebSockets based HomeBridge shim. - - Supports the following Insteon devices: Lights (dimmable and non-dimmable), Fans, Outlets, Door/Window Sensors, MorningLinc locks, Inline Lincs and I/O Lincs. - Also supports ZWave based locks. If elkEnabled is set to true then this will also expose your Elk Alarm Panel and all of your Elk Sensors. - - Turns out that HomeBridge platforms can only return a maximum of 100 devices. So if you end up exposing more then 100 devices through HomeBridge the HomeKit - software will fail adding the HomeBridge to your HomeKit network. To address this issue this platform provides an option to screen out devices based on - criteria specified in the config. - - Configuration sample: - - "platforms": [ - { - "platform": "isy-js", - "name": "isy-js", - "host": "10.0.1.12", - "username": "admin", - "password": "password", - "elkEnabled": true, - "ignoreDevices": [ - { "nameContains": "ApplianceLinc", "lastAddressDigit": "", "address": ""}, - { "nameContains": "Bedroom.Side Gate", "lastAddressDigit": "", "address": ""}, - { "nameContains": "Remote", "lastAddressDigit": "", "address": "" }, - { "nameContains": "Keypad", "lastAddressDigit": "2", "address": "" }, - ] - } - ] - - Fields: - "platform" - Must be set to isy-js - "name" - Can be set to whatever you want - "host" - IP address of the ISY - "username" - Your ISY username - "password" - Your ISY password - "elkEnabled" - true if there is an elk alarm panel connected to your ISY - "ignoreDevices" - Array of objects specifying criteria for screening out devices from the network. nameContains is the only required criteria. If the other criteria - are blank all devices will match those criteria (providing they match the name criteria). - "nameContains" - Specifies a substring to check against the names of the ISY devices. Required field for the criteria. - "lastAddressDigit" - Specifies a single digit in the ISY address of a device which should be used to match the device. Example use of this is for composite - devices like keypads so you can screen out the non-main buttons. - "address" - ISY address to match. - - Examples: - - { "nameContains": "Keypad", "lastAddressDigit": "2", "address": "" } - Ignore all devices which have the word Keypad in their name and whose last address digit is 2. - { "nameContains": "Remote", "lastAddressDigit": "", "address": "" } - Ignore all devices which have the word Remote in their name - { "nameContains": "", "lastAddressDigit": "", "address": "15 5 3 2"} - Ignore the device with an ISY address of 15 5 3 2. - - TODOS: Implement identify functions (beep perhaps?) and more device types. -*/ - - -var types = require("hap-nodejs/accessories/types.js"); -var isy = require('isy-js'); -var Service = require("hap-nodejs").Service; -var Characteristic = require("hap-nodejs").Characteristic; -var inherits = require('util').inherits; - -// Global device map. Needed to map incoming notifications to the corresponding HomeKit device for update. -var deviceMap = {}; - -// This function responds to changes in devices from the isy-js library. Uses the global device map to update -// the state. -// TODO: Move this to a member function of the ISYPlatform object so we don't need a global map. -function ISYChangeHandler(isy,device) { - var deviceToUpdate = deviceMap[device.address]; - if(deviceToUpdate != null) { - deviceToUpdate.handleExternalChange(); - } -} - -// Helper function to have ISYJSDEBUG control if debug output appears -function ISYJSDebugMessage(isy,message) { - if(process.env.ISYJSDEBUG != undefined) { - isy.log(message); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////// -// PLATFORM - -// Construct the ISY platform. log = Logger, config = homebridge cofnig -function ISYPlatform(log,config) { - this.log = log; - this.config = config; - this.host = config.host; - this.username = config.username; - this.password = config.password; - this.elkEnabled = config.elkEnabled; - this.isy = new isy.ISY(this.host, this.username,this.password, config.elkEnabled, ISYChangeHandler); -} - -// Checks the device against the configuration to see if it should be ignored. -ISYPlatform.prototype.shouldIgnore = function(device) { - var deviceAddress = device.address; - var deviceName = device.name; - for(var index = 0; index < this.config.ignoreDevices.length; index++) { - var rule = this.config.ignoreDevices[index]; - if(rule.nameContains != "") { - if(deviceName.indexOf(rule.nameContains) == -1) { - continue; - } - } - if(rule.lastAddressDigit != "") { - if(deviceAddress.indexOf(rule.lastAddressDigit,deviceAddress.length-2) == -1) { - continue; - } - } - if(rule.address != "") { - if(deviceAddress != rule.address) { - continue; - } - } - ISYJSDebugMessage(this,"Ignoring device: "+deviceName+" ["+deviceAddress+"] because of rule ["+rule.nameContains+"] ["+rule.lastAddressDigit+"] ["+rule.address+"]"); - return true; - - } - return false; -} - -// Calls the isy-js library, retrieves the list of devices, and maps them to appropriate ISYXXXXAccessory devices. -ISYPlatform.prototype.accessories = function(callback) { - var that = this; - this.isy.initialize(function() { - var results = []; - var deviceList = that.isy.getDeviceList(); - for(var index = 0; index < deviceList.length; index++) { - var device = deviceList[index]; - var homeKitDevice = null; - if(!that.shouldIgnore(device)) { - - if(device.deviceType == that.isy.DEVICE_TYPE_LIGHT || device.deviceType == that.isy.DEVICE_TYPE_DIMMABLE_LIGHT) { - homeKitDevice = new ISYLightAccessory(that.log,device); - } else if(device.deviceType == that.isy.DEVICE_TYPE_LOCK || device.deviceType == that.isy.DEVICE_TYPE_SECURE_LOCK) { - homeKitDevice = new ISYLockAccessory(that.log,device); - } else if(device.deviceType == that.isy.DEVICE_TYPE_OUTLET) { - homeKitDevice = new ISYOutletAccessory(that.log,device); - } else if(device.deviceType == that.isy.DEVICE_TYPE_FAN) { - homeKitDevice = new ISYFanAccessory(that.log,device); - } else if(device.deviceType == that.isy.DEVICE_TYPE_DOOR_WINDOW_SENSOR) { - homeKitDevice = new ISYDoorWindowSensorAccessory(that.log,device); - } else if(device.deviceType == that.isy.DEVICE_TYPE_ALARM_DOOR_WINDOW_SENSOR) { - homeKitDevice = new ISYDoorWindowSensorAccessory(that.log,device); - } else if(device.deviceType == that.isy.DEVICE_TYPE_ALARM_PANEL) { - homeKitDevice = new ISYElkAlarmPanelAccessory(that.log,device); - } - if(homeKitDevice != null) { - // Make sure the device is address to the global map - deviceMap[device.address] = homeKitDevice; - results.push(homeKitDevice); - } - } - } - if(that.isy.elkEnabled) { - var panelDevice = that.isy.getElkAlarmPanel(); - var panelDeviceHK = new ISYElkAlarmPanelAccessory(that.log,panelDevice); - deviceMap[panelDevice.address] = panelDeviceHK; - results.push(panelDeviceHK); - } - ISYJSDebugMessage(that,"Filtered device has: "+results.length+" devices"); - callback(results); - }); -} - -///////////////////////////////////////////////////////////////////////////////////////////////// -// BASE FOR ALL DEVICES - -// Provides common constructor tasks -function ISYAccessoryBaseSetup(accessory,log,device) { - accessory.log = log; - accessory.device = device; - accessory.address = device.address; - accessory.name = device.name; - accessory.uuid_base = device.isy.address+":"+device.address; -} - -///////////////////////////////////////////////////////////////////////////////////////////////// -// FANS - ISYFanAccessory -// Implemetnts the fan service for an isy fan device. - -// Constructs a fan accessory object. device is the isy-js device object and log is the logger. -function ISYFanAccessory(log,device) { - ISYAccessoryBaseSetup(this,log,device); -} - -ISYFanAccessory.prototype.identify = function(callback) { - // Do the identify action - callback(); -} - -// Translates the fan speed as an isy-js string into the corresponding homekit constant level. -// Homekit doesn't have steps for the fan speed and needs to have a value from 0 to 100. We -// split the range into 4 steps and map them to the 4 isy-js levels. -ISYFanAccessory.prototype.translateFanSpeedToHK = function(fanSpeed) { - if(fanSpeed == this.device.FAN_LEVEL_OFF) { - return 0; - } else if(fanSpeed == this.device.FAN_LEVEL_LOW) { - return 32; - } else if(fanSpeed == this.device.FAN_LEVEL_MEDIUM) { - return 67; - } else if(fanSpeed == this.device.FAN_LEVEL_HIGH) { - return 100; - } else { - ISYJSDebugMessage(this,"!!!! ERROR: Unknown fan speed: "+fanSpeed); - return 0; - } -} - -// Translates the fan level from homebridge into the isy-js level. Maps from the 0-100 -// to the four isy-js fan speed levels. -ISYFanAccessory.prototype.translateHKToFanSpeed = function(fanStateHK) { - if(fanStateHK == 0) { - return this.device.FAN_LEVEL_OFF; - } else if(fanStateHK > 0 && fanStateHK <=32) { - return this.device.FAN_LEVEL_LOW; - } else if(fanStateHK >= 33 && fanStateHK <= 67) { - return this.device.FAN_LEVEL_MEDIUM; - } else if(fanStateHK > 67) { - return this.device.FAN_LEVEL_HIGH; - } else { - ISYJSDebugMessage(this,"ERROR: Unknown fan state!"); - return this.device.FAN_LEVEL_OFF; - } -} - -// Returns the current state of the fan from the isy-js level to the 0-100 level of HK. -ISYFanAccessory.prototype.getFanRotationSpeed = function(callback) { - callback(null,this.translateFanSpeedToHK(this.device.getCurrentFanState())); -} - -// Sets the current state of the fan from the 0-100 level of HK to the isy-js level. -ISYFanAccessory.prototype.setFanRotationSpeed = function(fanStateHK,callback) { - var newFanState = this.translateHKToFanSpeed(fanStateHK); - ISYJSDebugMessage(this,"Sending command to set fan state to: "+newFanState); - if(newFanState != this.device.getCurrentFanState()) { - this.device.sendFanCommand(newFanState, function(result) { - callback(); - }); - } else { - ISYJSDebugMessage(this,"Fan command does not change actual speed"); - callback(); - } -} - -// Returns true if the fan is on -ISYFanAccessory.prototype.getIsFanOn = function() { - return (this.device.getCurrentFanState() != "Off"); -} - -// Returns the state of the fan to the homebridge system for the On characteristic -ISYFanAccessory.prototype.getFanOnState = function(callback) { - callback(null,this.getIsFanOn()); -} - -// Sets the fan state based on the value of the On characteristic. Default to Medium for on. -ISYFanAccessory.prototype.setFanOnState = function(onState,callback) { - if(onState != this.getIsFanOn()) { - if(onState) { - this.setFanRotationSpeed(this.translateFanSpeedToHK(this.device.FAN_LEVEL_MEDIUM), callback); - } else { - this.setFanRotationSpeed(this.translateFanSpeedToHK(this.device.FAN_LEVEL_OFF), callback); - } - } else { - ISYJSDebugMessage(this,"Fan command does not change actual state"); - callback(); - } -} - -// Mirrors change in the state of the underlying isj-js device object. -ISYFanAccessory.prototype.handleExternalChange = function() { - this.fanService - .setCharacteristic(Characteristic.On, this.getIsFanOn()); - - this.fanService - .setCharacteristic(Characteristic.RotationSpeed, this.translateFanSpeedToHK(this.device.getCurrentFanState())); -} - -// Returns the services supported by the fan device. -ISYFanAccessory.prototype.getServices = function() { - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "SmartHome") - .setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName) - .setCharacteristic(Characteristic.SerialNumber, this.device.address); - - var fanService = new Service.Fan(); - - this.fanService = fanService; - this.informationService = informationService; - - fanService - .getCharacteristic(Characteristic.On) - .on('set', this.setFanOnState.bind(this)); - - fanService - .getCharacteristic(Characteristic.On) - .on('get', this.getFanOnState.bind(this)); - - fanService - .addCharacteristic(new Characteristic.RotationSpeed()) - .on('get', this.getFanRotationSpeed.bind(this)); - - fanService - .getCharacteristic(Characteristic.RotationSpeed) - .on('set', this.setFanRotationSpeed.bind(this)); - - return [informationService, fanService]; -} - -///////////////////////////////////////////////////////////////////////////////////////////////// -// OUTLETS - ISYOutletAccessory -// Implements the Outlet service for ISY devices. - -// Constructs an outlet. log = HomeBridge logger, device = isy-js device to wrap -function ISYOutletAccessory(log,device) { - ISYAccessoryBaseSetup(this,log,device); -} - -// Handles the identify command -ISYOutletAccessory.prototype.identify = function(callback) { - // Do the identify action - callback(); -} - -// Handles a request to set the outlet state. Ignores redundant sets based on current states. -ISYOutletAccessory.prototype.setOutletState = function(outletState,callback) { - ISYJSDebugMessage(this,"Sending command to set outlet state to: "+outletState); - if(outletState != this.device.getCurrentOutletState()) { - this.device.sendOutletCommand(outletState, function(result) { - callback(); - }); - } else { - callback(); - } -} - -// Handles a request to get the current outlet state based on underlying isy-js device object. -ISYOutletAccessory.prototype.getOutletState = function(callback) { - callback(null,this.device.getCurrentOutletState()); -} - -// Handles a request to get the current in use state of the outlet. We set this to true always as -// there is no way to deterine this through the isy. -ISYOutletAccessory.prototype.getOutletInUseState = function(callback) { - callback(null, true); -} - -// Mirrors change in the state of the underlying isj-js device object. -ISYOutletAccessory.prototype.handleExternalChange = function() { - this.outletService - .setCharacteristic(Characteristic.On, this.device.getCurrentOutletState()); -} - -// Returns the set of services supported by this object. -ISYOutletAccessory.prototype.getServices = function() { - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "SmartHome") - .setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName) - .setCharacteristic(Characteristic.SerialNumber, this.device.address); - - var outletService = new Service.Outlet(); - - this.outletService = outletService; - this.informationService = informationService; - - outletService - .getCharacteristic(Characteristic.On) - .on('set', this.setOutletState.bind(this)); - - outletService - .getCharacteristic(Characteristic.On) - .on('get', this.getOutletState.bind(this)); - - outletService - .getCharacteristic(Characteristic.OutletInUse) - .on('get', this.getOutletInUseState.bind(this)); - - return [informationService, outletService]; -} - -///////////////////////////////////////////////////////////////////////////////////////////////// -// LOCKS - ISYLockAccessory -// Implements the lock service for isy-js devices. - -// Constructs a lock accessory. log = homebridge logger, device = isy-js device object being wrapped -function ISYLockAccessory(log,device) { - ISYAccessoryBaseSetup(this,log,device); -} - -// Handles an identify request -ISYLockAccessory.prototype.identify = function(callback) { - callback(); -} - -// Handles a set to the target lock state. Will ignore redundant commands. -ISYLockAccessory.prototype.setTargetLockState = function(lockState,callback) { - ISYJSDebugMessage(this,"Sending command to set lock state to: "+lockState); - if(lockState != this.getDeviceCurrentStateAsHK()) { - var targetLockValue = (lockState == 0) ? false : true; - this.device.sendLockCommand(targetLockValue, function(result) { - callback(); - }); - } else { - callback(); - } -} - -// Translates underlying lock state into the corresponding homekit state -ISYLockAccessory.prototype.getDeviceCurrentStateAsHK = function() { - return (this.device.getCurrentLockState() ? 1 : 0); -} - -// Handles request to get the current lock state for homekit -ISYLockAccessory.prototype.getLockCurrentState = function(callback) { - callback(null, this.getDeviceCurrentStateAsHK()); -} - -// Handles request to get the target lock state for homekit -ISYLockAccessory.prototype.getTargetLockState = function(callback) { - this.getLockCurrentState(callback); -} - -// Mirrors change in the state of the underlying isj-js device object. -ISYLockAccessory.prototype.handleExternalChange = function() { - this.lockService - .setCharacteristic(Characteristic.LockTargetState, this.getDeviceCurrentStateAsHK()); - this.lockService - .setCharacteristic(Characteristic.LockCurrentState, this.getDeviceCurrentStateAsHK()); -} - -// Returns the set of services supported by this object. -ISYLockAccessory.prototype.getServices = function() { - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "SmartHome") - .setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName) - .setCharacteristic(Characteristic.SerialNumber, this.device.address); - - var lockMechanismService = new Service.LockMechanism(); - - this.lockService = lockMechanismService; - this.informationService = informationService; - - lockMechanismService - .getCharacteristic(Characteristic.LockTargetState) - .on('set', this.setTargetLockState.bind(this)); - - lockMechanismService - .getCharacteristic(Characteristic.LockTargetState) - .on('get', this.getTargetLockState.bind(this)); - - lockMechanismService - .getCharacteristic(Characteristic.LockCurrentState) - .on('get', this.getLockCurrentState.bind(this)); - - return [informationService, lockMechanismService]; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////// -// LIGHTS -// Implements the Light service for homekit based on an underlying isy-js device. Is dimmable or not depending -// on if the underlying device is dimmable. - -// Constructs the light accessory. log = homebridge logger, device = isy-js device object being wrapped -function ISYLightAccessory(log,device) { - ISYAccessoryBaseSetup(this,log,device); - this.dimmable = (this.device.deviceType == "DimmableLight"); -} - -// Handles the identify command -ISYLightAccessory.prototype.identify = function(callback) { - this.device.sendLightCommand(true, function(result) { - this.device.sendLightCommand(false, function(result) { - callback(); - }); - }); -} - -// Handles request to set the current powerstate from homekit. Will ignore redundant commands. -ISYLightAccessory.prototype.setPowerState = function(powerOn,callback) { - ISYJSDebugMessage(this,"Setting powerstate to %s", powerOn); - if(powerOn != this.device.getCurrentLightState()) { - ISYJSDebugMessage(this,"Changing powerstate to "+powerOn); - this.device.sendLightCommand(powerOn, function(result) { - callback(); - }); - } else { - ISYJSDebugMessage(this,"Ignoring redundant setPowerState"); - callback(); - } -} - -// Mirrors change in the state of the underlying isj-js device object. -ISYLightAccessory.prototype.handleExternalChange = function() { - ISYJSDebugMessage(this,"Handling external change for light"); - this.lightService - .setCharacteristic(Characteristic.On, this.device.getCurrentLightState()); - if(this.device.deviceType == this.device.isy.DEVICE_TYPE_DIMMABLE_LIGHT) { - this.lightService - .setCharacteristic(Characteristic.Brightness, this.device.getCurrentLightDimState() ); - } -} - -// Handles request to get the current on state -ISYLightAccessory.prototype.getPowerState = function(callback) { - callback(null,this.device.getCurrentLightState()); -} - -// Handles request to set the brightness level of dimmable lights. Ignore redundant commands. -ISYLightAccessory.prototype.setBrightness = function(level,callback) { - ISYJSDebugMessage(this,"Setting brightness to %s", level); - if(level != this.device.getCurrentLightDimState()) { - ISYJSDebugMessage(this,"Changing Brightness to "+level); - this.device.sendLightDimCommand(level, function(result) { - callback(); - }); - } else { - ISYJSDebugMessage(this,"Ignoring redundant setBrightness"); - callback(); - } -} - -// Handles a request to get the current brightness level for dimmable lights. -ISYLightAccessory.prototype.getBrightness = function(callback) { - callback(null,this.device.getCurrentLightDimState()); -} - -// Returns the set of services supported by this object. -ISYLightAccessory.prototype.getServices = function() { - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "SmartHome") - .setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName) - .setCharacteristic(Characteristic.SerialNumber, this.device.address); - - var lightBulbService = new Service.Lightbulb(); - - this.informationService = informationService; - this.lightService = lightBulbService; - - lightBulbService - .getCharacteristic(Characteristic.On) - .on('set', this.setPowerState.bind(this)); - - lightBulbService - .getCharacteristic(Characteristic.On) - .on('get', this.getPowerState.bind(this)); - - if(this.dimmable) { - lightBulbService - .addCharacteristic(new Characteristic.Brightness()) - .on('get', this.getBrightness.bind(this)); - - lightBulbService - .getCharacteristic(Characteristic.Brightness) - .on('set', this.setBrightness.bind(this)); - } - - return [informationService, lightBulbService]; -} - -///////////////////////////////////////////////////////////////////////////////////////////////// -// CONTACT SENSOR - ISYDoorWindowSensorAccessory -// Implements the ContactSensor service. - -// Constructs a Door Window Sensor (contact sensor) accessory. log = HomeBridge logger, device = wrapped isy-js device. -function ISYDoorWindowSensorAccessory(log,device) { - ISYAccessoryBaseSetup(this,log,device); - this.doorWindowState = false; -} - -// Handles the identify command. -ISYDoorWindowSensorAccessory.prototype.identify = function(callback) { - // Do the identify action - callback(); -} - -// Translates the state of the underlying device object into the corresponding homekit compatible state -ISYDoorWindowSensorAccessory.prototype.translateCurrentDoorWindowState = function() { - return (this.device.getCurrentDoorWindowState()) ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED; -} - -// Handles the request to get he current door window state. -ISYDoorWindowSensorAccessory.prototype.getCurrentDoorWindowState = function(callback) { - callback(null,this.translateCurrentDoorWindowState()); -} - -// Mirrors change in the state of the underlying isj-js device object. -ISYDoorWindowSensorAccessory.prototype.handleExternalChange = function() { - this.sensorService - .setCharacteristic(Characteristic.ContactSensorState, this.translateCurrentDoorWindowState()); -} - -// Returns the set of services supported by this object. -ISYDoorWindowSensorAccessory.prototype.getServices = function() { - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "SmartHome") - .setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName) - .setCharacteristic(Characteristic.SerialNumber, this.device.address); - - var sensorService = new Service.ContactSensor(); - - this.sensorService = sensorService; - this.informationService = informationService; - - sensorService - .getCharacteristic(Characteristic.ContactSensorState) - .on('get', this.getCurrentDoorWindowState.bind(this)); - - return [informationService, sensorService]; -} - -///////////////////////////////////////////////////////////////////////////////////////////////// -// ELK SENSOR PANEL - ISYElkAlarmPanelAccessory -// Implements the SecuritySystem service for an elk security panel connected to the isy system - -// Constructs the alarm panel accessory. log = HomeBridge logger, device = underlying isy-js device being wrapped -function ISYElkAlarmPanelAccessory(log,device) { - ISYAccessoryBaseSetup(this,log,device); -} - -// Handles the identify command -ISYElkAlarmPanelAccessory.prototype.identify = function(callback) { - callback(); -} - -// Handles the request to set the alarm target state -ISYElkAlarmPanelAccessory.prototype.setAlarmTargetState = function(targetStateHK,callback) { - ISYJSDebugMessage(this,"Sending command to set alarm panel state to: "+targetStateHK); - var targetState = this.translateHKToAlarmTargetState(targetStateHK); - ISYJSDebugMessage(this,"Would send the target state of: "+targetState); - if(this.device.getAlarmMode() != targetState) { - this.device.sendSetAlarmModeCommand(targetState, function(result) { - callback(); - }); - } else { - ISYJSDebugMessage(this,"Redundant command, already in that state."); - callback(); - } -} - -// Translates from the current state of the elk alarm system into a homekit compatible state. The elk panel has a lot more -// possible states then can be directly represented by homekit so we map them. If the alarm is going off then it is tripped. -// If it is arming or armed it is considered armed. Stay maps to the state state, away to the away state, night to the night -// state. -ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() { - var tripState = this.device.getAlarmTripState(); - var sourceAlarmState = this.device.getAlarmState(); - var sourceAlarmMode = this.device.getAlarmMode(); - - if(tripState >= this.device.ALARM_TRIP_STATE_TRIPPED) { - return Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; - } else if(sourceAlarmState == this.device.ALARM_STATE_NOT_READY_TO_ARM || - sourceAlarmState == this.device.ALARM_STATE_READY_TO_ARM || - sourceAlarmState == this.device.ALARM_STATE_READY_TO_ARM_VIOLATION) { - return Characteristic.SecuritySystemCurrentState.DISARMED; - } else { - if(sourceAlarmMode == this.device.ALARM_MODE_STAY || sourceAlarmMode == this.device.ALARM_MODE_STAY_INSTANT ) { - return Characteristic.SecuritySystemCurrentState.STAY_ARM; - } else if(sourceAlarmMode == this.device.ALARM_MODE_AWAY || sourceAlarmMode == this.device.ALARM_MODE_VACATION) { - return Characteristic.SecuritySystemCurrentState.AWAY_ARM; - } else if(sourceAlarmMode == this.device.ALARM_MODE_NIGHT || sourceAlarmMode == this.device.ALARM_MODE_NIGHT_INSTANT) { - return Characteristic.SecuritySystemCurrentState.NIGHT_ARM; - } else { - ISYJSDebugMessage(this,"Setting to disarmed because sourceAlarmMode is "+sourceAlarmMode); - return Characteristic.SecuritySystemCurrentState.DISARMED; - } - } -} - -// Translates the current target state of hthe underlying alarm into the appropriate homekit value -ISYElkAlarmPanelAccessory.prototype.translateAlarmTargetStateToHK = function() { - var sourceAlarmState = this.device.getAlarmMode(); - if(sourceAlarmState == this.device.ALARM_MODE_STAY || sourceAlarmState == this.device.ALARM_MODE_STAY_INSTANT ) { - return Characteristic.SecuritySystemTargetState.STAY_ARM; - } else if(sourceAlarmState == this.device.ALARM_MODE_AWAY || sourceAlarmState == this.device.ALARM_MODE_VACATION) { - return Characteristic.SecuritySystemTargetState.AWAY_ARM; - } else if(sourceAlarmState == this.device.ALARM_MODE_NIGHT || sourceAlarmState == this.device.ALARM_MODE_NIGHT_INSTANT) { - return Characteristic.SecuritySystemTargetState.NIGHT_ARM; - } else { - return Characteristic.SecuritySystemTargetState.DISARM; - } -} - -// Translates the homekit version of the alarm target state into the appropriate elk alarm panel state -ISYElkAlarmPanelAccessory.prototype.translateHKToAlarmTargetState = function(state) { - if(state == Characteristic.SecuritySystemTargetState.STAY_ARM) { - return this.device.ALARM_MODE_STAY; - } else if(state == Characteristic.SecuritySystemTargetState.AWAY_ARM) { - return this.device.ALARM_MODE_AWAY; - } else if(state == Characteristic.SecuritySystemTargetState.NIGHT_ARM) { - return this.device.ALARM_MODE_NIGHT; - } else { - return this.device.ALARM_MODE_DISARMED; - } -} - -// Handles request to get the target alarm state -ISYElkAlarmPanelAccessory.prototype.getAlarmTargetState = function(callback) { - callback(null,this.translateAlarmTargetStateToHK()); -} - -// Handles request to get the current alarm state -ISYElkAlarmPanelAccessory.prototype.getAlarmCurrentState = function(callback) { - callback(null,this.translateAlarmCurrentStateToHK()); -} - -// Mirrors change in the state of the underlying isj-js device object. -ISYElkAlarmPanelAccessory.prototype.handleExternalChange = function() { - ISYJSDebugMessage(this,"Source device. Currenty state locally -"+this.device.getAlarmStatusAsText()); - ISYJSDebugMessage(this,"Got alarm change notification. Setting HK target state to: "+this.translateAlarmTargetStateToHK()+" Setting HK Current state to: "+this.translateAlarmCurrentStateToHK()); - this.alarmPanelService - .setCharacteristic(Characteristic.SecuritySystemTargetState, this.translateAlarmTargetStateToHK()); - this.alarmPanelService - .setCharacteristic(Characteristic.SecuritySystemCurrentState, this.translateAlarmCurrentStateToHK()); -} - -// Returns the set of services supported by this object. -ISYElkAlarmPanelAccessory.prototype.getServices = function() { - var informationService = new Service.AccessoryInformation(); - - informationService - .setCharacteristic(Characteristic.Manufacturer, "SmartHome") - .setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName) - .setCharacteristic(Characteristic.SerialNumber, this.device.address); - - var alarmPanelService = new Service.SecuritySystem(); - - this.alarmPanelService = alarmPanelService; - this.informationService = informationService; - - alarmPanelService - .getCharacteristic(Characteristic.SecuritySystemTargetState) - .on('set', this.setAlarmTargetState.bind(this)); - - alarmPanelService - .getCharacteristic(Characteristic.SecuritySystemTargetState) - .on('get', this.getAlarmTargetState.bind(this)); - - alarmPanelService - .getCharacteristic(Characteristic.SecuritySystemCurrentState) - .on('get', this.getAlarmCurrentState.bind(this)); - - return [informationService, alarmPanelService]; -} - -module.exports.platform = ISYPlatform; -module.exports.accessory = ISYFanAccessory; -module.exports.accessory = ISYLightAccessory; -module.exports.accessory = ISYLockAccessory; -module.exports.accessory = ISYOutletAccessory; -module.exports.accessory = ISYDoorWindowSensorAccessory; -module.exports.accessory = ISYElkAlarmPanelAccessory;