UNPKG

homebridge-knx

Version:

homebridge shim for KNX home automation.

348 lines (293 loc) 10.7 kB
/* * HOMEBRIDGE-KNX Addin for Fadini Combi 740 Gate Opener * * Erik Meinders / erik@easytocloud.com * * This is a module to handle a Fadini Gate opener. * * Normal operation is when push-contact on intercom or button on Fadini remote is pressed * * - opening gate * - remain open for 30 seconds * - closing gate * * I use KNX module to 'sense' when pysical button on intercom is pressed * this changes the KNX value of the Listen GA * * I do not intercept Fadini Remote (this is handled in hardware outside the house) * * Writing to the Set GA closes/opens the circuit through a second KNX device * * As long as the 'button is pressed' (actuator = ON) the gate remains open * When button released (actuator = OFF) the 'closing ritual' starts with a (30 seconds) timer. * This timer is configured in the hardware of the gate driver, not in this module! * * TravelTime and OpenTime are values you have to measure in your installation and enter in this config. * The other parameters can be used to 'override' those values * * Parameters for this module are : * * - KNX GA for Listen and Set * * - TravelTime - time it take to complete opening/closeing (measure!)- defaults to 15 seconds * - OpenTime - time gate remains open after contact is released (measure!) - defaults to 30 seconds * * - AutoClose - simulate push button contact (true) or open/close switch (false) - default true * - ExtendedOpen - open time before starting closing ritual - defaults to 0 seconds * * Fadini has single button operation to open/wait/close gate. * HomeKit expects two actions, one to open and one close garage door. * Setting AutoClose will reset HomeKit switch to off 200ms after switching it on, * mimicking the Fadini behaviour. This is the default. Alternatively, setting AutoClose to false * will leave the gate open until the switch is switched off in HomeKit * * ExtendedOpen can be used to keep the gate open for longer than the measured (30 seconds) period, * ExtendedOpen should then be set to the *total* time required and only works when AutoClose is true. * ExtendedOpen has to be larger than OpenTime! * * All information is only based on timing, no feedback/state sensors are available */ /* jshint esversion: 6, strict: true, node: true */ 'use strict'; /** * @type {HandlerPattern} */ var HandlerPattern = require('./handlerpattern.js'); var log = require('debug')('Fadini'); // HOMEKIT constants const HKOPEN = 0 const HKCLOSE = 1 const HKOPENING = 2 const HKCLOSING = 3 // KNX constants const KNXCLOSE = 0 const KNXOPEN = 1 /** * @class A custom handler for a fadini combi 740 conected to a KNX actuator * @extends HandlerPattern */ class Fadini extends HandlerPattern { onServiceInit() { if (!this.TravelTime) { this.TravelTime = this.myAPI.getLocalConstant("TravelTime"); if (!this.TravelTime) { this.TravelTime = 15 * 1000; // default to 15 secs if not given in config } else { this.TravelTime = this.TravelTime * 1000; //convert to millisecs } } if (!this.OpenTime) { this.OpenTime = this.myAPI.getLocalConstant("OpenTime"); if (!this.OpenTime) { this.OpenTime = 15 * 1000; // default to 15 secs if not given in config } else { this.OpenTime = this.OpenTime * 1000; //convert to millisecs } } if (!this.ExtendedOpen) { this.ExtendedOpen = this.myAPI.getLocalConstant("ExtendedOpen"); if (!this.ExtendedOpen) { this.ExtendedOpen = 0; // default to 0 secs if not given in config } else { this.ExtendedOpen = this.ExtendedOpen * 1000; //convert to millisecs } } if (!this.AutoClose) { this.AutoClose = this.myAPI.getLocalConstant("AutoClose"); if (this.AutoClose != false) this.AutoClose = true; } console.log(`INFO: Fadini module initialized with traveltime=${this.TravelTime} OpenTime=${this.OpenTime} AutoClose=${this.AutoClose} ExtendedOpen=${this.ExtendedOpen}`); } /* * onKNXValueChange is invoked if a KNX Bus value for one of the bound addresses is received * */ async onKNXValueChange(field, oldValue, knxValue) { var myCurrentDoorState=this.myAPI.getValue("CurrentDoorState"); // The value property of TargetDoorState must be one of the following: // Characteristic.TargetDoorState.KNXOPEN = 0; // Characteristic.TargetDoorState.KNXCLOSE = 1; log('INFO: on KNX Value Change(' + field + ", old="+ oldValue + ", new="+ knxValue+ ")"); log('INFO: myCurrentDoorState = ' + myCurrentDoorState); if (field === "TargetDoorState") { switch (knxValue ) { case KNXOPEN : log('KNX bus received value KNXOPEN --> TargetState = OPEN'); if ( myCurrentDoorState === HKOPEN ) { log('INFO: Already open - no action' + myCurrentDoorState); return; } if ( myCurrentDoorState === HKOPENING ) { log('INFO: Already open(ing) ' + myCurrentDoorState); return; } // enter opening mode log('INFO: HKOPENING for ' + this.TravelTime + 'msec'); this.myAPI.setValue("CurrentDoorState", HKOPENING); // OPENING this.myAPI.setValue("TargetDoorState", HKOPEN); // OPENING await new Promise (resolve => { setTimeout(resolve, this.TravelTime) }); log('INFO: HKOPENING completed, HKOPEN now '); this.myAPI.setValue("CurrentDoorState", HKOPEN); // OPEN break; case KNXCLOSE: log('INFO: KNX bus receive value KNXCLOSE --> TargetState = CLOSE'); if ( myCurrentDoorState === HKCLOSE ) { log('INFO: Already Closed ' + myCurrentDoorState); return; } if ( myCurrentDoorState === HKCLOSING ) { log('INFO: Already Closing ' + myCurrentDoorState); return; } // wait for potential running opening ritual to complete while ( (myCurrentDoorState = this.myAPI.getValue("CurrentDoorState")) != HKOPEN ) { log('INFO: Will check again in 2 seconds ' + myCurrentDoorState); await new Promise (resolve => { setTimeout(resolve, 2000) }); } // received KNXCLOSE is actually just end of open, which starts the 'staircase' timer // start timer - no state change log('INFO: Closing from open state - starting timer '+this.OpenTime+'msec'); await new Promise (resolve => { setTimeout(resolve, this.OpenTime) }); log('INFO: entering HKCLOSING for '+this.TravelTime+' msec'); this.myAPI.setValue("CurrentDoorState", HKCLOSING ); // CLOSING this.myAPI.setValue("TargetDoorState", HKCLOSE ); // CLOSING await new Promise (resolve => { setTimeout(resolve, this.TravelTime) }); log('INFO: HKCLOSING ended, HKCLOSE now'); this.myAPI.setValue("CurrentDoorState", HKCLOSE ); // CLOSED } } else { log ('WARNING: [ONKNX Value Change] Not processed ', + field ); } } // onKNXValueChange /* * onHKValueChange is invoked if HomeKit is changing characteristic values * */ onHKValueChange(field, oldValue, newValue) { var myCurrentDoorState=this.myAPI.getValue("CurrentDoorState"); log('INFO: on HK Value Change (' + field + ", old="+ oldValue + ", new="+ newValue + ")"); log('INFO: myCurrentDoorState = ' + myCurrentDoorState); if (field === "TargetDoorState") { // The value property of TargetDoorState must be one of the following: // Characteristic.TargetDoorState.OPEN = 0; // Characteristic.TargetDoorState.CLOSED = 1; switch (newValue) { case HKOPEN : // new state is Open, currentState determines action switch(myCurrentDoorState) { case HKOPEN: // IF DOOR IS OPEN log('INFO: [onHKValueChange] CurrentDoorState = 0 OPEN detected'); // Do nothing, opening already break; case HKCLOSE: // send pulse for Opening log('INFO: [onHKValueChange] CurrentDoorState = 1 --> Calling this.moveGate(KNXOPEN)'); this.moveGate(KNXOPEN); break; case HKOPENING: log('INFO: [onHKValueChange] CurrentDoorState = 2 OPENING detected'); // Do nothing, opening already break; case HKCLOSING: log('INFO: [onHKValueChange] CurrentDoorState = 3 CLOSING detected --> reOpen '); this.moveGate(KNXOPEN); break; default: log('EORR: [onHKValueChange] CurrentDoorState = not recognized'); } break; case HKCLOSE: switch(myCurrentDoorState){ case HKOPEN: // send pulse for Closing log('INFO: [onHKValueChange] Calling this.moveGate(KNXCLOSE)'); this.moveGate(KNXCLOSE); break; case HKCLOSE: log('INFO: [onHKValueChange] CurrentDoorState = 1 CLOSED detected - no change'); break; case HKOPENING: log('INFO: [onHKValueChange] CurrentDoorState = 2 OPENING detected - need to await fulll cycle'); this.moveGate(KNXCLOSE); break; case HKCLOSING: log('INFO: [onHKValueChange] CurrentDoorState = 3 CLOSING detected'); // Do nothing, closing already break; default: log('ERROR: [onHKValueChange] CurrentDoorState = not recognized'); } } // switch newValue } else { log('WARNING: [onHKValueChange] changed field is not recognized:'+field); } // TargetDoorState } // onHKValueChange /* * moveGate is used to send an pulse for open/close to the bus * */ async moveGate(toOpen) { log('INFO: moveGate called. toOpen = ' + toOpen); switch (toOpen) { case KNXOPEN: log('INFO: moveGate writes KNXOPEN as TargetDoorState'); this.myAPI.knxWrite("TargetDoorState", KNXOPEN, "DPT1"); if(this.AutoClose) { log('INFO: AutoClose enabled with 200 + '+ this.ExtendedOpen+' ms. delay.'); // button press should be a certain minimum time (200 ms) await new Promise (resolve => { setTimeout(resolve, this.ExtendedOpen+200) }); // simulate close event; ie. release of open button --> to close this.onHKValueChange("TargetDoorState", HKOPEN, HKCLOSE); } break; case KNXCLOSE: log('INFO: moveGate writes KNXCLOSE as TargetDoorState'); this.myAPI.knxWrite("TargetDoorState", KNXCLOSE, "DPT1"); break; } } } module.exports= Fadini; /* * your knx_config.json should contain something along the line of * { "DeviceName" : "Bridge", "Services" : [ { "ServiceType" : "GarageDoorOpener", "Handler" : "Fadini", "ServiceName" : "Gate", "Characteristics" : [ { "Type" : "TargetDoorState", "Set" : [ "1/0/42" ], "Listen": [ "1/1/42" ], "DPT" : "DPT1" }, { "Type" : "CurrentDoorState", "DPT" : "DPT5" }], "KNXReadRequests" : [ "1/1/42" ], "LocalConstants" : { "TravelTime" : 10, "OpenTime" : 30, "AutoClose" : true, "ExtendOpen" : 30 } }] } */