Skip to content

Commit

Permalink
feat: Event based updates
Browse files Browse the repository at this point in the history
Updates will be done based on Miele event server updates instead of poll based.
Unloads Homebridge by not polling every n seconds and increases responsiveness of the device.

BREAKING CHANGE: poll timout timer removed from configuration.
  • Loading branch information
QuickSander committed Mar 21, 2021
1 parent 2cea6e5 commit dd1eab9
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 147 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ It (currently) requires a
## Features
- Easy setup: guided process to retrieve token via OAuth2 from Miele.
- Automatic token refreshing.
- Event based.
- Start / stop (dish) washing machine program (with an option to disable to prevent unintentional program stop requests).
- Remaining run time.
- Washing machine / dish washer program target temperature.
- HomeKit identify support via Homebridge log.

## Breaking changes
### Versions > 2.8.0
- The introduction of event based updating removed the need for the _Poll interval_ setting. This option can be removed from
your config when you see a need to clean up your config.

### Versions > 2.5.2
- _Disable temperature sensor_ and _disable stop action ability_ need to be re-configured as the settings have become
finer grained (per specific device type instead of per group of device types).
Expand Down Expand Up @@ -59,7 +64,6 @@ Fridge / Fridge Freezer combination:
## Planned features
- Add support for oven, hob and coffee machine?
- Add Custom characteristic to display current program running.
- Event based instead of poll based.

