diff --git a/config-sample.json b/config-sample.json index 0e04cfe..a199ea6 100644 --- a/config-sample.json +++ b/config-sample.json @@ -115,7 +115,15 @@ "username": "your netatmo username", "password": "your netatmo password" } - } + }, + { + "platform": "HomeMatic", + "name": "HomeMatic CCU", + "ccu_ip": "192.168.0.100", + "filter_device":[], + "filter_channel":["BidCos-RF.KEQXXXXXXX:4", "BidCos-RF.LEQXXXXXXX:2"], + "outlets":[ "BidCos-RF.KEQXXXXXXX:4","BidCos-RF.IEQXXXXXXX:1"] + }, ], "accessories": [ diff --git a/package.json b/package.json index eb9e811..e1de444 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "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", diff --git a/platforms/HomeMatic.js b/platforms/HomeMatic.js new file mode 100644 index 0000000..a451451 --- /dev/null +++ b/platforms/HomeMatic.js @@ -0,0 +1,411 @@ +"use strict"; +// +// Homematic Platform Shim for HomeBridge +// +// V0.1 - 2015/10/29 +// - initial version +// - reintegrated Homematic Platform fork from https://github.com/thkl/homebridge/tree/xmlrpc + + +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)) { + + if ((ch.type == "SWITCH") || (ch.type == "BLIND") || (ch.type == "SHUTTER_CONTACT") || (ch.type == "DIMMER") || (ch.type == "CLIMATECONTROL_RT_TRANSCEIVER") ||  (ch.type == "MOTION_DETECTOR") ||  (ch.type == "KEYMATIC")) { + // 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); + that.foundAccessories.push(accessory); + } + + + } else { + that.log(device.name + " has no address"); + } + + }); + } else { + that.log(device.name + " has no channels or is filtered"); + } + + }); + + /* + accessory = new HomeMaticGenericChannel(that.log, that, "1234" , "DummyKM" , "KEYMATIC" , "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; + } + + }, + + + 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/HomematicChannel.js b/platforms/HomematicChannel.js new file mode 100644 index 0000000..5b6cff2 --- /dev/null +++ b/platforms/HomematicChannel.js @@ -0,0 +1,657 @@ +"use strict"; +var types = require("hap-nodejs/accessories/types.js"); + + +function HomeMaticGenericChannel(log, platform, id, name, type, adress, special) { + this.name = name; + this.type = type; + this.adress = adress; + this.log = log; + this.platform = platform; + this.state = []; + this.eventupdate = false; + this.special = special; + this.currentStateCharacteristic = []; + this.reverseDP = []; +} + + + +HomeMaticGenericChannel.prototype = { + + + // Return current States + query: function(dp, callback) { + if (this.state[dp] !== undefined) { + callback(this.state[dp]); + } else { + // that.log("No cached Value found start fetching and send temp 0 back"); + this.remoteGetValue(dp); + callback(0); + } + + }, + + dpvalue: function(dp, fallback) { + if (this.state[dp] !== undefined) { + return (this.state[dp]); + } else { + return fallback; + } + }, + + remoteGetValue: function(dp) { + var that = this; + that.platform.getValue(that.adress, dp, function(newValue) { + that.log("Remote Value Response for " + that.adress + "." + dp + "->" + 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; + }, + + reverse: function(value) { + if (value == "true") return "false"; + if (value == "false") return "true"; + if (value === 0) return 1; + if (value === 1) return 0; + if (value == "0") return "1"; + if (value == "1") return "0"; + return value; + }, + + cache: function(dp, value) { + var that = this; + + if ((that.reverseDP[dp] !== undefined) && (that.reverseDP[dp] === true)) { + value = that.reverse(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); + } + }, + + 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) { + + var 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.reverseDP["STATE"] = true; + 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 + } + + ); + } + + 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" + }); + } + + 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" + }); + } + + if (this.type == "CLIMATECONTROL_RT_TRANSCEIVER") { + + 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: ["pr", "ev"], + 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) { + that.delayed("set", "SET_TEMPERATURE", value, 500); + }, + onRead: function(callback) { + that.query("SET_TEMPERATURE", callback); + + }, + onRegister: function(characteristic) { + that.currentStateCharacteristic["SET_TEMPERATURE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("SET_TEMPERATURE"); + }, + 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") { + return types.THERMOSTAT_STYPE; + } + + if (this.type == "SHUTTER_CONTACT") { + return types.CONTACT_SENSOR_STYPE; + } + + if (this.type == "MOTION_DETECTOR") { + return types.MOTION_SENSOR_STYPE; + } + + + if (this.type == "KEYMATIC") { + return types.LOCK_MECHANISM_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 = HomeMaticGenericChannel; \ No newline at end of file