homebridge-blaq
Version:
Control and view your garage door(s) remotely with real-time updates using Konnected's BlaQ hardware
233 lines • 10.5 kB
JavaScript
import { AutoReconnectingEventSource } from './utils/eventsource.js';
import stripAnsi from 'strip-ansi';
import { createConnection } from 'net';
import { BlaQGarageDoorAccessory } from './accessory/garage-door.js';
import { BlaQGarageLightAccessory } from './accessory/garage-light.js';
import { BlaQGarageLockAccessory } from './accessory/garage-lock.js';
import { BlaQGarageMotionSensorAccessory } from './accessory/garage-motion-sensor.js';
import { BlaQGaragePreCloseWarningAccessory } from './accessory/garage-pre-close-warning.js';
import { BlaQGarageLearnModeAccessory } from './accessory/garage-learn-mode.js';
import { BlaQGarageObstructionSensorAccessory } from './accessory/garage-obstruction-sensor.js';
import { formatMAC } from './utils/formatters.js';
export class BlaQHub {
pluginConfig;
configDevice;
accessories = [];
eventSource;
host;
initialized = false;
friendlyName;
deviceMac;
port;
user;
pass;
eventsBeforeAccessoryInit = [];
initAccessoryCallback;
logger;
constructor(pluginConfig, configDevice, initAccessoryCallback, logger) {
this.pluginConfig = pluginConfig;
this.configDevice = configDevice;
logger.debug('Initializing BlaQHub...');
this.host = configDevice.host;
this.port = configDevice.port;
this.user = configDevice.username;
this.pass = configDevice.password;
this.initAccessoryCallback = initAccessoryCallback;
this.logger = logger;
this.reinitializeEventSource();
if (pluginConfig.enableNativeAPIHeartbeat) {
setInterval(() => {
this.performNativeAPIHeartbeat();
}, 5 * 60 * 1000);
}
logger.debug('Initialized BlaQHub!');
}
getAPIBaseURL() {
return `http://${this.host}:${this.port}`;
}
reinitializeEventSource() {
if (this.eventSource) {
this.eventSource.close();
}
this.accessories.forEach(accessory => accessory.resetSyncState());
this.eventSource = new AutoReconnectingEventSource({
host: this.host,
port: this.port,
user: this.user,
pass: this.pass,
logger: this.logger,
onStateUpdate: (stateEvent) => this.handleStateUpdate(stateEvent),
onLog: (logEvent) => this.handleLogUpdate(logEvent),
onPing: (pingEvent) => this.handlePingUpdate(pingEvent),
});
}
updateHostPort(host, port) {
const isChanged = host !== this.host || port !== this.port;
this.host = host;
this.port = port;
if (isChanged) {
this.reinitializeEventSource();
this.accessories.forEach(accessory => accessory.setAPIBaseURL(this.getAPIBaseURL()));
}
}
performNativeAPIHeartbeat() {
const socket = createConnection({ host: this.host, port: 6053 }, (() => setTimeout(() => {
socket.destroy();
}, 5 * 1000)));
}
possiblyFinalizeInit() {
if (!this.initialized && this.friendlyName && this.deviceMac) {
this.logger.info(`[${this.configDevice.displayName}] [init] Publishing accessories with device model:`, this.friendlyName);
this.initAccessories({
friendlyName: this.friendlyName,
serialNumber: this.deviceMac,
});
this.initialized = true;
this.eventsBeforeAccessoryInit.forEach(oldEvent => {
const getFuncToCall = {
'ping': (accessory) => accessory.handlePingEvent?.bind(accessory),
'log': (accessory) => accessory.handleLogEvent?.bind(accessory),
'state': (accessory) => accessory.handleStateEvent?.bind(accessory),
}[oldEvent.type];
this.accessories.forEach(accessory => {
const funcToCall = getFuncToCall(accessory);
if (funcToCall) {
funcToCall(oldEvent.event);
}
});
});
this.eventsBeforeAccessoryInit = [];
this.logger.debug(`[${this.configDevice.displayName}] [init] Accessories initialized!`);
}
}
handleStateUpdate(msg) {
if (!this.initialized) {
this.eventsBeforeAccessoryInit.push({ type: 'state', event: msg });
}
if (!this.initialized && msg.data !== '') {
try {
const b = JSON.parse(msg.data);
if (['text_sensor-device_id'].includes(b.id)) {
this.deviceMac = formatMAC(b.value);
}
this.possiblyFinalizeInit();
}
catch (e) {
this.logger.debug(`[${this.configDevice.displayName}] [init] Got event:`, msg);
this.logger.debug(`[${this.configDevice.displayName}] [init] Got event data:`, msg.data);
this.logger.error(`[${this.configDevice.displayName}] [init] Cannot parse BlaQTextSensorEvent`, e);
}
}
this.logger.debug(`[${this.configDevice.displayName}] Processing state event:`, msg.data);
this.accessories.forEach(accessory => {
if (accessory.handleStateEvent) {
accessory.handleStateEvent(msg);
}
});
}
handleLogUpdate(msg) {
if (!this.initialized) {
this.eventsBeforeAccessoryInit.push({ type: 'log', event: msg });
}
this.logger.debug(`[${this.configDevice.displayName}] GDO blaQ log:`, stripAnsi(msg.data));
this.accessories.forEach(accessory => {
if (accessory.handleLogEvent) {
accessory.handleLogEvent(msg);
}
});
}
initGarageDoorAccessory({ platform, accessory, friendlyName, serialNumber }) {
this.accessories.push(new BlaQGarageDoorAccessory({
platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(), type: this.pluginConfig.garageDoorType,
apiUser: this.user,
apiPass: this.pass,
}));
}
initGarageLightAccessory({ platform, accessory, friendlyName, serialNumber }) {
if (this.pluginConfig.enableLight ?? true) {
this.accessories.push(new BlaQGarageLightAccessory({
platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(),
apiUser: this.user,
apiPass: this.pass,
}));
}
}
initGarageLockAccessory({ platform, accessory, friendlyName, serialNumber }) {
if (this.pluginConfig.enableLockRemotes ?? true) {
this.accessories.push(new BlaQGarageLockAccessory({
platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(),
apiUser: this.user,
apiPass: this.pass,
}));
}
}
initGarageMotionSensorAccessory({ platform, accessory, friendlyName, serialNumber }) {
if (this.pluginConfig.enableMotionSensor ?? true) {
this.accessories.push(new BlaQGarageMotionSensorAccessory({
platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(),
apiUser: this.user,
apiPass: this.pass,
}));
}
}
initGaragePreCloseWarningAccessory({ platform, accessory, friendlyName, serialNumber }) {
if (this.pluginConfig.enablePreCloseWarning ?? true) {
this.accessories.push(new BlaQGaragePreCloseWarningAccessory({
platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(),
apiUser: this.user,
apiPass: this.pass,
}));
}
}
initGarageLearnModeAccessory({ platform, accessory, friendlyName, serialNumber }) {
if (this.pluginConfig.enableLearnMode ?? true) {
this.accessories.push(new BlaQGarageLearnModeAccessory({
platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(),
apiUser: this.user,
apiPass: this.pass,
}));
}
}
initGarageObstructionSensorAccessory({ platform, accessory, friendlyName, serialNumber }) {
if (this.pluginConfig.enableSeparateObstructionSensor ?? true) {
this.accessories.push(new BlaQGarageObstructionSensorAccessory({
platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(),
apiUser: this.user,
apiPass: this.pass,
}));
}
}
initAccessories({ friendlyName, serialNumber }) {
const { platform, accessory } = this.initAccessoryCallback(this.configDevice, friendlyName, serialNumber);
this.initGarageDoorAccessory({ platform, accessory, friendlyName, serialNumber });
this.initGarageLightAccessory({ platform, accessory, friendlyName, serialNumber });
this.initGarageLockAccessory({ platform, accessory, friendlyName, serialNumber });
this.initGarageMotionSensorAccessory({ platform, accessory, friendlyName, serialNumber });
this.initGaragePreCloseWarningAccessory({ platform, accessory, friendlyName, serialNumber });
this.initGarageLearnModeAccessory({ platform, accessory, friendlyName, serialNumber });
this.initGarageObstructionSensorAccessory({ platform, accessory, friendlyName, serialNumber });
}
handlePingUpdate(msg) {
if (!this.initialized) {
this.eventsBeforeAccessoryInit.push({ type: 'ping', event: msg });
}
if (!this.initialized && msg.data !== '') {
try {
const b = JSON.parse(msg.data);
this.friendlyName = b.title;
this.possiblyFinalizeInit();
}
catch (e) {
this.logger.debug(`[${this.configDevice.displayName}] [init] Got event:`, msg);
this.logger.debug(`[${this.configDevice.displayName}] [init] Got event data:`, msg.data);
this.logger.error(`[${this.configDevice.displayName}] [init] Cannot parse BlaQPingEvent`, e);
}
}
this.accessories.forEach(accessory => {
if (accessory.handleLogEvent) {
accessory.handleLogEvent(msg);
}
});
}
}
//# sourceMappingURL=hub.js.map