## Thanks
- [MichelRabozee](https://github.com/MichelRabozee)
14 changes: 6 additions & 8 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@
],
"description": "Determines in what language the device name will be displayed in HomeKit."
},
"pollInterval": {
"title": "Poll interval [s]",
"reconnectEventServerInterval": {
"title": "Force event server reconnect interval [min]",
"type": "number",
"required": false,
"minimum": 0,
"minimum": 1,
"default": 60,
"description": "The number of seconds between state retrievals or 0 to disable auto-update."
"description": "The interval at which a reconnect to the Miele event server is enforced."
},
"disableStopActionFor": {
"title": "Prevent stop/off request ability for",
Expand All @@ -53,9 +53,7 @@
"Washer",
"Dryer",
"WasherDryer",
"Dishwasher",
"Fridge",
"FridgeFreezer"
"Dishwasher"
]
}
},
Expand Down Expand Up @@ -102,7 +100,6 @@
"expandable": true,
"title": "General",
"items": [
"pollInterval",
"language"
]
},
Expand All @@ -112,6 +109,7 @@
"title": "Advanced",
"description": "",
"items": [
"reconnectEventServerInterval",
"disableStopActionFor",
"disableTempSensorFor",
"disableSetTargetTempFor"
Expand Down
103 changes: 52 additions & 51 deletions src/mieleBasePlatformAccessory.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
// Apacche License
// Copyright (c) 2020, Sander van Woensel

import { PlatformAccessory, CharacteristicGetCallback, CharacteristicSetCallback, Service, CharacteristicValue } from 'homebridge';
import { PlatformAccessory, CharacteristicSetCallback, Service, CharacteristicValue } from 'homebridge';

import { DEVICES_INFO_URL, CACHE_RETIREMENT_TIME_MS } from './settings';
import { MieleAtHomePlatform, createErrorString } from './platform';
import { DEVICES_INFO_URL, EVENT_SERVER_RECONNECT_DELAY_S } from './settings';
import { MieleAtHomePlatform } from './platform';
import { IMieleCharacteristic } from './mieleCharacteristics';

import axios from 'axios';

import EventSource from 'eventsource';

export enum MieleState {
Off = 1,
Expand Down Expand Up @@ -38,13 +37,10 @@ export interface MieleStatusResponse {
// Class Base Miele Accessory
//-------------------------------------------------------------------------------------------------
export abstract class MieleBasePlatformAccessory {
private stateUrl = DEVICES_INFO_URL + '/' + this.accessory.context.device.uniqueId + '/state';
private lastCacheUpdateTime = 0;
private cacheUpdateQueued = false;
private cachedResponse: MieleStatusResponse | null = null;
private eventUrl = DEVICES_INFO_URL + '/' + this.accessory.context.device.uniqueId + '/events';
protected mainService!: Service;
protected characteristics: IMieleCharacteristic[] = [];

private eventSource: EventSource | null = null;

//-------------------------------------------------------------------------------------------------
constructor(
Expand All @@ -61,30 +57,51 @@ export abstract class MieleBasePlatformAccessory {
.getCharacteristic(this.platform.Characteristic.Identify)
.on('set', this.identify.bind(this));

// Start polling
if(this.platform.pollInterval > 0) {
setInterval(this.update.bind(this), this.platform.pollInterval*1000);
}

}
this.connectToEventServer();

//-------------------------------------------------------------------------------------------------
protected isCacheRetired(): boolean {
const retired = (this.lastCacheUpdateTime < Date.now() - CACHE_RETIREMENT_TIME_MS) &&
!this.cacheUpdateQueued;
setInterval(this.connectToEventServer.bind(this), this.platform.reconnectEventServerInterval*60*1000);

if (retired) {
this.platform.log.debug('Cache retired. Status update enforced.');
}
return retired;
}

//-----------------------------------------------------------------------------------------------
protected getGeneric(characteristic: IMieleCharacteristic, callback: CharacteristicGetCallback) {
if (this.isCacheRetired()) {
this.update();
private connectToEventServer() {
// Close previous
if(this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
return characteristic.get(callback);

const config = this.platform.getHttpRequestConfig();
config.headers['Authorization'] = this.platform.token?.getAccessToken();
config.headers['Accept'] = 'text/event-stream';

this.eventSource = new EventSource(this.eventUrl, config);

this.eventSource.addEventListener('device', (event) => {
this.platform.log.debug(`${this.accessory.context.device.displayName}: Event '${event.type}' received.`);
const data = JSON.parse(event['data']);
this.update(data.state);
});

this.eventSource.onopen = (_message) => {
this.platform.log.info(`${this.accessory.context.device.displayName}: `+
'Connection with Miele event server succesfully (re-)established.');
};

// TODO: improve. Maybe check what Miele raises manually first
this.eventSource.onerror = (error) => {
this.eventSource?.close();
this.platform.log.error(`${this.accessory.context.device.displayName}: Error received from Miele event server: `+
`'${JSON.stringify(error)}'`);
this.mainService.setCharacteristic(this.platform.Characteristic.StatusFault, this.platform.Characteristic.StatusFault.GENERAL_FAULT);

this.platform.log.info(`${this.accessory.context.device.displayName}: Will attempt to reconnect to the Miele event server after`+
` ${EVENT_SERVER_RECONNECT_DELAY_S}[s].`);
setTimeout(()=> {
this.connectToEventServer();
}, EVENT_SERVER_RECONNECT_DELAY_S*1000);
};

}

//-----------------------------------------------------------------------------------------------
Expand All @@ -95,29 +112,13 @@ export abstract class MieleBasePlatformAccessory {

//-----------------------------------------------------------------------------------------------
// Update all characteristics
protected update(): void {
this.platform.log.debug(`Update called. Requesting: ${this.stateUrl}`);
this.cacheUpdateQueued = true;

axios.get(this.stateUrl, this.platform.getHttpRequestConfig()).then( (response) => {

if(JSON.stringify(response.data) !== JSON.stringify(this.cachedResponse)) {

for(const characteristic of this.characteristics) {
characteristic.update(response.data);
}
this.cachedResponse = response.data;
}

this.lastCacheUpdateTime = Date.now();
this.cacheUpdateQueued = false;

this.mainService.setCharacteristic(this.platform.Characteristic.StatusFault, this.platform.Characteristic.StatusFault.NO_FAULT);

}).catch(response => {
this.platform.log.error( createErrorString(response) );
this.mainService.setCharacteristic(this.platform.Characteristic.StatusFault, this.platform.Characteristic.StatusFault.GENERAL_FAULT);
});
protected update(deviceData: MieleStatusResponse): void {
for(const characteristic of this.characteristics) {
characteristic.update(deviceData);
}

this.mainService.setCharacteristic(this.platform.Characteristic.StatusFault, this.platform.Characteristic.StatusFault.NO_FAULT);

}

}
Loading

0 comments on commit dd1eab9

Please sign in to comment.