Merge branch 'nfarina/master'

This commit is contained in:
S'pht'Kr
2015-10-15 05:50:52 +02:00
11 changed files with 1508 additions and 75 deletions

View File

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

View File

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

View File

@@ -17,6 +17,8 @@ New 2015-09-19:
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;
@@ -38,6 +40,9 @@ function KNXDevice(log, config) {
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 {

12
app.js
View File

@@ -86,7 +86,7 @@ function loadAccessories() {
log("Initializing %s accessory...", accessoryType);
var accessoryInstance = new accessoryConstructor(log, accessoryConfig);
var accessory = createAccessory(accessoryInstance, accessoryName);
var accessory = createAccessory(accessoryInstance, accessoryName, accessoryType, accessoryConfig.uuid_base); //pass accessoryType for UUID generation, and optional parameter uuid_base which can be used instead of displayName for UUID generation
// add it to the bridge
bridge.addBridgedAccessory(accessory);
@@ -113,11 +113,11 @@ function loadPlatforms() {
log("Initializing %s platform...", platformType);
var platformInstance = new platformConstructor(log, platformConfig);
loadPlatformAccessories(platformInstance, log);
loadPlatformAccessories(platformInstance, log, platformType);
}
}
function loadPlatformAccessories(platformInstance, log) {
function loadPlatformAccessories(platformInstance, log, platformType) {
asyncCalls++;
platformInstance.accessories(once(function(foundAccessories){
asyncCalls--;
@@ -129,7 +129,7 @@ function loadPlatformAccessories(platformInstance, log) {
log("Initializing platform accessory '%s'...", accessoryName);
var accessory = createAccessory(accessoryInstance, accessoryName);
var accessory = createAccessory(accessoryInstance, accessoryName, platformType, accessoryInstance.uuid_base);
// add it to the bridge
bridge.addBridgedAccessory(accessory);
@@ -141,7 +141,7 @@ function loadPlatformAccessories(platformInstance, log) {
}));
}
function createAccessory(accessoryInstance, displayName) {
function createAccessory(accessoryInstance, displayName, accessoryType, uuid_base) {
var services = accessoryInstance.getServices();
@@ -159,7 +159,7 @@ function createAccessory(accessoryInstance, displayName) {
// The returned "services" for this accessory are simply an array of new-API-style
// Service instances which we can add to a created HAP-NodeJS Accessory directly.
var accessoryUUID = uuid.generate(accessoryInstance.constructor.name + ":" + displayName);
var accessoryUUID = uuid.generate(accessoryType + ":" + (uuid_base || displayName));
var accessory = new Accessory(displayName, accessoryUUID);

View File

@@ -23,6 +23,10 @@
"token" : "telldus token",
"token_secret" : "telldus token secret"
},
{
"platform" : "Telldus",
"name" : "Telldus"
},
{
"platform": "Wink",
"name": "Wink",
@@ -164,7 +168,20 @@
"off_url": "https://192.168.1.22:3030/devices/23222/off",
"brightness_url": "https://192.168.1.22:3030/devices/23222/brightness/%b",
"http_method": "POST"
},{
},
{
"accessory": "HttpHygrometer",
"name": "Kitchen",
"url": "http://host/URL",
"http_method": "GET"
},
{
"accessory": "HttpThermometer",
"name": "Garage",
"url": "http://home/URL",
"http_method": "GET"
},
{
"accessory": "ELKM1",
"name": "Security System",
"description": "Allows basic control of Elk M1 security system. You can use 1 of 3 arm modes: Away, Stay, Night. If you need to access all 3, create 3 accessories with different names.",

View File

@@ -31,6 +31,7 @@
"tough-cookie": "^2.0.0",
"request": "2.49.x",
"sonos": "0.8.x",
"telldus": "0.0.9",
"telldus-live": "0.2.x",
"teslams": "1.0.1",
"unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98",

755
platforms/HomeSeer.js Normal file
View File

@@ -0,0 +1,755 @@
'use strict';
//
// HomeSeer Platform Shim for HomeBridge
// V0.1 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/07
// - Initial version
// V0.2 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/10
// - Occupancy sensor fix
// V0.3 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/11
// - Added TemperatureUnit=F|C option to temperature sensors
// - Added negative temperature support to temperature sensors
// V0.4 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12
// - Added thermostat support
// V0.5 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12
// - Added Humidity sensor support
// V0.6 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12
// - Added Battery support
// - Added low battery support for all sensors
// - Added HomeSeer event support (using HomeKit switches...)
//
//
// Remember to add platform to config.json.
//
// You can get HomeSeer Device References by clicking a HomeSeer device name, then
// choosing the Advanced Tab.
//
// 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 Event", // Required - The HomeSeer event name
// "name":"Test" // Optional - HomeSeer event name is the default
// }
// ],
//
// "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
// },
// {
// "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":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 - Value for OFF
// "controlHeatValue":1, // Required - Value for HEAT
// "controlCoolValue":2, // Required - Value for COOL
// "controlAutoValue":3, // Required - 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":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
// }
// ]
// }
// ],
//
//
// 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)
// - ContactSensor (0=no contact, 1=contact - batteryRef, batteryThreshold option)
// - MotionSensor (0=no motion, 1=motion - batteryRef, batteryThreshold option)
// - LeakSensor (0=no leak, 1=leak - batteryRef, batteryThreshold option)
// - LightSensor (HomeSeer device value in Lux - batteryRef, batteryThreshold option)
// - HumiditySensor (HomeSeer device value in % - batteryRef, batteryThreshold option)
// - OccupancySensor (0=no occupancy, 1=occupancy - batteryRef, batteryThreshold option)
// - SmokeSensor (0=no smoke, 1=smoke - batteryRef, batteryThreshold option)
// - Battery (batteryThreshold option)
// - Door
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<this.config.events.length; i++ ) {
var event = new HomeSeerEvent( that.log, that.config, that.config.events[i] );
foundAccessories.push( event );
}
}
this.log("Fetching HomeSeer devices.");
var refList = "";
for( var i=0; i<this.config.accessories.length; i++ ) {
refList = refList + this.config.accessories[i].ref;
if( i < this.config.accessories.length - 1 )
refList = refList + ",";
}
var url = this.config["host"] + "/JSON?request=getstatus&ref=" + refList;
httpRequest( url, "GET", function(error, response, body) {
if (error) {
this.log('HomeSeer status function failed: %s', error.message);
callback( foundAccessories );
}
else {
this.log('HomeSeer status function succeeded!');
var response = JSON.parse( body );
for( var i=0; i<response.Devices.length; i++ ) {
var accessory = new HomeSeerAccessory( that.log, that.config, response.Devices[i] );
foundAccessories.push( accessory );
}
callback( foundAccessories );
}
}.bind(this));
}
}
function HomeSeerAccessory(log, platformConfig, status ) {
this.log = log;
this.ref = status.ref;
this.name = status.name
this.model = status.device_type_string;
this.onValue = "100";
this.offValue = "0";
this.access_url = platformConfig["host"] + "/JSON?";
this.control_url = this.access_url + "request=controldevicebyvalue&ref=" + this.ref + "&value=";
this.status_url = this.access_url + "request=getstatus&ref=" + this.ref;
for( var i=0; i<platformConfig.accessories.length; i++ ) {
if( platformConfig.accessories[i].ref == this.ref )
{
this.config = platformConfig.accessories[i];
break;
}
}
if( this.config.name )
this.name = this.config.name;
if( this.config.onValue )
this.onValue = this.config.onValue;
if( this.config.offValue )
this.offValue = this.config.offValue;
}
HomeSeerAccessory.prototype = {
identify: function(callback) {
callback();
},
setPowerState: function(powerOn, callback) {
var url;
if (powerOn) {
url = this.control_url + this.onValue;
this.log("Setting power state to on");
}
else {
url = this.control_url + this.offValue;
this.log("Setting power state to off");
}
httpRequest(url, 'GET', function(error, response, body) {
if (error) {
this.log('HomeSeer power function failed: %s', error.message);
callback(error);
}
else {
this.log('HomeSeer power function succeeded!');
callback();
}
}.bind(this));
},
getPowerState: function(callback) {
var url = this.status_url;
httpRequest(url, 'GET', function(error, response, body) {
if (error) {
this.log('HomeSeer get power function failed: %s', error.message);
callback( error, 0 );
}
else {
var status = JSON.parse( body );
var value = status.Devices[0].value;
this.log('HomeSeer get power function succeeded: value=' + value );
if( value == 0 )
callback( null, 0 );
else
callback( null, 1 );
}
}.bind(this));
},
setValue: function(level, callback) {
var url = this.control_url + level;
this.log("Setting value to %s", level);
httpRequest(url, 'GET', function(error, response, body) {
if (error) {
this.log('HomeSeer set value function failed: %s', error.message);
callback(error);
}
else {
this.log('HomeSeer set value function succeeded!');
callback();
}
}.bind(this));
},
getValue: function(callback) {
var url = this.status_url;
httpRequest(url, 'GET', function(error, response, body) {
if (error) {
this.log('HomeSeer get value function failed: %s', error.message);
callback( error, 0 );
}
else {
var status = JSON.parse( body );
var value = status.Devices[0].value;
this.log('HomeSeer get value function succeeded: value=' + value );
callback( null, value );
}
}.bind(this));
},
setTemperature: function(temperature, callback) {
this.log("Setting temperature to %s", temperature);
if( this.config.temperatureUnit == "F" ) {
temperature = temperature*9/5+32;
}
var url = this.control_url + temperature;
httpRequest(url, 'GET', function(error, response, body) {
if (error) {
this.log('HomeSeer set temperature function failed: %s', error.message);
callback(error);
}
else {
this.log('HomeSeer set temperature function succeeded!');
callback();
}
}.bind(this));
},
getTemperature: function(callback) {
var url = this.status_url;
httpRequest(url, 'GET', function(error, response, body) {
if (error) {
this.log('HomeSeer get temperature function failed: %s', error.message);
callback( error, 0 );
}
else {
var status = JSON.parse( body );
var value = status.Devices[0].value;
this.log('HomeSeer get temperature function succeeded: value=' + value );
if( this.config.temperatureUnit == "F" ) {
value = (value-32)*5/9;
}
callback( null, value );
}
}.bind(this));
},
getThermostatCurrentHeatingCoolingState: function(callback) {
var ref = this.config.stateRef;
var url = this.access_url + "request=getstatus&ref=" + ref;
httpRequest(url, 'GET', function(error, response, body) {
if (error) {
this.log('HomeSeer get thermostat current heating cooling state function failed: %s', error.message);
callback( error, 0 );
}
else {
var status = JSON.parse( body );
var value = status.Devices[0].value;
this.log('HomeSeer get thermostat current heating cooling state function succeeded: value=' + value );
if( this.config.stateOffValues.indexOf(value) != -1 )
callback( null, 0 );
else if( this.config.stateHeatValues.indexOf(value) != -1 )
callback( null, 1 );
else if( this.config.stateCoolValues.indexOf(value) != -1 )
callback( null, 2 );
else if( this.config.stateAutoValues.indexOf(value) != -1 )
callback( null, 3 );
else {
this.log( "Error: value for thermostat current heating cooling state not in offValues, heatValues, coolValues or autoValues" );
callback( null, 0 );
}
}
}.bind(this));
},
setThermostatCurrentHeatingCoolingState: function(state, callback) {
this.log("Setting thermostat current heating cooling state to %s", state);
var ref = this.config.controlRef;
var value = 0;
if( state == 0 )
value = this.config.controlOffValue;
else if( state == 1 )
value = this.config.controlHeatValue;
else if( state == 2 )
value = this.config.controlCoolValue;
else if( state == 3 )
value = this.config.controlAutoValue;
var url = this.access_url + "request=controldevicebyvalue&ref=" + ref + "&value=" + value;
httpRequest(url, 'GET', function(error, response, body) {
if (error) {
this.log('HomeSeer set thermostat current heating cooling state function failed: %s', error.message);
callback(error);
}
else {
this.log('HomeSeer set thermostat current heating cooling state function succeeded!');
callback();
}
}.bind(this));
},
getThermostatTargetTemperature: function(callback) {
var ref = this.config.setPointRef;
var url = this.access_url + "request=getstatus&ref=" + ref;
httpRequest(url, 'GET', function(error, response, body) {
if (error) {
this.log('HomeSeer get thermostat target temperature function failed: %s', error.message);
callback( error, 0 );
}
else {
var status = JSON.parse( body );
var value = status.Devices[0].value;
this.log('HomeSeer get thermostat target temperature function succeeded: value=' + value );
if( this.config.temperatureUnit == "F" ) {
value = (value-32)*5/9;
}
callback( null, value );
}
}.bind(this));
},
setThermostatTargetTemperature: function(temperature, callback) {
this.log("Setting thermostat target temperature to %s", temperature);
if( this.config.temperatureUnit == "F" ) {
temperature = temperature*9/5+32;
}
var ref = this.config.setPointRef;
var url = this.access_url + "request=controldevicebyvalue&ref=" + ref + "&value=" + temperature;
httpRequest(url, 'GET', function(error, response, body) {
if (error) {
this.log('HomeSeer set thermostat target temperature function failed: %s', error.message);
callback(error);
}
else {
this.log('HomeSeer set thermostat target temperature function succeeded!');
callback();
}
}.bind(this));
},
getThermostatTemperatureDisplayUnits: function(callback) {
if( this.config.temperatureUnit == "F" )
callback( null, 1 );
else
callback( null, 0 );
},
getLowBatteryStatus: function(callback) {
var ref = this.config.batteryRef;
var url = this.access_url + "request=getstatus&ref=" + ref;
httpRequest(url, 'GET', function(error, response, body) {
if (error) {
this.log('HomeSeer get battery status function failed: %s', error.message);
callback( error, 0 );
}
else {
var status = JSON.parse( body );
var value = status.Devices[0].value;
var minValue = 10;
this.log('HomeSeer get battery status function succeeded: value=' + value );
if( this.config.batteryThreshold ) {
minValue = this.config.batteryThreshold;
}
if( value > minValue )
callback( null, 0 );
else
callback( null, 1 );
}
}.bind(this));
},
getServices: function() {
var services = []
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "HomeSeer")
.setCharacteristic(Characteristic.Model, this.model )
.setCharacteristic(Characteristic.SerialNumber, "HS " + this.config.type + " ref " + this.ref);
services.push( informationService );
switch( this.config.type ) {
case "Lightbulb": {
var lightbulbService = new Service.Lightbulb();
lightbulbService
.getCharacteristic(Characteristic.On)
.on('set', this.setPowerState.bind(this))
.on('get', this.getPowerState.bind(this));
if( this.config.can_dim == null || this.config.can_dim == true ) {
lightbulbService
.addCharacteristic(new Characteristic.Brightness())
.on('set', this.setValue.bind(this))
.on('get', this.getValue.bind(this));
}
services.push( lightbulbService );
break;
}
case "Fan": {
var fanService = new Service.Fan();
fanService
.getCharacteristic(Characteristic.On)
.on('set', this.setPowerState.bind(this))
.on('get', this.getPowerState.bind(this));
services.push( fanService );
break;
}
case "Switch": {
var switchService = new Service.Switch();
switchService
.getCharacteristic(Characteristic.On)
.on('set', this.setPowerState.bind(this))
.on('get', this.getPowerState.bind(this));
services.push( switchService );
break;
}
case "Outlet": {
var outletService = new Service.Outlet();
outletService
.getCharacteristic(Characteristic.On)
.on('set', this.setPowerState.bind(this))
.on('get', this.getPowerState.bind(this));
services.push( outletService );
break;
}
case "TemperatureSensor": {
var temperatureSensorService = new Service.TemperatureSensor();
temperatureSensorService
.getCharacteristic(Characteristic.CurrentTemperature)
.on('get', this.getTemperature.bind(this));
temperatureSensorService
.getCharacteristic(Characteristic.CurrentTemperature).setProps( {minValue: -100} );
if( this.config.batteryRef ) {
temperatureSensorService
.addCharacteristic(new Characteristic.StatusLowBattery())
.on('get', this.getLowBatteryStatus.bind(this));
}
services.push( temperatureSensorService );
break;
}
case "ContactSensor": {
var contactSensorService = new Service.ContactSensor();
contactSensorService
.getCharacteristic(Characteristic.ContactSensorState)
.on('get', this.getPowerState.bind(this));
if( this.config.batteryRef ) {
contactSensorService
.addCharacteristic(new Characteristic.StatusLowBattery())
.on('get', this.getLowBatteryStatus.bind(this));
}
services.push( contactSensorService );
break;
}
case "MotionSensor": {
var motionSensorService = new Service.MotionSensor();
motionSensorService
.getCharacteristic(Characteristic.MotionDetected)
.on('get', this.getPowerState.bind(this));
if( this.config.batteryRef ) {
motionSensorService
.addCharacteristic(new Characteristic.StatusLowBattery())
.on('get', this.getLowBatteryStatus.bind(this));
}
services.push( motionSensorService );
break;
}
case "LeakSensor": {
var leakSensorService = new Service.LeakSensor();
leakSensorService
.getCharacteristic(Characteristic.LeakDetected)
.on('get', this.getPowerState.bind(this));
if( this.config.batteryRef ) {
leakSensorService
.addCharacteristic(new Characteristic.StatusLowBattery())
.on('get', this.getLowBatteryStatus.bind(this));
}
services.push( leakSensorService );
break;
}
case "LightSensor": {
var lightSensorService = new Service.LightSensor();
lightSensorService
.getCharacteristic(Characteristic.CurrentAmbientLightLevel)
.on('get', this.getValue.bind(this));
if( this.config.batteryRef ) {
lightSensorService
.addCharacteristic(new Characteristic.StatusLowBattery())
.on('get', this.getLowBatteryStatus.bind(this));
}
services.push( lightSensorService );
break;
}
case "HumiditySensor": {
var humiditySensorService = new Service.HumiditySensor();
humiditySensorService
.getCharacteristic(Characteristic.CurrentRelativeHumidity)
.on('get', this.getValue.bind(this));
if( this.config.batteryRef ) {
humiditySensorService
.addCharacteristic(new Characteristic.StatusLowBattery())
.on('get', this.getLowBatteryStatus.bind(this));
}
services.push( humiditySensorService );
break;
}
case "OccupancySensor": {
var occupancySensorService = new Service.OccupancySensor();
occupancySensorService
.getCharacteristic(Characteristic.OccupancyDetected)
.on('get', this.getPowerState.bind(this));
if( this.config.batteryRef ) {
occupancySensorService
.addCharacteristic(new Characteristic.StatusLowBattery())
.on('get', this.getLowBatteryStatus.bind(this));
}
services.push( occupancySensorService );
break;
}
case "SmokeSensor": {
var smokeSensorService = new Service.SmokeSensor();
smokeSensorService
.getCharacteristic(Characteristic.SmokeDetected)
.on('get', this.getPowerState.bind(this));
if( this.config.batteryRef ) {
temperatureSensorService
.addCharacteristic(new Characteristic.StatusLowBattery())
.on('get', this.getLowBatteryStatus.bind(this));
}
services.push( smokeSensorService );
break;
}
case "Door": {
var doorService = new Service.Door();
doorService
.getCharacteristic(Characteristic.CurrentPosition)
.on('get', this.getValue.bind(this));
doorService
.getCharacteristic(Characteristic.TargetPosition)
.on('set', this.setValue.bind(this));
services.push( doorService );
break;
}
case "Battery": {
this.config.batteryRef = this.ref;
var batteryService = new Service.BatteryService();
batteryService
.getCharacteristic(Characteristic.BatteryLevel)
.on('get', this.getValue.bind(this));
batteryService
.getCharacteristic(Characteristic.StatusLowBattery)
.on('get', this.getLowBatteryStatus.bind(this));
services.push( batteryService );
break;
}
case "Thermostat": {
var thermostatService = new Service.Thermostat();
thermostatService
.getCharacteristic(Characteristic.CurrentTemperature)
.on('get', this.getTemperature.bind(this));
thermostatService
.getCharacteristic(Characteristic.TargetTemperature)
.on('get', this.getThermostatTargetTemperature.bind(this));
if( this.config.setPointReadOnly === null || this.config.setPointReadOnly === false )
thermostatService
.getCharacteristic(Characteristic.TargetTemperature)
.on('set', this.setThermostatTargetTemperature.bind(this));
thermostatService
.getCharacteristic(Characteristic.CurrentHeatingCoolingState)
.on('get', this.getThermostatCurrentHeatingCoolingState.bind(this));
thermostatService
.getCharacteristic(Characteristic.TargetHeatingCoolingState)
.on('get', this.getThermostatCurrentHeatingCoolingState.bind(this));
thermostatService
.getCharacteristic(Characteristic.TargetHeatingCoolingState)
.on('set', this.setThermostatCurrentHeatingCoolingState.bind(this));
thermostatService
.getCharacteristic(Characteristic.TemperatureDisplayUnits)
.on('get', this.getThermostatTemperatureDisplayUnits.bind(this));
services.push( thermostatService );
break;
}
default:{
var lightbulbService = new Service.Lightbulb();
lightbulbService
.getCharacteristic(Characteristic.On)
.on('set', this.setPowerState.bind(this))
.on('get', this.getPowerState.bind(this));
lightbulbService
.addCharacteristic(new Characteristic.Brightness())
.on('set', this.setValue.bind(this))
.on('get', this.getValue.bind(this));
services.push( lightbulbService );
break;
}
}
return services;
}
}
function HomeSeerEvent(log, platformConfig, eventConfig ) {
this.log = log;
this.config = eventConfig;
this.name = eventConfig.eventName
this.model = "HomeSeer Event";
this.access_url = platformConfig["host"] + "/JSON?";
this.launch_url = this.access_url + "request=runevent&group=" + encodeURIComponent(this.config.eventGroup) + "&name=" + encodeURIComponent(this.config.eventName);
if( this.config.name )
this.name = this.config.name;
}
HomeSeerEvent.prototype = {
identify: function(callback) {
callback();
},
launchEvent: function(value, callback) {
this.log("Setting event value to %s", value);
httpRequest(this.launch_url, 'GET', function(error, response, body) {
if (error) {
this.log('HomeSeer run event function failed: %s', error.message);
callback(error);
}
else {
this.log('HomeSeer run event function succeeded!');
callback();
}
}.bind(this));
},
getServices: function() {
var services = []
var informationService = new Service.AccessoryInformation();
informationService
.setCharacteristic(Characteristic.Manufacturer, "HomeSeer")
.setCharacteristic(Characteristic.Model, this.model )
.setCharacteristic(Characteristic.SerialNumber, "HS Event " + this.config.eventGroup + " " + this.config.eventName);
services.push( informationService );
var switchService = new Service.Switch();
switchService
.getCharacteristic(Characteristic.On)
.on('set', this.launchEvent.bind(this));
services.push( switchService );
return services;
}
}
module.exports.platform = HomeSeerPlatform;

View File

@@ -44,7 +44,11 @@ NestPlatform.prototype = {
function NestThermostatAccessory(log, name, device, deviceId) {
// device info
this.name = name;
if (name) {
this.name = name;
} else {
this.name = "Nest";
}
this.model = device.model_version;
this.serial = device.serial_number;
this.deviceId = deviceId;
@@ -390,4 +394,4 @@ NestThermostatAccessory.prototype = {
}
module.exports.accessory = NestThermostatAccessory;
module.exports.platform = NestPlatform;
module.exports.platform = NestPlatform;

265
platforms/Telldus.js Normal file
View File

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

View File

@@ -23,6 +23,7 @@ function YamahaAVRPlatform(log, config){
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});
}
@@ -30,24 +31,24 @@ function YamahaAVRPlatform(log, config){
YamahaAVRPlatform.AudioVolume = function() {
Characteristic.call(this, 'Audio Volume', '00001001-0000-1000-8000-135D67EC4377');
this.format = 'uint8';
this.unit = 'percentage';
this.maximumValue = 100;
this.minimumValue = 0;
this.stepValue = 1;
this.readable = true;
this.writable = true;
this.supportsEventNotification = true;
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.format = 'bool';
this.readable = true;
this.writable = true;
this.supportsEventNotification = true;
this.setProps({
format: Characteristic.Formats.UINT8,
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY]
});
this.value = this.getDefaultValue();
};
inherits(YamahaAVRPlatform.Muting, Characteristic);
@@ -75,25 +76,44 @@ YamahaAVRPlatform.prototype = {
var accessories = [];
var timer, timeElapsed = 0, checkCyclePeriod = 5000;
browser.on('serviceUp', function(service){
// 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];
that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\"");
var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig);
accessories.push(accessory);
if(accessories.length >= this.expectedDevices)
timeoutFunction(); // We're done, call the timeout function now.
//callback([accessory]);
}, function(err){
return;
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
@@ -119,15 +139,15 @@ YamahaAVRPlatform.prototype = {
}
};
function YamahaAVRAccessory(log, config, mdnsService, yamaha, sysConfig) {
function YamahaAVRAccessory(log, config, name, yamaha, sysConfig) {
this.log = log;
this.config = config;
this.mdnsService = mdnsService;
this.yamaha = yamaha;
this.sysConfig = sysConfig;
this.name = mdnsService.name;
this.serviceName = mdnsService.name + " Speakers";
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;

View File

@@ -11,6 +11,7 @@ function ZWayServerPlatform(log, config){
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;
@@ -82,7 +83,20 @@ ZWayServerPlatform.prototype = {
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...");
@@ -90,10 +104,10 @@ ZWayServerPlatform.prototype = {
//Note: Order matters!
var primaryDeviceClasses = [
"thermostat",
"sensorMultilevel.Temperature",
"switchMultilevel",
"switchBinary",
"sensorBinary.Door/Window"
"sensorBinary.Door/Window",
"sensorMultilevel.Temperature"
];
var that = this;
@@ -109,14 +123,39 @@ ZWayServerPlatform.prototype = {
var groupedDevices = {};
for(var i = 0; i < devices.length; i++){
var vdev = devices[i];
if(vdev.tags.indexOf("Homebridge:Skip") >= 0) { debug("Tag says skip!"); continue; }
var gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2');
var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, primary: undefined});
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});
gd.devices.push(vdev);
gd.types[ZWayServerPlatform.getVDevTypeKey(vdev)] = gd.devices.length - 1;
gd.types[vdev.deviceType] = gd.devices.length - 1; // also include the deviceType only as a possibility
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 = gd.devices.length - 1;
//gd.types[tk] = gd.primary;
}
if(gd.types[tk] === undefined){
gd.types[tk] = gd.devices.length - 1;
} else {
gd.extras[tk] = gd.extras[tk] || [];
gd.extras[tk].push(gd.devices.length - 1);
}
if(tk !== vdev.deviceType) gd.types[vdev.deviceType] = gd.devices.length - 1; // also include the deviceType only as a possibility
}
//TODO: Make a second pass, re-splitting any devices that don't make sense together
for(var gdid in groupedDevices) {
if(!groupedDevices.hasOwnProperty(gdid)) continue;
@@ -128,12 +167,17 @@ ZWayServerPlatform.prototype = {
}
var accessory = null;
for(var ti = 0; ti < primaryDeviceClasses.length; ti++){
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.");
//debug("Using primary device with type " + primaryDeviceClasses[ti] + ", " + name + " (" + pd.id + ") as primary.");
accessory = new ZWayServerAccessory(name, gd, that);
break;
}
@@ -145,7 +189,6 @@ ZWayServerPlatform.prototype = {
foundAccessories.push(accessory);
}
//foundAccessories = foundAccessories.slice(0, 10); // Limit to a few devices for testing...
callback(foundAccessories);
// Start the polling process...
@@ -172,6 +215,11 @@ ZWayServerPlatform.prototype = {
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++){
@@ -222,30 +270,92 @@ ZWayServerAccessory.prototype = {
});
},
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));
break;
case "sensorMultilevel.Temperature":
services.push(new Service.TemperatureSensor(vdev.metrics.title));
break;
case "switchMultilevel":
services.push(new Service.Lightbulb(vdev.metrics.title));
break;
case "battery.Battery":
services.push(new Service.BatteryService(vdev.metrics.title));
case "thermostat":
services.push(new Service.Thermostat(vdev.metrics.title, vdev.id));
break;
case "switchBinary":
services.push(new Service.Switch(vdev.metrics.title));
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":
services.push(new Service.GarageDoorOpener(vdev.metrics.title));
services.push(new Service.GarageDoorOpener(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));
services.push(new Service.LightSensor(vdev.metrics.title, vdev.id));
break;
}
@@ -272,6 +382,8 @@ ZWayServerAccessory.prototype = {
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
@@ -309,8 +421,8 @@ ZWayServerAccessory.prototype = {
return null;
}
,
configureCharacteristic: function(cx, vdev){
var that = this;
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] = [];
@@ -324,12 +436,18 @@ ZWayServerAccessory.prototype = {
cx.value = cx.zway_getValueFromVDev(vdev);
cx.on('get', function(callback, context){
debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"...");
callback(false, that.name);
callback(false, accessory.name);
});
cx.writable = false;
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;
@@ -381,6 +499,76 @@ ZWayServerAccessory.prototype = {
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));
cx.writeable = false;
//cx.on('set', function(level, callback){
// this.command(vdev, "exact", {level: "on", "color.r": 255, "color.g": 0, "color.b": 0}).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));
cx.writeable = false;
//cx.on('set', function(level, callback){
// this.command(vdev, "exact", {level: "on", "color.r": 255, "color.g": 0, "color.b": 0}).then(function(result){
// callback();
// });
//}.bind(this));
return cx;
}
if(cx instanceof Characteristic.CurrentTemperature){
cx.zway_getValueFromVDev = function(vdev){
return vdev.metrics.level;
@@ -528,7 +716,7 @@ ZWayServerAccessory.prototype = {
if(cx instanceof Characteristic.StatusLowBattery){
cx.zway_getValueFromVDev = function(vdev){
return vdev.metrics.level <= that.platform.batteryLow ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
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){
@@ -591,14 +779,29 @@ ZWayServerAccessory.prototype = {
success = false;
debug("ERROR! Failed to configure required characteristic \"" + service.characteristics[i].displayName + "\"!");
}
cx = this.configureCharacteristic(cx, vdev);
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);
var vdev = this.getVDevForCharacteristic(cx, vdev);
if(!vdev) continue;
cx = this.configureCharacteristic(cx, vdev);
if(cx) service.addCharacteristic(cx);
//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;
}
@@ -606,17 +809,30 @@ ZWayServerAccessory.prototype = {
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, "VDev-" + this.devDesc.devices[this.devDesc.primary].h) //FIXME: Is this valid?);
.setCharacteristic(Characteristic.SerialNumber, accId);
var services = [informationService];
services = services.concat(this.getVDevServices(this.devDesc.devices[this.devDesc.primary]));
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"]){
@@ -655,7 +871,7 @@ ZWayServerAccessory.prototype = {
extraCxs = []; // to wipe out any already setup cxs.
break;
}
this.configureCharacteristic(cx, vdev2);
this.configureCharacteristic(cx, vdev2, service);
extraCxs.push(cx);
}
for(var j = 0; j < extraCxs.length; j++)