homebridge-texecom-connect
Version:
A Homebridge plugin to integrate with Texecom Connect SmartCom.
284 lines • 10.7 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TexecomConnectPlatform = void 0;
const events_1 = require("events");
const net = __importStar(require("net"));
const carbon_monoxide_sensor_accessory_1 = require("./accessory/carbon-monoxide-sensor-accessory");
const contact_sensor_accessory_1 = require("./accessory/contact-sensor-accessory");
const motion_sensor_accessory_1 = require("./accessory/motion-sensor-accessory");
const security_system_current_accessory_1 = require("./accessory/security-system-current-accessory");
const security_system_target_accessory_1 = require("./accessory/security-system-target-accessory");
const smoke_sensor_accessory_1 = require("./accessory/smoke-sensor-accessory");
const messages_1 = require("./interfaces/messages");
const settings_1 = require("./settings");
/**
* Texecom Connect Platform.
*/
class TexecomConnectPlatform {
accessories;
accessoryEvent;
api;
characteristic;
config;
configAccessories;
connection;
log;
service;
lastSocketResponse;
constructor(log, config, api) {
this.api = api;
this.config = config;
this.log = log;
this.accessories = [];
this.accessoryEvent = new events_1.EventEmitter();
this.characteristic = this.api.hap.Characteristic;
this.service = this.api.hap.Service;
this.lastSocketResponse = 0;
this.sanitiseConfig();
this.configAccessories = [...this.config.areas, ...this.config.zones]
.filter((accessory) => typeof accessory.name === "string"
&& accessory.name.length > 1
&& typeof accessory.number === "number"
&& accessory.number > 0);
this.api
.on("didFinishLaunching", this.onStartUp.bind(this))
.on("shutdown", this.onShutdown.bind(this));
}
/**
* Configure Accessory after Platform Init
*/
configureAccessory(accessory) {
this.accessories.push(accessory);
}
getAccessoryId(configAccessory, prefix) {
const accessoryLength = 3;
const idNumber = Number(configAccessory.number)
.toFixed()
.padStart(accessoryLength, "0");
const idPrefix = prefix
?? (configAccessory.accessory === "security"
? messages_1.Messages.disarmUpdate
: messages_1.Messages.zoneUpdate);
return idPrefix + idNumber;
}
/**
* Deprecate old Accessories which are invalid.
*/
deprecateAccessories() {
this.accessories
.filter((accessory) => !this.configAccessories.some((configAccessory) => configAccessory.accessory === accessory.context.config.accessory
&& configAccessory.name === accessory.context.config.name
&& configAccessory.number === accessory.context.config.number
&& accessory.UUID === this.api.hap.uuid.generate(this.getAccessoryId(configAccessory))))
.forEach((accessory) => {
this.api.unregisterPlatformAccessories(settings_1.pluginName, settings_1.platformName, [accessory]);
});
}
/**
* Discover new Accessories.
*/
discoverDevices() {
this.configAccessories
.forEach((configAccessory) => {
const uuid = this.api.hap.uuid.generate(this.getAccessoryId(configAccessory));
let accessory = this.accessories.find((acc) => acc.UUID === uuid);
if (accessory === undefined) {
accessory = new this.api.platformAccessory(configAccessory.name, uuid);
this.accessories.push(accessory);
this.api.registerPlatformAccessories(settings_1.pluginName, settings_1.platformName, [accessory]);
}
this.initAccessory(accessory, configAccessory);
this.api.updatePlatformAccessories([accessory]);
});
}
handleMessage(dataString) {
let message;
switch (dataString.length) {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
case 2:
message = {
event: dataString[0],
state: dataString.endsWith("Y"),
};
break;
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
case 5:
message = {
event: dataString.slice(0, -1),
state: dataString.endsWith("1"),
};
break;
default:
message = {
event: dataString,
state: false,
};
}
this.accessoryEvent.emit(message.event, message.state);
}
initAccessory(accessory, config) {
accessory.displayName = config.name;
accessory.context.config = config;
switch (config.accessory) {
case "contact":
new contact_sensor_accessory_1.ContactSensorAccessory(this, accessory);
break;
case "smoke":
new smoke_sensor_accessory_1.SmokeSensorAccessory(this, accessory);
break;
case "carbon-monoxide":
new carbon_monoxide_sensor_accessory_1.CarbonMonoxideSensorAccessory(this, accessory);
break;
case "security":
new security_system_current_accessory_1.SecuritySystemAccessory(this, accessory);
new security_system_target_accessory_1.SecuritySystemTargetAccessory(this, accessory);
break;
default:
new motion_sensor_accessory_1.MotionSensorAccessory(this, accessory);
}
this.log.info("Accessory Loaded:", accessory.displayName);
}
/**
* Shutdown
*/
onShutdown() {
this.socketShutdown();
}
/**
* Start Up
*/
onStartUp() {
this.deprecateAccessories();
this.discoverDevices();
this.socketStartUp();
this.socketVerify();
}
parseData(data) {
const events = this.sanitiseEventData(data);
if (events === undefined
|| events.length === 0
|| events[0] === undefined) {
return;
}
if (!events[0].startsWith("\"")) {
this.log.debug("Socket Data:", events[0]);
if (this.connection?.destroyed === false) {
this.connection.destroy(new RangeError("Invalid data from SmartCOM"));
}
return;
}
events.forEach((event) => {
this.log.debug("Socket Data:", event);
this.lastSocketResponse = Date.now();
this.handleMessage(event.substring(1));
});
}
sanitiseConfig() {
const maxPort = 65535;
const defaultPort = 10001;
this.config.port = typeof this.config.port === "number"
&& this.config.port >= 1
&& this.config.port <= maxPort
? Math.floor(this.config.port)
: defaultPort;
this.config.host = typeof this.config.host === "string"
? this.config.host.trim()
: "127.0.0.1";
if (!Array.isArray(this.config.areas)) {
this.config.areas = [];
}
if (!Array.isArray(this.config.zones)) {
this.config.zones = [];
}
}
sanitiseEventData(data) {
return typeof data === "string" || Buffer.isBuffer(data)
? data
.toString()
.trim()
.split("\n")
.map((value) => value.trim())
.filter((value) => value.length > 0)
: undefined;
}
/**
* Restarts the socket connection to SmartCom.
*/
socketRestart() {
this.log.info("Reconnecting to SmartCom - %s:%s", this.config.host, this.config.port);
this.socketShutdown();
const reconnectTimeout = 10000;
global.setTimeout(this.socketStartUp.bind(this), reconnectTimeout);
}
/**
* Shutdown the socket connection to SmartCom.
*/
socketShutdown() {
if (this.connection?.destroyed === false) {
this.connection.destroy();
}
this.lastSocketResponse = 0;
}
/**
* Start the socket connection to SmartCom.
*/
socketStartUp() {
this.connection = net
.createConnection(this.config.port, this.config.host)
.setEncoding("utf8")
.on("connect", () => {
this.log.info("Connected to SmartCom - %s:%s", this.config.host, this.config.port);
})
.on("error", (error) => {
if (error.code === "ECONNREFUSED") {
this.log.error("Unable to connect to %s:%s", this.config.host, this.config.port);
}
else {
this.log.error(error.message);
}
})
.on("close", (hadError) => {
if (hadError) {
this.socketRestart();
}
else {
this.log.info("Disconnected from SmartCom - %s:%s", this.config.host, this.config.port);
}
})
.on("data", this.parseData.bind(this));
}
socketVerify() {
const verificationTimeout = 10000;
global.setTimeout(() => {
if (this.lastSocketResponse === 0
&& this.connection?.destroyed === false) {
this.connection.destroy(new Error("No response from SmartCom"));
}
}, verificationTimeout);
}
}
exports.TexecomConnectPlatform = TexecomConnectPlatform;
//# sourceMappingURL=texecom-connect-platform.js.map