@camera.ui/sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/CONTRIBUTING.md +1 -0
- package/LICENSE.md +22 -0
- package/README.md +5 -0
- package/dist/camera/config.js +1 -0
- package/dist/camera/detection.js +1 -0
- package/dist/camera/device.js +1 -0
- package/dist/camera/index.js +5 -0
- package/dist/camera/streaming.js +1 -0
- package/dist/camera/types.js +1 -0
- package/dist/external.js +1 -0
- package/dist/index.d.ts +5042 -0
- package/dist/index.js +16 -0
- package/dist/manager/index.js +1 -0
- package/dist/manager/types.js +1 -0
- package/dist/plugin/contract.js +120 -0
- package/dist/plugin/index.js +3 -0
- package/dist/plugin/interfaces.js +1 -0
- package/dist/plugin/types.js +1 -0
- package/dist/sensor/audio.js +118 -0
- package/dist/sensor/base.js +460 -0
- package/dist/sensor/battery.js +101 -0
- package/dist/sensor/contact.js +33 -0
- package/dist/sensor/doorbell.js +63 -0
- package/dist/sensor/face.js +124 -0
- package/dist/sensor/guards.js +133 -0
- package/dist/sensor/index.js +19 -0
- package/dist/sensor/licensePlate.js +116 -0
- package/dist/sensor/light.js +66 -0
- package/dist/sensor/motion.js +103 -0
- package/dist/sensor/object.js +137 -0
- package/dist/sensor/ptz.js +159 -0
- package/dist/sensor/siren.js +73 -0
- package/dist/sensor/types.js +46 -0
- package/dist/service/base.js +96 -0
- package/dist/service/index.js +3 -0
- package/dist/service/services.js +1 -0
- package/dist/service/types.js +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/schema.js +1 -0
- package/dist/storage/storages.js +1 -0
- package/package.json +61 -0
- package/tsconfig.node.json +30 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract Sensor Base Class
|
|
3
|
+
*
|
|
4
|
+
* Properties are intercepted via Proxy and automatically synchronized via RPC.
|
|
5
|
+
* Plugin developers can simply write `sensor.detected = true` and the update
|
|
6
|
+
* is automatically propagated.
|
|
7
|
+
*
|
|
8
|
+
* @template TProperties - The properties interface for this sensor
|
|
9
|
+
* @template TStorage - The storage values interface for this sensor
|
|
10
|
+
* @template TCapability - The capability enum type for this sensor (default: string)
|
|
11
|
+
*/
|
|
12
|
+
export class Sensor {
|
|
13
|
+
/** Unique sensor ID */
|
|
14
|
+
id;
|
|
15
|
+
/** Camera ID this sensor belongs to (set by addSensor) */
|
|
16
|
+
_cameraId;
|
|
17
|
+
/** Plugin ID that provides this sensor (set by addSensor) */
|
|
18
|
+
_pluginId;
|
|
19
|
+
/** Whether this sensor is online/available */
|
|
20
|
+
_online = true;
|
|
21
|
+
/** Internal properties storage */
|
|
22
|
+
_propertiesStore;
|
|
23
|
+
/** Proxied properties for intercepting writes */
|
|
24
|
+
_propertiesProxy;
|
|
25
|
+
/** Update function for RPC propagation */
|
|
26
|
+
_updateFn;
|
|
27
|
+
/** Property change listeners */
|
|
28
|
+
_listeners = new Set();
|
|
29
|
+
/** Simple property change listeners (for SensorLike interface) */
|
|
30
|
+
_simpleListeners = new Set();
|
|
31
|
+
/** Capability change listeners (for SensorLike interface) */
|
|
32
|
+
_capabilityListeners = new Set();
|
|
33
|
+
/** Storage instance for sensor-type-specific configuration (set by system) */
|
|
34
|
+
_storage;
|
|
35
|
+
/** Sensor capabilities */
|
|
36
|
+
_capabilities = [];
|
|
37
|
+
/** Capability update function for RPC propagation */
|
|
38
|
+
_capabilitiesUpdateFn;
|
|
39
|
+
/** Whether this sensor is assigned to be the active provider for its type */
|
|
40
|
+
_isAssigned = false;
|
|
41
|
+
/** Assignment change listeners */
|
|
42
|
+
_assignmentListeners = new Set();
|
|
43
|
+
/**
|
|
44
|
+
* Get schemas for sensor-type-specific configuration
|
|
45
|
+
*
|
|
46
|
+
* @returns An array of JsonSchema objects for configuration
|
|
47
|
+
*/
|
|
48
|
+
schema;
|
|
49
|
+
/**
|
|
50
|
+
* Display name for UI, HomeKit, logs etc.
|
|
51
|
+
* Initially equals name, can be changed by user via UI.
|
|
52
|
+
* Persisted in sensor storage under '_displayName'.
|
|
53
|
+
*/
|
|
54
|
+
displayName = '';
|
|
55
|
+
/**
|
|
56
|
+
* Whether this sensor requires video frames for detection.
|
|
57
|
+
* - true: Frame-based detection (e.g., rust-motion, ML object detection)
|
|
58
|
+
* - false: External detection (e.g., Ring, ONVIF events, SMTP)
|
|
59
|
+
*
|
|
60
|
+
* Override in subclass to specify behavior.
|
|
61
|
+
* DetectorSensor subclasses set this to true.
|
|
62
|
+
*/
|
|
63
|
+
_requiresFrames;
|
|
64
|
+
/**
|
|
65
|
+
* Create a new sensor instance.
|
|
66
|
+
*
|
|
67
|
+
* Note: The id is a random UUID used for RPC namespaces.
|
|
68
|
+
* The storage key is based on the stable 'name' property.
|
|
69
|
+
*/
|
|
70
|
+
constructor() {
|
|
71
|
+
this.id = crypto.randomUUID();
|
|
72
|
+
// Initialize empty storage
|
|
73
|
+
this._propertiesStore = {};
|
|
74
|
+
// Create Proxy for property interception
|
|
75
|
+
this._propertiesProxy = new Proxy(this._propertiesStore, {
|
|
76
|
+
set: (target, prop, value) => {
|
|
77
|
+
const key = prop;
|
|
78
|
+
const previousValue = target[key];
|
|
79
|
+
// Only update if value changed
|
|
80
|
+
if (previousValue !== value) {
|
|
81
|
+
target[key] = value;
|
|
82
|
+
// Fire-and-forget RPC update
|
|
83
|
+
this._updateFn?.(prop, value);
|
|
84
|
+
// Notify local listeners (prop is always a valid property enum value)
|
|
85
|
+
this._notifyListeners(prop, value, previousValue);
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
},
|
|
89
|
+
get: (target, prop) => target[prop],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Initialize sensor with update function (called by backend)
|
|
94
|
+
*
|
|
95
|
+
* @param updateFn - The property update function
|
|
96
|
+
*
|
|
97
|
+
* @internal
|
|
98
|
+
*/
|
|
99
|
+
_init(updateFn) {
|
|
100
|
+
this._updateFn = updateFn;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Set property value from external source (e.g., RPC)
|
|
104
|
+
* This bypasses the proxy to avoid re-broadcasting
|
|
105
|
+
*
|
|
106
|
+
* @param property - The property name
|
|
107
|
+
*
|
|
108
|
+
* @param value - The new property value
|
|
109
|
+
*
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
_setPropertyInternal(property, value) {
|
|
113
|
+
const previousValue = this._propertiesStore[property];
|
|
114
|
+
if (previousValue !== value) {
|
|
115
|
+
this._propertiesStore[property] = value;
|
|
116
|
+
// property is always a valid property enum value (K extends keyof TProperties)
|
|
117
|
+
this._notifyListeners(property, value, previousValue);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get all current property values
|
|
122
|
+
*
|
|
123
|
+
* @internal
|
|
124
|
+
*
|
|
125
|
+
* @returns The current properties
|
|
126
|
+
*/
|
|
127
|
+
_getProperties() {
|
|
128
|
+
return { ...this._propertiesStore };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Access to proxied properties for derived classes
|
|
132
|
+
*/
|
|
133
|
+
get props() {
|
|
134
|
+
return this._propertiesProxy;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Read-only access to raw properties (no proxy)
|
|
138
|
+
*/
|
|
139
|
+
get rawProps() {
|
|
140
|
+
return this._propertiesStore;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Whether this sensor is online/available
|
|
144
|
+
*/
|
|
145
|
+
get online() {
|
|
146
|
+
return this._online;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Set online state
|
|
150
|
+
*
|
|
151
|
+
* @param online - The new online state
|
|
152
|
+
*
|
|
153
|
+
* @internal
|
|
154
|
+
*/
|
|
155
|
+
_setOnline(online) {
|
|
156
|
+
this._online = online;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Camera ID this sensor belongs to
|
|
160
|
+
* Set automatically by CameraDevice.addSensor()
|
|
161
|
+
*/
|
|
162
|
+
get cameraId() {
|
|
163
|
+
if (!this._cameraId) {
|
|
164
|
+
throw new Error('Sensor not attached to camera. Call camera.addSensor() first.');
|
|
165
|
+
}
|
|
166
|
+
return this._cameraId;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Set camera ID (called by CameraDeviceProxy.addSensor)
|
|
170
|
+
*
|
|
171
|
+
* @param cameraId - The camera ID
|
|
172
|
+
*
|
|
173
|
+
* @internal
|
|
174
|
+
*/
|
|
175
|
+
_setCameraId(cameraId) {
|
|
176
|
+
this._cameraId = cameraId;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Plugin ID that provides this sensor
|
|
180
|
+
* Set automatically by CameraDevice.addSensor()
|
|
181
|
+
*/
|
|
182
|
+
get pluginId() {
|
|
183
|
+
return this._pluginId;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Set plugin ID (called by CameraDeviceProxy.addSensor)
|
|
187
|
+
*
|
|
188
|
+
* @param pluginId - The plugin ID
|
|
189
|
+
*
|
|
190
|
+
* @internal
|
|
191
|
+
*/
|
|
192
|
+
_setPluginId(pluginId) {
|
|
193
|
+
this._pluginId = pluginId;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Storage for sensor-type-specific configuration
|
|
197
|
+
* Available after sensor is added via CameraDevice.addSensor()
|
|
198
|
+
* Only available if schemas returns non-empty array
|
|
199
|
+
*/
|
|
200
|
+
get storage() {
|
|
201
|
+
return this._storage;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Set storage instance (called by CameraDeviceProxy after addSensor)
|
|
205
|
+
*
|
|
206
|
+
* @param storage - The storage instance
|
|
207
|
+
*
|
|
208
|
+
* @internal
|
|
209
|
+
*/
|
|
210
|
+
_setStorage(storage) {
|
|
211
|
+
this._storage = storage;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get sensor capabilities
|
|
215
|
+
* Returns a type-safe array of capabilities for this sensor type.
|
|
216
|
+
*/
|
|
217
|
+
get capabilities() {
|
|
218
|
+
return this._capabilities;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Set sensor capabilities
|
|
222
|
+
* Updates the capabilities and broadcasts to consumers via RPC.
|
|
223
|
+
* Capabilities are automatically deduplicated.
|
|
224
|
+
*
|
|
225
|
+
* @param value - The new capabilities array
|
|
226
|
+
*/
|
|
227
|
+
set capabilities(value) {
|
|
228
|
+
// Deduplicate capabilities
|
|
229
|
+
this._capabilities = [...new Set(value)];
|
|
230
|
+
// Broadcast to SensorController (for RPC propagation)
|
|
231
|
+
this._capabilitiesUpdateFn?.(this._capabilities);
|
|
232
|
+
// Notify local listeners
|
|
233
|
+
this._notifyCapabilityListeners();
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Check if sensor has a specific capability
|
|
237
|
+
*
|
|
238
|
+
* @param capability - The capability to check
|
|
239
|
+
*
|
|
240
|
+
* @returns True if the sensor has the capability
|
|
241
|
+
*/
|
|
242
|
+
hasCapability(capability) {
|
|
243
|
+
return this._capabilities.includes(capability);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Initialize capabilities update function (called by backend)
|
|
247
|
+
*
|
|
248
|
+
* @param updateFn - The capability update function
|
|
249
|
+
*
|
|
250
|
+
* @internal
|
|
251
|
+
*/
|
|
252
|
+
_initCapabilities(updateFn) {
|
|
253
|
+
this._capabilitiesUpdateFn = updateFn;
|
|
254
|
+
}
|
|
255
|
+
// ========== Assignment Status ==========
|
|
256
|
+
/**
|
|
257
|
+
* Whether this sensor is assigned to be the active provider
|
|
258
|
+
* for its sensor type on this camera.
|
|
259
|
+
*
|
|
260
|
+
* A sensor is "assigned" when the user has selected this plugin
|
|
261
|
+
* to provide this sensor type for the camera. Unassigned sensors
|
|
262
|
+
* are still registered but their events are filtered out.
|
|
263
|
+
*
|
|
264
|
+
* Use this to conditionally initialize resources:
|
|
265
|
+
* - Only start ONVIF event subscriptions if assigned
|
|
266
|
+
* - Only connect to external services if needed
|
|
267
|
+
*/
|
|
268
|
+
get isAssigned() {
|
|
269
|
+
return this._isAssigned;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Subscribe to assignment changes.
|
|
273
|
+
*
|
|
274
|
+
* Called when the user changes which plugin provides this sensor type.
|
|
275
|
+
* Use this to react dynamically:
|
|
276
|
+
* - Start/stop event loops
|
|
277
|
+
* - Connect/disconnect from external services
|
|
278
|
+
*
|
|
279
|
+
* @param callback - Called with true when assigned, false when unassigned
|
|
280
|
+
*
|
|
281
|
+
* @returns A function to unsubscribe
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* sensor.onAssignmentChanged((assigned) => {
|
|
286
|
+
* if (assigned) {
|
|
287
|
+
* this.startEventLoop();
|
|
288
|
+
* } else {
|
|
289
|
+
* this.stopEventLoop();
|
|
290
|
+
* }
|
|
291
|
+
* });
|
|
292
|
+
* ```
|
|
293
|
+
*/
|
|
294
|
+
onAssignmentChanged(callback) {
|
|
295
|
+
this._assignmentListeners.add(callback);
|
|
296
|
+
return () => this._assignmentListeners.delete(callback);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Set assignment status (called by system)
|
|
300
|
+
*
|
|
301
|
+
* @param assigned - Whether this sensor is now assigned
|
|
302
|
+
*
|
|
303
|
+
* @internal
|
|
304
|
+
*/
|
|
305
|
+
_setAssigned(assigned) {
|
|
306
|
+
if (this._isAssigned !== assigned) {
|
|
307
|
+
this._isAssigned = assigned;
|
|
308
|
+
for (const listener of this._assignmentListeners) {
|
|
309
|
+
try {
|
|
310
|
+
listener(assigned);
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
// Ignore listener errors
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// ========== SensorLike Interface Methods ==========
|
|
319
|
+
/**
|
|
320
|
+
* Get a property value by name (SensorLike interface)
|
|
321
|
+
*
|
|
322
|
+
* @param property - The property name
|
|
323
|
+
*
|
|
324
|
+
* @returns The property value or undefined if not found
|
|
325
|
+
*/
|
|
326
|
+
getPropertyValue(property) {
|
|
327
|
+
return this._propertiesStore[property];
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get all property values (SensorLike interface)
|
|
331
|
+
*
|
|
332
|
+
* @returns All property values as a record
|
|
333
|
+
*/
|
|
334
|
+
getAllPropertyValues() {
|
|
335
|
+
return { ...this._propertiesStore };
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Set a property value (SensorLike interface)
|
|
339
|
+
* For Control sensors, this triggers the property setter through the proxy.
|
|
340
|
+
*
|
|
341
|
+
* @param property - The property name
|
|
342
|
+
*
|
|
343
|
+
* @param value - The new property value
|
|
344
|
+
*/
|
|
345
|
+
async setPropertyValue(property, value) {
|
|
346
|
+
this._propertiesProxy[property] = value;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Add a simple property change listener (SensorLike interface)
|
|
350
|
+
* This is for consumers who just want property/value pairs.
|
|
351
|
+
*
|
|
352
|
+
* @param callback - The callback function to invoke on property changes
|
|
353
|
+
*
|
|
354
|
+
* @returns A function to remove the listener
|
|
355
|
+
*/
|
|
356
|
+
onPropertyChanged(callback) {
|
|
357
|
+
this._simpleListeners.add(callback);
|
|
358
|
+
return () => this._simpleListeners.delete(callback);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Add a detailed property change listener
|
|
362
|
+
* This includes full event details like previous value, timestamps, etc.
|
|
363
|
+
*
|
|
364
|
+
* @param callback - The callback function to invoke on detailed property changes
|
|
365
|
+
*
|
|
366
|
+
* @returns A function to remove the listener
|
|
367
|
+
*/
|
|
368
|
+
onPropertyChangedDetailed(callback) {
|
|
369
|
+
this._listeners.add(callback);
|
|
370
|
+
return () => this._listeners.delete(callback);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Add a capability change listener (SensorLike interface)
|
|
374
|
+
* Called when the sensor's capabilities are updated at runtime.
|
|
375
|
+
*
|
|
376
|
+
* @param callback - The callback function to invoke on capability changes
|
|
377
|
+
*
|
|
378
|
+
* @returns A function to remove the listener
|
|
379
|
+
*/
|
|
380
|
+
onCapabilitiesChanged(callback) {
|
|
381
|
+
this._capabilityListeners.add(callback);
|
|
382
|
+
return () => this._capabilityListeners.delete(callback);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Notify all capability listeners of a capability change
|
|
386
|
+
*/
|
|
387
|
+
_notifyCapabilityListeners() {
|
|
388
|
+
for (const listener of this._capabilityListeners) {
|
|
389
|
+
try {
|
|
390
|
+
listener(this._capabilities);
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
// Ignore listener errors
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Notify all listeners of a property change
|
|
399
|
+
*
|
|
400
|
+
* @param property - The property name (must be a valid SensorPropertyType)
|
|
401
|
+
*
|
|
402
|
+
* @param value - The new property value
|
|
403
|
+
*
|
|
404
|
+
* @param previousValue - The previous property value
|
|
405
|
+
*/
|
|
406
|
+
_notifyListeners(property, value, previousValue) {
|
|
407
|
+
// Skip notification if sensor not yet attached to camera
|
|
408
|
+
// (e.g., during constructor initialization)
|
|
409
|
+
if (!this._cameraId) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const event = {
|
|
413
|
+
cameraId: this._cameraId,
|
|
414
|
+
sensorId: this.id,
|
|
415
|
+
sensorType: this.type,
|
|
416
|
+
property,
|
|
417
|
+
value,
|
|
418
|
+
previousValue,
|
|
419
|
+
timestamp: Date.now(),
|
|
420
|
+
};
|
|
421
|
+
// Notify detailed listeners
|
|
422
|
+
for (const listener of this._listeners) {
|
|
423
|
+
try {
|
|
424
|
+
listener(event);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// Ignore listener errors
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Notify simple listeners
|
|
431
|
+
for (const listener of this._simpleListeners) {
|
|
432
|
+
try {
|
|
433
|
+
listener(property, value);
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
// Ignore listener errors
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Serialize sensor for transport
|
|
442
|
+
*
|
|
443
|
+
* @returns The serialized sensor representation
|
|
444
|
+
*/
|
|
445
|
+
toJSON() {
|
|
446
|
+
return {
|
|
447
|
+
id: this.id,
|
|
448
|
+
type: this.type,
|
|
449
|
+
name: this.name,
|
|
450
|
+
displayName: this.displayName || this.name,
|
|
451
|
+
category: this.category,
|
|
452
|
+
cameraId: this.cameraId,
|
|
453
|
+
pluginId: this.pluginId,
|
|
454
|
+
online: this._online,
|
|
455
|
+
properties: this._getProperties(),
|
|
456
|
+
capabilities: this._capabilities,
|
|
457
|
+
requiresFrames: this._requiresFrames,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Sensor } from './base.js';
|
|
2
|
+
import { ChargingState, SensorCategory, SensorType } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Battery Capability enum
|
|
5
|
+
* Describes what Battery features this sensor supports
|
|
6
|
+
* Note: "level" property is always available (standard)
|
|
7
|
+
*/
|
|
8
|
+
export var BatteryCapability;
|
|
9
|
+
(function (BatteryCapability) {
|
|
10
|
+
/** Supports low battery detection */
|
|
11
|
+
BatteryCapability["LowBattery"] = "lowBattery";
|
|
12
|
+
/** Supports charging state detection */
|
|
13
|
+
BatteryCapability["Charging"] = "charging";
|
|
14
|
+
})(BatteryCapability || (BatteryCapability = {}));
|
|
15
|
+
/**
|
|
16
|
+
* Battery info property keys
|
|
17
|
+
*/
|
|
18
|
+
export var BatteryProperty;
|
|
19
|
+
(function (BatteryProperty) {
|
|
20
|
+
BatteryProperty["Level"] = "level";
|
|
21
|
+
BatteryProperty["Charging"] = "charging";
|
|
22
|
+
BatteryProperty["Low"] = "low";
|
|
23
|
+
})(BatteryProperty || (BatteryProperty = {}));
|
|
24
|
+
/**
|
|
25
|
+
* Battery Info
|
|
26
|
+
*
|
|
27
|
+
* Read-only hardware status for battery-powered cameras.
|
|
28
|
+
*/
|
|
29
|
+
export class BatteryInfo extends Sensor {
|
|
30
|
+
type = SensorType.Battery;
|
|
31
|
+
category = SensorCategory.Info;
|
|
32
|
+
name;
|
|
33
|
+
/** Threshold for low battery warning (default: 20%) */
|
|
34
|
+
lowBatteryThreshold = 20;
|
|
35
|
+
constructor(name = 'Battery') {
|
|
36
|
+
super();
|
|
37
|
+
this.name = name;
|
|
38
|
+
// Initialize defaults
|
|
39
|
+
this.props.level = 100;
|
|
40
|
+
this.props.charging = ChargingState.NotCharging;
|
|
41
|
+
this.props.low = false;
|
|
42
|
+
}
|
|
43
|
+
// ========== Properties (Getter/Setter Pairs) ==========
|
|
44
|
+
/** Battery level (0-100) */
|
|
45
|
+
get level() {
|
|
46
|
+
return this.rawProps.level;
|
|
47
|
+
}
|
|
48
|
+
/** Set battery level (0-100) */
|
|
49
|
+
set level(value) {
|
|
50
|
+
const clampedValue = Math.max(0, Math.min(100, value));
|
|
51
|
+
this.props.level = clampedValue;
|
|
52
|
+
// Auto-update low battery state
|
|
53
|
+
this.props.low = clampedValue <= this.lowBatteryThreshold;
|
|
54
|
+
}
|
|
55
|
+
/** Charging state */
|
|
56
|
+
get charging() {
|
|
57
|
+
return this.rawProps.charging;
|
|
58
|
+
}
|
|
59
|
+
/** Set charging state */
|
|
60
|
+
set charging(value) {
|
|
61
|
+
this.props.charging = value;
|
|
62
|
+
}
|
|
63
|
+
/** Whether battery is low */
|
|
64
|
+
get low() {
|
|
65
|
+
return this.rawProps.low;
|
|
66
|
+
}
|
|
67
|
+
/** Set low battery state */
|
|
68
|
+
set low(value) {
|
|
69
|
+
this.props.low = value;
|
|
70
|
+
}
|
|
71
|
+
// ========== Convenience Methods ==========
|
|
72
|
+
/**
|
|
73
|
+
* Update battery state
|
|
74
|
+
*
|
|
75
|
+
* @param level - Battery level (0-100)
|
|
76
|
+
*
|
|
77
|
+
* @param charging - Charging state
|
|
78
|
+
*/
|
|
79
|
+
setBattery(level, charging) {
|
|
80
|
+
this.level = level;
|
|
81
|
+
if (charging !== undefined) {
|
|
82
|
+
this.props.charging = charging;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if battery is charging
|
|
87
|
+
*
|
|
88
|
+
* @returns True if battery is charging, false otherwise
|
|
89
|
+
*/
|
|
90
|
+
isCharging() {
|
|
91
|
+
return this.charging === ChargingState.Charging;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if battery is fully charged
|
|
95
|
+
*
|
|
96
|
+
* @returns True if battery is fully charged, false otherwise
|
|
97
|
+
*/
|
|
98
|
+
isFullyCharged() {
|
|
99
|
+
return this.charging === ChargingState.Full || this.level === 100;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Sensor } from './base.js';
|
|
2
|
+
import { SensorCategory, SensorType } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Contact sensor property keys
|
|
5
|
+
*/
|
|
6
|
+
export var ContactProperty;
|
|
7
|
+
(function (ContactProperty) {
|
|
8
|
+
ContactProperty["Detected"] = "detected";
|
|
9
|
+
})(ContactProperty || (ContactProperty = {}));
|
|
10
|
+
/**
|
|
11
|
+
* Contact Sensor
|
|
12
|
+
*
|
|
13
|
+
* Detects contact state (open/closed) for doors, windows, etc.
|
|
14
|
+
*/
|
|
15
|
+
export class ContactSensor extends Sensor {
|
|
16
|
+
type = SensorType.Contact;
|
|
17
|
+
category = SensorCategory.Sensor;
|
|
18
|
+
name;
|
|
19
|
+
constructor(name = 'Contact Sensor') {
|
|
20
|
+
super();
|
|
21
|
+
this.name = name;
|
|
22
|
+
// Initialize defaults
|
|
23
|
+
this.props.detected = false;
|
|
24
|
+
}
|
|
25
|
+
/** Whether contact is detected (closed) */
|
|
26
|
+
get detected() {
|
|
27
|
+
return this.rawProps.detected;
|
|
28
|
+
}
|
|
29
|
+
/** Set contact detected state */
|
|
30
|
+
set detected(value) {
|
|
31
|
+
this.props.detected = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Sensor } from './base.js';
|
|
2
|
+
import { SensorCategory, SensorType } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Doorbell trigger property keys
|
|
5
|
+
*/
|
|
6
|
+
export var DoorbellProperty;
|
|
7
|
+
(function (DoorbellProperty) {
|
|
8
|
+
DoorbellProperty["Ring"] = "ring";
|
|
9
|
+
})(DoorbellProperty || (DoorbellProperty = {}));
|
|
10
|
+
/**
|
|
11
|
+
* Doorbell Trigger
|
|
12
|
+
*
|
|
13
|
+
* Event-based trigger for doorbell rings.
|
|
14
|
+
* Ring property auto-resets after a short delay.
|
|
15
|
+
*/
|
|
16
|
+
export class DoorbellTrigger extends Sensor {
|
|
17
|
+
type = SensorType.Doorbell;
|
|
18
|
+
category = SensorCategory.Trigger;
|
|
19
|
+
name;
|
|
20
|
+
_resetTimeout;
|
|
21
|
+
constructor(name = 'Doorbell') {
|
|
22
|
+
super();
|
|
23
|
+
this.name = name;
|
|
24
|
+
// Initialize defaults
|
|
25
|
+
this.props.ring = false;
|
|
26
|
+
}
|
|
27
|
+
// ========== Properties (Getter/Setter Pairs) ==========
|
|
28
|
+
/** Whether doorbell is currently ringing */
|
|
29
|
+
get ring() {
|
|
30
|
+
return this.rawProps.ring;
|
|
31
|
+
}
|
|
32
|
+
/** Set ring state */
|
|
33
|
+
set ring(value) {
|
|
34
|
+
this.props.ring = value;
|
|
35
|
+
}
|
|
36
|
+
// ========== Convenience Methods ==========
|
|
37
|
+
/**
|
|
38
|
+
* Trigger doorbell ring
|
|
39
|
+
* Auto-resets after duration (default: 1000ms)
|
|
40
|
+
*
|
|
41
|
+
* @param resetAfterMs - Duration in milliseconds to auto-reset ring state
|
|
42
|
+
*/
|
|
43
|
+
triggerRing(resetAfterMs = 1000) {
|
|
44
|
+
// Clear any existing reset timeout
|
|
45
|
+
if (this._resetTimeout) {
|
|
46
|
+
clearTimeout(this._resetTimeout);
|
|
47
|
+
}
|
|
48
|
+
this.props.ring = true;
|
|
49
|
+
// Auto-reset after duration
|
|
50
|
+
this._resetTimeout = setTimeout(() => {
|
|
51
|
+
this.props.ring = false;
|
|
52
|
+
this._resetTimeout = undefined;
|
|
53
|
+
}, resetAfterMs);
|
|
54
|
+
}
|
|
55
|
+
/** Reset ring state immediately */
|
|
56
|
+
resetRing() {
|
|
57
|
+
if (this._resetTimeout) {
|
|
58
|
+
clearTimeout(this._resetTimeout);
|
|
59
|
+
this._resetTimeout = undefined;
|
|
60
|
+
}
|
|
61
|
+
this.props.ring = false;
|
|
62
|
+
}
|
|
63
|
+
}
|