@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.
- package/.gitea/workflows/default_nottags.yaml +66 -0
- package/.gitea/workflows/default_tags.yaml +124 -0
- package/.vscode/launch.json +11 -0
- package/.vscode/settings.json +26 -0
- package/changelog.md +91 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/device/device.classes.device.d.ts +205 -0
- package/dist_ts/device/device.classes.device.js +344 -0
- package/dist_ts/devicemanager.classes.devicemanager.d.ts +210 -0
- package/dist_ts/devicemanager.classes.devicemanager.js +835 -0
- package/dist_ts/discovery/discovery.classes.homeassistant.d.ts +101 -0
- package/dist_ts/discovery/discovery.classes.homeassistant.js +313 -0
- package/dist_ts/discovery/discovery.classes.mdns.d.ts +90 -0
- package/dist_ts/discovery/discovery.classes.mdns.js +291 -0
- package/dist_ts/discovery/discovery.classes.networkscanner.d.ts +64 -0
- package/dist_ts/discovery/discovery.classes.networkscanner.js +535 -0
- package/dist_ts/discovery/discovery.classes.ssdp.d.ts +112 -0
- package/dist_ts/discovery/discovery.classes.ssdp.js +255 -0
- package/dist_ts/factories/index.d.ts +190 -0
- package/dist_ts/factories/index.js +350 -0
- package/dist_ts/features/feature.abstract.d.ts +118 -0
- package/dist_ts/features/feature.abstract.js +172 -0
- package/dist_ts/features/feature.camera.d.ts +95 -0
- package/dist_ts/features/feature.camera.js +162 -0
- package/dist_ts/features/feature.climate.d.ts +174 -0
- package/dist_ts/features/feature.climate.js +328 -0
- package/dist_ts/features/feature.cover.d.ts +121 -0
- package/dist_ts/features/feature.cover.js +217 -0
- package/dist_ts/features/feature.fan.d.ts +127 -0
- package/dist_ts/features/feature.fan.js +235 -0
- package/dist_ts/features/feature.light.d.ts +156 -0
- package/dist_ts/features/feature.light.js +296 -0
- package/dist_ts/features/feature.lock.d.ts +103 -0
- package/dist_ts/features/feature.lock.js +170 -0
- package/dist_ts/features/feature.playback.d.ts +98 -0
- package/dist_ts/features/feature.playback.js +191 -0
- package/dist_ts/features/feature.power.d.ts +79 -0
- package/dist_ts/features/feature.power.js +173 -0
- package/dist_ts/features/feature.print.d.ts +78 -0
- package/dist_ts/features/feature.print.js +226 -0
- package/dist_ts/features/feature.scan.d.ts +79 -0
- package/dist_ts/features/feature.scan.js +315 -0
- package/dist_ts/features/feature.sensor.d.ts +95 -0
- package/dist_ts/features/feature.sensor.js +147 -0
- package/dist_ts/features/feature.snmp.d.ts +88 -0
- package/dist_ts/features/feature.snmp.js +175 -0
- package/dist_ts/features/feature.switch.d.ts +75 -0
- package/dist_ts/features/feature.switch.js +126 -0
- package/dist_ts/features/feature.volume.d.ts +104 -0
- package/dist_ts/features/feature.volume.js +186 -0
- package/dist_ts/features/index.d.ts +19 -0
- package/dist_ts/features/index.js +23 -0
- package/dist_ts/helpers/helpers.iprange.d.ts +35 -0
- package/dist_ts/helpers/helpers.iprange.js +151 -0
- package/dist_ts/helpers/helpers.retry.d.ts +25 -0
- package/dist_ts/helpers/helpers.retry.js +69 -0
- package/dist_ts/index.d.ts +15 -0
- package/dist_ts/index.js +40 -0
- package/dist_ts/interfaces/feature.interfaces.d.ts +255 -0
- package/dist_ts/interfaces/feature.interfaces.js +6 -0
- package/dist_ts/interfaces/homeassistant.interfaces.d.ts +550 -0
- package/dist_ts/interfaces/homeassistant.interfaces.js +111 -0
- package/dist_ts/interfaces/index.d.ts +330 -0
- package/dist_ts/interfaces/index.js +16 -0
- package/dist_ts/interfaces/smarthome.interfaces.d.ts +282 -0
- package/dist_ts/interfaces/smarthome.interfaces.js +7 -0
- package/dist_ts/paths.d.ts +1 -0
- package/dist_ts/paths.js +3 -0
- package/dist_ts/plugins.d.ts +23 -0
- package/dist_ts/plugins.js +30 -0
- package/dist_ts/protocols/index.d.ts +12 -0
- package/dist_ts/protocols/index.js +21 -0
- package/dist_ts/protocols/protocol.escl.d.ts +58 -0
- package/dist_ts/protocols/protocol.escl.js +364 -0
- package/dist_ts/protocols/protocol.homeassistant.d.ts +248 -0
- package/dist_ts/protocols/protocol.homeassistant.js +611 -0
- package/dist_ts/protocols/protocol.ipp.d.ts +45 -0
- package/dist_ts/protocols/protocol.ipp.js +284 -0
- package/dist_ts/protocols/protocol.nut.d.ts +160 -0
- package/dist_ts/protocols/protocol.nut.js +364 -0
- package/dist_ts/protocols/protocol.sane.d.ts +104 -0
- package/dist_ts/protocols/protocol.sane.js +597 -0
- package/dist_ts/protocols/protocol.snmp.d.ts +133 -0
- package/dist_ts/protocols/protocol.snmp.js +333 -0
- package/dist_ts/protocols/protocol.upnp.d.ts +207 -0
- package/dist_ts/protocols/protocol.upnp.js +442 -0
- package/dist_ts/protocols/protocol.upssnmp.d.ts +160 -0
- package/dist_ts/protocols/protocol.upssnmp.js +263 -0
- package/npmextra.json +24 -0
- package/package.json +39 -0
- package/readme.hints.md +115 -0
- package/readme.md +624 -0
- package/test/test.ts +261 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/device/device.classes.device.ts +454 -0
- package/ts/devicemanager.classes.devicemanager.ts +1219 -0
- package/ts/discovery/discovery.classes.homeassistant.ts +376 -0
- package/ts/discovery/discovery.classes.mdns.ts +347 -0
- package/ts/discovery/discovery.classes.networkscanner.ts +648 -0
- package/ts/discovery/discovery.classes.ssdp.ts +357 -0
- package/ts/factories/index.ts +696 -0
- package/ts/features/feature.abstract.ts +251 -0
- package/ts/features/feature.camera.ts +214 -0
- package/ts/features/feature.climate.ts +407 -0
- package/ts/features/feature.cover.ts +278 -0
- package/ts/features/feature.fan.ts +296 -0
- package/ts/features/feature.light.ts +369 -0
- package/ts/features/feature.lock.ts +223 -0
- package/ts/features/feature.playback.ts +246 -0
- package/ts/features/feature.power.ts +231 -0
- package/ts/features/feature.print.ts +297 -0
- package/ts/features/feature.scan.ts +370 -0
- package/ts/features/feature.sensor.ts +202 -0
- package/ts/features/feature.snmp.ts +235 -0
- package/ts/features/feature.switch.ts +170 -0
- package/ts/features/feature.volume.ts +256 -0
- package/ts/features/index.ts +25 -0
- package/ts/helpers/helpers.iprange.ts +172 -0
- package/ts/helpers/helpers.retry.ts +100 -0
- package/ts/index.ts +165 -0
- package/ts/interfaces/feature.interfaces.ts +359 -0
- package/ts/interfaces/homeassistant.interfaces.ts +666 -0
- package/ts/interfaces/index.ts +421 -0
- package/ts/interfaces/smarthome.interfaces.ts +421 -0
- package/ts/paths.ts +5 -0
- package/ts/plugins.ts +41 -0
- package/ts/protocols/index.ts +59 -0
- package/ts/protocols/protocol.escl.ts +439 -0
- package/ts/protocols/protocol.homeassistant.ts +737 -0
- package/ts/protocols/protocol.ipp.ts +329 -0
- package/ts/protocols/protocol.nut.ts +471 -0
- package/ts/protocols/protocol.sane.ts +694 -0
- package/ts/protocols/protocol.snmp.ts +439 -0
- package/ts/protocols/protocol.upnp.ts +627 -0
- package/ts/protocols/protocol.upssnmp.ts +377 -0
- 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
|
+
}
|