From 08e398adbb52576875c08649fc246696bc101f98 Mon Sep 17 00:00:00 2001 From: Bobby Prochnow Date: Sun, 4 Feb 2024 08:33:45 -0600 Subject: [PATCH] Basic ON/OFF support for restoreIot devices (#106) Co-authored-by: Dusty Greif --- packages/homebridge-hatch-baby-rest/api.ts | 3 + .../homebridge-hatch-baby-rest/platform.ts | 9 ++- .../restore-accessory.ts | 3 +- .../homebridge-hatch-baby-rest/restore-iot.ts | 66 +++++++++++++++ packages/shared/hatch-sleep-types.ts | 80 +++++++++++++++++++ 5 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 packages/homebridge-hatch-baby-rest/restore-iot.ts diff --git a/packages/homebridge-hatch-baby-rest/api.ts b/packages/homebridge-hatch-baby-rest/api.ts index 14f1a08..0741bb2 100644 --- a/packages/homebridge-hatch-baby-rest/api.ts +++ b/packages/homebridge-hatch-baby-rest/api.ts @@ -12,6 +12,7 @@ import { RestPlus } from './rest-plus' import { RestIot } from './rest-iot' import { RestMini } from './rest-mini' import { Restore } from './restore' +import { RestoreIot } from './restore-iot' import { BehaviorSubject } from 'rxjs' import { IotDevice } from './iot-device' import { debounceTime } from 'rxjs/operators' @@ -24,6 +25,7 @@ const knownProducts = [ Product.riotPlus, Product.restMini, Product.restore, + Product.restoreIot, ], productFetchQueryString = knownProducts .map((product) => 'iotProducts=' + product) @@ -180,6 +182,7 @@ export class HatchBabyApi { restIotPluses: createDevices(Product.riotPlus, RestIot), restMinis: createDevices(Product.restMini, RestMini), restores: createDevices(Product.restore, Restore), + restoreIots: createDevices(Product.restoreIot, RestoreIot), } } } diff --git a/packages/homebridge-hatch-baby-rest/platform.ts b/packages/homebridge-hatch-baby-rest/platform.ts index 25f78a5..c430189 100644 --- a/packages/homebridge-hatch-baby-rest/platform.ts +++ b/packages/homebridge-hatch-baby-rest/platform.ts @@ -13,6 +13,7 @@ import type { import { RestoreAccessory } from './restore-accessory' import { RestIot } from './rest-iot' import { Restore } from './restore' +import { RestoreIot } from './restore-iot' export const pluginName = 'homebridge-hatch-baby-rest' export const platformName = 'HatchBabyRest' @@ -65,7 +66,7 @@ export class HatchBabyRestPlatform implements DynamicPlatformPlugin { this.config.email && this.config.password ? new HatchBabyApi(this.config) : undefined, - { restPluses, restMinis, restores, restIots, restIotPluses } = + { restPluses, restMinis, restores, restoreIots, restIots, restIotPluses } = hatchBabyApi ? await hatchBabyApi.getDevices() : { @@ -74,6 +75,7 @@ export class HatchBabyRestPlatform implements DynamicPlatformPlugin { restores: [], restIots: [], restIotPluses: [], + restoreIots: [], }, { api } = this, cachedAccessoryIds = Object.keys(this.homebridgeAccessories), @@ -86,10 +88,11 @@ export class HatchBabyRestPlatform implements DynamicPlatformPlugin { ...restIots, ...restIotPluses, ...restores, + ...restoreIots, ] this.log.info( - `Configuring ${restPluses.length} Rest+, ${restMinis.length} Rest Mini, ${restIots.length} Rest 2nd Gen, ${restIotPluses.length} Rest+ 2nd Gen, and ${restores.length} Restore`, + `Configuring ${restPluses.length} Rest+, ${restMinis.length} Rest Mini, ${restIots.length} Rest 2nd Gen, ${restIotPluses.length} Rest+ 2nd Gen, ${restores.length} Restore, and ${restoreIots.length} Restore 2nd Gen`, ) devices.forEach((device) => { @@ -111,7 +114,7 @@ export class HatchBabyRestPlatform implements DynamicPlatformPlugin { homebridgeAccessory = this.homebridgeAccessories[uuid] || createHomebridgeAccessory() - if (device instanceof Restore || device instanceof RestIot) { + if (device instanceof Restore || device instanceof RestIot || device instanceof RestoreIot) { new RestoreAccessory(device, homebridgeAccessory) } else if ('onBrightness' in device) { new LightAndSoundMachineAccessory(device, homebridgeAccessory) diff --git a/packages/homebridge-hatch-baby-rest/restore-accessory.ts b/packages/homebridge-hatch-baby-rest/restore-accessory.ts index 846b4be..c727706 100644 --- a/packages/homebridge-hatch-baby-rest/restore-accessory.ts +++ b/packages/homebridge-hatch-baby-rest/restore-accessory.ts @@ -3,10 +3,11 @@ import { PlatformAccessory } from 'homebridge' import { BaseAccessory } from '../shared/base-accessory' import { RestIot } from './rest-iot' import { Restore } from './restore' +import { RestoreIot } from './restore-iot' import { logInfo } from '../shared/util' export class RestoreAccessory extends BaseAccessory { - constructor(restore: Restore | RestIot, accessory: PlatformAccessory) { + constructor(restore: Restore | RestIot | RestoreIot, accessory: PlatformAccessory) { super(restore, accessory) const { Service, Characteristic } = hap, diff --git a/packages/homebridge-hatch-baby-rest/restore-iot.ts b/packages/homebridge-hatch-baby-rest/restore-iot.ts new file mode 100644 index 0000000..440392e --- /dev/null +++ b/packages/homebridge-hatch-baby-rest/restore-iot.ts @@ -0,0 +1,66 @@ +import { + IotDeviceInfo, + RestoreIotState, + RestIotFavorite, +} from '../shared/hatch-sleep-types' +import { distinctUntilChanged, map } from 'rxjs/operators' +import { BaseDevice } from '../shared/base-accessory' +import { IotDevice } from './iot-device' +import { BehaviorSubject } from 'rxjs' +import { thingShadow as AwsIotDevice } from 'aws-iot-device-sdk' +import { apiPath, RestClient } from './rest-client' + +export class RestoreIot extends IotDevice implements BaseDevice { + readonly model = 'RestoreIot' + + constructor( + public readonly info: IotDeviceInfo, + public readonly onIotClient: BehaviorSubject, + public readonly restClient: RestClient + ) { + super(info, onIotClient) + } + + onSomeContentPlaying = this.onState.pipe( + map((state) => state.current.playing !== 'none'), + distinctUntilChanged() + ) + + onFirmwareVersion = this.onState.pipe(map((state) => state.deviceInfo.f)) + + private setCurrent( + playing: RestoreIotState['current']['playing'], + step: number, + srId: number + ) { + this.update({ + current: { + playing, + step, + srId, + }, + }) + } + + async turnOnRoutine() { + const routines = await this.fetchRoutines() + this.setCurrent('routine', 1, routines[0].id) + } + + turnOff() { + this.setCurrent('none', 0, 0) + } + + async fetchRoutines() { + const routinePath = apiPath( + `service/app/routine/v2/fetch?macAddress=${encodeURIComponent( + this.info.macAddress + )}&types=routine` + ), + routines = await this.restClient.request({ + url: routinePath, + method: 'GET', + }) + return routines.sort((a, b) => a.displayOrder - b.displayOrder) + } +} diff --git a/packages/shared/hatch-sleep-types.ts b/packages/shared/hatch-sleep-types.ts index 65a7987..e7db228 100644 --- a/packages/shared/hatch-sleep-types.ts +++ b/packages/shared/hatch-sleep-types.ts @@ -492,3 +492,83 @@ export interface RestMiniState { connected: boolean rssi: number } + +export interface RestoreIotState { + env: 'prod' | string + alarmsDisabled: boolean + nightlightOn: boolean + nightlightIntensity: number + toddlerLockOn: boolean + snoozeDuration: number + current: { + srId: number + playing: 'none' | 'remote' | 'routine' | string + step: number + color: { + i: number + id: number + r: number + g: number + b: number + w: number + duration: number + until: 'indefinite' + } + sound: IotSound + } + dataVersion: string + sleepScene: { + srId: number + enabled: boolean + } + timer: { s: string; d: number } + timezone: string + rF: { + v: string + i: boolean + u: string + } + deviceInfo: { f: string; fR: number; hwVersion: string } + clock: { + i: number + turnOffAt: string + turnOnAt: string + flags: number + turnOffMode: 'never' | string + turnDimAt: string + turnBrightAt: string + } + toddlerLock: { + turnOffAt: string + turnOnAt: string + turnOnMode: 'never' | string + } + lucky: number + LDR: 'OK' | string + LWTP: boolean + debug: number + logging: number + owned: boolean + lastReset: 'PowerOn' | string + ota: { status: string; downloadProgress: number; installProgress: number } + REX: { lock: number; key: number; command: 'none' | string } + connected: boolean + rssi: number + streaming: { status: 'none' | string } + knockThreshold: number + knockDuration: number + activeTap: boolean + knockAxis: number + snooze: { + active: boolean + startTime: string + } + hwDebugFlags: number + touch: { + flags: number + poll_rate_hz: number + refire_delay_ms: number + refire_rate_hz: number + step_size: number + } +} \ No newline at end of file