// Libs // @ts-ignore import miio from 'miio' import { Service } from 'homebridge' // XiaoMi import { getDeviceId } from './xiaomi.utils' // Utils import { Shared } from '../shared' import { debounceAsync, sleep } from '../utils' import { DeviceInstance, MiDeviceProps, MiIdentify } from '../main' class Device { // Base private device?: DeviceInstance private readonly identify: MiIdentify // Static constructor (identify: MiIdentify) { this.identify = identify } // Connection status private get isConnected () { Shared.log.debug(`Device is Checking connection status for ${this.identify.name} ${this.identify.address}`) if (!this.device) return false const now = Date.now() const diff = now - this.device.timeout this.device.timeout = now const connected = diff < Shared.config.reConnectThreshold connected ? Shared.log.debug(`Device's latest connection time is less than reConnectThreshold:${Shared.config.reConnectThreshold}, maybe still connecting`) : Shared.log.debug(`Device is over the reConnectThreshold:${Shared.config.reConnectThreshold}, maybe disconnected`) return connected } // Retry time Exponential backoff private static increaseReConnectFailureThreshold () { const increasedValue = Shared.config.reConnectFailureThreshold * 2 Shared.config.reConnectFailureThreshold = Math.min(increasedValue, Shared.config.reConnectFailureThresholdIncreaseLimit) Shared.log.debug(`Device's reConnectFailureThreshold is increase to ${Shared.config.reConnectFailureThreshold}(ms)`) } private static resetReConnectFailureThreshold () { Shared.config.reConnectFailureThreshold = Shared.config.reConnectFailureThresholdDefault Shared.log.debug(`Device's reConnectFailureThreshold is reset to ${Shared.config.reConnectFailureThreshold}(ms)`) } // Instance private requestInstance = async (): Promise => { // Guard if (this.isConnected) return this.device! // Do connect Shared.log.debug(`Service trying to get the devices instance of ${this.identify.name} ${this.identify.address}`) try { // Log Shared.log.info(`${this.identify.name} ${this.identify.address} start ${!!this.device ? 're-' : ''}connecting.`) // Create device instance const device = await miio.device({ address: this.identify.address, token: this.identify.token }) as DeviceInstance device.did = getDeviceId(device.id) device.timeout = Date.now() // Update this.device = device // Log Shared.log.info(`${this.identify.name} ${this.identify.address} is connected.`) // Reset failure reconnect threshold Device.resetReConnectFailureThreshold() return this.device } catch (e) { // Retry if failure if (this.isConnected) return this.device! Device.increaseReConnectFailureThreshold() Shared.log.info(`${this.identify.name} ${this.identify.address} ${this.identify.token} connection failure, wait ${Shared.config.reConnectFailureThreshold / 1000}s for reconnecting ...`) Shared.log.debug(e) await sleep(Shared.config.reConnectFailureThreshold) return this.requestInstance() } } public getInstance = debounceAsync(this.requestInstance, Shared.config.requestConnectDebounceThreshold) } export class MiDevice { // HomeBridge readonly service: Service // Device readonly identify: MiIdentify readonly device: Device readonly specs: S constructor (props: MiDeviceProps) { // HomeBridge this.service = props.service // Device this.identify = props.identify this.specs = props.specs this.device = new Device(props.identify) } }