@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,296 @@
1
+ /**
2
+ * Light Feature
3
+ * Provides control for smart lights (brightness, color, effects)
4
+ */
5
+ import { Feature } from './feature.abstract.js';
6
+ /**
7
+ * Light Feature - brightness, color, and effect control
8
+ *
9
+ * Protocol-agnostic: works with Home Assistant, Hue, MQTT, Zigbee, etc.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const light = device.getFeature<LightFeature>('light');
14
+ * if (light) {
15
+ * await light.turnOn({ brightness: 200 });
16
+ * await light.setColorTemp(4000); // 4000K warm white
17
+ * await light.setRgbColor(255, 100, 50);
18
+ * }
19
+ * ```
20
+ */
21
+ export class LightFeature extends Feature {
22
+ type = 'light';
23
+ protocol;
24
+ /** Entity ID (e.g., "light.living_room") */
25
+ entityId;
26
+ /** Capabilities */
27
+ capabilities;
28
+ /** Current state */
29
+ _isOn = false;
30
+ _brightness;
31
+ _colorTemp;
32
+ _colorTempMireds;
33
+ _rgbColor;
34
+ _hsColor;
35
+ _xyColor;
36
+ _effect;
37
+ /** Protocol client for controlling the light */
38
+ protocolClient;
39
+ constructor(device, port, options) {
40
+ super(device, port, options);
41
+ this.protocol = options.protocol;
42
+ this.entityId = options.entityId;
43
+ this.protocolClient = options.protocolClient;
44
+ // Set capabilities with defaults
45
+ this.capabilities = {
46
+ supportsBrightness: options.capabilities?.supportsBrightness ?? false,
47
+ supportsColorTemp: options.capabilities?.supportsColorTemp ?? false,
48
+ supportsRgb: options.capabilities?.supportsRgb ?? false,
49
+ supportsHs: options.capabilities?.supportsHs ?? false,
50
+ supportsXy: options.capabilities?.supportsXy ?? false,
51
+ supportsEffects: options.capabilities?.supportsEffects ?? false,
52
+ supportsTransition: options.capabilities?.supportsTransition ?? true,
53
+ effects: options.capabilities?.effects,
54
+ minMireds: options.capabilities?.minMireds,
55
+ maxMireds: options.capabilities?.maxMireds,
56
+ minColorTempKelvin: options.capabilities?.minColorTempKelvin,
57
+ maxColorTempKelvin: options.capabilities?.maxColorTempKelvin,
58
+ };
59
+ }
60
+ // ============================================================================
61
+ // Properties
62
+ // ============================================================================
63
+ /**
64
+ * Get current on/off state (cached)
65
+ */
66
+ get isOn() {
67
+ return this._isOn;
68
+ }
69
+ /**
70
+ * Get current brightness 0-255 (cached)
71
+ */
72
+ get brightness() {
73
+ return this._brightness;
74
+ }
75
+ /**
76
+ * Get current color temperature in Kelvin (cached)
77
+ */
78
+ get colorTemp() {
79
+ return this._colorTemp;
80
+ }
81
+ /**
82
+ * Get current color temperature in Mireds (cached)
83
+ */
84
+ get colorTempMireds() {
85
+ return this._colorTempMireds;
86
+ }
87
+ /**
88
+ * Get current RGB color (cached)
89
+ */
90
+ get rgbColor() {
91
+ return this._rgbColor;
92
+ }
93
+ /**
94
+ * Get current HS color [hue 0-360, saturation 0-100] (cached)
95
+ */
96
+ get hsColor() {
97
+ return this._hsColor;
98
+ }
99
+ /**
100
+ * Get current XY color (cached)
101
+ */
102
+ get xyColor() {
103
+ return this._xyColor;
104
+ }
105
+ /**
106
+ * Get current effect (cached)
107
+ */
108
+ get effect() {
109
+ return this._effect;
110
+ }
111
+ /**
112
+ * Get available effects
113
+ */
114
+ get effects() {
115
+ return this.capabilities.effects;
116
+ }
117
+ // ============================================================================
118
+ // Connection
119
+ // ============================================================================
120
+ async doConnect() {
121
+ // Fetch initial state
122
+ try {
123
+ const state = await this.protocolClient.getState(this.entityId);
124
+ this.updateStateInternal(state);
125
+ }
126
+ catch {
127
+ // Ignore errors fetching initial state
128
+ }
129
+ }
130
+ async doDisconnect() {
131
+ // Nothing to disconnect
132
+ }
133
+ // ============================================================================
134
+ // Light Control
135
+ // ============================================================================
136
+ /**
137
+ * Turn on the light
138
+ * @param options Optional settings to apply when turning on
139
+ */
140
+ async turnOn(options) {
141
+ await this.protocolClient.turnOn(this.entityId, options);
142
+ this._isOn = true;
143
+ if (options?.brightness !== undefined) {
144
+ this._brightness = options.brightness;
145
+ }
146
+ if (options?.colorTemp !== undefined) {
147
+ this._colorTemp = options.colorTemp;
148
+ }
149
+ if (options?.rgb !== undefined) {
150
+ this._rgbColor = options.rgb;
151
+ }
152
+ this.emit('state:changed', this.getState());
153
+ }
154
+ /**
155
+ * Turn off the light
156
+ * @param options Optional transition time
157
+ */
158
+ async turnOff(options) {
159
+ await this.protocolClient.turnOff(this.entityId, options);
160
+ this._isOn = false;
161
+ this.emit('state:changed', this.getState());
162
+ }
163
+ /**
164
+ * Toggle the light
165
+ */
166
+ async toggle() {
167
+ await this.protocolClient.toggle(this.entityId);
168
+ this._isOn = !this._isOn;
169
+ this.emit('state:changed', this.getState());
170
+ }
171
+ /**
172
+ * Set brightness level
173
+ * @param brightness Brightness 0-255
174
+ * @param transition Optional transition time in seconds
175
+ */
176
+ async setBrightness(brightness, transition) {
177
+ if (!this.capabilities.supportsBrightness) {
178
+ throw new Error('Light does not support brightness control');
179
+ }
180
+ const clamped = Math.max(0, Math.min(255, Math.round(brightness)));
181
+ await this.protocolClient.setBrightness(this.entityId, clamped, transition);
182
+ this._brightness = clamped;
183
+ if (clamped > 0) {
184
+ this._isOn = true;
185
+ }
186
+ this.emit('state:changed', this.getState());
187
+ }
188
+ /**
189
+ * Set color temperature in Kelvin
190
+ * @param kelvin Color temperature in Kelvin (e.g., 2700 warm, 6500 cool)
191
+ * @param transition Optional transition time in seconds
192
+ */
193
+ async setColorTemp(kelvin, transition) {
194
+ if (!this.capabilities.supportsColorTemp) {
195
+ throw new Error('Light does not support color temperature');
196
+ }
197
+ // Clamp to supported range if available
198
+ let clamped = kelvin;
199
+ if (this.capabilities.minColorTempKelvin && this.capabilities.maxColorTempKelvin) {
200
+ clamped = Math.max(this.capabilities.minColorTempKelvin, Math.min(this.capabilities.maxColorTempKelvin, kelvin));
201
+ }
202
+ await this.protocolClient.setColorTemp(this.entityId, clamped, transition);
203
+ this._colorTemp = clamped;
204
+ this._colorTempMireds = Math.round(1000000 / clamped);
205
+ this._isOn = true;
206
+ this.emit('state:changed', this.getState());
207
+ }
208
+ /**
209
+ * Set RGB color
210
+ * @param r Red 0-255
211
+ * @param g Green 0-255
212
+ * @param b Blue 0-255
213
+ * @param transition Optional transition time in seconds
214
+ */
215
+ async setRgbColor(r, g, b, transition) {
216
+ if (!this.capabilities.supportsRgb) {
217
+ throw new Error('Light does not support RGB color');
218
+ }
219
+ const clampedR = Math.max(0, Math.min(255, Math.round(r)));
220
+ const clampedG = Math.max(0, Math.min(255, Math.round(g)));
221
+ const clampedB = Math.max(0, Math.min(255, Math.round(b)));
222
+ await this.protocolClient.setRgbColor(this.entityId, clampedR, clampedG, clampedB, transition);
223
+ this._rgbColor = [clampedR, clampedG, clampedB];
224
+ this._isOn = true;
225
+ this.emit('state:changed', this.getState());
226
+ }
227
+ /**
228
+ * Set light effect
229
+ * @param effect Effect name from available effects
230
+ */
231
+ async setEffect(effect) {
232
+ if (!this.capabilities.supportsEffects) {
233
+ throw new Error('Light does not support effects');
234
+ }
235
+ await this.protocolClient.setEffect(this.entityId, effect);
236
+ this._effect = effect;
237
+ this._isOn = true;
238
+ this.emit('state:changed', this.getState());
239
+ }
240
+ /**
241
+ * Get current state as object
242
+ */
243
+ getState() {
244
+ return {
245
+ isOn: this._isOn,
246
+ brightness: this._brightness,
247
+ colorTemp: this._colorTemp,
248
+ colorTempMireds: this._colorTempMireds,
249
+ rgbColor: this._rgbColor,
250
+ hsColor: this._hsColor,
251
+ xyColor: this._xyColor,
252
+ effect: this._effect,
253
+ };
254
+ }
255
+ /**
256
+ * Refresh state from the device
257
+ */
258
+ async refreshState() {
259
+ const state = await this.protocolClient.getState(this.entityId);
260
+ this.updateStateInternal(state);
261
+ return state;
262
+ }
263
+ /**
264
+ * Update state from external source (e.g., state change event)
265
+ */
266
+ updateState(state) {
267
+ this.updateStateInternal(state);
268
+ this.emit('state:changed', state);
269
+ }
270
+ /**
271
+ * Internal state update
272
+ */
273
+ updateStateInternal(state) {
274
+ this._isOn = state.isOn;
275
+ this._brightness = state.brightness;
276
+ this._colorTemp = state.colorTemp;
277
+ this._colorTempMireds = state.colorTempMireds;
278
+ this._rgbColor = state.rgbColor;
279
+ this._hsColor = state.hsColor;
280
+ this._xyColor = state.xyColor;
281
+ this._effect = state.effect;
282
+ }
283
+ // ============================================================================
284
+ // Feature Info
285
+ // ============================================================================
286
+ getFeatureInfo() {
287
+ return {
288
+ ...this.getBaseFeatureInfo(),
289
+ type: 'light',
290
+ protocol: this.protocol,
291
+ capabilities: this.capabilities,
292
+ currentState: this.getState(),
293
+ };
294
+ }
295
+ }
296
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmVhdHVyZS5saWdodC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2ZlYXR1cmVzL2ZlYXR1cmUubGlnaHQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztHQUdHO0FBRUgsT0FBTyxFQUFFLE9BQU8sRUFBeUIsTUFBTSx1QkFBdUIsQ0FBQztBQXdCdkU7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFDSCxNQUFNLE9BQU8sWUFBYSxTQUFRLE9BQU87SUFDdkIsSUFBSSxHQUFHLE9BQWdCLENBQUM7SUFDeEIsUUFBUSxDQUFpQjtJQUV6Qyw0Q0FBNEM7SUFDNUIsUUFBUSxDQUFTO0lBRWpDLG1CQUFtQjtJQUNILFlBQVksQ0FBcUI7SUFFakQsb0JBQW9CO0lBQ1YsS0FBSyxHQUFZLEtBQUssQ0FBQztJQUN2QixXQUFXLENBQVU7SUFDckIsVUFBVSxDQUFVO0lBQ3BCLGdCQUFnQixDQUFVO0lBQzFCLFNBQVMsQ0FBNEI7SUFDckMsUUFBUSxDQUFvQjtJQUM1QixRQUFRLENBQW9CO0lBQzVCLE9BQU8sQ0FBVTtJQUUzQixnREFBZ0Q7SUFDeEMsY0FBYyxDQUF1QjtJQUU3QyxZQUNFLE1BQXdCLEVBQ3hCLElBQVksRUFDWixPQUE2QjtRQUU3QixLQUFLLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQztRQUM3QixJQUFJLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUM7UUFDakMsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDO1FBQ2pDLElBQUksQ0FBQyxjQUFjLEdBQUcsT0FBTyxDQUFDLGNBQWMsQ0FBQztRQUU3QyxpQ0FBaUM7UUFDakMsSUFBSSxDQUFDLFlBQVksR0FBRztZQUNsQixrQkFBa0IsRUFBRSxPQUFPLENBQUMsWUFBWSxFQUFFLGtCQUFrQixJQUFJLEtBQUs7WUFDckUsaUJBQWlCLEVBQUUsT0FBTyxDQUFDLFlBQVksRUFBRSxpQkFBaUIsSUFBSSxLQUFL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
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Lock Feature
3
+ * Provides control for smart locks
4
+ */
5
+ import { Feature, type TDeviceReference } from './feature.abstract.js';
6
+ import type { IFeatureOptions } from '../interfaces/feature.interfaces.js';
7
+ import type { TLockProtocol, TLockState, ILockCapabilities, ILockStateInfo, ILockFeatureInfo, ILockProtocolClient } from '../interfaces/smarthome.interfaces.js';
8
+ /**
9
+ * Options for creating a LockFeature
10
+ */
11
+ export interface ILockFeatureOptions extends IFeatureOptions {
12
+ /** Protocol type */
13
+ protocol: TLockProtocol;
14
+ /** Entity ID (for Home Assistant) */
15
+ entityId: string;
16
+ /** Protocol client for controlling the lock */
17
+ protocolClient: ILockProtocolClient;
18
+ /** Whether the lock supports physical open */
19
+ supportsOpen?: boolean;
20
+ }
21
+ /**
22
+ * Lock Feature - control for smart locks
23
+ *
24
+ * Protocol-agnostic: works with Home Assistant, MQTT, August, Yale, etc.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const lock = device.getFeature<LockFeature>('lock');
29
+ * if (lock) {
30
+ * await lock.lock();
31
+ * console.log(`Lock is ${lock.isLocked ? 'locked' : 'unlocked'}`);
32
+ * }
33
+ * ```
34
+ */
35
+ export declare class LockFeature extends Feature {
36
+ readonly type: "lock";
37
+ readonly protocol: TLockProtocol;
38
+ /** Entity ID (e.g., "lock.front_door") */
39
+ readonly entityId: string;
40
+ /** Capabilities */
41
+ readonly capabilities: ILockCapabilities;
42
+ /** Current state */
43
+ protected _lockState: TLockState;
44
+ protected _isLocked: boolean;
45
+ /** Protocol client for controlling the lock */
46
+ private protocolClient;
47
+ constructor(device: TDeviceReference, port: number, options: ILockFeatureOptions);
48
+ /**
49
+ * Get current lock state (cached)
50
+ */
51
+ get lockState(): TLockState;
52
+ /**
53
+ * Check if locked (cached)
54
+ */
55
+ get isLocked(): boolean;
56
+ /**
57
+ * Check if unlocked
58
+ */
59
+ get isUnlocked(): boolean;
60
+ /**
61
+ * Check if currently locking
62
+ */
63
+ get isLocking(): boolean;
64
+ /**
65
+ * Check if currently unlocking
66
+ */
67
+ get isUnlocking(): boolean;
68
+ /**
69
+ * Check if jammed
70
+ */
71
+ get isJammed(): boolean;
72
+ protected doConnect(): Promise<void>;
73
+ protected doDisconnect(): Promise<void>;
74
+ /**
75
+ * Lock the lock
76
+ */
77
+ lock(): Promise<void>;
78
+ /**
79
+ * Unlock the lock
80
+ */
81
+ unlock(): Promise<void>;
82
+ /**
83
+ * Open the lock (physically open the door if supported)
84
+ */
85
+ open(): Promise<void>;
86
+ /**
87
+ * Get current state as object
88
+ */
89
+ getState(): ILockStateInfo;
90
+ /**
91
+ * Refresh state from the device
92
+ */
93
+ refreshState(): Promise<ILockStateInfo>;
94
+ /**
95
+ * Update state from external source
96
+ */
97
+ updateState(state: ILockStateInfo): void;
98
+ /**
99
+ * Internal state update
100
+ */
101
+ private updateStateInternal;
102
+ getFeatureInfo(): ILockFeatureInfo;
103
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Lock Feature
3
+ * Provides control for smart locks
4
+ */
5
+ import { Feature } from './feature.abstract.js';
6
+ /**
7
+ * Lock Feature - control for smart locks
8
+ *
9
+ * Protocol-agnostic: works with Home Assistant, MQTT, August, Yale, etc.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const lock = device.getFeature<LockFeature>('lock');
14
+ * if (lock) {
15
+ * await lock.lock();
16
+ * console.log(`Lock is ${lock.isLocked ? 'locked' : 'unlocked'}`);
17
+ * }
18
+ * ```
19
+ */
20
+ export class LockFeature extends Feature {
21
+ type = 'lock';
22
+ protocol;
23
+ /** Entity ID (e.g., "lock.front_door") */
24
+ entityId;
25
+ /** Capabilities */
26
+ capabilities;
27
+ /** Current state */
28
+ _lockState = 'unknown';
29
+ _isLocked = false;
30
+ /** Protocol client for controlling the lock */
31
+ protocolClient;
32
+ constructor(device, port, options) {
33
+ super(device, port, options);
34
+ this.protocol = options.protocol;
35
+ this.entityId = options.entityId;
36
+ this.protocolClient = options.protocolClient;
37
+ this.capabilities = {
38
+ supportsOpen: options.supportsOpen ?? false,
39
+ };
40
+ }
41
+ // ============================================================================
42
+ // Properties
43
+ // ============================================================================
44
+ /**
45
+ * Get current lock state (cached)
46
+ */
47
+ get lockState() {
48
+ return this._lockState;
49
+ }
50
+ /**
51
+ * Check if locked (cached)
52
+ */
53
+ get isLocked() {
54
+ return this._isLocked;
55
+ }
56
+ /**
57
+ * Check if unlocked
58
+ */
59
+ get isUnlocked() {
60
+ return this._lockState === 'unlocked';
61
+ }
62
+ /**
63
+ * Check if currently locking
64
+ */
65
+ get isLocking() {
66
+ return this._lockState === 'locking';
67
+ }
68
+ /**
69
+ * Check if currently unlocking
70
+ */
71
+ get isUnlocking() {
72
+ return this._lockState === 'unlocking';
73
+ }
74
+ /**
75
+ * Check if jammed
76
+ */
77
+ get isJammed() {
78
+ return this._lockState === 'jammed';
79
+ }
80
+ // ============================================================================
81
+ // Connection
82
+ // ============================================================================
83
+ async doConnect() {
84
+ try {
85
+ const state = await this.protocolClient.getState(this.entityId);
86
+ this.updateStateInternal(state);
87
+ }
88
+ catch {
89
+ // Ignore errors fetching initial state
90
+ }
91
+ }
92
+ async doDisconnect() {
93
+ // Nothing to disconnect
94
+ }
95
+ // ============================================================================
96
+ // Lock Control
97
+ // ============================================================================
98
+ /**
99
+ * Lock the lock
100
+ */
101
+ async lock() {
102
+ await this.protocolClient.lock(this.entityId);
103
+ this._lockState = 'locking';
104
+ this.emit('state:changed', this.getState());
105
+ }
106
+ /**
107
+ * Unlock the lock
108
+ */
109
+ async unlock() {
110
+ await this.protocolClient.unlock(this.entityId);
111
+ this._lockState = 'unlocking';
112
+ this.emit('state:changed', this.getState());
113
+ }
114
+ /**
115
+ * Open the lock (physically open the door if supported)
116
+ */
117
+ async open() {
118
+ if (!this.capabilities.supportsOpen) {
119
+ throw new Error('Lock does not support physical open');
120
+ }
121
+ await this.protocolClient.open(this.entityId);
122
+ this._lockState = 'unlocked';
123
+ this._isLocked = false;
124
+ this.emit('state:changed', this.getState());
125
+ }
126
+ /**
127
+ * Get current state as object
128
+ */
129
+ getState() {
130
+ return {
131
+ state: this._lockState,
132
+ isLocked: this._isLocked,
133
+ };
134
+ }
135
+ /**
136
+ * Refresh state from the device
137
+ */
138
+ async refreshState() {
139
+ const state = await this.protocolClient.getState(this.entityId);
140
+ this.updateStateInternal(state);
141
+ return state;
142
+ }
143
+ /**
144
+ * Update state from external source
145
+ */
146
+ updateState(state) {
147
+ this.updateStateInternal(state);
148
+ this.emit('state:changed', state);
149
+ }
150
+ /**
151
+ * Internal state update
152
+ */
153
+ updateStateInternal(state) {
154
+ this._lockState = state.state;
155
+ this._isLocked = state.isLocked;
156
+ }
157
+ // ============================================================================
158
+ // Feature Info
159
+ // ============================================================================
160
+ getFeatureInfo() {
161
+ return {
162
+ ...this.getBaseFeatureInfo(),
163
+ type: 'lock',
164
+ protocol: this.protocol,
165
+ capabilities: this.capabilities,
166
+ currentState: this.getState(),
167
+ };
168
+ }
169
+ }
170
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmVhdHVyZS5sb2NrLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvZmVhdHVyZXMvZmVhdHVyZS5sb2NrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7R0FHRztBQUVILE9BQU8sRUFBRSxPQUFPLEVBQXlCLE1BQU0sdUJBQXVCLENBQUM7QUF5QnZFOzs7Ozs7Ozs7Ozs7O0dBYUc7QUFDSCxNQUFNLE9BQU8sV0FBWSxTQUFRLE9BQU87SUFDdEIsSUFBSSxHQUFHLE1BQWUsQ0FBQztJQUN2QixRQUFRLENBQWdCO0lBRXhDLDBDQUEwQztJQUMxQixRQUFRLENBQVM7SUFFakMsbUJBQW1CO0lBQ0gsWUFBWSxDQUFvQjtJQUVoRCxvQkFBb0I7SUFDVixVQUFVLEdBQWUsU0FBUyxDQUFDO0lBQ25DLFNBQVMsR0FBWSxLQUFLLENBQUM7SUFFckMsK0NBQStDO0lBQ3ZDLGNBQWMsQ0FBc0I7SUFFNUMsWUFDRSxNQUF3QixFQUN4QixJQUFZLEVBQ1osT0FBNEI7UUFFNUIsS0FBSyxDQUFDLE1BQU0sRUFBRSxJQUFJLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDN0IsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDO1FBQ2pDLElBQUksQ0FBQyxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUNqQyxJQUFJLENBQUMsY0FBYyxHQUFHLE9BQU8sQ0FBQyxjQUFjLENBQUM7UUFFN0MsSUFBSSxDQUFDLFlBQVksR0FBRztZQUNsQixZQUFZLEVBQUUsT0FBTyxDQUFDLFlBQVksSUFBSSxLQUFLO1NBQzVDLENBQUM7SUFDSixDQUFDO0lBRUQsK0VBQStFO0lBQy9FLGFBQWE7SUFDYiwrRUFBK0U7SUFFL0U7O09BRUc7SUFDSCxJQUFXLFNBQVM7UUFDbEIsT0FBTyxJQUFJLENBQUMsVUFBVSxDQUFDO0lBQ3pCLENBQUM7SUFFRDs7T0FFRztJQUNILElBQVcsUUFBUTtRQUNqQixPQUFPLElBQUksQ0FBQyxTQUFT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
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Playback Feature
3
+ * Provides media playback control capability
4
+ */
5
+ import { Feature, type TDeviceReference } from './feature.abstract.js';
6
+ import type { TPlaybackProtocol, TPlaybackState, ITrackInfo, IPlaybackStatus, IPlaybackFeatureInfo, IFeatureOptions } from '../interfaces/feature.interfaces.js';
7
+ /**
8
+ * Options for creating a PlaybackFeature
9
+ */
10
+ export interface IPlaybackFeatureOptions extends IFeatureOptions {
11
+ protocol: TPlaybackProtocol;
12
+ supportsQueue?: boolean;
13
+ supportsSeek?: boolean;
14
+ /** Protocol-specific client instance (Sonos, AirPlay, etc.) */
15
+ protocolClient?: unknown;
16
+ }
17
+ /**
18
+ * Playback Feature - provides media playback control
19
+ *
20
+ * Abstract feature that can be backed by different protocols (Sonos, AirPlay, Chromecast, DLNA).
21
+ * Concrete implementations should extend this class for protocol-specific behavior.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const playback = device.getFeature<PlaybackFeature>('playback');
26
+ * if (playback) {
27
+ * await playback.play('spotify:track:123');
28
+ * const status = await playback.getPlaybackStatus();
29
+ * console.log(`Playing: ${status.track?.title}`);
30
+ * }
31
+ * ```
32
+ */
33
+ export declare class PlaybackFeature extends Feature {
34
+ readonly type: "playback";
35
+ readonly protocol: TPlaybackProtocol;
36
+ supportsQueue: boolean;
37
+ supportsSeek: boolean;
38
+ protected _playbackState: TPlaybackState;
39
+ protected _currentTrack: ITrackInfo | null;
40
+ protected _position: number;
41
+ protected _duration: number;
42
+ protected protocolClient: unknown;
43
+ constructor(device: TDeviceReference, port: number, options: IPlaybackFeatureOptions);
44
+ get playbackState(): TPlaybackState;
45
+ get currentTrack(): ITrackInfo | null;
46
+ get position(): number;
47
+ get duration(): number;
48
+ get isPlaying(): boolean;
49
+ protected doConnect(): Promise<void>;
50
+ protected doDisconnect(): Promise<void>;
51
+ /**
52
+ * Start playback
53
+ * @param uri Optional URI to play. If not provided, resumes current playback.
54
+ */
55
+ play(uri?: string): Promise<void>;
56
+ /**
57
+ * Pause playback
58
+ */
59
+ pause(): Promise<void>;
60
+ /**
61
+ * Stop playback
62
+ */
63
+ stop(): Promise<void>;
64
+ /**
65
+ * Skip to next track
66
+ */
67
+ next(): Promise<void>;
68
+ /**
69
+ * Go to previous track
70
+ */
71
+ previous(): Promise<void>;
72
+ /**
73
+ * Seek to position
74
+ * @param seconds Position in seconds
75
+ */
76
+ seek(seconds: number): Promise<void>;
77
+ /**
78
+ * Get current track info
79
+ */
80
+ getCurrentTrack(): Promise<ITrackInfo | null>;
81
+ /**
82
+ * Get playback status
83
+ */
84
+ getPlaybackStatus(): Promise<IPlaybackStatus>;
85
+ /**
86
+ * Update track info (called by protocol-specific implementations)
87
+ */
88
+ protected updateTrack(track: ITrackInfo | null): void;
89
+ /**
90
+ * Update position (called by protocol-specific implementations)
91
+ */
92
+ protected updatePosition(position: number, duration: number): void;
93
+ getFeatureInfo(): IPlaybackFeatureInfo;
94
+ /**
95
+ * Create from discovery metadata
96
+ */
97
+ static fromDiscovery(device: TDeviceReference, port: number, protocol: TPlaybackProtocol, metadata: Record<string, unknown>): PlaybackFeature;
98
+ }