Skip to content

Commit

Permalink
Polling behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
dubocr committed Jun 24, 2020
1 parent e07a7f3 commit 3cf2b96
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 48 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Minimal configuration sample:
{
"platform": "Tahoma",
"name": "My TaHoma Box",
"user": "user@me.com",
"password": "MyPassw0rd",
}
Expand All @@ -40,7 +40,8 @@ Configuration parameters:
| `user` | String | null | mandatory, your TaHoma/Connexoon/Cozytouch/E.Connect account username |
| `password` | String | null | mandatory, your TaHoma/Connexoon/Cozytouch/E.Connect account password |
| `service` | String | 'TaHoma' | optional, service name ('TaHoma', 'Connexoon', 'Connexoon RTS', 'Cozytouch' or 'Rexel') |
| `refreshPeriod` | Integer | 600 | optional, device states refresh period in seconds |
| `refreshPeriod` | Integer | 1800 | optional, device states refresh period in seconds |
| `pollingPeriod` | Integer | 10 | optional, bridge polling period in seconds |
| `exclude` | String[] | [] | optional, list of protocols (hue,enocean,zwave,io,rts) or device (name) to exclude |
| `exposeScenarios` | Boolean | false | optional, expose TaHoma/Connexoon/Cozytouch scenarios as HomeKit switches. Could also specify a list of string corresponding to scenarios names to expose |
| `forceType` | Object | {} | optional, list of device (name) to force with another type (see below). Ex. Fan recognised as Light can be force to Fan type |
Expand All @@ -59,7 +60,8 @@ Configuration parameters:
| `initPosition` | Integer | 50 | optional, default position for UpDown rollershutter |
| `defaultPosition` | Integer | 0 | optional, final position for UpDown rollershutter after any command |
| `reverse` | Boolean | false | optional, reverse up/down in case of bad mounting |
| `blindMode` | Boolean | false | optional, control horizonally adjustable blinds with just one slider. When setting ``blindMode: true`` the blinds work in the following way: Opening the blinds or setting them to 100% will fully open them. Closing the blinds or setting them to 0% will fully close them. Setting the blinds to a value between 1% and 99% will first close the blinds and then adjust thier horizontal tilt in a way that 99% means fully horizonal = more light, and 1% means nearly closed = less light. |
| `blindMode` | String | null | optional, define main slider action (slates orientation or closure). By default, both closure and orientation will be set. When setting ``blindMode: "orientation"`` the blinds work in the following way: Opening the blinds or setting them to 100% will fully open them. Closing the blinds or setting them to 0% will fully close them. Setting the blinds to a value between 1% and 99% will first close the blinds and then adjust thier horizontal tilt in a way that 99% means fully horizonal = more light, and 1% means nearly closed = less light.
When setting ``blindMode: "closure"`` the blinds work in the following way: Closing the blinds or setting them to 0% will fully close them. Opening the blinds or setting them to 100% will open them with specified orientation. |

