UNPKG

homebridge-garagedoor-iologik

Version:

A Homebridge plugin that connects Hörmann Supramatic garage doors to Apple HomeKit

189 lines (162 loc) 6.35 kB
// Hörmann Supramatic garage door opener plugin for Homebridge // Copyright © 2019 Alexander Thoukydides 'use strict'; const IoLogik = require('./iologik'); const EventEmitter = require('events'); // DI channels const DI = { open: 0, // Indicates that the door is fully closed closed: 1 // Indicates that the door is fully open }; // DO relay channels const DO = { open: 2, // Toggle opening the door partial: 3, // Toggle partially opening the door close: 4, // Toggle closing the door light: 5 // Toggle the light }; // Timings (seconds) const TIME = { poll: 1, // Interval between successive polls of door position move: 25, // Time required to fully open or fully close light_ext: 900, // Illumination time via radio/external button light_move: 300 // Illumination switch-off delay after movement stops }; const MS = 1000; // UAP1 (Universal Adapter Print) interface for Hörmann Supramatic module.exports = class UAP1 extends EventEmitter { // Create a new UAP1 object constructor(host, log) { super(); this.log = log || console.log; // Construct an ioLogik API this.host = host; this.iologik = new IoLogik(host, log); // Read and log the ioLogic system information this.logDeviceInfo(); // Start polling the door state this.currentState = 'closed'; this.targetState = 'closed'; this.pollDoorPosition(); // Assume that the light is initially off this.lightState = false; } // Read and log device information async logDeviceInfo() { try { let sysinfo = await this.iologik.getSysInfo(); for (let key in sysinfo) { this.log(key + ': ' + sysinfo[key]); } } catch (e) { this.emit('error', e); } } // Poll for the garage door position async pollDoorPosition() { try { let di = await this.iologik.getDI(); this.updateDoorPosition(di[DI.closed] ? 'closed' : (di[DI.open] ? 'open' : 'unknown')); } catch (e) { this.emit('error', e); } setTimeout(() => this.pollDoorPosition(), TIME.poll * MS); } // Set the target door state async setTargetDoorState(value, callback) { const action = { open: { state: 'opening', relay: DO.open }, closed: { state: 'closing', relay: DO.close } }[value]; if (this.currentState == value || this.currentState == action.state) { this.log('Door is already ' + this.currentState); } else { // Start opening or closing the door try { await this.pulseRelay(action.relay); } catch (e) { this.emit('error', e); return callback(e); } // Update the door state to match this.updateDoorPosition(action.state); } callback(); } // The door position has been updated updateDoorPosition(state) { // Apply the new door state let currentState = this.currentState; if ((state == 'open' && currentState != 'closing') || (state == 'opening' && currentState != 'open') || (state == 'closing' && currentState != 'closed') || (state == 'closed' && currentState != 'opening') || (state == 'stopped')) { // State is well defined, and not start or stop of motion currentState = state; } else if (state == 'unknown') { // Door not fully open or closed, so check for start of motion if (currentState == 'open') currentState = 'closing'; else if (currentState == 'closed') currentState = 'opening'; } // No further action required unless the current door state has changed if (this.currentState == currentState) return; // The door will only continue moving for a short period clearTimeout(this.moveTimeout); if (currentState == 'opening' || currentState == 'closing') { this.moveTimeout = setTimeout(() => { this.updateDoorPosition('stopped'); }, TIME.move * MS); } // Update the target state based on the new door state if (currentState == 'open' || currentState == 'opening') { this.targetState = 'open'; } else if (currentState == 'closed' || currentState == 'closing') { this.targetState = 'closed'; } // The light will have been switched on with the door movement this.updateLightState(true, TIME.light_move); // Emit a notification this.currentState = currentState; this.emit('door', currentState, this.targetState); } // Switch the light on or off async setLightOn(value, callback) { if (this.lightState == value) { this.log('Light is already in the requested state'); } else { // Toggle the light on/off try { await this.pulseRelay(DO.light); } catch (e) { this.emit('error', e); return callback(e); } // Update the expected state of the light this.updateLightState(value, value ? TIME.light_ext : 0); } callback(); } // The light has been switched on updateLightState(state, duration) { if (this.lightState != state) { // The state of the light has changed this.lightState = state; this.emit('light', state); } // The light will switch off after a delay clearTimeout(this.lightTimeout); if (duration) { this.lightTimeout = setTimeout(() => { this.log('Light switched off after ' + duration + ' seconds'); this.updateLightState(false); }, duration * MS); } } // Pulse an ioLogik relay async pulseRelay(index) { this.log('Pulsing ioLogik relay #' + index); return this.iologik.pulseDO(index); } }