UNPKG

homebridge-hatch-baby-rest

Version:
119 lines (118 loc) 4.32 kB
import { BehaviorSubject, firstValueFrom, skip, Subject } from 'rxjs'; import { filter } from 'rxjs/operators'; import { delay, logDebug, logError } from "../shared/util.js"; function assignState(previousState, changes) { const state = Object.assign({}, previousState); for (const key in changes) { if (typeof changes[key] === 'object') { state[key] = assignState(previousState[key] || {}, changes[key]); } else { state[key] = changes[key]; } } return state; } export const MAX_IOT_VALUE = 65535; export function convertFromPercentage(percentage) { return Math.ceil((percentage / 100) * MAX_IOT_VALUE); } export function convertToPercentage(value) { return Math.floor((value * 100) / MAX_IOT_VALUE); } export class IotDevice { onCurrentState = new BehaviorSubject(null); get mqttClient() { return this.onIotClient.getValue(); } onStatusToken = new Subject(); previousUpdatePromise = Promise.resolve(); onState = this.onCurrentState.pipe(filter((state) => state !== null)); get id() { return this.info.id; } get name() { return this.info.name; } get macAddress() { return this.info.macAddress; } info; onIotClient; constructor(info, onIotClient) { this.info = info; this.onIotClient = onIotClient; onIotClient .pipe(skip(1)) .subscribe((client) => this.registerMqttClient(client)); this.registerMqttClient(onIotClient.getValue()); } registerMqttClient(mqttClient) { const { thingName } = this.info; let getClientToken; mqttClient.on('close', () => { logDebug('MQTT client closed'); }); mqttClient.on('offline', () => { logDebug('MQTT client offline'); }); mqttClient.on('status', (topic, message, clientToken, status) => { if (topic !== thingName) { // status for a different thing return; } this.onStatusToken.next(clientToken); if (clientToken === getClientToken) { const { state } = status; this.onCurrentState.next(assignState(state.reported, state.desired)); } }); mqttClient.on('foreignStateChange', (topic, message, s) => { const currentState = this.onCurrentState.getValue(); if (!currentState || topic !== thingName) { return; } this.onCurrentState.next(assignState(assignState(currentState, s.state.reported), s.state.desired)); }); this.previousUpdatePromise = this.previousUpdatePromise .catch((_) => { // ignore errors, they shouldn't be possible void _; }) .then(() => new Promise((resolve) => { mqttClient.on('connect', () => { mqttClient.register(thingName, {}, () => { getClientToken = mqttClient.get(thingName); resolve(firstValueFrom(this.onStatusToken.pipe(filter((token) => token === getClientToken)))); }); }); })); } getCurrentState() { return firstValueFrom(this.onState); } update(update) { this.previousUpdatePromise = this.previousUpdatePromise .catch((_) => { // ignore errors, they shouldn't be possible void _; }) .then(() => { if (!this.mqttClient) { logError(`Unable to Update ${this.name} - No MQTT Client Registered`); return; } const updateToken = this.mqttClient.update(this.info.thingName, { state: { desired: update, }, }); if (!updateToken) { logError(`Failed to apply update to ${this.name} because another update was in progress: ${JSON.stringify(update)}`); } const requestComplete = firstValueFrom(this.onStatusToken.pipe(filter((token) => token === updateToken))); // wait a max of 30 seconds to finish request return Promise.race([requestComplete, delay(30000)]); }); } }