diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index db93fd4..5801249 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -8,9 +8,12 @@ new features include: - Window - WindowCovering - ContactSensor -New 2015-0918: +New 2015-09-18: - Services Switch and Outlet - Code cleanup +New 2015-09-19: +- GarageDoorOpener Service +- MotionSensor Service * */ var Service = require("HAP-NodeJS").Service; @@ -148,20 +151,7 @@ KNXDevice.prototype = { this.knxread (groupAddresses); } }, -/** Write special type routines - * - */ - // special types - knxwrite_percent: function(callback, groupAddress, value) { - var numericValue = 0; - if (value && value>=0 && value <= 100) { - numericValue = 255*value/100; // convert 1..100 to 1..255 for KNX bus - } else { - this.log("[ERROR] Percentage value ot of bounds "); - numericValue = 0; - } - this.knxwrite(callback, groupAddress,'DPT5',numericValue); - }, + /** Registering routines * */ @@ -210,11 +200,22 @@ KNXDevice.prototype = { this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); var hk_value = Math.round(val*10)/10; if (hk_value>=characteristic.minimumValue && hk_value<=characteristic.maximumValue) { - characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decoimal for HomeKit + characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decimal for HomeKit } else { this.log("Value %s out of bounds %s...%s ",hk_value, characteristic.minimumValue, characteristic.maximumValue); } - + }.bind(this)); + }, + //integer + knxregister_int: function(addresses, characteristic) { + this.log("knx registering FLOAT " + addresses); + knxd_registerGA(addresses, function(val, src, dest, type){ + this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); + if (val>=(characteristic.minimumValue || 0) && val<=(characteristic.maximumValue || 255)) { + characteristic.setValue(val, undefined, 'fromKNXBus'); + } else { + this.log("Value %s out of bounds %s...%s ",hk_value, (characteristic.minimumValue || 0), (characteristic.maximumValue || 255)); + } }.bind(this)); }, knxregister_HVAC: function(addresses, characteristic) { @@ -322,6 +323,21 @@ KNXDevice.prototype = { this.knxwrite(callback, gaddress,'DPT5',numericValue); } }, + setInt: function(value, callback, context, gaddress) { + if (context === 'fromKNXBus') { + this.log("event ping pong, exit!"); + if (callback) { + callback(); + } + } else { + var numericValue = 0; + if (value && value>=0 && value<=255) { + numericValue = value; // assure 1..255 for KNX bus + } + this.log("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!"); @@ -406,14 +422,20 @@ KNXDevice.prototype = { 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: + default: { this.log("[ERROR] unknown type passed"); - throw new Error("[ERROR] unknown type passed"); + throw new Error("[ERROR] unknown type passed"); + } } } if ([config.Set].concat(config.Listen || []).length>0) { @@ -432,6 +454,9 @@ KNXDevice.prototype = { 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; @@ -498,6 +523,64 @@ KNXDevice.prototype = { } 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("GarageDoorOpener CurrentDoorState characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.CurrentDoorState, "Int", config.CurrentDoorState); + } + if (config.TargetDoorState) { + this.log("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("GarageDoorOpener ObstructionDetected characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.ObstructionDetected, "Bool", config.ObstructionDetected); + } + //optionals + if (config.LockCurrentState) { + this.log("GarageDoorOpener LockCurrentState characteristic enabled"); + myService.addCharacteristic(Characteristic.LockCurrentState); + this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Int", config.LockCurrentState); + } + if (config.LockTargetState) { + this.log("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; @@ -584,6 +667,48 @@ KNXDevice.prototype = { //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("MotionSensor MotionDetected characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.MotionDetected, "Bool", config.MotionDetected); + } + //optionals + if (config.StatusActive) { + this.log("MotionSensor StatusActive characteristic enabled"); + myService.addCharacteristic(Characteristic.StatusActive); + this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive); + } + if (config.StatusFault) { + this.log("MotionSensor StatusFault characteristic enabled"); + myService.addCharacteristic(Characteristic.StatusFault); + this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault); + } + if (config.StatusTampered) { + this.log("MotionSensor StatusTampered characteristic enabled"); + myService.addCharacteristic(Characteristic.StatusTampered); + this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered); + } + if (config.StatusLowBattery) { + this.log("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); @@ -657,11 +782,6 @@ KNXDevice.prototype = { // TargetTemperature if available if (config.TargetTemperature) { this.log("Thermostat TargetTemperature characteristic enabled"); - - // DEBUG - console.log("default value: " + myService.getCharacteristic(Characteristic.TargetTemperature).value); - // DEBUG - // default boundary too narrow for thermostats myService.getCharacteristic(Characteristic.TargetTemperature).minimumValue=0; // °C myService.getCharacteristic(Characteristic.TargetTemperature).maximumValue=40; // °C @@ -790,7 +910,7 @@ KNXDevice.prototype = { informationService .setCharacteristic(Characteristic.Manufacturer, "Opensource Community") .setCharacteristic(Characteristic.Model, "KNX Universal Device") - .setCharacteristic(Characteristic.SerialNumber, "Version 1.1"); + .setCharacteristic(Characteristic.SerialNumber, "Version 1.1.2"); accessoryServices.push(informationService); @@ -814,6 +934,9 @@ KNXDevice.prototype = { case "ContactSensor": accessoryServices.push(this.getContactSenserService(configService)); break; + case "GarageDoorOpener": + accessoryServices.push(this.getGarageDoorOpenerService(configService)); + break; case "Lightbulb": accessoryServices.push(this.getLightbulbService(configService)); break; @@ -823,6 +946,9 @@ KNXDevice.prototype = { case "LockMechanism": accessoryServices.push(this.getLockMechanismService(configService)); break; + case "MotionSensor": + accessoryServices.push(this.getMotionSensorService(configService)); + break; case "Switch": accessoryServices.push(this.getSwitchService(configService)); break; diff --git a/platforms/KNX-sample-config.json b/platforms/KNX-sample-config.json index d74a14f..2c29736 100644 --- a/platforms/KNX-sample-config.json +++ b/platforms/KNX-sample-config.json @@ -7,8 +7,8 @@ }, "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 knxdevice.md file in folder accessories!" + "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 knxdevice.md file in folder accessories!", "platforms": [ { "platform": "KNX", @@ -74,7 +74,7 @@ }, { "accessory_type": "knxdevice", - "description":"sample device with multiple services. Multiple services of different types are widely supported", + "description": "sample device with multiple services. Multiple services of different types are widely supported", "name": "Office", "services": [ { @@ -102,11 +102,11 @@ "type": "WindowCovering", "description": "iOS9 Window covering (blinds etc) type, still WIP", "name": "Blinds", - "Target": { + "TargetPosition": { "Set": "1/2/3", "Listen": "1/2/4" }, - "Current": { + "CurrentPosition": { "Set": "1/3/1", "Listen": "1/3/2" }, @@ -115,22 +115,40 @@ } } ] - },{ - "accessory_type": "knxdevice", - - "description":"sample contact sensor device", - "name": "Office", - "services": [ - { - "type": "ContactSensor", - "name": "Office Door", - "ContactSensorState": { - "Listen": "5/3/5" + }, + { + "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.md b/platforms/KNX.md index 937af45..c3b4fe3 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -71,47 +71,83 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres # Supported Services and their characteristics ## ContactSensor -- ContactSensorState: DPT 1, 0 as contact **OR** -- ContactSensorStateContact1: DPT 1, 1 as contact +- ContactSensorState: DPT 1.002, 0 as contact **OR** +- 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; + -- StatusActive: DPT 1, 1 as true -- StatusFault: DPT 1, 1 as true -- StatusTampered: DPT 1, 1 as true -- StatusLowBattery: DPT 1, 1 as true ## Lightbulb - - On: DPT 1, 1 as on, 0 as off - - Brightness: DPT5 percentage, 100% (=255) the brightest + - On: DPT 1.001, 1 as on, 0 as off + - Brightness: DPT5.001 percentage, 100% (=255) the brightest ## LightSensor -- CurrentAmbientLightLevel: DPT 9, 0 to 100000 Lux +- CurrentAmbientLightLevel: DPT 9.004, 0 to 100000 Lux -## LockMechanism +## LockMechanism (This is poorly mapped!) - LockCurrentState: DPT 1, 1 as secured **OR (but not both:)** - LockCurrentStateSecured0: DPT 1, 0 as secured - LockTargetState: DPT 1, 1 as secured **OR** - 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, 1 as on, 0 as off - - OutletInUse: DPT 1, 1 as on, 0 as off + - On: DPT 1.001, 1 as on, 0 as off + - OutletInUse: DPT 1.011, 1 as on, 0 as off ## Switch - - On: DPT 1, 1 as on, 0 as off + - On: DPT 1.001, 1 as on, 0 as off ## TemperatureSensor -- CurrentTemperature: DPT9 in °C [listen only] +- CurrentTemperature: DPT9.001 in °C [listen only] ## Thermostat -- CurrentTemperature: DPT9 in °C [listen only] -- TargetTemperature: DPT9, values 0..40°C only, all others are ignored -- CurrentHeatingCoolingState: DPT5 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only] -- TargetHeatingCoolingState: as above +- CurrentTemperature: DPT9.001 in °C [listen only] +- 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 percentage -- TargetPosition: DPT5 percentage -- PositionState: DPT5 value [listen only] +- CurrentPosition: DPT5.001 percentage +- TargetPosition: DPT5.001 percentage +- PositionState: DPT5.005 value [listen only: 0 Increasing, 1 Decreasing, 2 Stopped] ## WindowCovering - CurrentPosition: DPT5 percentage