| GarageDoorOpener parameters| Type | Default | Note |
|----------------------------|----------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
Expand All @@ -83,7 +85,7 @@ Full configuration example:
{
"platform": "Tahoma",
"name": "My Tahoma",
"user": "user@me.com",
"password": "MyPassw0rd",
"service": "TaHoma",
Expand Down
65 changes: 47 additions & 18 deletions overkiz-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ function OverkizApi(log, config) {
// Default values
this.debug = config['debug'] || false;
this.debugUrl = config['debugUrl'] || false;
this.alwaysPoll = config['alwaysPoll'] || false;
this.pollingPeriod = config['pollingPeriod'] || 2; // Poll for events every 2 seconds by default
this.execPollingPeriod = config['execPollingPeriod'] || 2; // Poll for execution events every 2 seconds by default
this.pollingPeriod = config['pollingPeriod'] || 0; // Don't continuously poll for events by default
this.refreshPeriod = config['refreshPeriod'] || (60 * 30); // Refresh device states every 30 minutes by default
this.service = config['service'] || 'TaHoma';

Expand All @@ -131,7 +131,8 @@ function OverkizApi(log, config) {
this.stateChangedEventListener = null;

var that = this;
this.eventpoll = pollingtoevent(function(done) {

var pollCallback = function(done) {
if(that.debug) {
//that.log('Polling ('+that.isLoggedIn+'/'+that.listenerId+')');
}
Expand All @@ -143,17 +144,14 @@ function OverkizApi(log, config) {
done(error, data);
});
} else {
if(that.alwaysPoll && that.debug) {
if(that.pollingPeriod > 0 && that.debug) {
that.log('No listener registered while in always poll mode');
}
done(null, []);
}
}, {
longpolling: true,
interval: (1000 * this.pollingPeriod)
});
};

this.eventpoll.on("longpoll", function(data) {
var longPollCallback = function(data) {
for (event of data) {
//that.log(event);
if (event.name == 'DeviceStateChangedEvent') {
Expand All @@ -172,26 +170,53 @@ function OverkizApi(log, config) {
cb(event.newState, event.failureType == undefined ? null : event.failureType, event);
if (event.timeToNextState == -1) { // No more state expected for this execution
delete that.executionCallback[event.execId];
if(that.executionCallback.length == 0) {
that.runningCommands = 0;
} else {
if(that.hasExecution()) {
that.runningCommands--;
} else {
that.runningCommands = 0;
}
if(!that.alwaysPoll && Object.keys(that.executionCallback).length == 0) { // Unregister listener when no more execution running
if(that.pollingPeriod == 0 && !that.hasExecution()) { // Unregister listener when no more execution running
that.unregisterListener();
}
}
}
}
}
};

this.execpoll = pollingtoevent(function(done) {
if(that.hasExecution()) {
pollCallback(done);
}
}, {
longpolling: true,
interval: (1000 * this.execPollingPeriod)
});
this.execpoll.on("longpoll", longPollCallback);

this.eventpoll.on("error", function(error) {
that.log("Error with listener " + that.listenerId + " => " + error);
this.execpoll.on("error", function(error) {
that.log("[EXECPOLLING] Error with listener " + that.listenerId + " => " + error);
that.listenerId = null;
that.registerListener();
});

if(this.pollingPeriod > 0) {
this.eventpoll = pollingtoevent(function(done) {
if(!that.hasExecution()) {
pollCallback(done);
}
}, {
longpolling: true,
interval: (1000 * this.pollingPeriod)
});
this.eventpoll.on("longpoll", longPollCallback);
this.eventpoll.on("error", function(error) {
that.log("[POLLING] Error with listener " + that.listenerId + " => " + error);
that.listenerId = null;
that.registerListener();
});
}

var refreshpoll = pollingtoevent(function(done) {
if(that.debug) {
that.log('Refresh ALL ('+that.isLoggedIn+')');
Expand Down Expand Up @@ -225,6 +250,10 @@ function OverkizApi(log, config) {
}

OverkizApi.prototype = {
hasExecution: function() {
return Object.keys(this.executionCallback).length > 0;
},

urlForQuery: function(query) {
if(this.debugUrl) {
return this.debugUrl + "&query=" + query;
Expand Down Expand Up @@ -320,7 +349,7 @@ OverkizApi.prototype = {
} else if (json && json.success) {
that.isLoggedIn = true;
myRequest(authCallback);
if(that.alwaysPoll) {
if(that.pollingPeriod > 0) {
that.registerListener();
}
} else if (json && json.error) {
Expand Down Expand Up @@ -351,7 +380,7 @@ OverkizApi.prototype = {
} else {
that.listenerId = null;
that.log("Error while registering listener");
setTimeout(that.registerListener.bind(that), 1000 * that.pollingPeriod);
setTimeout(that.registerListener.bind(that), 1000 * 30);
}
});
}
Expand Down Expand Up @@ -446,7 +475,7 @@ OverkizApi.prototype = {
if (error == null) {
callback(ExecutionState.INITIALIZED, error, json); // Init OK
that.executionCallback[json.execId] = callback;
if(!that.alwaysPoll) {
if(that.pollingPeriod == 0) {
that.registerListener();
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "homebridge-tahoma",
"version": "0.3.40",
"version": "0.3.41",
"description": "Sample Platform plugin for TaHoma and Cozytouch services (Somfy,Atlantic,Thermor,Sauter): https://github.com/dubocr/homebridge-tahoma",
"author": "Romain DUBOC",
"license": "ISC",
Expand Down
110 changes: 85 additions & 25 deletions services/WindowCovering.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ class WindowCovering extends AbstractService {
Log = log;
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;

this.defaultPosition = config['defaultPosition'] || 0;
this.initPosition = config['initPosition'] !== undefined ? config['initPosition'] : (config['defaultPosition'] || 50);
this.reverse = config['reverse'] || false;
this.blindMode = config['blindMode'] || false;
this.cycle = config['cycle'] || false;

this.service = new Service[this.constructor.name](device.getName());

this.currentPosition = this.service.getCharacteristic(Characteristic.CurrentPosition);
Expand Down Expand Up @@ -47,9 +47,10 @@ class WindowCovering extends AbstractService {
* HomeKit '100' (Open) => 0% Closure
**/
setTarget(requestedValue, callback) {
Log('setTarget ' + this.targetPosition.value + '/' + this.targetAngle.value);
var commands = [];
var value = this.reverse ? (100 - requestedValue) : requestedValue;

switch(this.device.widget) {
case 'PositionableHorizontalAwning':
commands.push(new Command('setDeployment', value));
Expand Down Expand Up @@ -96,7 +97,7 @@ class WindowCovering extends AbstractService {
}
break;

case 'PositionableScreen':
case 'PositionableScreen':
case 'PositionableScreenUno':
case 'PositionableHorizontalAwningUno':
case 'PositionableRollerShutter':
Expand All @@ -116,19 +117,23 @@ class WindowCovering extends AbstractService {
break;

case 'PositionableExteriorVenetianBlind':
if(this.blindMode) {
if(value < 100) {

if(this.blindMode == 'orientation') {
if(requestedValue < 100) {
commands.push(new Command('setClosureAndOrientation', [100, (100-value)]));
} else {
commands.push(new Command('setClosure', 0));
commands.push(new Command('setClosure', (100-value)));
}
} else {
} else if(this.blindMode == 'closure') {
var closure = 100-value;
/*
if(requestedValue > 0) {
var orientation = Math.round((this.targetAngle.value + 90)/1.8);
commands.push(new Command('setClosureAndOrientation', [closure, orientation]));
*/
commands.push(new Command('setClosure', closure));
} else {
commands.push(new Command('setClosure', closure));
}
} else {
return this.setClosureAndOrientation(callback);
}
break;

Expand All @@ -155,7 +160,7 @@ class WindowCovering extends AbstractService {
callback(error);
break;
case ExecutionState.IN_PROGRESS:
var positionState = (value == 100 || value > this.currentPosition.value) ? Characteristic.PositionState.INCREASING : Characteristic.PositionState.DECREASING;
var positionState = (this.targetPosition.value == 100 || this.targetPosition.value > this.currentPosition.value) ? Characteristic.PositionState.INCREASING : Characteristic.PositionState.DECREASING;
this.positionState.updateValue(positionState);
break;
case ExecutionState.COMPLETED:
Expand Down Expand Up @@ -193,17 +198,17 @@ class WindowCovering extends AbstractService {
* Triggered when Homekit try to modify the Characteristic.TargetAngle
**/
setAngle(value, callback) {
Log('setAngle ' + this.targetPosition.value + '/' + this.targetAngle.value);
var commands = [];

switch(this.device.widget) {
default:
var orientation = Math.round((value + 90)/1.8);
if(this.targetPosition.value != this.currentPosition.value) {
var closure = 100-this.targetPosition.value;
commands.push(new Command('setClosureAndOrientation', [closure, orientation]));
} else {
commands.push(new Command('setOrientation', orientation));
}
if(this.blindMode && this.targetPosition.value == this.currentPosition.value) {
var orientation = Math.round((value + 90)/1.8);
commands.push(new Command('setOrientation', orientation));
} else {
return this.setClosureAndOrientation(callback);
}
break;
}
this.device.executeCommand(commands, function(status, error, data) {
Expand All @@ -222,7 +227,58 @@ class WindowCovering extends AbstractService {
}
}.bind(this), callback);
}


/**
* Helper
**/
setClosureAndOrientation(callback) {
Log('setClosureAndOrientation ' + this.targetPosition.value + '/' + this.targetAngle.value);
var commands = [];

switch(this.device.widget) {
default:
var orientation = Math.round((this.targetAngle.value + 90)/1.8);
var target = this.reverse ? (100 - this.targetPosition.value) : this.targetPosition.value;
var closure = 100 - target;
commands.push(new Command('setClosureAndOrientation', [closure, orientation]));
break;
}
this.device.executeCommand(commands, function(status, error, data) {
switch (status) {
case ExecutionState.INITIALIZED:
callback(error);
break;
case ExecutionState.IN_PROGRESS:
var positionState = (this.targetPosition.value == 100 || this.targetPosition.value > this.currentPosition.value) ? Characteristic.PositionState.INCREASING : Characteristic.PositionState.DECREASING;
this.positionState.updateValue(positionState);
break;
case ExecutionState.COMPLETED:
this.positionState.updateValue(Characteristic.PositionState.STOPPED);
if(this.device.stateless) {
this.currentAngle.updateValue(this.targetAngle.value);
if(this.defaultPosition) {
this.currentPosition.updateValue(this.defaultPosition);
this.targetPosition.updateValue(this.defaultPosition);
} else {
this.currentPosition.updateValue(this.targetPosition.value);
}
}
if(this.obstruction != null && this.obstruction.value == true) {
this.obstruction.updateValue(false);
}
break;
case ExecutionState.FAILED:
this.positionState.updateValue(Characteristic.PositionState.STOPPED);
this.targetAngle.updateValue(this.currentAngle.value); // Update target position in case of cancellation
this.targetPosition.updateValue(this.currentPosition.value); // Update target position in case of cancellation
if(this.obstruction != null) {
this.obstruction.updateValue(error == 'WHILEEXEC_BLOCKED_BY_HAZARD');
}
break;
}
}.bind(this), callback);
}

onStateUpdate(name, value) {
var currentPosition = null, targetPosition = null, currentAngle = null, targetAngle = null;
switch(name) {
Expand All @@ -242,8 +298,8 @@ class WindowCovering extends AbstractService {
currentAngle = Math.round(value * 1.8 - 90);
targetAngle = currentAngle;
break;


case 'core:SlatsOrientationState':
currentPosition = this.reverse ? (100 - value) : value;
targetPosition = currentPosition;
Expand All @@ -254,11 +310,15 @@ class WindowCovering extends AbstractService {
default: break;
}

if(this.blindMode && ['core:OpenClosedState', 'core:SlateOrientationState'].includes(name)) {
if(this.blindMode == 'orientation' && ['core:OpenClosedState', 'core:SlateOrientationState'].includes(name)) {
if(this.device.states['core:OpenClosedState'] == 'closed') {
var orientation = this.device.states['core:SlateOrientationState'];
if(Number.isInteger(orientation))
var orientation = 100-this.device.states['core:SlateOrientationState'];
if(Number.isInteger(orientation)) {
currentPosition = orientation;
if(Math.round(orientation/5) == Math.round(this.targetPosition.value/5)) {
this.targetPosition.updateValue(orientation);
}
}
} else {
currentPosition = 0;
}
Expand Down

0 comments on commit 3cf2b96

Please sign in to comment.