import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' import { interval, Subject } from 'rxjs' import { debounceTime, skipWhile, take, tap } from 'rxjs/operators' import type { ResideoDevice } from '../api/resideoClient.js' import type { ResideoPlatform } from '../platform.js' import type { payload } from '../settings.js' import { deviceBase } from './device.js' export class Valve extends deviceBase { private Valve!: { Name: CharacteristicValue Service: Service Active: CharacteristicValue InUse: CharacteristicValue ValveType: CharacteristicValue } valveType!: number valveUpdateInProgress!: boolean doValveUpdate!: Subject constructor( readonly platform: ResideoPlatform, accessory: PlatformAccessory, device: ResideoDevice, ) { super(platform, accessory, device) this.getValveConfigSettings(accessory) this.doValveUpdate = new Subject() this.valveUpdateInProgress = false accessory.context.Valve = accessory.context.Valve ?? {} this.Valve = { Name: accessory.context.Valve.Name ?? accessory.displayName, Service: accessory.getService(this.hap.Service.Valve) ?? accessory.addService(this.hap.Service.Valve) as Service, Active: accessory.context.Active ?? this.hap.Characteristic.Active.INACTIVE, InUse: accessory.context.InUse ?? this.hap.Characteristic.InUse.NOT_IN_USE, ValveType: accessory.context.ValveType ?? this.hap.Characteristic.ValveType.GENERIC_VALVE, } accessory.context.Valve = this.Valve as object this.Valve.Service .setCharacteristic(this.hap.Characteristic.Name, this.Valve.Name) .setCharacteristic(this.hap.Characteristic.ValveType, this.valveType) .getCharacteristic(this.hap.Characteristic.Active) .onGet(() => this.Valve.Active) .onSet(this.setActive.bind(this)) this.Valve.Service .getCharacteristic(this.hap.Characteristic.InUse) .onGet(() => this.Valve.InUse) this.refreshStatus() interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.valveUpdateInProgress)) .subscribe(async () => { await this.refreshStatus() }) this.doValveUpdate .pipe( tap(() => { this.valveUpdateInProgress = true }), debounceTime(this.devicePushRate * 1000), ) .subscribe(async () => { try { await this.pushChanges() } catch (e: any) { const action = 'pushChanges' await this.resideoAPIError(e, action) this.errorLog(`Valve ${accessory.displayName}: doValveUpdate pushChanges: ${JSON.stringify(e)}`) } interval(this.deviceRefreshRate * 500) .pipe(skipWhile(() => this.valveUpdateInProgress), take(1)) .subscribe(async () => { await this.refreshStatus() }) this.valveUpdateInProgress = false }) } async parseStatus(device: ResideoDevice): Promise { // Assume device is online if globalDeviceType is present (customize as needed) this.Valve.Active = this.hap.Characteristic.Active.ACTIVE this.accessory.context.Active = this.Valve.Active // No valveStatus available, so default to NOT_IN_USE this.Valve.InUse = this.hap.Characteristic.InUse.NOT_IN_USE if (this.Valve.InUse !== this.accessory.context.InUse) { this.successLog(`Valve ${this.accessory.displayName} (refreshStatus) device: ${JSON.stringify(device)}`) this.accessory.context.InUse = this.Valve.InUse } } async refreshStatus(): Promise { try { // Use the new ResideoClient to fetch device state const deviceId = String(this.device.deviceId) const deviceState = await this.platform.client.getDeviceState(deviceId) this.debugLog(`Valve ${this.accessory.displayName} (refreshStatus) device: ${JSON.stringify(deviceState)}`) this.device = { ...this.device, ...deviceState } this.parseStatus(this.device as ResideoDevice) this.updateHomeKitCharacteristics() } catch (e: any) { const action = 'refreshStatus' // retry logic removed; not present in new ResideoDevice await this.resideoAPIError(e, action) this.apiError(e) } } async pushChanges(): Promise { try { // The new API client should have a method for sending state changes; placeholder below const payload: payload = { state: this.Valve.Active === this.hap.Characteristic.Active.ACTIVE ? 'open' : 'closed', } // TODO: Implement this in ResideoClient if not present // await this.platform.client.setValveState(this.device.deviceID, payload) this.debugLog(`Would send payload to API: ${JSON.stringify(payload)}`) this.debugLog(`Valve ${this.accessory.displayName} pushChanges: ${JSON.stringify(payload)}`) const action = 'pushChanges' await this.statusCode(200, action) } catch (e: any) { const action = 'pushChanges' await this.resideoAPIError(e, action) this.errorLog(`pushChanges: ${JSON.stringify(e)}`) this.errorLog(`Valve ${this.accessory.displayName} failed pushChanges, Error Message: ${JSON.stringify(e.message)}`) } } async updateHomeKitCharacteristics(): Promise { this.Valve.Service.updateCharacteristic(this.hap.Characteristic.Active, this.Valve.Active) this.accessory.context.Active = this.Valve.Active this.debugLog(`Valve ${this.accessory.displayName} updateCharacteristic Active: ${this.Valve.Active}`) this.Valve.Service.updateCharacteristic(this.hap.Characteristic.InUse, this.Valve.InUse) this.accessory.context.InUse = this.Valve.InUse this.debugLog(`Valve ${this.accessory.displayName} updateCharacteristic InUse: ${this.Valve.InUse}`) } setActive(value: CharacteristicValue) { this.debugLog(`Valve ${this.accessory.displayName} Set Active: ${value}`) this.Valve.Active = value this.doValveUpdate.next() } async getValveConfigSettings(accessory: PlatformAccessory) { // No valveType info in ResideoDevice, default to GENERIC_VALVE this.valveType = this.hap.Characteristic.ValveType.GENERIC_VALVE accessory.context.valveType = this.valveType } async apiError(e: any): Promise { this.Valve.Service.updateCharacteristic(this.hap.Characteristic.Active, e) } }