@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
+ * Fan Feature
3
+ * Provides control for fans (speed, oscillation, direction)
4
+ */
5
+
6
+ import { Feature, type TDeviceReference } from './feature.abstract.js';
7
+ import type { IFeatureOptions } from '../interfaces/feature.interfaces.js';
8
+ import type {
9
+ TFanProtocol,
10
+ TFanDirection,
11
+ IFanCapabilities,
12
+ IFanState,
13
+ IFanFeatureInfo,
14
+ IFanProtocolClient,
15
+ } from '../interfaces/smarthome.interfaces.js';
16
+
17
+ /**
18
+ * Options for creating a FanFeature
19
+ */
20
+ export interface IFanFeatureOptions extends IFeatureOptions {
21
+ /** Protocol type */
22
+ protocol: TFanProtocol;
23
+ /** Entity ID (for Home Assistant) */
24
+ entityId: string;
25
+ /** Protocol client for controlling the fan */
26
+ protocolClient: IFanProtocolClient;
27
+ /** Fan capabilities */
28
+ capabilities?: Partial<IFanCapabilities>;
29
+ }
30
+
31
+ /**
32
+ * Fan Feature - speed, oscillation, and direction control
33
+ *
34
+ * Protocol-agnostic: works with Home Assistant, MQTT, Bond, etc.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const fan = device.getFeature<FanFeature>('fan');
39
+ * if (fan) {
40
+ * await fan.turnOn(75); // 75% speed
41
+ * await fan.setOscillating(true);
42
+ * }
43
+ * ```
44
+ */
45
+ export class FanFeature extends Feature {
46
+ public readonly type = 'fan' as const;
47
+ public readonly protocol: TFanProtocol;
48
+
49
+ /** Entity ID (e.g., "fan.bedroom") */
50
+ public readonly entityId: string;
51
+
52
+ /** Capabilities */
53
+ public readonly capabilities: IFanCapabilities;
54
+
55
+ /** Current state */
56
+ protected _isOn: boolean = false;
57
+ protected _percentage?: number;
58
+ protected _presetMode?: string;
59
+ protected _oscillating?: boolean;
60
+ protected _direction?: TFanDirection;
61
+
62
+ /** Protocol client for controlling the fan */
63
+ private protocolClient: IFanProtocolClient;
64
+
65
+ constructor(
66
+ device: TDeviceReference,
67
+ port: number,
68
+ options: IFanFeatureOptions
69
+ ) {
70
+ super(device, port, options);
71
+ this.protocol = options.protocol;
72
+ this.entityId = options.entityId;
73
+ this.protocolClient = options.protocolClient;
74
+
75
+ this.capabilities = {
76
+ supportsSpeed: options.capabilities?.supportsSpeed ?? true,
77
+ supportsOscillate: options.capabilities?.supportsOscillate ?? false,
78
+ supportsDirection: options.capabilities?.supportsDirection ?? false,
79
+ supportsPresetModes: options.capabilities?.supportsPresetModes ?? false,
80
+ presetModes: options.capabilities?.presetModes,
81
+ speedCount: options.capabilities?.speedCount,
82
+ };
83
+ }
84
+
85
+ // ============================================================================
86
+ // Properties
87
+ // ============================================================================
88
+
89
+ /**
90
+ * Get current on/off state (cached)
91
+ */
92
+ public get isOn(): boolean {
93
+ return this._isOn;
94
+ }
95
+
96
+ /**
97
+ * Get current speed percentage 0-100 (cached)
98
+ */
99
+ public get percentage(): number | undefined {
100
+ return this._percentage;
101
+ }
102
+
103
+ /**
104
+ * Get current preset mode (cached)
105
+ */
106
+ public get presetMode(): string | undefined {
107
+ return this._presetMode;
108
+ }
109
+
110
+ /**
111
+ * Get oscillating state (cached)
112
+ */
113
+ public get oscillating(): boolean | undefined {
114
+ return this._oscillating;
115
+ }
116
+
117
+ /**
118
+ * Get direction (cached)
119
+ */
120
+ public get direction(): TFanDirection | undefined {
121
+ return this._direction;
122
+ }
123
+
124
+ /**
125
+ * Get available preset modes
126
+ */
127
+ public get presetModes(): string[] | undefined {
128
+ return this.capabilities.presetModes;
129
+ }
130
+
131
+ // ============================================================================
132
+ // Connection
133
+ // ============================================================================
134
+
135
+ protected async doConnect(): Promise<void> {
136
+ try {
137
+ const state = await this.protocolClient.getState(this.entityId);
138
+ this.updateStateInternal(state);
139
+ } catch {
140
+ // Ignore errors fetching initial state
141
+ }
142
+ }
143
+
144
+ protected async doDisconnect(): Promise<void> {
145
+ // Nothing to disconnect
146
+ }
147
+
148
+ // ============================================================================
149
+ // Fan Control
150
+ // ============================================================================
151
+
152
+ /**
153
+ * Turn on the fan
154
+ * @param percentage Optional speed percentage
155
+ */
156
+ public async turnOn(percentage?: number): Promise<void> {
157
+ await this.protocolClient.turnOn(this.entityId, percentage);
158
+ this._isOn = true;
159
+ if (percentage !== undefined) {
160
+ this._percentage = percentage;
161
+ }
162
+ this.emit('state:changed', this.getState());
163
+ }
164
+
165
+ /**
166
+ * Turn off the fan
167
+ */
168
+ public async turnOff(): Promise<void> {
169
+ await this.protocolClient.turnOff(this.entityId);
170
+ this._isOn = false;
171
+ this.emit('state:changed', this.getState());
172
+ }
173
+
174
+ /**
175
+ * Toggle the fan
176
+ */
177
+ public async toggle(): Promise<void> {
178
+ await this.protocolClient.toggle(this.entityId);
179
+ this._isOn = !this._isOn;
180
+ this.emit('state:changed', this.getState());
181
+ }
182
+
183
+ /**
184
+ * Set speed percentage
185
+ * @param percentage Speed 0-100
186
+ */
187
+ public async setPercentage(percentage: number): Promise<void> {
188
+ if (!this.capabilities.supportsSpeed) {
189
+ throw new Error('Fan does not support speed control');
190
+ }
191
+
192
+ const clamped = Math.max(0, Math.min(100, Math.round(percentage)));
193
+ await this.protocolClient.setPercentage(this.entityId, clamped);
194
+ this._percentage = clamped;
195
+ this._isOn = clamped > 0;
196
+ this.emit('state:changed', this.getState());
197
+ }
198
+
199
+ /**
200
+ * Set preset mode
201
+ * @param mode Preset mode name
202
+ */
203
+ public async setPresetMode(mode: string): Promise<void> {
204
+ if (!this.capabilities.supportsPresetModes) {
205
+ throw new Error('Fan does not support preset modes');
206
+ }
207
+
208
+ await this.protocolClient.setPresetMode(this.entityId, mode);
209
+ this._presetMode = mode;
210
+ this._isOn = true;
211
+ this.emit('state:changed', this.getState());
212
+ }
213
+
214
+ /**
215
+ * Set oscillating state
216
+ * @param oscillating Whether to oscillate
217
+ */
218
+ public async setOscillating(oscillating: boolean): Promise<void> {
219
+ if (!this.capabilities.supportsOscillate) {
220
+ throw new Error('Fan does not support oscillation');
221
+ }
222
+
223
+ await this.protocolClient.setOscillating(this.entityId, oscillating);
224
+ this._oscillating = oscillating;
225
+ this.emit('state:changed', this.getState());
226
+ }
227
+
228
+ /**
229
+ * Set direction
230
+ * @param direction forward or reverse
231
+ */
232
+ public async setDirection(direction: TFanDirection): Promise<void> {
233
+ if (!this.capabilities.supportsDirection) {
234
+ throw new Error('Fan does not support direction control');
235
+ }
236
+
237
+ await this.protocolClient.setDirection(this.entityId, direction);
238
+ this._direction = direction;
239
+ this.emit('state:changed', this.getState());
240
+ }
241
+
242
+ /**
243
+ * Get current state as object
244
+ */
245
+ public getState(): IFanState {
246
+ return {
247
+ isOn: this._isOn,
248
+ percentage: this._percentage,
249
+ presetMode: this._presetMode,
250
+ oscillating: this._oscillating,
251
+ direction: this._direction,
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Refresh state from the device
257
+ */
258
+ public async refreshState(): Promise<IFanState> {
259
+ const state = await this.protocolClient.getState(this.entityId);
260
+ this.updateStateInternal(state);
261
+ return state;
262
+ }
263
+
264
+ /**
265
+ * Update state from external source
266
+ */
267
+ public updateState(state: IFanState): void {
268
+ this.updateStateInternal(state);
269
+ this.emit('state:changed', state);
270
+ }
271
+
272
+ /**
273
+ * Internal state update
274
+ */
275
+ private updateStateInternal(state: IFanState): void {
276
+ this._isOn = state.isOn;
277
+ this._percentage = state.percentage;
278
+ this._presetMode = state.presetMode;
279
+ this._oscillating = state.oscillating;
280
+ this._direction = state.direction;
281
+ }
282
+
283
+ // ============================================================================
284
+ // Feature Info
285
+ // ============================================================================
286
+
287
+ public getFeatureInfo(): IFanFeatureInfo {
288
+ return {
289
+ ...this.getBaseFeatureInfo(),
290
+ type: 'fan',
291
+ protocol: this.protocol,
292
+ capabilities: this.capabilities,
293
+ currentState: this.getState(),
294
+ };
295
+ }
296
+ }
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Light Feature
3
+ * Provides control for smart lights (brightness, color, effects)
4
+ */
5
+
6
+ import { Feature, type TDeviceReference } from './feature.abstract.js';
7
+ import type { IFeatureOptions } from '../interfaces/feature.interfaces.js';
8
+ import type {
9
+ TLightProtocol,
10
+ ILightCapabilities,
11
+ ILightState,
12
+ ILightFeatureInfo,
13
+ ILightProtocolClient,
14
+ } from '../interfaces/smarthome.interfaces.js';
15
+
16
+ /**
17
+ * Options for creating a LightFeature
18
+ */
19
+ export interface ILightFeatureOptions extends IFeatureOptions {
20
+ /** Protocol type */
21
+ protocol: TLightProtocol;
22
+ /** Entity ID (for Home Assistant) */
23
+ entityId: string;
24
+ /** Protocol client for controlling the light */
25
+ protocolClient: ILightProtocolClient;
26
+ /** Light capabilities */
27
+ capabilities?: Partial<ILightCapabilities>;
28
+ }
29
+
30
+ /**
31
+ * Light Feature - brightness, color, and effect control
32
+ *
33
+ * Protocol-agnostic: works with Home Assistant, Hue, MQTT, Zigbee, etc.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const light = device.getFeature<LightFeature>('light');
38
+ * if (light) {
39
+ * await light.turnOn({ brightness: 200 });
40
+ * await light.setColorTemp(4000); // 4000K warm white
41
+ * await light.setRgbColor(255, 100, 50);
42
+ * }
43
+ * ```
44
+ */
45
+ export class LightFeature extends Feature {
46
+ public readonly type = 'light' as const;
47
+ public readonly protocol: TLightProtocol;
48
+
49
+ /** Entity ID (e.g., "light.living_room") */
50
+ public readonly entityId: string;
51
+
52
+ /** Capabilities */
53
+ public readonly capabilities: ILightCapabilities;
54
+
55
+ /** Current state */
56
+ protected _isOn: boolean = false;
57
+ protected _brightness?: number;
58
+ protected _colorTemp?: number;
59
+ protected _colorTempMireds?: number;
60
+ protected _rgbColor?: [number, number, number];
61
+ protected _hsColor?: [number, number];
62
+ protected _xyColor?: [number, number];
63
+ protected _effect?: string;
64
+
65
+ /** Protocol client for controlling the light */
66
+ private protocolClient: ILightProtocolClient;
67
+
68
+ constructor(
69
+ device: TDeviceReference,
70
+ port: number,
71
+ options: ILightFeatureOptions
72
+ ) {
73
+ super(device, port, options);
74
+ this.protocol = options.protocol;
75
+ this.entityId = options.entityId;
76
+ this.protocolClient = options.protocolClient;
77
+
78
+ // Set capabilities with defaults
79
+ this.capabilities = {
80
+ supportsBrightness: options.capabilities?.supportsBrightness ?? false,
81
+ supportsColorTemp: options.capabilities?.supportsColorTemp ?? false,
82
+ supportsRgb: options.capabilities?.supportsRgb ?? false,
83
+ supportsHs: options.capabilities?.supportsHs ?? false,
84
+ supportsXy: options.capabilities?.supportsXy ?? false,
85
+ supportsEffects: options.capabilities?.supportsEffects ?? false,
86
+ supportsTransition: options.capabilities?.supportsTransition ?? true,
87
+ effects: options.capabilities?.effects,
88
+ minMireds: options.capabilities?.minMireds,
89
+ maxMireds: options.capabilities?.maxMireds,
90
+ minColorTempKelvin: options.capabilities?.minColorTempKelvin,
91
+ maxColorTempKelvin: options.capabilities?.maxColorTempKelvin,
92
+ };
93
+ }
94
+
95
+ // ============================================================================
96
+ // Properties
97
+ // ============================================================================
98
+
99
+ /**
100
+ * Get current on/off state (cached)
101
+ */
102
+ public get isOn(): boolean {
103
+ return this._isOn;
104
+ }
105
+
106
+ /**
107
+ * Get current brightness 0-255 (cached)
108
+ */
109
+ public get brightness(): number | undefined {
110
+ return this._brightness;
111
+ }
112
+
113
+ /**
114
+ * Get current color temperature in Kelvin (cached)
115
+ */
116
+ public get colorTemp(): number | undefined {
117
+ return this._colorTemp;
118
+ }
119
+
120
+ /**
121
+ * Get current color temperature in Mireds (cached)
122
+ */
123
+ public get colorTempMireds(): number | undefined {
124
+ return this._colorTempMireds;
125
+ }
126
+
127
+ /**
128
+ * Get current RGB color (cached)
129
+ */
130
+ public get rgbColor(): [number, number, number] | undefined {
131
+ return this._rgbColor;
132
+ }
133
+
134
+ /**
135
+ * Get current HS color [hue 0-360, saturation 0-100] (cached)
136
+ */
137
+ public get hsColor(): [number, number] | undefined {
138
+ return this._hsColor;
139
+ }
140
+
141
+ /**
142
+ * Get current XY color (cached)
143
+ */
144
+ public get xyColor(): [number, number] | undefined {
145
+ return this._xyColor;
146
+ }
147
+
148
+ /**
149
+ * Get current effect (cached)
150
+ */
151
+ public get effect(): string | undefined {
152
+ return this._effect;
153
+ }
154
+
155
+ /**
156
+ * Get available effects
157
+ */
158
+ public get effects(): string[] | undefined {
159
+ return this.capabilities.effects;
160
+ }
161
+
162
+ // ============================================================================
163
+ // Connection
164
+ // ============================================================================
165
+
166
+ protected async doConnect(): Promise<void> {
167
+ // Fetch initial state
168
+ try {
169
+ const state = await this.protocolClient.getState(this.entityId);
170
+ this.updateStateInternal(state);
171
+ } catch {
172
+ // Ignore errors fetching initial state
173
+ }
174
+ }
175
+
176
+ protected async doDisconnect(): Promise<void> {
177
+ // Nothing to disconnect
178
+ }
179
+
180
+ // ============================================================================
181
+ // Light Control
182
+ // ============================================================================
183
+
184
+ /**
185
+ * Turn on the light
186
+ * @param options Optional settings to apply when turning on
187
+ */
188
+ public async turnOn(options?: {
189
+ brightness?: number;
190
+ colorTemp?: number;
191
+ rgb?: [number, number, number];
192
+ transition?: number;
193
+ }): Promise<void> {
194
+ await this.protocolClient.turnOn(this.entityId, options);
195
+ this._isOn = true;
196
+ if (options?.brightness !== undefined) {
197
+ this._brightness = options.brightness;
198
+ }
199
+ if (options?.colorTemp !== undefined) {
200
+ this._colorTemp = options.colorTemp;
201
+ }
202
+ if (options?.rgb !== undefined) {
203
+ this._rgbColor = options.rgb;
204
+ }
205
+ this.emit('state:changed', this.getState());
206
+ }
207
+
208
+ /**
209
+ * Turn off the light
210
+ * @param options Optional transition time
211
+ */
212
+ public async turnOff(options?: { transition?: number }): Promise<void> {
213
+ await this.protocolClient.turnOff(this.entityId, options);
214
+ this._isOn = false;
215
+ this.emit('state:changed', this.getState());
216
+ }
217
+
218
+ /**
219
+ * Toggle the light
220
+ */
221
+ public async toggle(): Promise<void> {
222
+ await this.protocolClient.toggle(this.entityId);
223
+ this._isOn = !this._isOn;
224
+ this.emit('state:changed', this.getState());
225
+ }
226
+
227
+ /**
228
+ * Set brightness level
229
+ * @param brightness Brightness 0-255
230
+ * @param transition Optional transition time in seconds
231
+ */
232
+ public async setBrightness(brightness: number, transition?: number): Promise<void> {
233
+ if (!this.capabilities.supportsBrightness) {
234
+ throw new Error('Light does not support brightness control');
235
+ }
236
+
237
+ const clamped = Math.max(0, Math.min(255, Math.round(brightness)));
238
+ await this.protocolClient.setBrightness(this.entityId, clamped, transition);
239
+ this._brightness = clamped;
240
+ if (clamped > 0) {
241
+ this._isOn = true;
242
+ }
243
+ this.emit('state:changed', this.getState());
244
+ }
245
+
246
+ /**
247
+ * Set color temperature in Kelvin
248
+ * @param kelvin Color temperature in Kelvin (e.g., 2700 warm, 6500 cool)
249
+ * @param transition Optional transition time in seconds
250
+ */
251
+ public async setColorTemp(kelvin: number, transition?: number): Promise<void> {
252
+ if (!this.capabilities.supportsColorTemp) {
253
+ throw new Error('Light does not support color temperature');
254
+ }
255
+
256
+ // Clamp to supported range if available
257
+ let clamped = kelvin;
258
+ if (this.capabilities.minColorTempKelvin && this.capabilities.maxColorTempKelvin) {
259
+ clamped = Math.max(
260
+ this.capabilities.minColorTempKelvin,
261
+ Math.min(this.capabilities.maxColorTempKelvin, kelvin)
262
+ );
263
+ }
264
+
265
+ await this.protocolClient.setColorTemp(this.entityId, clamped, transition);
266
+ this._colorTemp = clamped;
267
+ this._colorTempMireds = Math.round(1000000 / clamped);
268
+ this._isOn = true;
269
+ this.emit('state:changed', this.getState());
270
+ }
271
+
272
+ /**
273
+ * Set RGB color
274
+ * @param r Red 0-255
275
+ * @param g Green 0-255
276
+ * @param b Blue 0-255
277
+ * @param transition Optional transition time in seconds
278
+ */
279
+ public async setRgbColor(r: number, g: number, b: number, transition?: number): Promise<void> {
280
+ if (!this.capabilities.supportsRgb) {
281
+ throw new Error('Light does not support RGB color');
282
+ }
283
+
284
+ const clampedR = Math.max(0, Math.min(255, Math.round(r)));
285
+ const clampedG = Math.max(0, Math.min(255, Math.round(g)));
286
+ const clampedB = Math.max(0, Math.min(255, Math.round(b)));
287
+
288
+ await this.protocolClient.setRgbColor(this.entityId, clampedR, clampedG, clampedB, transition);
289
+ this._rgbColor = [clampedR, clampedG, clampedB];
290
+ this._isOn = true;
291
+ this.emit('state:changed', this.getState());
292
+ }
293
+
294
+ /**
295
+ * Set light effect
296
+ * @param effect Effect name from available effects
297
+ */
298
+ public async setEffect(effect: string): Promise<void> {
299
+ if (!this.capabilities.supportsEffects) {
300
+ throw new Error('Light does not support effects');
301
+ }
302
+
303
+ await this.protocolClient.setEffect(this.entityId, effect);
304
+ this._effect = effect;
305
+ this._isOn = true;
306
+ this.emit('state:changed', this.getState());
307
+ }
308
+
309
+ /**
310
+ * Get current state as object
311
+ */
312
+ public getState(): ILightState {
313
+ return {
314
+ isOn: this._isOn,
315
+ brightness: this._brightness,
316
+ colorTemp: this._colorTemp,
317
+ colorTempMireds: this._colorTempMireds,
318
+ rgbColor: this._rgbColor,
319
+ hsColor: this._hsColor,
320
+ xyColor: this._xyColor,
321
+ effect: this._effect,
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Refresh state from the device
327
+ */
328
+ public async refreshState(): Promise<ILightState> {
329
+ const state = await this.protocolClient.getState(this.entityId);
330
+ this.updateStateInternal(state);
331
+ return state;
332
+ }
333
+
334
+ /**
335
+ * Update state from external source (e.g., state change event)
336
+ */
337
+ public updateState(state: ILightState): void {
338
+ this.updateStateInternal(state);
339
+ this.emit('state:changed', state);
340
+ }
341
+
342
+ /**
343
+ * Internal state update
344
+ */
345
+ private updateStateInternal(state: ILightState): void {
346
+ this._isOn = state.isOn;
347
+ this._brightness = state.brightness;
348
+ this._colorTemp = state.colorTemp;
349
+ this._colorTempMireds = state.colorTempMireds;
350
+ this._rgbColor = state.rgbColor;
351
+ this._hsColor = state.hsColor;
352
+ this._xyColor = state.xyColor;
353
+ this._effect = state.effect;
354
+ }
355
+
356
+ // ============================================================================
357
+ // Feature Info
358
+ // ============================================================================
359
+
360
+ public getFeatureInfo(): ILightFeatureInfo {
361
+ return {
362
+ ...this.getBaseFeatureInfo(),
363
+ type: 'light',
364
+ protocol: this.protocol,
365
+ capabilities: this.capabilities,
366
+ currentState: this.getState(),
367
+ };
368
+ }
369
+ }