From 756bc0f37e446e06dcbfedd15ff2396e36c1300b Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sun, 19 Sep 2021 18:33:53 -0700 Subject: [PATCH] fix(away): allowing rooms to be set to away --- .idea/jsLinters/eslint.xml | 6 ++ config.schema.json | 7 ++ package-lock.json | 14 +-- package.json | 2 +- src/platform.ts | 23 ++++- src/roomPlatformAccessory.ts | 81 +++++++++------ src/structurePlatformAccessory.ts | 158 ++++++++++++++++++++++++++++++ 7 files changed, 250 insertions(+), 41 deletions(-) create mode 100644 .idea/jsLinters/eslint.xml create mode 100644 src/structurePlatformAccessory.ts diff --git a/.idea/jsLinters/eslint.xml b/.idea/jsLinters/eslint.xml new file mode 100644 index 0000000..541945b --- /dev/null +++ b/.idea/jsLinters/eslint.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/config.schema.json b/config.schema.json index 1a5e7ed..ba0ebfa 100644 --- a/config.schema.json +++ b/config.schema.json @@ -54,6 +54,13 @@ "required": false, "default": false }, + "hidePrimaryStructure": { + "title": "Hide primary structure", + "description": "Hides the primary structure thermostat", + "type": "boolean", + "required": false, + "default": true + }, "hideVentTemperatureSensors": { "title": "Hide vent temperature sensors", "description": "Hides the Vent Temperature Sensors", diff --git a/package-lock.json b/package-lock.json index 2535d6e..61fcb00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "license": "ISC", "dependencies": { "class-transformer": "^0.4.0", - "flair-api-ts": "^1.0.26", + "flair-api-ts": "^1.0.28", "reflect-metadata": "^0.1.13" }, "devDependencies": { @@ -2479,9 +2479,9 @@ } }, "node_modules/flair-api-ts": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/flair-api-ts/-/flair-api-ts-1.0.26.tgz", - "integrity": "sha512-T7a42pmyc78yQEWkzH4nOJGCOvK9wGT9XyOUlxOZX0FxV+UxkRD8b4I9ROjPeEASHrR7QFiPnSHoQCYbZ3NBHQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/flair-api-ts/-/flair-api-ts-1.0.28.tgz", + "integrity": "sha512-MfWJeEvjBpDtwQtKqhIsq2S7aSXr+oJwL9SkR9AKRTGIXzSXz7KN8ehtL4uh7t5DeKSLuLLvTx473sqa8qaKKg==", "dependencies": { "axios": "^0.21.1", "moment": "^2.29.1", @@ -11201,9 +11201,9 @@ } }, "flair-api-ts": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/flair-api-ts/-/flair-api-ts-1.0.26.tgz", - "integrity": "sha512-T7a42pmyc78yQEWkzH4nOJGCOvK9wGT9XyOUlxOZX0FxV+UxkRD8b4I9ROjPeEASHrR7QFiPnSHoQCYbZ3NBHQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/flair-api-ts/-/flair-api-ts-1.0.28.tgz", + "integrity": "sha512-MfWJeEvjBpDtwQtKqhIsq2S7aSXr+oJwL9SkR9AKRTGIXzSXz7KN8ehtL4uh7t5DeKSLuLLvTx473sqa8qaKKg==", "requires": { "axios": "^0.21.1", "moment": "^2.29.1", diff --git a/package.json b/package.json index 14d1fce..6813bff 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ ], "dependencies": { "class-transformer": "^0.4.0", - "flair-api-ts": "^1.0.26", + "flair-api-ts": "^1.0.28", "reflect-metadata": "^0.1.13" }, "devDependencies": { diff --git a/src/platform.ts b/src/platform.ts index 0f32fe8..1b079a5 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -23,6 +23,7 @@ import { } from 'flair-api-ts'; import { plainToClass } from 'class-transformer'; import { getRandomIntInclusive } from './utils'; +import {FlairStructurePlatformAccessory} from './structurePlatformAccessory'; /** * HomebridgePlatform @@ -38,10 +39,12 @@ export class FlairPlatform implements DynamicPlatformPlugin { private client?: Client; - private structure?: Structure; + public structure?: Structure; private rooms: [FlairRoomPlatformAccessory?] = []; + private primaryStructureAccessory?: FlairStructurePlatformAccessory; + private _hasValidConfig?: boolean; private _hasValidCredentials?: boolean; @@ -151,6 +154,9 @@ export class FlairPlatform implements DynamicPlatformPlugin { room.updateFromStructure(this.structure); } } + if (this.primaryStructureAccessory) { + this.primaryStructureAccessory.updateFromStructure(this.structure); + } return this.structure; } @@ -234,6 +240,10 @@ export class FlairPlatform implements DynamicPlatformPlugin { ), ); }); + } else if (accessory.context.type === Structure.type) { + this.log.info('Restoring structure from cache:', accessory.displayName); + accessory.context.device = plainToClass(Structure, accessory.context.device); + this.primaryStructureAccessory = new FlairStructurePlatformAccessory(this, accessory, this.client!); } // add the restored accessory to the accessories cache so we can track if it has already been registered @@ -254,6 +264,10 @@ export class FlairPlatform implements DynamicPlatformPlugin { promisesToResolve.push(this.addDevices(await this.client!.getVents())); } + if (!this.config.hidePrimaryStructure) { + promisesToResolve.push(this.addDevices([await this.client!.getPrimaryStructure()])); + } + if (!this.config.hidePuckRooms) { promisesToResolve.push( this.addDevices( @@ -332,6 +346,13 @@ export class FlairPlatform implements DynamicPlatformPlugin { ), ); }); + } else if (device instanceof Structure) { + accessory.context.type = Structure.type; + this.primaryStructureAccessory = new FlairStructurePlatformAccessory( + this, + accessory, + this.client!, + ); } else { continue; } diff --git a/src/roomPlatformAccessory.ts b/src/roomPlatformAccessory.ts index 4c0d8c0..544594c 100644 --- a/src/roomPlatformAccessory.ts +++ b/src/roomPlatformAccessory.ts @@ -41,7 +41,7 @@ export class FlairRoomPlatformAccessory { .setCharacteristic(this.platform.Characteristic.SerialNumber, this.room.id!); this.thermostatService = this.accessory.getService(this.platform.Service.Thermostat) - ?? this.accessory.addService(this.platform.Service.Thermostat); + ?? this.accessory.addService(this.platform.Service.Thermostat); this.thermostatService.setPrimaryService(true); this.thermostatService .setCharacteristic(this.platform.Characteristic.Name, accessory.context.device.name) @@ -49,11 +49,11 @@ export class FlairRoomPlatformAccessory { .setCharacteristic(this.platform.Characteristic.TargetTemperature, this.room.setPointC!) .setCharacteristic( this.platform.Characteristic.TargetHeatingCoolingState, - this.getTargetHeatingCoolingStateFromStructure(this.structure)!, + this.getTargetHeatingCoolingStateFromStructureAndRoom(this.structure)!, ) .setCharacteristic( this.platform.Characteristic.CurrentHeatingCoolingState, - this.getCurrentHeatingCoolingStateFromStructure(this.structure)!, + this.getCurrentHeatingCoolingStateFromStructureAndRoom(this.structure)!, ) .setCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.room.currentHumidity!); @@ -63,31 +63,35 @@ export class FlairRoomPlatformAccessory { this.thermostatService.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState) .on(CharacteristicEventTypes.SET, this.setTargetHeatingCoolingState.bind(this)); - // .on(CharacteristicEventTypes.GET, this.getTargetTemperature.bind(this)) setInterval(async () => { await this.getNewRoomReadings(); - }, (platform.config.pollInterval+ getRandomIntInclusive(1,20)) * 1000); + }, (platform.config.pollInterval + getRandomIntInclusive(1, 20)) * 1000); this.getNewRoomReadings(); } - setTargetHeatingCoolingState(value: CharacteristicValue, callback: CharacteristicSetCallback):void { + setTargetHeatingCoolingState(value: CharacteristicValue, callback: CharacteristicSetCallback): void { if (value === this.platform.Characteristic.TargetHeatingCoolingState.OFF) { - this.platform.setStructureMode(FlairMode.AUTO, StructureHeatCoolMode.OFF).then((structure: Structure) => { + this.client.setRoomAway(this.room, true).then((room: Room) => { + this.updateRoomReadingsFromRoom(room); + this.platform.log.debug('Set Room to away', value); + // you must call the callback function callback(null); - this.updateFromStructure(structure); }); - } else if(value === this.platform.Characteristic.TargetHeatingCoolingState.COOL) { + } else if (value === this.platform.Characteristic.TargetHeatingCoolingState.COOL) { + this.setRoomActive(); this.platform.setStructureMode(FlairMode.AUTO, StructureHeatCoolMode.COOL).then((structure: Structure) => { callback(null); this.updateFromStructure(structure); }); - } else if(value === this.platform.Characteristic.TargetHeatingCoolingState.HEAT) { + } else if (value === this.platform.Characteristic.TargetHeatingCoolingState.HEAT) { + this.setRoomActive(); this.platform.setStructureMode(FlairMode.AUTO, StructureHeatCoolMode.HEAT).then((structure: Structure) => { callback(null); this.updateFromStructure(structure); }); - } else if(value === this.platform.Characteristic.TargetHeatingCoolingState.AUTO) { + } else if (value === this.platform.Characteristic.TargetHeatingCoolingState.AUTO) { + this.setRoomActive(); this.platform.setStructureMode(FlairMode.AUTO, StructureHeatCoolMode.AUTO).then((structure: Structure) => { callback(null); this.updateFromStructure(structure); @@ -95,9 +99,17 @@ export class FlairRoomPlatformAccessory { } } + setRoomActive(): void { + if (this.room.active) { + return; + } + this.client.setRoomAway(this.room, false).then((room: Room) => { + this.platform.log.debug('Set Room to active'); + }); + } - setTargetTemperature(value: CharacteristicValue, callback: CharacteristicSetCallback):void { + setTargetTemperature(value: CharacteristicValue, callback: CharacteristicSetCallback): void { this.client.setRoomSetPoint(this.room, value as number).then((room: Room) => { this.updateRoomReadingsFromRoom(room); this.platform.log.debug('Set Characteristic Temperature -> ', value); @@ -107,7 +119,7 @@ export class FlairRoomPlatformAccessory { } - getTargetTemperature(callback: CharacteristicGetCallback):void { + getTargetTemperature(callback: CharacteristicGetCallback): void { this.getNewRoomReadings().then((room: Room) => { callback(null, room.setPointC); }); @@ -126,27 +138,19 @@ export class FlairRoomPlatformAccessory { return this.room; } - public updateFromStructure(structure: Structure):void { + public updateFromStructure(structure: Structure): void { this.structure = structure; // push the new value to HomeKit - this.thermostatService - .updateCharacteristic( - this.platform.Characteristic.TargetHeatingCoolingState, - this.getTargetHeatingCoolingStateFromStructure(this.structure)!, - ) - .updateCharacteristic( - this.platform.Characteristic.CurrentHeatingCoolingState, - this.getCurrentHeatingCoolingStateFromStructure(this.structure)!, - ); + this.updateRoomReadingsFromRoom(this.room); this.platform.log.debug( `Pushed updated current structure state for ${this.room.name!} to HomeKit:`, - this.structure.structureHeatCoolMode!, + this.structure.structureHeatCoolMode!, ); } - updateRoomReadingsFromRoom(room: Room):void { + updateRoomReadingsFromRoom(room: Room): void { this.accessory.context.device = room; this.room = room; @@ -154,19 +158,26 @@ export class FlairRoomPlatformAccessory { this.thermostatService .updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.room.currentTemperatureC!) .updateCharacteristic(this.platform.Characteristic.TargetTemperature, this.room.setPointC!) - .updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.room.currentHumidity!); + .updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, this.room.currentHumidity!) + .updateCharacteristic( + this.platform.Characteristic.TargetHeatingCoolingState, + this.getTargetHeatingCoolingStateFromStructureAndRoom(this.structure)!, + ) + .updateCharacteristic( + this.platform.Characteristic.CurrentHeatingCoolingState, + this.getCurrentHeatingCoolingStateFromStructureAndRoom(this.structure)!, + ); this.platform.log.debug( `Pushed updated current temperature state for ${this.room.name!} to HomeKit:`, - this.room.currentTemperatureC!, + this.room.currentTemperatureC!, ); } - private getCurrentHeatingCoolingStateFromStructure(structure: Structure) { - if (structure.structureHeatCoolMode === StructureHeatCoolMode.OFF) { + private getCurrentHeatingCoolingStateFromStructureAndRoom(structure: Structure) { + if (!this.room.active) { return this.platform.Characteristic.CurrentHeatingCoolingState.OFF; } - if (structure.structureHeatCoolMode === StructureHeatCoolMode.COOL) { return this.platform.Characteristic.CurrentHeatingCoolingState.COOL; } @@ -176,13 +187,17 @@ export class FlairRoomPlatformAccessory { } if (structure.structureHeatCoolMode === StructureHeatCoolMode.AUTO) { + //TODO: When the structure api shows the current thermostat mode change this to that. + // For now active always means cool. return this.platform.Characteristic.CurrentHeatingCoolingState.COOL; } + + return this.platform.Characteristic.CurrentHeatingCoolingState.OFF; } - private getTargetHeatingCoolingStateFromStructure(structure: Structure) { - if (structure.structureHeatCoolMode === StructureHeatCoolMode.OFF) { + private getTargetHeatingCoolingStateFromStructureAndRoom(structure: Structure) { + if (!this.room.active) { return this.platform.Characteristic.TargetHeatingCoolingState.OFF; } @@ -197,6 +212,8 @@ export class FlairRoomPlatformAccessory { if (structure.structureHeatCoolMode === StructureHeatCoolMode.AUTO) { return this.platform.Characteristic.TargetHeatingCoolingState.AUTO; } + + return this.platform.Characteristic.TargetHeatingCoolingState.AUTO; } } diff --git a/src/structurePlatformAccessory.ts b/src/structurePlatformAccessory.ts new file mode 100644 index 0000000..e88a732 --- /dev/null +++ b/src/structurePlatformAccessory.ts @@ -0,0 +1,158 @@ +import type {PlatformAccessory, Service} from 'homebridge'; +import { + CharacteristicEventTypes, + CharacteristicGetCallback, + CharacteristicSetCallback, + CharacteristicValue, +} from 'homebridge'; + +import {FlairPlatform} from './platform'; +import {FlairMode, Structure, StructureHeatCoolMode, Client} from 'flair-api-ts'; + +/** + * Platform Accessory + * An instance of this class is created for each accessory your platform registers + * Each accessory may expose multiple services of different service types. + */ +export class FlairStructurePlatformAccessory { + private accessoryInformationService: Service; + private thermostatService: Service; + + private client: Client; + private structure: Structure; + + + constructor( + private readonly platform: FlairPlatform, + private readonly accessory: PlatformAccessory, + client: Client, + ) { + this.structure = this.accessory.context.device; + this.client = client; + + // set accessory information + this.accessoryInformationService = this.accessory.getService(this.platform.Service.AccessoryInformation)! + .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Flair') + .setCharacteristic(this.platform.Characteristic.Model, 'Structure') + .setCharacteristic(this.platform.Characteristic.SerialNumber, this.structure.id!); + + this.thermostatService = this.accessory.getService(this.platform.Service.Thermostat) + ?? this.accessory.addService(this.platform.Service.Thermostat); + this.thermostatService.setPrimaryService(true); + this.thermostatService + .setCharacteristic(this.platform.Characteristic.Name, accessory.context.device.name) + .setCharacteristic(this.platform.Characteristic.CurrentTemperature, this.structure.setPointTemperatureC!) + .setCharacteristic(this.platform.Characteristic.TargetTemperature, this.structure.setPointTemperatureC!) + .setCharacteristic( + this.platform.Characteristic.TargetHeatingCoolingState, + this.getTargetHeatingCoolingState(this.structure)!, + ) + .setCharacteristic( + this.platform.Characteristic.CurrentHeatingCoolingState, + this.getCurrentHeatingCoolingState(this.structure)!, + ); + + this.thermostatService.getCharacteristic(this.platform.Characteristic.TargetTemperature) + .on(CharacteristicEventTypes.SET, this.setTargetTemperature.bind(this)) + .on(CharacteristicEventTypes.GET, this.getTargetTemperature.bind(this)); + + this.thermostatService.getCharacteristic(this.platform.Characteristic.TargetHeatingCoolingState) + .on(CharacteristicEventTypes.SET, this.setTargetHeatingCoolingState.bind(this)); + } + + setTargetHeatingCoolingState(value: CharacteristicValue, callback: CharacteristicSetCallback): void { + if (value === this.platform.Characteristic.TargetHeatingCoolingState.OFF) { + this.platform.setStructureMode(FlairMode.AUTO, StructureHeatCoolMode.OFF).then((structure: Structure) => { + callback(null); + this.updateFromStructure(structure); + }); + } else if (value === this.platform.Characteristic.TargetHeatingCoolingState.COOL) { + this.platform.setStructureMode(FlairMode.AUTO, StructureHeatCoolMode.COOL).then((structure: Structure) => { + callback(null); + this.updateFromStructure(structure); + }); + } else if (value === this.platform.Characteristic.TargetHeatingCoolingState.HEAT) { + this.platform.setStructureMode(FlairMode.AUTO, StructureHeatCoolMode.HEAT).then((structure: Structure) => { + callback(null); + this.updateFromStructure(structure); + }); + } else if (value === this.platform.Characteristic.TargetHeatingCoolingState.AUTO) { + this.platform.setStructureMode(FlairMode.AUTO, StructureHeatCoolMode.AUTO).then((structure: Structure) => { + callback(null); + this.updateFromStructure(structure); + }); + } + } + + setTargetTemperature(value: CharacteristicValue, callback: CharacteristicSetCallback): void { + this.client.setStructureSetPoint(this.structure, value as number).then((structure: Structure) => { + // you must call the callback function + callback(null); + this.updateFromStructure(structure); + this.platform.log.debug('Set Characteristic Temperature -> ', value); + + }); + + } + + getTargetTemperature(callback: CharacteristicGetCallback): void { + callback(null, this.platform.structure?.setPointTemperatureC); + } + + public updateFromStructure(structure: Structure): void { + this.structure = structure; + + // push the new value to HomeKit + this.thermostatService + .updateCharacteristic(this.platform.Characteristic.TargetTemperature, this.structure.setPointTemperatureC!) + .updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.structure.setPointTemperatureC!) + .updateCharacteristic( + this.platform.Characteristic.TargetHeatingCoolingState, + this.getTargetHeatingCoolingState(this.structure)!, + ) + .updateCharacteristic( + this.platform.Characteristic.CurrentHeatingCoolingState, + this.getCurrentHeatingCoolingState(this.structure)!, + ); + + this.platform.log.debug( + `Pushed updated current structure state for ${this.structure.name!} to HomeKit:`, + this.structure.structureHeatCoolMode!, + ); + } + + private getCurrentHeatingCoolingState(structure: Structure) { + if (structure.structureHeatCoolMode === StructureHeatCoolMode.COOL) { + return this.platform.Characteristic.CurrentHeatingCoolingState.COOL; + } + + if (structure.structureHeatCoolMode === StructureHeatCoolMode.HEAT) { + return this.platform.Characteristic.CurrentHeatingCoolingState.HEAT; + } + + if (structure.structureHeatCoolMode === StructureHeatCoolMode.AUTO) { + //TODO: When the structure api shows the current thermostat mode change this to that. + // For now active always means cool. + return this.platform.Characteristic.CurrentHeatingCoolingState.COOL; + } + + return this.platform.Characteristic.CurrentHeatingCoolingState.OFF; + } + + private getTargetHeatingCoolingState(structure: Structure) { + if (structure.structureHeatCoolMode === StructureHeatCoolMode.COOL) { + return this.platform.Characteristic.TargetHeatingCoolingState.COOL; + } + + if (structure.structureHeatCoolMode === StructureHeatCoolMode.HEAT) { + return this.platform.Characteristic.TargetHeatingCoolingState.HEAT; + } + + if (structure.structureHeatCoolMode === StructureHeatCoolMode.AUTO) { + return this.platform.Characteristic.TargetHeatingCoolingState.AUTO; + } + + return this.platform.Characteristic.TargetHeatingCoolingState.OFF; + } + +}