Skip to content

Commit

Permalink
Improved error handling to avoid HB restarts
Browse files Browse the repository at this point in the history
  • Loading branch information
jxg81 committed Jul 25, 2022
1 parent ae8f225 commit 4848888
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 76 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"private": false,
"displayName": "Homebridge Actron Que",
"name": "homebridge-actron-que",
"version": "1.0.2",
"version": "1.0.3",
"description": "Homebridge plugin for controlling Actron Que controller systems",
"license": "Apache-2.0",
"repository": {
Expand Down
6 changes: 5 additions & 1 deletion src/hvac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ export class HvacUnit {
this.type = 'actronQue';
this.apiInterface = new QueApi(username, password, this.name, this.log, serialNo);
await this.apiInterface.initalizer();
this.serialNo = this.apiInterface.actronSerial;
if (this.apiInterface.actronSerial) {
this.serialNo = this.apiInterface.actronSerial;
} else {
throw Error('Failed to locate device serial number. Please check your config file');
}
return this.serialNo;
}

Expand Down
141 changes: 73 additions & 68 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ export class ActronQuePlatform implements DynamicPlatformPlugin {
this.password = config['password'];
if (config['deviceSerial']) {
this.userProvidedSerialNo = config['deviceSerial'];
this.log.debug('Serial number should only be specified if you have multiple systems in your Que account', this.userProvidedSerialNo);
this.log.debug('Serial number should only be added if you have multiple Que systems. Serial provided ->', this.userProvidedSerialNo);
} else {
this.userProvidedSerialNo = '';
}
if (config['zonesFollowMaster']) {
this.zonesFollowMaster = config['zonesFollowMaster'];
this.log.debug('Will zones follow chnages to the master controller automatically?', this.zonesFollowMaster);
this.log.debug('Control All Zones for Master is set to', this.zonesFollowMaster);
} else {
this.zonesFollowMaster = true;
}
Expand Down Expand Up @@ -83,75 +83,80 @@ export class ActronQuePlatform implements DynamicPlatformPlugin {
}

async discoverDevices() {
// Instantiate an instance of HvacUnit and connect the actronQueApi
this.hvacInstance = new HvacUnit(this.clientName, this.log, this.zonesFollowMaster);
const hvacSerial = await this.hvacInstance.actronQueApi(this.username, this.password, this.userProvidedSerialNo);
// Make sure we have havc master and zone data before adding devices
await this.hvacInstance.getStatus();
const devices: DiscoveredDevices[] = [
{
type: 'masterController',
uniqueId: hvacSerial,
displayName: this.clientName,
instance: this.hvacInstance,
},
{
type: 'outdoorUnit',
uniqueId: hvacSerial + '-outdoorUnit',
displayName: this.clientName + '-outdoorUnit',
instance: this.hvacInstance,
},
];
for (const zone of this.hvacInstance.zoneInstances) {
devices.push({
type: 'zoneController',
uniqueId: zone.sensorId,
displayName: zone.zoneName,
instance: zone,
});
}
this.log.debug('Discovered Devices \n', devices);
// loop over the discovered devices and register each one if it has not already been registered
for (const device of devices) {
try {
// Instantiate an instance of HvacUnit and connect the actronQueApi
this.hvacInstance = new HvacUnit(this.clientName, this.log, this.zonesFollowMaster);
let hvacSerial = '';
hvacSerial = await this.hvacInstance.actronQueApi(this.username, this.password, this.userProvidedSerialNo);
// Make sure we have havc master and zone data before adding devices
await this.hvacInstance.getStatus();
const devices: DiscoveredDevices[] = [
{
type: 'masterController',
uniqueId: hvacSerial,
displayName: this.clientName,
instance: this.hvacInstance,
},
{
type: 'outdoorUnit',
uniqueId: hvacSerial + '-outdoorUnit',
displayName: this.clientName + '-outdoorUnit',
instance: this.hvacInstance,
},
];
for (const zone of this.hvacInstance.zoneInstances) {
devices.push({
type: 'zoneController',
uniqueId: zone.sensorId,
displayName: zone.zoneName,
instance: zone,
});
}
this.log.debug('Discovered Devices \n', devices);
// loop over the discovered devices and register each one if it has not already been registered
for (const device of devices) {
// create uuid first then see if an accessory with the same uuid has already been registered and restored from
// the cached devices we stored in the `configureAccessory` method above
const uuid = this.api.hap.uuid.generate(device.uniqueId);
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);

// Create and/or restore cached accessories
if (existingAccessory && device.type === 'masterController') {
this.log.info('Restoring Master Controller accessory from cache:', existingAccessory.displayName);
new MasterControllerAccessory(this, existingAccessory);

} else if (existingAccessory && device.type === 'zoneController') {
this.log.info('Restoring Zone Controller accessory from cache:', existingAccessory.displayName);
new ZoneControllerAccessory(this, existingAccessory, device.instance as HvacZone);

} else if (existingAccessory && device.type === 'outdoorUnit') {
this.log.info('Restoring Outdoor Unit accessory from cache:', existingAccessory.displayName);
new OutdoorUnitAccessory(this, existingAccessory);

} else if (!existingAccessory && device.type === 'masterController'){
this.log.info('Adding new accessory:', device.displayName);
const accessory = new this.api.platformAccessory(device.displayName, uuid);
accessory.context.device = device;
new MasterControllerAccessory(this, accessory);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);

} else if (!existingAccessory && device.type === 'zoneController'){
this.log.info('Adding new accessory:', device.displayName);
const accessory = new this.api.platformAccessory(device.displayName, uuid);
accessory.context.device = device;
new ZoneControllerAccessory(this, accessory, device.instance as HvacZone);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);

} else if (!existingAccessory && device.type === 'outdoorUnit'){
this.log.info('Adding new accessory:', device.displayName);
const accessory = new this.api.platformAccessory(device.displayName, uuid);
accessory.context.device = device;
new OutdoorUnitAccessory(this, accessory);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
const uuid = this.api.hap.uuid.generate(device.uniqueId);
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);

// Create and/or restore cached accessories
if (existingAccessory && device.type === 'masterController') {
this.log.info('Restoring Master Controller accessory from cache:', existingAccessory.displayName);
new MasterControllerAccessory(this, existingAccessory);

} else if (existingAccessory && device.type === 'zoneController') {
this.log.info('Restoring Zone Controller accessory from cache:', existingAccessory.displayName);
new ZoneControllerAccessory(this, existingAccessory, device.instance as HvacZone);

} else if (existingAccessory && device.type === 'outdoorUnit') {
this.log.info('Restoring Outdoor Unit accessory from cache:', existingAccessory.displayName);
new OutdoorUnitAccessory(this, existingAccessory);

} else if (!existingAccessory && device.type === 'masterController'){
this.log.info('Adding new accessory:', device.displayName);
const accessory = new this.api.platformAccessory(device.displayName, uuid);
accessory.context.device = device;
new MasterControllerAccessory(this, accessory);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);

} else if (!existingAccessory && device.type === 'zoneController'){
this.log.info('Adding new accessory:', device.displayName);
const accessory = new this.api.platformAccessory(device.displayName, uuid);
accessory.context.device = device;
new ZoneControllerAccessory(this, accessory, device.instance as HvacZone);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);

} else if (!existingAccessory && device.type === 'outdoorUnit'){
this.log.info('Adding new accessory:', device.displayName);
const accessory = new this.api.platformAccessory(device.displayName, uuid);
accessory.context.device = device;
new OutdoorUnitAccessory(this, accessory);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
}
}
} catch {
this.log.error('Plugin disabled, please review error log and check your config file then restart Homebridge');
}
}
}
22 changes: 16 additions & 6 deletions src/queApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,24 @@ export default class QueApi {

case (200):
return response.json();

// If the bearer token has expired then generate new token, update request and retry
// error will be generated after max retries is reached, default of 2
// error will be generated after max retries is reached, default of 3
case(401):
await this.tokenGenerator();
requestContent.headers.set('Authorization', `Bearer ${this.bearerToken.token}`);
if (retries > 0) {
if (retries > 1) {
await wait();
await this.tokenGenerator();
requestContent.headers.set('Authorization', `Bearer ${this.bearerToken.token}`);
return this.manageApiRequest(requestContent, retries -1);
} else if (retries > 0) {
// after two failed attempts, completely clear the token files from persistent storage
// create new and try again with a new refresh and bearer token and try one more time
this.log.debug('clearing all token data and trying one more time following multiple 401 errors');
fs.writeFileSync(this.refreshTokenFile, '{"expires": 0, "token": ""}');
fs.writeFileSync(this.bearerTokenFile, '{"expires": 0, "token": ""}');
this.refreshToken = JSON.parse(fs.readFileSync(this.refreshTokenFile).toString());
this.bearerToken = JSON.parse(fs.readFileSync(this.bearerTokenFile).toString());
await this.tokenGenerator();
requestContent.headers.set('Authorization', `Bearer ${this.bearerToken.token}`);
return this.manageApiRequest(requestContent, retries -1);
} else {
throw Error(`Maximum retires excced on failed Authorisation: http status code = ${response.status}`);
Expand Down Expand Up @@ -140,7 +150,7 @@ export default class QueApi {
}),
});
// this is wrapped in a try/catch to help identify potential user/pass related errors
let response;
let response: object = {};
try {
response = await this.manageApiRequest(preparedRequest);
} catch (error) {
Expand Down

0 comments on commit 4848888

Please sign in to comment.