@ecobridge.xyz/devicemanager 3.0.2

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.
Files changed (137) hide show
  1. package/.gitea/workflows/default_nottags.yaml +66 -0
  2. package/.gitea/workflows/default_tags.yaml +124 -0
  3. package/.vscode/launch.json +11 -0
  4. package/.vscode/settings.json +26 -0
  5. package/changelog.md +91 -0
  6. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  7. package/dist_ts/00_commitinfo_data.js +9 -0
  8. package/dist_ts/device/device.classes.device.d.ts +205 -0
  9. package/dist_ts/device/device.classes.device.js +344 -0
  10. package/dist_ts/devicemanager.classes.devicemanager.d.ts +210 -0
  11. package/dist_ts/devicemanager.classes.devicemanager.js +835 -0
  12. package/dist_ts/discovery/discovery.classes.homeassistant.d.ts +101 -0
  13. package/dist_ts/discovery/discovery.classes.homeassistant.js +313 -0
  14. package/dist_ts/discovery/discovery.classes.mdns.d.ts +90 -0
  15. package/dist_ts/discovery/discovery.classes.mdns.js +291 -0
  16. package/dist_ts/discovery/discovery.classes.networkscanner.d.ts +64 -0
  17. package/dist_ts/discovery/discovery.classes.networkscanner.js +535 -0
  18. package/dist_ts/discovery/discovery.classes.ssdp.d.ts +112 -0
  19. package/dist_ts/discovery/discovery.classes.ssdp.js +255 -0
  20. package/dist_ts/factories/index.d.ts +190 -0
  21. package/dist_ts/factories/index.js +350 -0
  22. package/dist_ts/features/feature.abstract.d.ts +118 -0
  23. package/dist_ts/features/feature.abstract.js +172 -0
  24. package/dist_ts/features/feature.camera.d.ts +95 -0
  25. package/dist_ts/features/feature.camera.js +162 -0
  26. package/dist_ts/features/feature.climate.d.ts +174 -0
  27. package/dist_ts/features/feature.climate.js +328 -0
  28. package/dist_ts/features/feature.cover.d.ts +121 -0
  29. package/dist_ts/features/feature.cover.js +217 -0
  30. package/dist_ts/features/feature.fan.d.ts +127 -0
  31. package/dist_ts/features/feature.fan.js +235 -0
  32. package/dist_ts/features/feature.light.d.ts +156 -0
  33. package/dist_ts/features/feature.light.js +296 -0
  34. package/dist_ts/features/feature.lock.d.ts +103 -0
  35. package/dist_ts/features/feature.lock.js +170 -0
  36. package/dist_ts/features/feature.playback.d.ts +98 -0
  37. package/dist_ts/features/feature.playback.js +191 -0
  38. package/dist_ts/features/feature.power.d.ts +79 -0
  39. package/dist_ts/features/feature.power.js +173 -0
  40. package/dist_ts/features/feature.print.d.ts +78 -0
  41. package/dist_ts/features/feature.print.js +226 -0
  42. package/dist_ts/features/feature.scan.d.ts +79 -0
  43. package/dist_ts/features/feature.scan.js +315 -0
  44. package/dist_ts/features/feature.sensor.d.ts +95 -0
  45. package/dist_ts/features/feature.sensor.js +147 -0
  46. package/dist_ts/features/feature.snmp.d.ts +88 -0
  47. package/dist_ts/features/feature.snmp.js +175 -0
  48. package/dist_ts/features/feature.switch.d.ts +75 -0
  49. package/dist_ts/features/feature.switch.js +126 -0
  50. package/dist_ts/features/feature.volume.d.ts +104 -0
  51. package/dist_ts/features/feature.volume.js +186 -0
  52. package/dist_ts/features/index.d.ts +19 -0
  53. package/dist_ts/features/index.js +23 -0
  54. package/dist_ts/helpers/helpers.iprange.d.ts +35 -0
  55. package/dist_ts/helpers/helpers.iprange.js +151 -0
  56. package/dist_ts/helpers/helpers.retry.d.ts +25 -0
  57. package/dist_ts/helpers/helpers.retry.js +69 -0
  58. package/dist_ts/index.d.ts +15 -0
  59. package/dist_ts/index.js +40 -0
  60. package/dist_ts/interfaces/feature.interfaces.d.ts +255 -0
  61. package/dist_ts/interfaces/feature.interfaces.js +6 -0
  62. package/dist_ts/interfaces/homeassistant.interfaces.d.ts +550 -0
  63. package/dist_ts/interfaces/homeassistant.interfaces.js +111 -0
  64. package/dist_ts/interfaces/index.d.ts +330 -0
  65. package/dist_ts/interfaces/index.js +16 -0
  66. package/dist_ts/interfaces/smarthome.interfaces.d.ts +282 -0
  67. package/dist_ts/interfaces/smarthome.interfaces.js +7 -0
  68. package/dist_ts/paths.d.ts +1 -0
  69. package/dist_ts/paths.js +3 -0
  70. package/dist_ts/plugins.d.ts +23 -0
  71. package/dist_ts/plugins.js +30 -0
  72. package/dist_ts/protocols/index.d.ts +12 -0
  73. package/dist_ts/protocols/index.js +21 -0
  74. package/dist_ts/protocols/protocol.escl.d.ts +58 -0
  75. package/dist_ts/protocols/protocol.escl.js +364 -0
  76. package/dist_ts/protocols/protocol.homeassistant.d.ts +248 -0
  77. package/dist_ts/protocols/protocol.homeassistant.js +611 -0
  78. package/dist_ts/protocols/protocol.ipp.d.ts +45 -0
  79. package/dist_ts/protocols/protocol.ipp.js +284 -0
  80. package/dist_ts/protocols/protocol.nut.d.ts +160 -0
  81. package/dist_ts/protocols/protocol.nut.js +364 -0
  82. package/dist_ts/protocols/protocol.sane.d.ts +104 -0
  83. package/dist_ts/protocols/protocol.sane.js +597 -0
  84. package/dist_ts/protocols/protocol.snmp.d.ts +133 -0
  85. package/dist_ts/protocols/protocol.snmp.js +333 -0
  86. package/dist_ts/protocols/protocol.upnp.d.ts +207 -0
  87. package/dist_ts/protocols/protocol.upnp.js +442 -0
  88. package/dist_ts/protocols/protocol.upssnmp.d.ts +160 -0
  89. package/dist_ts/protocols/protocol.upssnmp.js +263 -0
  90. package/npmextra.json +24 -0
  91. package/package.json +39 -0
  92. package/readme.hints.md +115 -0
  93. package/readme.md +624 -0
  94. package/test/test.ts +261 -0
  95. package/ts/00_commitinfo_data.ts +8 -0
  96. package/ts/device/device.classes.device.ts +454 -0
  97. package/ts/devicemanager.classes.devicemanager.ts +1219 -0
  98. package/ts/discovery/discovery.classes.homeassistant.ts +376 -0
  99. package/ts/discovery/discovery.classes.mdns.ts +347 -0
  100. package/ts/discovery/discovery.classes.networkscanner.ts +648 -0
  101. package/ts/discovery/discovery.classes.ssdp.ts +357 -0
  102. package/ts/factories/index.ts +696 -0
  103. package/ts/features/feature.abstract.ts +251 -0
  104. package/ts/features/feature.camera.ts +214 -0
  105. package/ts/features/feature.climate.ts +407 -0
  106. package/ts/features/feature.cover.ts +278 -0
  107. package/ts/features/feature.fan.ts +296 -0
  108. package/ts/features/feature.light.ts +369 -0
  109. package/ts/features/feature.lock.ts +223 -0
  110. package/ts/features/feature.playback.ts +246 -0
  111. package/ts/features/feature.power.ts +231 -0
  112. package/ts/features/feature.print.ts +297 -0
  113. package/ts/features/feature.scan.ts +370 -0
  114. package/ts/features/feature.sensor.ts +202 -0
  115. package/ts/features/feature.snmp.ts +235 -0
  116. package/ts/features/feature.switch.ts +170 -0
  117. package/ts/features/feature.volume.ts +256 -0
  118. package/ts/features/index.ts +25 -0
  119. package/ts/helpers/helpers.iprange.ts +172 -0
  120. package/ts/helpers/helpers.retry.ts +100 -0
  121. package/ts/index.ts +165 -0
  122. package/ts/interfaces/feature.interfaces.ts +359 -0
  123. package/ts/interfaces/homeassistant.interfaces.ts +666 -0
  124. package/ts/interfaces/index.ts +421 -0
  125. package/ts/interfaces/smarthome.interfaces.ts +421 -0
  126. package/ts/paths.ts +5 -0
  127. package/ts/plugins.ts +41 -0
  128. package/ts/protocols/index.ts +59 -0
  129. package/ts/protocols/protocol.escl.ts +439 -0
  130. package/ts/protocols/protocol.homeassistant.ts +737 -0
  131. package/ts/protocols/protocol.ipp.ts +329 -0
  132. package/ts/protocols/protocol.nut.ts +471 -0
  133. package/ts/protocols/protocol.sane.ts +694 -0
  134. package/ts/protocols/protocol.snmp.ts +439 -0
  135. package/ts/protocols/protocol.upnp.ts +627 -0
  136. package/ts/protocols/protocol.upssnmp.ts +377 -0
  137. package/tsconfig.json +12 -0
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Abstract Feature base class
3
+ * Features are composable capabilities that can be attached to devices
4
+ */
5
+
6
+ import * as plugins from '../plugins.js';
7
+ import type {
8
+ TFeatureType,
9
+ TFeatureState,
10
+ IFeature,
11
+ IFeatureInfo,
12
+ IFeatureOptions,
13
+ } from '../interfaces/feature.interfaces.js';
14
+ import type { IRetryOptions } from '../interfaces/index.js';
15
+ import { withRetry } from '../helpers/helpers.retry.js';
16
+
17
+ /**
18
+ * Forward reference to Device to avoid circular dependency
19
+ * The actual Device class will set this reference
20
+ */
21
+ export type TDeviceReference = {
22
+ id: string;
23
+ name: string;
24
+ address: string;
25
+ port: number;
26
+ };
27
+
28
+ /**
29
+ * Abstract base class for all features
30
+ * Provides common functionality for connection management, state tracking, and retry logic
31
+ */
32
+ export abstract class Feature extends plugins.events.EventEmitter implements IFeature {
33
+ /**
34
+ * The feature type identifier
35
+ */
36
+ public abstract readonly type: TFeatureType;
37
+
38
+ /**
39
+ * The protocol used by this feature
40
+ */
41
+ public abstract readonly protocol: string;
42
+
43
+ /**
44
+ * The port used by this feature
45
+ */
46
+ protected _port: number;
47
+
48
+ /**
49
+ * Current feature state
50
+ */
51
+ protected _state: TFeatureState = 'disconnected';
52
+
53
+ /**
54
+ * Last error encountered
55
+ */
56
+ protected _lastError?: Error;
57
+
58
+ /**
59
+ * Reference to the parent device
60
+ */
61
+ protected _device: TDeviceReference;
62
+
63
+ /**
64
+ * Retry configuration
65
+ */
66
+ protected _retryConfig: Required<IRetryOptions>;
67
+
68
+ /**
69
+ * Additional metadata from discovery
70
+ */
71
+ protected _metadata: Record<string, unknown>;
72
+
73
+ constructor(
74
+ device: TDeviceReference,
75
+ port: number,
76
+ options?: IFeatureOptions
77
+ ) {
78
+ super();
79
+ this._device = device;
80
+ this._port = port;
81
+ this._metadata = options?.metadata ?? {};
82
+
83
+ // Setup retry config
84
+ const retryOpts = options?.retryOptions ?? {};
85
+ this._retryConfig = {
86
+ maxRetries: retryOpts.maxRetries ?? 3,
87
+ baseDelay: retryOpts.baseDelay ?? 1000,
88
+ maxDelay: retryOpts.maxDelay ?? 30000,
89
+ multiplier: retryOpts.multiplier ?? 2,
90
+ jitter: retryOpts.jitter ?? true,
91
+ };
92
+ }
93
+
94
+ // ============================================================================
95
+ // Public Properties
96
+ // ============================================================================
97
+
98
+ /**
99
+ * Get the feature port
100
+ */
101
+ public get port(): number {
102
+ return this._port;
103
+ }
104
+
105
+ /**
106
+ * Get the current feature state
107
+ */
108
+ public get state(): TFeatureState {
109
+ return this._state;
110
+ }
111
+
112
+ /**
113
+ * Check if the feature is connected
114
+ */
115
+ public get isConnected(): boolean {
116
+ return this._state === 'connected';
117
+ }
118
+
119
+ /**
120
+ * Get the last error
121
+ */
122
+ public get lastError(): Error | undefined {
123
+ return this._lastError;
124
+ }
125
+
126
+ /**
127
+ * Get the device address
128
+ */
129
+ public get address(): string {
130
+ return this._device.address;
131
+ }
132
+
133
+ /**
134
+ * Get the device ID
135
+ */
136
+ public get deviceId(): string {
137
+ return this._device.id;
138
+ }
139
+
140
+ // ============================================================================
141
+ // Connection Management
142
+ // ============================================================================
143
+
144
+ /**
145
+ * Connect to the feature endpoint
146
+ */
147
+ public async connect(): Promise<void> {
148
+ if (this._state === 'connected') {
149
+ return;
150
+ }
151
+
152
+ if (this._state === 'connecting') {
153
+ throw new Error(`Feature ${this.type} is already connecting`);
154
+ }
155
+
156
+ this.setState('connecting');
157
+ this._lastError = undefined;
158
+
159
+ try {
160
+ await this.withRetry(() => this.doConnect());
161
+ this.setState('connected');
162
+ this.emit('connected');
163
+ } catch (error) {
164
+ this._lastError = error instanceof Error ? error : new Error(String(error));
165
+ this.setState('error');
166
+ this.emit('error', this._lastError);
167
+ throw this._lastError;
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Disconnect from the feature endpoint
173
+ */
174
+ public async disconnect(): Promise<void> {
175
+ if (this._state === 'disconnected') {
176
+ return;
177
+ }
178
+
179
+ try {
180
+ await this.doDisconnect();
181
+ } finally {
182
+ this.setState('disconnected');
183
+ this.emit('disconnected');
184
+ }
185
+ }
186
+
187
+ // ============================================================================
188
+ // Abstract Methods (implemented by subclasses)
189
+ // ============================================================================
190
+
191
+ /**
192
+ * Perform the actual connection logic
193
+ * Implemented by each feature subclass
194
+ */
195
+ protected abstract doConnect(): Promise<void>;
196
+
197
+ /**
198
+ * Perform the actual disconnection logic
199
+ * Implemented by each feature subclass
200
+ */
201
+ protected abstract doDisconnect(): Promise<void>;
202
+
203
+ /**
204
+ * Get feature-specific info for serialization
205
+ */
206
+ public abstract getFeatureInfo(): IFeatureInfo;
207
+
208
+ // ============================================================================
209
+ // Helper Methods
210
+ // ============================================================================
211
+
212
+ /**
213
+ * Set the feature state and emit change event
214
+ */
215
+ protected setState(state: TFeatureState): void {
216
+ if (this._state !== state) {
217
+ const oldState = this._state;
218
+ this._state = state;
219
+ this.emit('state:changed', { oldState, newState: state });
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Execute an operation with retry logic
225
+ */
226
+ protected async withRetry<T>(operation: () => Promise<T>): Promise<T> {
227
+ return withRetry(operation, this._retryConfig);
228
+ }
229
+
230
+ /**
231
+ * Get base feature info
232
+ */
233
+ protected getBaseFeatureInfo(): IFeatureInfo {
234
+ return {
235
+ type: this.type,
236
+ protocol: this.protocol,
237
+ port: this._port,
238
+ state: this._state,
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Clear error state
244
+ */
245
+ public clearError(): void {
246
+ this._lastError = undefined;
247
+ if (this._state === 'error') {
248
+ this.setState('disconnected');
249
+ }
250
+ }
251
+ }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Camera Feature
3
+ * Provides control for smart cameras (snapshots, streams)
4
+ */
5
+
6
+ import { Feature, type TDeviceReference } from './feature.abstract.js';
7
+ import type { IFeatureOptions } from '../interfaces/feature.interfaces.js';
8
+ import type {
9
+ TCameraProtocol,
10
+ ICameraCapabilities,
11
+ ICameraState,
12
+ ICameraFeatureInfo,
13
+ ICameraProtocolClient,
14
+ } from '../interfaces/smarthome.interfaces.js';
15
+
16
+ /**
17
+ * Options for creating a CameraFeature
18
+ */
19
+ export interface ICameraFeatureOptions extends IFeatureOptions {
20
+ /** Protocol type */
21
+ protocol: TCameraProtocol;
22
+ /** Entity ID (for Home Assistant) */
23
+ entityId: string;
24
+ /** Protocol client for the camera */
25
+ protocolClient: ICameraProtocolClient;
26
+ /** Camera capabilities */
27
+ capabilities?: Partial<ICameraCapabilities>;
28
+ }
29
+
30
+ /**
31
+ * Camera Feature - snapshot and stream access
32
+ *
33
+ * Protocol-agnostic: works with Home Assistant, ONVIF, RTSP, etc.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const camera = device.getFeature<CameraFeature>('camera');
38
+ * if (camera) {
39
+ * const snapshot = await camera.getSnapshot();
40
+ * const streamUrl = await camera.getStreamUrl();
41
+ * }
42
+ * ```
43
+ */
44
+ export class CameraFeature extends Feature {
45
+ public readonly type = 'camera' as const;
46
+ public readonly protocol: TCameraProtocol;
47
+
48
+ /** Entity ID (e.g., "camera.front_door") */
49
+ public readonly entityId: string;
50
+
51
+ /** Capabilities */
52
+ public readonly capabilities: ICameraCapabilities;
53
+
54
+ /** Current state */
55
+ protected _isRecording: boolean = false;
56
+ protected _isStreaming: boolean = false;
57
+ protected _motionDetected: boolean = false;
58
+
59
+ /** Protocol client for the camera */
60
+ private protocolClient: ICameraProtocolClient;
61
+
62
+ constructor(
63
+ device: TDeviceReference,
64
+ port: number,
65
+ options: ICameraFeatureOptions
66
+ ) {
67
+ super(device, port, options);
68
+ this.protocol = options.protocol;
69
+ this.entityId = options.entityId;
70
+ this.protocolClient = options.protocolClient;
71
+
72
+ this.capabilities = {
73
+ supportsStream: options.capabilities?.supportsStream ?? true,
74
+ supportsPtz: options.capabilities?.supportsPtz ?? false,
75
+ supportsSnapshot: options.capabilities?.supportsSnapshot ?? true,
76
+ supportsMotionDetection: options.capabilities?.supportsMotionDetection ?? false,
77
+ frontendStreamType: options.capabilities?.frontendStreamType,
78
+ streamUrl: options.capabilities?.streamUrl,
79
+ };
80
+ }
81
+
82
+ // ============================================================================
83
+ // Properties
84
+ // ============================================================================
85
+
86
+ /**
87
+ * Check if recording (cached)
88
+ */
89
+ public get isRecording(): boolean {
90
+ return this._isRecording;
91
+ }
92
+
93
+ /**
94
+ * Check if streaming (cached)
95
+ */
96
+ public get isStreaming(): boolean {
97
+ return this._isStreaming;
98
+ }
99
+
100
+ /**
101
+ * Check if motion detected (cached)
102
+ */
103
+ public get motionDetected(): boolean {
104
+ return this._motionDetected;
105
+ }
106
+
107
+ // ============================================================================
108
+ // Connection
109
+ // ============================================================================
110
+
111
+ protected async doConnect(): Promise<void> {
112
+ try {
113
+ const state = await this.protocolClient.getState(this.entityId);
114
+ this.updateStateInternal(state);
115
+ } catch {
116
+ // Ignore errors fetching initial state
117
+ }
118
+ }
119
+
120
+ protected async doDisconnect(): Promise<void> {
121
+ // Nothing to disconnect
122
+ }
123
+
124
+ // ============================================================================
125
+ // Camera Access
126
+ // ============================================================================
127
+
128
+ /**
129
+ * Get a snapshot image from the camera
130
+ * @returns Buffer containing image data
131
+ */
132
+ public async getSnapshot(): Promise<Buffer> {
133
+ if (!this.capabilities.supportsSnapshot) {
134
+ throw new Error('Camera does not support snapshots');
135
+ }
136
+
137
+ return this.protocolClient.getSnapshot(this.entityId);
138
+ }
139
+
140
+ /**
141
+ * Get snapshot URL
142
+ * @returns URL for the snapshot image
143
+ */
144
+ public async getSnapshotUrl(): Promise<string> {
145
+ if (!this.capabilities.supportsSnapshot) {
146
+ throw new Error('Camera does not support snapshots');
147
+ }
148
+
149
+ return this.protocolClient.getSnapshotUrl(this.entityId);
150
+ }
151
+
152
+ /**
153
+ * Get stream URL
154
+ * @returns URL for the video stream
155
+ */
156
+ public async getStreamUrl(): Promise<string> {
157
+ if (!this.capabilities.supportsStream) {
158
+ throw new Error('Camera does not support streaming');
159
+ }
160
+
161
+ return this.protocolClient.getStreamUrl(this.entityId);
162
+ }
163
+
164
+ /**
165
+ * Get current state as object
166
+ */
167
+ public getState(): ICameraState {
168
+ return {
169
+ isRecording: this._isRecording,
170
+ isStreaming: this._isStreaming,
171
+ motionDetected: this._motionDetected,
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Refresh state from the device
177
+ */
178
+ public async refreshState(): Promise<ICameraState> {
179
+ const state = await this.protocolClient.getState(this.entityId);
180
+ this.updateStateInternal(state);
181
+ return state;
182
+ }
183
+
184
+ /**
185
+ * Update state from external source
186
+ */
187
+ public updateState(state: ICameraState): void {
188
+ this.updateStateInternal(state);
189
+ this.emit('state:changed', state);
190
+ }
191
+
192
+ /**
193
+ * Internal state update
194
+ */
195
+ private updateStateInternal(state: ICameraState): void {
196
+ this._isRecording = state.isRecording;
197
+ this._isStreaming = state.isStreaming;
198
+ this._motionDetected = state.motionDetected;
199
+ }
200
+
201
+ // ============================================================================
202
+ // Feature Info
203
+ // ============================================================================
204
+
205
+ public getFeatureInfo(): ICameraFeatureInfo {
206
+ return {
207
+ ...this.getBaseFeatureInfo(),
208
+ type: 'camera',
209
+ protocol: this.protocol,
210
+ capabilities: this.capabilities,
211
+ currentState: this.getState(),
212
+ };
213
+ }
214
+ }