diff --git a/src/homebridge/electric-mat.device.ts b/src/homebridge/electric-mat.device.ts index 935ccdb..207a55d 100644 --- a/src/homebridge/electric-mat.device.ts +++ b/src/homebridge/electric-mat.device.ts @@ -55,6 +55,7 @@ export default class ElectricMat { TargetHeaterCoolerState, CurrentTemperature, HeatingThresholdTemperature, + LockPhysicalControls, TemperatureDisplayUnits, }, Service: { @@ -104,6 +105,11 @@ export default class ElectricMat { .onGet(this.getTemperature.bind(this)) .onSet(this.setTemperature.bind(this)); + // lock + heater.getCharacteristic(LockPhysicalControls) + .onGet(this.getLocked.bind(this)) + .onSet(this.setLocked.bind(this)); + // subscribe to device events device.activeChanges.subscribe((isActive) => { heater.updateCharacteristic(Active, isActive ? Active.ACTIVE : Active.INACTIVE); @@ -112,6 +118,9 @@ export default class ElectricMat { heater.updateCharacteristic(CurrentTemperature, temperature); heater.updateCharacteristic(HeatingThresholdTemperature, temperature); }); + device.lockedChanges.subscribe((isLocked) => { + heater.updateCharacteristic(LockPhysicalControls, isLocked); + }); return heater; } @@ -214,7 +223,7 @@ export default class ElectricMat { this.log.info('Set Active:', isActive ? 'ACTIVE' : 'INACTIVE'); - await this.service.setActive(this.device, isActive); + await this.service.activate(this.device, isActive); } private async getHeaterState(): Promise { @@ -251,7 +260,7 @@ export default class ElectricMat { this.log.info('Set Heating State:', isActive ? 'HEAT' : 'OFF'); - await this.service.setActive(this.device, isActive); + await this.service.activate(this.device, isActive); } private async getTemperature(): Promise { @@ -269,4 +278,20 @@ export default class ElectricMat { await this.service.setTemperature(this.device, temperature); } + + private async getLocked(): Promise { + const { isLocked } = this.device; + + this.log.info('Get Locked:', isLocked); + + return isLocked; + } + + private async setLocked(value: CharacteristicValue) { + const isLocked = !!value; + + this.log.info('Set Locked:', isLocked); + + await this.service.lock(this.device, isLocked); + } } diff --git a/src/navien/navien.api.ts b/src/navien/navien.api.ts index 6ba6c5b..f15f41a 100644 --- a/src/navien/navien.api.ts +++ b/src/navien/navien.api.ts @@ -2,6 +2,7 @@ import assert from 'assert'; import { Logger } from 'homebridge'; import fetch, { BodyInit, HeadersInit, Response } from 'node-fetch'; +import { OperationMode } from '../aws/interfaces'; import { API_URL } from './constants'; import { ApiException } from './exceptions'; import { CommonResponse, Device, DevicesResponse, ResponseCode } from './interfaces'; @@ -152,9 +153,9 @@ export class NavienApi { return this.controlDevice(device); } - public setActive(device: Device, isActive: boolean) { + public setOperationMode(device: Device, value: OperationMode) { return this.controlDevice(device, { - operationMode: isActive ? 1 : 0, + operationMode: value, }); } @@ -185,4 +186,10 @@ export class NavienApi { }, }); } + + public setChildLock(device: Device, isLocked: boolean) { + return this.controlDevice(device, { + childLock: isLocked, + }); + } } diff --git a/src/navien/navien.device.ts b/src/navien/navien.device.ts index 61d9ecc..6a58d26 100644 --- a/src/navien/navien.device.ts +++ b/src/navien/navien.device.ts @@ -8,13 +8,16 @@ import { NavienApi } from './navien.api'; const DEFALUT_IS_ACTIVE = false; const DEFALUT_TEMPERATURE = 30; +const DEFALUT_IS_LOCKED = false; export class NavienDevice { private _isActive = DEFALUT_IS_ACTIVE; private _temperature = DEFALUT_TEMPERATURE; + private _isLocked = DEFALUT_IS_LOCKED; private readonly isActiveSubject = new BehaviorSubject(DEFALUT_IS_ACTIVE); private readonly temperatureSubject = new BehaviorSubject(DEFALUT_TEMPERATURE); + private readonly isLockedSubject = new BehaviorSubject(DEFALUT_IS_LOCKED); private readonly subcription: Subscription; constructor( @@ -32,11 +35,12 @@ export class NavienDevice { const leftTemperature = state.heater.left.temperature.set; const rightTemperature = state.heater.right.temperature.set; const temperature = leftTemperature; // TODO: handle left and right - const locked = state.childLock; - this.log.info('[AWS PubSub] current status:', { name: this.name, isActive, leftTemperature, rightTemperature, locked }); + const isLocked = state.childLock; + this.log.info('[AWS PubSub] current status:', { name: this.name, isActive, leftTemperature, rightTemperature, isLocked }); this.isActive = isActive; this.temperature = temperature; + this.isLocked = isLocked; }); } @@ -97,6 +101,18 @@ export class NavienDevice { this.temperatureSubject.next(value); } + get isLocked() { + return this._isLocked; + } + + set isLocked(value: boolean) { + if (this._isLocked === value) { + return; + } + this._isLocked = value; + this.isLockedSubject.next(value); + } + get activeChanges() { return this.isActiveSubject.asObservable(); } @@ -105,18 +121,26 @@ export class NavienDevice { return this.temperatureSubject.asObservable(); } + get lockedChanges() { + return this.isLockedSubject.asObservable(); + } + initialize() { return this.api.initializeDevice(this.json); } - setActive(isActive: boolean) { - return this.api.setActive(this.json, isActive); + activate(isActive: boolean) { + return this.api.setOperationMode(this.json, isActive ? OperationMode.ON : OperationMode.OFF); } setTemperature(temperature: number) { return this.api.setTemperature(this.json, temperature, this.functions.heatRange); } + lock(isLocked: boolean) { + return this.api.setChildLock(this.json, isLocked); + } + dispose() { this.subcription.unsubscribe(); } diff --git a/src/navien/navien.service.ts b/src/navien/navien.service.ts index 4d16090..7cc9ad2 100644 --- a/src/navien/navien.service.ts +++ b/src/navien/navien.service.ts @@ -77,10 +77,10 @@ export class NavienService { return devices; } - public async setActive(device: NavienDevice, isActive: boolean) { + public async activate(device: NavienDevice, isActive: boolean) { this.log.info('Setting active to', isActive, 'for device', device.name); - const success = await device.setActive(isActive).then(() => true).catch((error) => { + const success = await device.activate(isActive).then(() => true).catch((error) => { if (error instanceof NavienException) { this.log.error(error.toString()); return false; @@ -113,6 +113,24 @@ export class NavienService { } else { this.log.error('Failed to set temperature to', temperature, 'for device', device.name); } + } + + public async lock(device: NavienDevice, isLocked: boolean) { + this.log.info('Setting lock to', isLocked, 'for device', device.name); + + const success = await device.lock(isLocked).then(() => true).catch((error) => { + if (error instanceof NavienException) { + this.log.error(error.toString()); + return false; + } + this.log.error('Unknown error while setting lock for device', device.name, ':', error); + return false; + }); + if (success) { + this.log.info('Lock set to', isLocked, 'for device', device.name); + } else { + this.log.error('Failed to set lock to', isLocked, 'for device', device.name); + } } }