forked from dgreif/ring
-
Notifications
You must be signed in to change notification settings - Fork 0
/
base-accessory.ts
159 lines (141 loc) · 4.07 KB
/
base-accessory.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import { hap } from './hap'
import { shareReplay, take } from 'rxjs/operators'
import { lastValueFrom, Observable } from 'rxjs'
import { RingPlatformConfig } from './config'
import {
Characteristic,
Logging,
PlatformAccessory,
Service,
CharacteristicEventTypes,
CharacteristicGetCallback,
CharacteristicValue,
CharacteristicSetCallback,
WithUUID,
} from 'homebridge'
function isServiceInstance(
serviceType: WithUUID<typeof Service> | Service
): serviceType is Service {
return typeof (serviceType as any) === 'object'
}
export type CharacteristicType = WithUUID<{ new (): Characteristic }>
export type ServiceType = WithUUID<typeof Service> | Service
export abstract class BaseAccessory<T extends { name: string }> {
abstract readonly device: T
abstract readonly accessory: PlatformAccessory
abstract readonly logger: Logging
abstract readonly config: RingPlatformConfig
private servicesInUse: Service[] = []
initBase() {
this.pruneUnusedServices()
}
getService(
serviceType: ServiceType,
name = this.device.name,
subType?: string
) {
if (isServiceInstance(serviceType)) {
return serviceType
}
const debug = process.env.RING_DEBUG === 'true'
if (debug) {
name = 'TEST ' + name
}
const existingService = subType
? this.accessory.getServiceById(serviceType, subType)
: this.accessory.getService(serviceType),
service =
existingService || this.accessory.addService(serviceType, name, subType)
if (
debug &&
existingService &&
existingService.displayName &&
name !== existingService.displayName
) {
throw new Error(
`Overlapping services for device ${this.device.name} - ${name} != ${existingService.displayName} - ${serviceType}`
)
}
if (!this.servicesInUse.includes(service)) {
this.servicesInUse.push(service)
}
return service
}
registerObservableCharacteristic<U extends CharacteristicValue>({
characteristicType,
serviceType,
serviceSubType,
onValue,
setValue,
name,
requestUpdate,
}: {
characteristicType: CharacteristicType
serviceType: ServiceType
serviceSubType?: string
onValue: Observable<U>
setValue?: (value: U) => any
name?: string
requestUpdate?: () => any
}) {
const service = this.getService(serviceType, name, serviceSubType),
characteristic = service.getCharacteristic(characteristicType),
onCachedValue = onValue.pipe(shareReplay(1))
onCachedValue.subscribe((value) => {
characteristic.updateValue(value)
})
if (requestUpdate) {
// Only register for GET if an async request should be made to get an updated value
onCachedValue.pipe(take(1)).subscribe(() => {
// allow GET once a value is cached
characteristic.on(
CharacteristicEventTypes.GET,
async (callback: CharacteristicGetCallback) => {
try {
const value = await lastValueFrom(onCachedValue.pipe(take(1)))
callback(null, value)
requestUpdate()
} catch (e) {
callback(e)
}
}
)
})
}
if (setValue) {
characteristic.on(
CharacteristicEventTypes.SET,
(
newValue: CharacteristicValue,
callback: CharacteristicSetCallback
) => {
Promise.resolve(setValue(newValue as U)).catch((e) => {
this.logger.error(e)
})
callback()
}
)
}
}
pruneUnusedServices() {
const safeServiceUUIDs = [
hap.Service.CameraRTPStreamManagement.UUID,
hap.Service.CameraControl.UUID,
]
this.accessory.services.forEach((service) => {
if (
!this.servicesInUse.includes(service) &&
!safeServiceUUIDs.includes(service.UUID)
) {
this.logger.info(
'Pruning unused service',
service.UUID,
service.displayName || service.name,
'from',
this.device.name
)
this.accessory.removeService(service)
}
})
}
}