homebridge-knx
Version:
homebridge shim for KNX home automation.
263 lines (245 loc) • 7.81 kB
JavaScript
/* The Dimmer issue is one of the oldest annoyances with Homekit:
* HomeKit assumes that everyone wants to have each dimmable light switched on at 100% brightness,
* and that an additional ON command is good also when adjusting the brightness.
* Both are wrong.
*
* There are multiple KNX dimmers on the market (and gateways to dali and others) that behave differently.
*
*
*/
/* jshint esversion: 6, strict: true, node: true */
'use strict';
/**
* @type {HandlerPattern}
*/
var HandlerPattern = require('./handlerpattern.js');
var log = require('debug')('DimmerHandler');
/**
* @class A custom handler to modify HomeKit's annoying dimmer control
* @extends HandlerPattern
*/
class Dimmer extends HandlerPattern {
constructor(knxAPI) {
super(knxAPI); // call the super constructor first. Always.
this.timer = undefined;
this.lastState = false; // off (assumption)
}
onServiceInit() {
this.lastKnownBrightness = this.myAPI.getLocalConstant('InitBrightness') || 100.0;
this.valueField = this.myAPI.getLocalConstant('ValueField') || 'Brightness';
}
/****
* onKNXValueChange is invoked if a Bus value for one of the bound addresses is received
*
*/
onKNXValueChange(field, oldValue, knxValue) {
// value for HomeKit
var newValue;
log('INFO: onKNXValueChange(' + field + ", "+ oldValue + ", "+ knxValue+ ") ");
if (this.timer) {
/**
* was switched on recently:
* KNX may switch off again
* KNX may not:
* - change brightness within the timer period
* - repeat the "switch on" command
*/
if (this.lastState===true) {
// still in switch on timeout period
if (field === 'On' && knxValue === 0) {
// switched off
this.lastState=false;
clearTimeout(this.timer);
this.timer = undefined;
this.myAPI.setValue('On', false);
}
}
} else {
/**
* Was not recently switched on:
* KNX may change all conditions
*/
if (this.lastState===false) {
/**
* was off before
* KNX may
* - switch on
* - change brightness 1%..100% (Zero percent would mean switch off, and that is already the case
*
*/
if (field === 'On' && knxValue === 1) {
// switching on
this.lastState=true;
clearTimeout(this.timer);
this.timer = undefined;
this.myAPI.setValue('On', true);
} else if (field === this.valueField && knxValue>0) {
// Brightness > larger than 0
this.lastState=true;
clearTimeout(this.timer);
this.timer = undefined;
this.myAPI.setValue('On', true);
this.myAPI.setValue(this.valueField, knxValue);
}
} else {
/**
* was on before
* KNX may change
* - switch off
* - change brightness to any value
*/
if (field === 'On' && knxValue ===0) {
// switching off outside timer
this.lastState=false;
clearTimeout(this.timer);
this.timer = undefined;
this.myAPI.setValue('On', false);
}
if (field === this.valueField) {
if (knxValue>0) {
this.lastState=true;
this.myAPI.setValue(this.valueField, knxValue);
} else {
// switch off!
this.lastState=false;
clearTimeout(this.timer);
this.timer = undefined;
this.myAPI.setValue('On', false);
this.myAPI.setValue(this.valueField, knxValue);
}
}
}
}
} // onBusValueChange
/****
* onHKValueChange is invoked if HomeKit is changing characteristic values
*
*/
onHKValueChange(field, oldValue, newValue) {
//
log('INFO: onHKValueChange(' + field + ", "+ oldValue + ", "+ newValue + ")");
if (field === 'On') {
if (newValue===true) {
if (this.timer) {
// the timer is still running, so we do not accept any new ON commands from homekit
//clearTimeout(this.timer);
} else {
if (this.lastState === false){
// light was off before
if (this.myAPI.getLocalConstant('RestoreLastBrightness')) {
// use only on 1 ping, vassily
// restore brightness
this.myAPI.knxWrite(this.valueField, this.lastKnownBrightness);
} else {
// we only use the ON command once to switch the light on
this.myAPI.knxWrite('On',true);
}
// start the countdown
this.timer = setTimeout(function() {
this.timer = undefined;
}.bind(this), this.myAPI.getLocalConstant('FadingTimeMS'));
this.lastState = true;
} else {
// light was already on
// filter out the command completely
// do nothing
}
}
} else {
// switching off
if (this.lastState===true) {
// if it was on before, switch it off
this.myAPI.knxWrite('On', false);
this.lastState=false;
clearTimeout(this.timer);
this.timer = undefined;
}
}
} else if (field===this.valueField) {
var knxValue = newValue;
if (this.lastState===false) {
// it was off before
if (newValue===100) {
// HomeKit meant "On"
if (this.myAPI.getLocalConstant('RestoreLastBrightness')) {
// use only on 1 ping, vassily
// restore brightness
this.myAPI.knxWrite(this.valueField, this.lastKnownBrightness);
} else {
// we only use the ON command once to switch the light on
this.myAPI.knxWrite('On',true);
}
// start the countdown
this.timer = setTimeout(function() {
this.timer = undefined;
}.bind(this), this.myAPI.getLocalConstant('FadingTimeMS'));
this.lastState = true;
} else {
this.myAPI.knxWrite(this.valueField, knxValue);
this.lastKnownBrightness = knxValue;
}
//start to count down
this.timer = setTimeout(function() {
this.timer = undefined;
}.bind(this), this.myAPI.getLocalConstant('FadingTimeMS'));
this.lastState = true;
} else {
// it was already lit
if (!(this.timer && newValue===100)) {
// not 100% and within the timeout period, good to send!
this.myAPI.knxWrite(this.valueField, knxValue);
this.lastKnownBrightness = knxValue;
// start new timeout to avoid flickering by knx feedback
clearTimeout(this.timer);
this.timer = setTimeout(function() {
this.timer = undefined;
}.bind(this), this.myAPI.getLocalConstant('FadingTimeMS'));
this.lastState = true;
}
}
}
} // onHKValueChange
} // class
module.exports= Dimmer;
/* **********************************************************************************************************************
* The config for that should look like this
* Reverse keyword is not allowed for custom handlers
*
* Customizable Dimmer control
* "RestoreLastBrightness": true
* If set to true, it will resume to last known brightness on switching on (regardless of
* brightness selected in HomeKit).
* If set to false, it will only use an ON command and filter any brightness from the first
* commands within FadingTimeMS milliseconds.
* "FadingTimeMS": 100
* The time in milliseconds that the handler will suppress brightness and ON commands when switching on.
* "InitBrightness": 100.0
* The Brightness that will used if the previous brightness is not known, due to restart of the handler e.g.
* "ValueField": "Brightness"
* The value field to change. This is "Brightness" for lights but can be something else like
* "RotationSpeed" for fans
*
*
"Services": [{
"ServiceType": "Lightbulb",
"Handler": "Dimmer",
"ServiceName": "dimmable lunch table light",
"Characteristics": [{
"Type": "On",
"Set": "1/2/1",
"Listen": ["1/2/3"]
},{
"Type": "Brightness",
"Set": "1/3/1",
"Listen": ["1/3/3"]
}],
"LocalConstants": {
"FadingTimeMS": 100,
"InitBrightness":30.0,
"RestoreLastBrightness":true
}
}]
*
*
*
*/