@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,202 @@
1
+ /**
2
+ * Sensor Feature
3
+ * Provides read-only state for sensors (temperature, humidity, power, etc.)
4
+ */
5
+
6
+ import { Feature, type TDeviceReference } from './feature.abstract.js';
7
+ import type { IFeatureOptions } from '../interfaces/feature.interfaces.js';
8
+ import type {
9
+ TSensorProtocol,
10
+ TSensorDeviceClass,
11
+ TSensorStateClass,
12
+ ISensorCapabilities,
13
+ ISensorState,
14
+ ISensorFeatureInfo,
15
+ ISensorProtocolClient,
16
+ } from '../interfaces/smarthome.interfaces.js';
17
+
18
+ /**
19
+ * Options for creating a SensorFeature
20
+ */
21
+ export interface ISensorFeatureOptions extends IFeatureOptions {
22
+ /** Protocol type */
23
+ protocol: TSensorProtocol;
24
+ /** Entity ID (for Home Assistant) */
25
+ entityId: string;
26
+ /** Protocol client for reading sensor state */
27
+ protocolClient: ISensorProtocolClient;
28
+ /** Device class (temperature, humidity, etc.) */
29
+ deviceClass?: TSensorDeviceClass;
30
+ /** State class (measurement, total, etc.) */
31
+ stateClass?: TSensorStateClass;
32
+ /** Unit of measurement */
33
+ unit?: string;
34
+ /** Precision (decimal places) */
35
+ precision?: number;
36
+ }
37
+
38
+ /**
39
+ * Sensor Feature - read-only state values
40
+ *
41
+ * Protocol-agnostic: works with Home Assistant, MQTT, SNMP, etc.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const sensor = device.getFeature<SensorFeature>('sensor');
46
+ * if (sensor) {
47
+ * await sensor.refreshState();
48
+ * console.log(`Temperature: ${sensor.value} ${sensor.unit}`);
49
+ * }
50
+ * ```
51
+ */
52
+ export class SensorFeature extends Feature {
53
+ public readonly type = 'sensor' as const;
54
+ public readonly protocol: TSensorProtocol;
55
+
56
+ /** Entity ID (e.g., "sensor.living_room_temperature") */
57
+ public readonly entityId: string;
58
+
59
+ /** Capabilities */
60
+ public readonly capabilities: ISensorCapabilities;
61
+
62
+ /** Current state */
63
+ protected _value: string | number | boolean = '';
64
+ protected _numericValue?: number;
65
+ protected _unit?: string;
66
+ protected _lastUpdated: Date = new Date();
67
+
68
+ /** Protocol client for reading sensor state */
69
+ private protocolClient: ISensorProtocolClient;
70
+
71
+ constructor(
72
+ device: TDeviceReference,
73
+ port: number,
74
+ options: ISensorFeatureOptions
75
+ ) {
76
+ super(device, port, options);
77
+ this.protocol = options.protocol;
78
+ this.entityId = options.entityId;
79
+ this.protocolClient = options.protocolClient;
80
+ this.capabilities = {
81
+ deviceClass: options.deviceClass,
82
+ stateClass: options.stateClass,
83
+ unit: options.unit,
84
+ precision: options.precision,
85
+ };
86
+ this._unit = options.unit;
87
+ }
88
+
89
+ // ============================================================================
90
+ // Properties
91
+ // ============================================================================
92
+
93
+ /**
94
+ * Get current value (cached)
95
+ */
96
+ public get value(): string | number | boolean {
97
+ return this._value;
98
+ }
99
+
100
+ /**
101
+ * Get numeric value if available (cached)
102
+ */
103
+ public get numericValue(): number | undefined {
104
+ return this._numericValue;
105
+ }
106
+
107
+ /**
108
+ * Get unit of measurement
109
+ */
110
+ public get unit(): string | undefined {
111
+ return this._unit;
112
+ }
113
+
114
+ /**
115
+ * Get device class
116
+ */
117
+ public get deviceClass(): TSensorDeviceClass | undefined {
118
+ return this.capabilities.deviceClass;
119
+ }
120
+
121
+ /**
122
+ * Get state class
123
+ */
124
+ public get stateClass(): TSensorStateClass | undefined {
125
+ return this.capabilities.stateClass;
126
+ }
127
+
128
+ /**
129
+ * Get last updated timestamp
130
+ */
131
+ public get lastUpdated(): Date {
132
+ return this._lastUpdated;
133
+ }
134
+
135
+ // ============================================================================
136
+ // Connection
137
+ // ============================================================================
138
+
139
+ protected async doConnect(): Promise<void> {
140
+ // Fetch initial state
141
+ try {
142
+ const state = await this.protocolClient.getState(this.entityId);
143
+ this.updateStateInternal(state);
144
+ } catch {
145
+ // Ignore errors fetching initial state
146
+ }
147
+ }
148
+
149
+ protected async doDisconnect(): Promise<void> {
150
+ // Nothing to disconnect
151
+ }
152
+
153
+ // ============================================================================
154
+ // Sensor Reading
155
+ // ============================================================================
156
+
157
+ /**
158
+ * Refresh state from the device
159
+ */
160
+ public async refreshState(): Promise<ISensorState> {
161
+ const state = await this.protocolClient.getState(this.entityId);
162
+ this.updateStateInternal(state);
163
+ return state;
164
+ }
165
+
166
+ /**
167
+ * Update state from external source (e.g., state change event)
168
+ */
169
+ public updateState(state: ISensorState): void {
170
+ this.updateStateInternal(state);
171
+ this.emit('state:changed', state);
172
+ }
173
+
174
+ /**
175
+ * Internal state update
176
+ */
177
+ private updateStateInternal(state: ISensorState): void {
178
+ this._value = state.value;
179
+ this._numericValue = state.numericValue;
180
+ this._unit = state.unit || this._unit;
181
+ this._lastUpdated = state.lastUpdated;
182
+ }
183
+
184
+ // ============================================================================
185
+ // Feature Info
186
+ // ============================================================================
187
+
188
+ public getFeatureInfo(): ISensorFeatureInfo {
189
+ return {
190
+ ...this.getBaseFeatureInfo(),
191
+ type: 'sensor',
192
+ protocol: this.protocol,
193
+ capabilities: this.capabilities,
194
+ currentState: {
195
+ value: this._value,
196
+ numericValue: this._numericValue,
197
+ unit: this._unit,
198
+ lastUpdated: this._lastUpdated,
199
+ },
200
+ };
201
+ }
202
+ }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * SNMP Feature
3
+ * Provides SNMP query capability for network management
4
+ */
5
+
6
+ import { Feature, type TDeviceReference } from './feature.abstract.js';
7
+ import type {
8
+ TSnmpVersion,
9
+ ISnmpVarbind,
10
+ ISnmpFeatureInfo,
11
+ IFeatureOptions,
12
+ } from '../interfaces/feature.interfaces.js';
13
+
14
+ /**
15
+ * Options for creating an SnmpFeature
16
+ */
17
+ export interface ISnmpFeatureOptions extends IFeatureOptions {
18
+ version?: TSnmpVersion;
19
+ community?: string;
20
+ // SNMPv3 options
21
+ username?: string;
22
+ authProtocol?: 'md5' | 'sha';
23
+ authKey?: string;
24
+ privProtocol?: 'des' | 'aes';
25
+ privKey?: string;
26
+ }
27
+
28
+ /**
29
+ * SNMP Feature - provides SNMP query capability
30
+ *
31
+ * Allows querying devices via SNMP for monitoring and management.
32
+ * Many network devices (printers, switches, UPS) support SNMP.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const snmp = device.getFeature<SnmpFeature>('snmp');
37
+ * if (snmp) {
38
+ * const sysDescr = await snmp.get('1.3.6.1.2.1.1.1.0');
39
+ * console.log(`System: ${sysDescr.value}`);
40
+ *
41
+ * const interfaces = await snmp.walk('1.3.6.1.2.1.2.2.1');
42
+ * console.log(`Found ${interfaces.length} interface entries`);
43
+ * }
44
+ * ```
45
+ */
46
+ export class SnmpFeature extends Feature {
47
+ public readonly type = 'snmp' as const;
48
+ public readonly protocol = 'snmp';
49
+
50
+ // Configuration
51
+ public readonly version: TSnmpVersion;
52
+ public readonly community: string;
53
+
54
+ // System info (populated after connect)
55
+ public sysDescr?: string;
56
+ public sysObjectId?: string;
57
+ public sysName?: string;
58
+ public sysLocation?: string;
59
+ public sysContact?: string;
60
+ public sysUpTime?: number;
61
+
62
+ constructor(
63
+ device: TDeviceReference,
64
+ port: number,
65
+ options?: ISnmpFeatureOptions
66
+ ) {
67
+ super(device, port ?? 161, options);
68
+ this.version = options?.version ?? 'v2c';
69
+ this.community = options?.community ?? 'public';
70
+ }
71
+
72
+ // ============================================================================
73
+ // Connection
74
+ // ============================================================================
75
+
76
+ protected async doConnect(): Promise<void> {
77
+ // Test connection by getting system description
78
+ try {
79
+ const result = await this.get('1.3.6.1.2.1.1.1.0'); // sysDescr
80
+ if (result) {
81
+ this.sysDescr = String(result.value);
82
+ }
83
+ } catch {
84
+ // Connection test failed
85
+ throw new Error('SNMP connection failed');
86
+ }
87
+ }
88
+
89
+ protected async doDisconnect(): Promise<void> {
90
+ // SNMP is connectionless, nothing to disconnect
91
+ }
92
+
93
+ // ============================================================================
94
+ // SNMP Operations
95
+ // ============================================================================
96
+
97
+ /**
98
+ * Get a single OID value
99
+ */
100
+ public async get(oid: string): Promise<ISnmpVarbind> {
101
+ // This would use the SNMP library to perform the query
102
+ // Placeholder implementation
103
+ return {
104
+ oid,
105
+ type: 0,
106
+ value: null,
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Get multiple OID values
112
+ */
113
+ public async getMultiple(oids: string[]): Promise<ISnmpVarbind[]> {
114
+ const results: ISnmpVarbind[] = [];
115
+ for (const oid of oids) {
116
+ results.push(await this.get(oid));
117
+ }
118
+ return results;
119
+ }
120
+
121
+ /**
122
+ * Get next OID in MIB tree
123
+ */
124
+ public async getNext(oid: string): Promise<ISnmpVarbind> {
125
+ // Placeholder
126
+ return {
127
+ oid,
128
+ type: 0,
129
+ value: null,
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Walk a subtree of the MIB
135
+ */
136
+ public async walk(baseOid: string): Promise<ISnmpVarbind[]> {
137
+ const results: ISnmpVarbind[] = [];
138
+ // This would iterate through the MIB tree using getNext
139
+ return results;
140
+ }
141
+
142
+ /**
143
+ * Bulk get (SNMPv2c/v3)
144
+ */
145
+ public async getBulk(
146
+ oids: string[],
147
+ nonRepeaters: number = 0,
148
+ maxRepetitions: number = 10
149
+ ): Promise<ISnmpVarbind[]> {
150
+ if (this.version === 'v1') {
151
+ throw new Error('getBulk not supported in SNMPv1');
152
+ }
153
+ // Placeholder
154
+ return [];
155
+ }
156
+
157
+ /**
158
+ * Set an OID value
159
+ */
160
+ public async set(oid: string, type: number, value: unknown): Promise<ISnmpVarbind> {
161
+ // Placeholder
162
+ return {
163
+ oid,
164
+ type,
165
+ value,
166
+ };
167
+ }
168
+
169
+ // ============================================================================
170
+ // System Info
171
+ // ============================================================================
172
+
173
+ /**
174
+ * Get all system info OIDs
175
+ */
176
+ public async getSystemInfo(): Promise<{
177
+ sysDescr?: string;
178
+ sysObjectId?: string;
179
+ sysName?: string;
180
+ sysLocation?: string;
181
+ sysContact?: string;
182
+ sysUpTime?: number;
183
+ }> {
184
+ const oids = [
185
+ '1.3.6.1.2.1.1.1.0', // sysDescr
186
+ '1.3.6.1.2.1.1.2.0', // sysObjectID
187
+ '1.3.6.1.2.1.1.3.0', // sysUpTime
188
+ '1.3.6.1.2.1.1.4.0', // sysContact
189
+ '1.3.6.1.2.1.1.5.0', // sysName
190
+ '1.3.6.1.2.1.1.6.0', // sysLocation
191
+ ];
192
+
193
+ const results = await this.getMultiple(oids);
194
+
195
+ return {
196
+ sysDescr: results[0]?.value as string,
197
+ sysObjectId: results[1]?.value as string,
198
+ sysUpTime: results[2]?.value as number,
199
+ sysContact: results[3]?.value as string,
200
+ sysName: results[4]?.value as string,
201
+ sysLocation: results[5]?.value as string,
202
+ };
203
+ }
204
+
205
+ // ============================================================================
206
+ // Serialization
207
+ // ============================================================================
208
+
209
+ public getFeatureInfo(): ISnmpFeatureInfo {
210
+ return {
211
+ ...this.getBaseFeatureInfo(),
212
+ type: 'snmp',
213
+ version: this.version,
214
+ community: this.version !== 'v3' ? this.community : undefined,
215
+ sysDescr: this.sysDescr,
216
+ sysName: this.sysName,
217
+ sysLocation: this.sysLocation,
218
+ };
219
+ }
220
+
221
+ // ============================================================================
222
+ // Static Factory
223
+ // ============================================================================
224
+
225
+ public static fromDiscovery(
226
+ device: TDeviceReference,
227
+ port: number,
228
+ metadata: Record<string, unknown>
229
+ ): SnmpFeature {
230
+ return new SnmpFeature(device, port ?? 161, {
231
+ version: metadata.version as TSnmpVersion ?? 'v2c',
232
+ community: metadata.community as string ?? 'public',
233
+ });
234
+ }
235
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Switch Feature
3
+ * Provides binary on/off control for smart switches, outlets, etc.
4
+ */
5
+
6
+ import { Feature, type TDeviceReference } from './feature.abstract.js';
7
+ import type { IFeatureOptions } from '../interfaces/feature.interfaces.js';
8
+ import type {
9
+ TSwitchProtocol,
10
+ ISwitchCapabilities,
11
+ ISwitchState,
12
+ ISwitchFeatureInfo,
13
+ ISwitchProtocolClient,
14
+ } from '../interfaces/smarthome.interfaces.js';
15
+
16
+ /**
17
+ * Options for creating a SwitchFeature
18
+ */
19
+ export interface ISwitchFeatureOptions extends IFeatureOptions {
20
+ /** Protocol type */
21
+ protocol: TSwitchProtocol;
22
+ /** Entity ID (for Home Assistant) */
23
+ entityId: string;
24
+ /** Protocol client for controlling the switch */
25
+ protocolClient: ISwitchProtocolClient;
26
+ /** Device class */
27
+ deviceClass?: 'outlet' | 'switch';
28
+ }
29
+
30
+ /**
31
+ * Switch Feature - binary on/off control
32
+ *
33
+ * Protocol-agnostic: works with Home Assistant, MQTT, Tasmota, Tuya, etc.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const sw = device.getFeature<SwitchFeature>('switch');
38
+ * if (sw) {
39
+ * await sw.turnOn();
40
+ * await sw.toggle();
41
+ * console.log(`Switch is ${sw.isOn ? 'on' : 'off'}`);
42
+ * }
43
+ * ```
44
+ */
45
+ export class SwitchFeature extends Feature {
46
+ public readonly type = 'switch' as const;
47
+ public readonly protocol: TSwitchProtocol;
48
+
49
+ /** Entity ID (e.g., "switch.living_room") */
50
+ public readonly entityId: string;
51
+
52
+ /** Capabilities */
53
+ public readonly capabilities: ISwitchCapabilities;
54
+
55
+ /** Current state */
56
+ protected _isOn: boolean = false;
57
+
58
+ /** Protocol client for controlling the switch */
59
+ private protocolClient: ISwitchProtocolClient;
60
+
61
+ constructor(
62
+ device: TDeviceReference,
63
+ port: number,
64
+ options: ISwitchFeatureOptions
65
+ ) {
66
+ super(device, port, options);
67
+ this.protocol = options.protocol;
68
+ this.entityId = options.entityId;
69
+ this.protocolClient = options.protocolClient;
70
+ this.capabilities = {
71
+ deviceClass: options.deviceClass,
72
+ };
73
+ }
74
+
75
+ // ============================================================================
76
+ // Properties
77
+ // ============================================================================
78
+
79
+ /**
80
+ * Get current on/off state (cached)
81
+ */
82
+ public get isOn(): boolean {
83
+ return this._isOn;
84
+ }
85
+
86
+ // ============================================================================
87
+ // Connection
88
+ // ============================================================================
89
+
90
+ protected async doConnect(): Promise<void> {
91
+ // Fetch initial state
92
+ try {
93
+ const state = await this.protocolClient.getState(this.entityId);
94
+ this._isOn = state.isOn;
95
+ } catch {
96
+ // Ignore errors fetching initial state
97
+ }
98
+ }
99
+
100
+ protected async doDisconnect(): Promise<void> {
101
+ // Nothing to disconnect
102
+ }
103
+
104
+ // ============================================================================
105
+ // Switch Control
106
+ // ============================================================================
107
+
108
+ /**
109
+ * Turn on the switch
110
+ */
111
+ public async turnOn(): Promise<void> {
112
+ await this.protocolClient.turnOn(this.entityId);
113
+ this._isOn = true;
114
+ this.emit('state:changed', { isOn: true });
115
+ }
116
+
117
+ /**
118
+ * Turn off the switch
119
+ */
120
+ public async turnOff(): Promise<void> {
121
+ await this.protocolClient.turnOff(this.entityId);
122
+ this._isOn = false;
123
+ this.emit('state:changed', { isOn: false });
124
+ }
125
+
126
+ /**
127
+ * Toggle the switch
128
+ */
129
+ public async toggle(): Promise<void> {
130
+ await this.protocolClient.toggle(this.entityId);
131
+ this._isOn = !this._isOn;
132
+ this.emit('state:changed', { isOn: this._isOn });
133
+ }
134
+
135
+ /**
136
+ * Refresh state from the device
137
+ */
138
+ public async refreshState(): Promise<ISwitchState> {
139
+ const state = await this.protocolClient.getState(this.entityId);
140
+ this._isOn = state.isOn;
141
+ return state;
142
+ }
143
+
144
+ /**
145
+ * Update state from external source (e.g., state change event)
146
+ */
147
+ public updateState(state: ISwitchState): void {
148
+ const changed = this._isOn !== state.isOn;
149
+ this._isOn = state.isOn;
150
+ if (changed) {
151
+ this.emit('state:changed', state);
152
+ }
153
+ }
154
+
155
+ // ============================================================================
156
+ // Feature Info
157
+ // ============================================================================
158
+
159
+ public getFeatureInfo(): ISwitchFeatureInfo {
160
+ return {
161
+ ...this.getBaseFeatureInfo(),
162
+ type: 'switch',
163
+ protocol: this.protocol,
164
+ capabilities: this.capabilities,
165
+ currentState: {
166
+ isOn: this._isOn,
167
+ },
168
+ };
169
+ }
170
+ }