homebridge-punt
Version:
Fhem-Gateway and Accessory-Simulator Plugin for Homebridge
318 lines (250 loc) • 10.2 kB
JavaScript
'use strict';
var Utils = require('./utils.js').Utils;
var Gateway = require('./gateway.js').Gateway;
var Service, Characteristic, PuntView, Simulator;
var global, gateway, puntview, simulator;
var accessory_config, master_accessory, reload, reload_name;
Number.prototype.pad = function (len) {
return (new Array(len+1).join("0") + this).slice(-len);
}
module.exports = {
Accessory: Accessory
}
function Accessory(params) {
this.log = params.log;
//this.log.debug("params.p_config: %s", JSON.stringify(params.p_config));
PuntView = params.PuntView;
Simulator = params.Simulator;
Service = params.Service;
Characteristic = params.Characteristic;
global = params.p_config.global || { "optionCharacteristics": ""}
gateway = params.p_config.gateway;
puntview = params.p_config.puntview || { "run": false };
simulator = params.p_config.simulator || { "run": false };
accessory_config = params.accessory_config;
reload = params.reload;
reload_name = params.reload_name;
//this.log.debug("Accessory %s", JSON.stringify(accessory_config));
this.uuid = params.uuid;
this.name = accessory_config.name;
this.service_name = accessory_config.service;
this.i_device = {};
this.i_value = {};
this.i_label = {};
this.i_props = {};
this.service;
var g_params = {
"log": this.log,
"p_config": params.p_config,
"Characteristic": Characteristic,
"name": this.name,
"service_name": this.service_name,
"i_device": this.i_device,
"i_value": this.i_value
};
if (gateway.run) {
this.Gateway = new Gateway(g_params);
}
}
Accessory.prototype.save_and_setValue = function (trigger, t_characteristic, value) {
var sc = this.service.getCharacteristic(Characteristic[t_characteristic]);
//this.log.debug("Accessory.save_and_setValue %s %s %s", trigger, t_characteristic, value);
//this.log.debug("Accessory.save_and_setValue %s", JSON.stringify(sc));
switch (sc.props.format) {
case "bool":
if (value == "undef") value = false;
value = (value == 0 || value == false) ? false : true;
break;
case "int":
case "uint8":
case "uint16":
case "unit32":
case "float":
if (value == "undef") value = 0;
if (value < sc.props.minValue || value > sc.props.maxalue) {
this.log.error("Accessory.save_and_setValue %s %s value >%s< outside range [trigger: %s].", this.name, t_characteristic, value, trigger);
}
break;
default:
// todo string, tlv8,
this.log.warn("Accessory.save_and_setValue %s %s %s format unknown [trigger: %s].", this.name, t_characteristic, value, trigger);
}
this.i_value[t_characteristic] = value;
this.setLabel(trigger, t_characteristic);
var context = this.i_label[t_characteristic];
//context is also used by the hap-server ('get' and 'set' event) - "context": {"keepalive":true, ...
//this.log.debug("Accessory.save_and_setValue %s %s %s %s %s ", trigger, this.name, t_characteristic, value, JSON.stringify(context));
if (typeof(context) !== "undefined") {
sc.setValue(value, null, context);
}
else {
sc.setValue(value);
}
}
Accessory.prototype.setLabel = function(trigger, t_characteristic) {
var now = new Date();
var timestamp = now.getHours().pad(2)+":"+now.getMinutes().pad(2)+":"+now.getSeconds().pad(2);
// +","+now.getMilliseconds();
this.i_label[t_characteristic] = {
"timestamp": timestamp,
"trigger": trigger
};
}
Accessory.prototype.addService = function(newAccessory) {
try {
this.service = new Service[this.service_name](this.name);
}
catch (err) {
this.log.error("Accessory.addService '%s' '%s' undefined.", this.name, this.service_name);
process.exit(1);
}
newAccessory.addService(this.service);
}
Accessory.prototype.configureAccessory = function(accessory) {
//this.log.debug("accessory.configureAccessory %s", JSON.stringify(accessory.services));
this.service = accessory.getService(Service[this.service_name]);
this.service.on('characteristic-change', this.characteristic_change.bind(this));
accessory.on('identify', function(paired, callback) {this.identify(paired, callback)}.bind(this));
//this.log.debug("Accessory.configureAccessory %s %s %s\n", this.name, this.service_name, JSON.stringify(this.service.characteristics));
var c;
for (var i in this.service.characteristics) {
c = this.service.characteristics[i].displayName.replace(/\s/g, "");
//this.log.debug("Accessory.configureAccessory %s %s %s", this.name, this.service_name, c);
if (c != "Name") {
this.allocate(c);
this.setProps(c);
this.i_value[c] = "blank";
this.i_props[c] = JSON.parse(JSON.stringify(this.service.getCharacteristic(Characteristic[c]).props));
this.setControl(c);
//this.log.debug("Accessory.configureAccessory %s %s %s %s", this.name, this.service_name, c, JSON.stringify(this.i_props));
}
}
//this.log.debug("Accessory.configureAccessory %s %s %s\n", this.name, this.service_name, JSON.stringify(accessory_config));
// note: if the accessories are restored from cachedAccessories, the optionalCharacteristics are stored in characteristics.
for (var i in this.service.optionalCharacteristics) {
c = this.service.optionalCharacteristics[i].displayName.replace(/\s/g, "");
if (typeof(accessory_config[c]) !== "undefined" || global.optionCharacteristics == "all") {
//this.log.debug("Accessory.configureAccessory %s %s optional %s", this.name, this.service_name, c);
if (c != "Name") {
this.allocate(c);
this.setProps(c);
this.i_value[c] = "blank";
this.i_props[c] = JSON.parse(JSON.stringify(this.service.getCharacteristic(Characteristic[c]).props));
this.setControl(c);
}
}
}
if (typeof(master_accessory) === "undefined") {
master_accessory = accessory.displayName;
//this.log.debug("Accessory.configureAccessory master_accessory %s", master_accessory);
}
}
Accessory.prototype.allocate = function(c) {
var self = this;
var sc = this.service.getCharacteristic(Characteristic[c]);
sc.on('get', function(callback, context) {self.get(callback, context, this.displayName)});
if (sc.props.perms.indexOf("pw") > -1) {
//this.log.debug("Accessory.allocate 'set' event %s %s", this.name, c);
sc.on('set', function(value, callback, context) {self.set(value, callback, context, this.displayName)});
}
}
Accessory.prototype.setProps = function(c) {
if (typeof(accessory_config[c]) !== "undefined") {
if (accessory_config[c] != "default") {
this.service.getCharacteristic(Characteristic[c]).setProps(accessory_config[c]);
}
//this.log.debug("Accessory.setProps %s %s %s", this.name, this.service_name, c, accessory_config[c]);
//this.log.debug("Accessory.setProps %s %s %s", this.name, this.service_name, c, Characteristic[c]);
}
}
// Controls used by jQueryMobile
Accessory.prototype.setControl = function (c) {
if (this.i_props[c].format == "bool") {
this.i_props[c].control = "button";
}
else if (this.i_props[c].minValue != null) {
this.i_props[c].control = "slider";
}
else if (/Status/.test(c)) {
this.i_props[c].control = "flipswitch";
//this.log.debug("%s %s %s", this.name, this.service_name, this.i_props[c].control);
}
else if (/Detected/.test(c)) { // exception ObstructionDetected, MotionDetected (BOOL)
this.i_props[c].control = "flipswitch_d";
}
else {
// todo general selction
switch (c) {
case "ChargingState":
case "ContactSensorState":
case "LockTargetState":
case "TargetDoorState":
this.i_props[c].control = "flipswitch";
break;
case "ContactSensorState":
this.i_props[c].control = "flipswitch_d";
break;
case "CurrentDoorState": // 4
case "LockCurrentState": // 3
case "PositionState": // 2
case "SecuritySystemCurrentState": // 4
case "SecuritySystemTargetState": // 3
case "TargetHeatingCoolingState": // 3
// select option 0 .. 4 todo 0 .. x
this.i_props[c].control = "select";
break;
default:
this.i_props[c].control = "undefined";
}
}
}
Accessory.prototype.Longpoll = function() {
if (gateway.run && gateway.longpoll) {
this.Gateway.Longpoll(master_accessory, function(trigger, t_characteristic, value) {
this.save_and_setValue(trigger, t_characteristic, value);
}.bind(this));
}
}
Accessory.prototype.get = function(callback, context, displayName) {
var c = displayName.replace(/\s/g, "");
//this.log.debug("Accessory.get %s", c);
if (gateway.run) {
this.Gateway.get(c, callback, context);
}
else {
if (simulator.run) Simulator.get(this.uuid, c, callback, context);
}
}
Accessory.prototype.set = function(value, callback, context, displayName) {
var c = displayName.replace(/\s/g, "");
//this.log.debug("Accessory.set 1 %s %s %s %s %s", this.name, this.service_name, c, value, JSON.stringify(context));
this.i_value[c] = value;
if (typeof(context) !== "undefined" && typeof(context.trigger) === "undefined") {
this.setLabel("homekit", c);
}
if (this.name == reload_name && Utils.n2b(value)) {
//this.log.debug("Accessory.set %s %s", this.name, this.service_name);
reload();
}
if (gateway.run) {
this.Gateway.set(c, value, callback, context);
}
else {
if (simulator.run) Simulator.set(this.uuid, c, value, callback, context);
}
}
Accessory.prototype.characteristic_change = function(objValue) {
//this.log.debug("Accessory.c_change %s %s", this.name, this.service_name);
if (puntview.run) PuntView.characteristic_change(this.uuid, objValue);
if (simulator.run) Simulator.characteristic_change(this.uuid, objValue);
}
Accessory.prototype.identify = function (paired, callback) {
this.log("Accessory.identify %s", this.name);
if (gateway.run && "On" in this.i_value) {
this.Gateway.identify(callback);
}
else {
// todo if (simulator.run) Simulator.identify(callback);
callback();
}
}