@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,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switch Feature
|
|
3
|
+
* Provides binary on/off control for smart switches, outlets, etc.
|
|
4
|
+
*/
|
|
5
|
+
import { Feature } from './feature.abstract.js';
|
|
6
|
+
/**
|
|
7
|
+
* Switch Feature - binary on/off control
|
|
8
|
+
*
|
|
9
|
+
* Protocol-agnostic: works with Home Assistant, MQTT, Tasmota, Tuya, etc.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const sw = device.getFeature<SwitchFeature>('switch');
|
|
14
|
+
* if (sw) {
|
|
15
|
+
* await sw.turnOn();
|
|
16
|
+
* await sw.toggle();
|
|
17
|
+
* console.log(`Switch is ${sw.isOn ? 'on' : 'off'}`);
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export class SwitchFeature extends Feature {
|
|
22
|
+
type = 'switch';
|
|
23
|
+
protocol;
|
|
24
|
+
/** Entity ID (e.g., "switch.living_room") */
|
|
25
|
+
entityId;
|
|
26
|
+
/** Capabilities */
|
|
27
|
+
capabilities;
|
|
28
|
+
/** Current state */
|
|
29
|
+
_isOn = false;
|
|
30
|
+
/** Protocol client for controlling the switch */
|
|
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
|
+
deviceClass: options.deviceClass,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Properties
|
|
43
|
+
// ============================================================================
|
|
44
|
+
/**
|
|
45
|
+
* Get current on/off state (cached)
|
|
46
|
+
*/
|
|
47
|
+
get isOn() {
|
|
48
|
+
return this._isOn;
|
|
49
|
+
}
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// Connection
|
|
52
|
+
// ============================================================================
|
|
53
|
+
async doConnect() {
|
|
54
|
+
// Fetch initial state
|
|
55
|
+
try {
|
|
56
|
+
const state = await this.protocolClient.getState(this.entityId);
|
|
57
|
+
this._isOn = state.isOn;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Ignore errors fetching initial state
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async doDisconnect() {
|
|
64
|
+
// Nothing to disconnect
|
|
65
|
+
}
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Switch Control
|
|
68
|
+
// ============================================================================
|
|
69
|
+
/**
|
|
70
|
+
* Turn on the switch
|
|
71
|
+
*/
|
|
72
|
+
async turnOn() {
|
|
73
|
+
await this.protocolClient.turnOn(this.entityId);
|
|
74
|
+
this._isOn = true;
|
|
75
|
+
this.emit('state:changed', { isOn: true });
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Turn off the switch
|
|
79
|
+
*/
|
|
80
|
+
async turnOff() {
|
|
81
|
+
await this.protocolClient.turnOff(this.entityId);
|
|
82
|
+
this._isOn = false;
|
|
83
|
+
this.emit('state:changed', { isOn: false });
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Toggle the switch
|
|
87
|
+
*/
|
|
88
|
+
async toggle() {
|
|
89
|
+
await this.protocolClient.toggle(this.entityId);
|
|
90
|
+
this._isOn = !this._isOn;
|
|
91
|
+
this.emit('state:changed', { isOn: this._isOn });
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Refresh state from the device
|
|
95
|
+
*/
|
|
96
|
+
async refreshState() {
|
|
97
|
+
const state = await this.protocolClient.getState(this.entityId);
|
|
98
|
+
this._isOn = state.isOn;
|
|
99
|
+
return state;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Update state from external source (e.g., state change event)
|
|
103
|
+
*/
|
|
104
|
+
updateState(state) {
|
|
105
|
+
const changed = this._isOn !== state.isOn;
|
|
106
|
+
this._isOn = state.isOn;
|
|
107
|
+
if (changed) {
|
|
108
|
+
this.emit('state:changed', state);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// ============================================================================
|
|
112
|
+
// Feature Info
|
|
113
|
+
// ============================================================================
|
|
114
|
+
getFeatureInfo() {
|
|
115
|
+
return {
|
|
116
|
+
...this.getBaseFeatureInfo(),
|
|
117
|
+
type: 'switch',
|
|
118
|
+
protocol: this.protocol,
|
|
119
|
+
capabilities: this.capabilities,
|
|
120
|
+
currentState: {
|
|
121
|
+
isOn: this._isOn,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmVhdHVyZS5zd2l0Y2guanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9mZWF0dXJlcy9mZWF0dXJlLnN3aXRjaC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFFSCxPQUFPLEVBQUUsT0FBTyxFQUF5QixNQUFNLHVCQUF1QixDQUFDO0FBd0J2RTs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILE1BQU0sT0FBTyxhQUFjLFNBQVEsT0FBTztJQUN4QixJQUFJLEdBQUcsUUFBaUIsQ0FBQztJQUN6QixRQUFRLENBQWtCO0lBRTFDLDZDQUE2QztJQUM3QixRQUFRLENBQVM7SUFFakMsbUJBQW1CO0lBQ0gsWUFBWSxDQUFzQjtJQUVsRCxvQkFBb0I7SUFDVixLQUFLLEdBQVksS0FBSyxDQUFDO0lBRWpDLGlEQUFpRDtJQUN6QyxjQUFjLENBQXdCO0lBRTlDLFlBQ0UsTUFBd0IsRUFDeEIsSUFBWSxFQUNaLE9BQThCO1FBRTlCLEtBQUssQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQzdCLElBQUksQ0FBQyxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUNqQyxJQUFJLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUM7UUFDakMsSUFBSSxDQUFDLGNBQWMsR0FBRyxPQUFPLENBQUMsY0FBYyxDQUFDO1FBQzdDLElBQUksQ0FBQyxZQUFZLEdBQUc7WUFDbEIsV0FBVyxFQUFFLE9BQU8sQ0FBQyxXQUFXO1NBQ2pDLENBQUM7SUFDSixDQUFDO0lBRUQsK0VBQStFO0lBQy9FLGFBQWE7SUFDYiwrRUFBK0U7SUFFL0U7O09BRUc7SUFDSCxJQUFXLElBQUk7UUFDYixPQUFPLElBQUksQ0FBQyxLQUFLLENBQUM7SUFDcEIsQ0FBQztJQUVELCtFQUErRTtJQUMvRSxhQUFhO0lBQ2IsK0VBQStFO0lBRXJFLEtBQUssQ0FBQyxTQUFTO1FBQ3ZCLHNCQUFzQjtRQUN0QixJQUFJLENB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
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Volume Feature
|
|
3
|
+
* Provides volume control capability
|
|
4
|
+
*/
|
|
5
|
+
import { Feature, type TDeviceReference } from './feature.abstract.js';
|
|
6
|
+
import type { IVolumeFeatureInfo, IFeatureOptions } from '../interfaces/feature.interfaces.js';
|
|
7
|
+
/**
|
|
8
|
+
* Options for creating a VolumeFeature
|
|
9
|
+
*/
|
|
10
|
+
export interface IVolumeFeatureOptions extends IFeatureOptions {
|
|
11
|
+
/** Volume control protocol (usually same as device protocol) */
|
|
12
|
+
volumeProtocol?: string;
|
|
13
|
+
/** Minimum volume level */
|
|
14
|
+
minVolume?: number;
|
|
15
|
+
/** Maximum volume level */
|
|
16
|
+
maxVolume?: number;
|
|
17
|
+
/** Volume step increment */
|
|
18
|
+
volumeStep?: number;
|
|
19
|
+
/** Whether mute is supported */
|
|
20
|
+
supportsMute?: boolean;
|
|
21
|
+
/** Protocol-specific volume controller */
|
|
22
|
+
volumeController?: IVolumeController;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Interface for protocol-specific volume control
|
|
26
|
+
*/
|
|
27
|
+
export interface IVolumeController {
|
|
28
|
+
getVolume(): Promise<number>;
|
|
29
|
+
setVolume(level: number): Promise<void>;
|
|
30
|
+
getMute(): Promise<boolean>;
|
|
31
|
+
setMute(muted: boolean): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Volume Feature - provides volume control capability
|
|
35
|
+
*
|
|
36
|
+
* Separated from PlaybackFeature because some devices have volume control
|
|
37
|
+
* without playback capability (e.g., amplifiers, HDMI matrix switches).
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const volume = device.getFeature<VolumeFeature>('volume');
|
|
42
|
+
* if (volume) {
|
|
43
|
+
* const current = await volume.getVolume();
|
|
44
|
+
* await volume.setVolume(current + 10);
|
|
45
|
+
* await volume.toggleMute();
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare class VolumeFeature extends Feature {
|
|
50
|
+
readonly type: "volume";
|
|
51
|
+
readonly protocol: string;
|
|
52
|
+
readonly minVolume: number;
|
|
53
|
+
readonly maxVolume: number;
|
|
54
|
+
readonly volumeStep: number;
|
|
55
|
+
readonly supportsMute: boolean;
|
|
56
|
+
protected _volume: number;
|
|
57
|
+
protected _muted: boolean;
|
|
58
|
+
protected volumeController: IVolumeController | null;
|
|
59
|
+
constructor(device: TDeviceReference, port: number, options?: IVolumeFeatureOptions);
|
|
60
|
+
/**
|
|
61
|
+
* Get current volume level (cached)
|
|
62
|
+
*/
|
|
63
|
+
get volume(): number;
|
|
64
|
+
/**
|
|
65
|
+
* Get mute state (cached)
|
|
66
|
+
*/
|
|
67
|
+
get muted(): boolean;
|
|
68
|
+
protected doConnect(): Promise<void>;
|
|
69
|
+
protected doDisconnect(): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Get current volume level
|
|
72
|
+
*/
|
|
73
|
+
getVolume(): Promise<number>;
|
|
74
|
+
/**
|
|
75
|
+
* Set volume level
|
|
76
|
+
* @param level Volume level (clamped to min/max)
|
|
77
|
+
*/
|
|
78
|
+
setVolume(level: number): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Increase volume by step
|
|
81
|
+
*/
|
|
82
|
+
volumeUp(step?: number): Promise<number>;
|
|
83
|
+
/**
|
|
84
|
+
* Decrease volume by step
|
|
85
|
+
*/
|
|
86
|
+
volumeDown(step?: number): Promise<number>;
|
|
87
|
+
/**
|
|
88
|
+
* Get mute state
|
|
89
|
+
*/
|
|
90
|
+
getMute(): Promise<boolean>;
|
|
91
|
+
/**
|
|
92
|
+
* Set mute state
|
|
93
|
+
*/
|
|
94
|
+
setMute(muted: boolean): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Toggle mute state
|
|
97
|
+
*/
|
|
98
|
+
toggleMute(): Promise<boolean>;
|
|
99
|
+
getFeatureInfo(): IVolumeFeatureInfo;
|
|
100
|
+
/**
|
|
101
|
+
* Create from discovery metadata
|
|
102
|
+
*/
|
|
103
|
+
static fromDiscovery(device: TDeviceReference, port: number, protocol: string, metadata: Record<string, unknown>): VolumeFeature;
|
|
104
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Volume Feature
|
|
3
|
+
* Provides volume control capability
|
|
4
|
+
*/
|
|
5
|
+
import { Feature } from './feature.abstract.js';
|
|
6
|
+
/**
|
|
7
|
+
* Volume Feature - provides volume control capability
|
|
8
|
+
*
|
|
9
|
+
* Separated from PlaybackFeature because some devices have volume control
|
|
10
|
+
* without playback capability (e.g., amplifiers, HDMI matrix switches).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const volume = device.getFeature<VolumeFeature>('volume');
|
|
15
|
+
* if (volume) {
|
|
16
|
+
* const current = await volume.getVolume();
|
|
17
|
+
* await volume.setVolume(current + 10);
|
|
18
|
+
* await volume.toggleMute();
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class VolumeFeature extends Feature {
|
|
23
|
+
type = 'volume';
|
|
24
|
+
protocol;
|
|
25
|
+
// Capabilities
|
|
26
|
+
minVolume;
|
|
27
|
+
maxVolume;
|
|
28
|
+
volumeStep;
|
|
29
|
+
supportsMute;
|
|
30
|
+
// Current state
|
|
31
|
+
_volume = 0;
|
|
32
|
+
_muted = false;
|
|
33
|
+
// Volume controller (protocol-specific)
|
|
34
|
+
volumeController = null;
|
|
35
|
+
constructor(device, port, options) {
|
|
36
|
+
super(device, port, options);
|
|
37
|
+
this.protocol = options?.volumeProtocol ?? 'generic';
|
|
38
|
+
this.minVolume = options?.minVolume ?? 0;
|
|
39
|
+
this.maxVolume = options?.maxVolume ?? 100;
|
|
40
|
+
this.volumeStep = options?.volumeStep ?? 5;
|
|
41
|
+
this.supportsMute = options?.supportsMute ?? true;
|
|
42
|
+
if (options?.volumeController) {
|
|
43
|
+
this.volumeController = options.volumeController;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Properties
|
|
48
|
+
// ============================================================================
|
|
49
|
+
/**
|
|
50
|
+
* Get current volume level (cached)
|
|
51
|
+
*/
|
|
52
|
+
get volume() {
|
|
53
|
+
return this._volume;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get mute state (cached)
|
|
57
|
+
*/
|
|
58
|
+
get muted() {
|
|
59
|
+
return this._muted;
|
|
60
|
+
}
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Connection
|
|
63
|
+
// ============================================================================
|
|
64
|
+
async doConnect() {
|
|
65
|
+
// Fetch initial state if we have a controller
|
|
66
|
+
if (this.volumeController) {
|
|
67
|
+
try {
|
|
68
|
+
this._volume = await this.volumeController.getVolume();
|
|
69
|
+
this._muted = await this.volumeController.getMute();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Ignore errors fetching initial state
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async doDisconnect() {
|
|
77
|
+
// Nothing to disconnect for volume control
|
|
78
|
+
}
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Volume Control
|
|
81
|
+
// ============================================================================
|
|
82
|
+
/**
|
|
83
|
+
* Get current volume level
|
|
84
|
+
*/
|
|
85
|
+
async getVolume() {
|
|
86
|
+
if (this.volumeController) {
|
|
87
|
+
this._volume = await this.volumeController.getVolume();
|
|
88
|
+
}
|
|
89
|
+
return this._volume;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Set volume level
|
|
93
|
+
* @param level Volume level (clamped to min/max)
|
|
94
|
+
*/
|
|
95
|
+
async setVolume(level) {
|
|
96
|
+
const clampedLevel = Math.max(this.minVolume, Math.min(this.maxVolume, level));
|
|
97
|
+
if (this.volumeController) {
|
|
98
|
+
await this.volumeController.setVolume(clampedLevel);
|
|
99
|
+
}
|
|
100
|
+
const oldVolume = this._volume;
|
|
101
|
+
this._volume = clampedLevel;
|
|
102
|
+
if (oldVolume !== clampedLevel) {
|
|
103
|
+
this.emit('volume:changed', { oldVolume, newVolume: clampedLevel });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Increase volume by step
|
|
108
|
+
*/
|
|
109
|
+
async volumeUp(step) {
|
|
110
|
+
const increment = step ?? this.volumeStep;
|
|
111
|
+
const newVolume = Math.min(this.maxVolume, this._volume + increment);
|
|
112
|
+
await this.setVolume(newVolume);
|
|
113
|
+
return this._volume;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Decrease volume by step
|
|
117
|
+
*/
|
|
118
|
+
async volumeDown(step) {
|
|
119
|
+
const decrement = step ?? this.volumeStep;
|
|
120
|
+
const newVolume = Math.max(this.minVolume, this._volume - decrement);
|
|
121
|
+
await this.setVolume(newVolume);
|
|
122
|
+
return this._volume;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get mute state
|
|
126
|
+
*/
|
|
127
|
+
async getMute() {
|
|
128
|
+
if (this.volumeController && this.supportsMute) {
|
|
129
|
+
this._muted = await this.volumeController.getMute();
|
|
130
|
+
}
|
|
131
|
+
return this._muted;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Set mute state
|
|
135
|
+
*/
|
|
136
|
+
async setMute(muted) {
|
|
137
|
+
if (!this.supportsMute) {
|
|
138
|
+
throw new Error('Mute not supported');
|
|
139
|
+
}
|
|
140
|
+
if (this.volumeController) {
|
|
141
|
+
await this.volumeController.setMute(muted);
|
|
142
|
+
}
|
|
143
|
+
const oldMuted = this._muted;
|
|
144
|
+
this._muted = muted;
|
|
145
|
+
if (oldMuted !== muted) {
|
|
146
|
+
this.emit('mute:changed', { oldMuted, newMuted: muted });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Toggle mute state
|
|
151
|
+
*/
|
|
152
|
+
async toggleMute() {
|
|
153
|
+
const currentMuted = await this.getMute();
|
|
154
|
+
await this.setMute(!currentMuted);
|
|
155
|
+
return this._muted;
|
|
156
|
+
}
|
|
157
|
+
// ============================================================================
|
|
158
|
+
// Serialization
|
|
159
|
+
// ============================================================================
|
|
160
|
+
getFeatureInfo() {
|
|
161
|
+
return {
|
|
162
|
+
...this.getBaseFeatureInfo(),
|
|
163
|
+
type: 'volume',
|
|
164
|
+
minVolume: this.minVolume,
|
|
165
|
+
maxVolume: this.maxVolume,
|
|
166
|
+
volumeStep: this.volumeStep,
|
|
167
|
+
supportsMute: this.supportsMute,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// ============================================================================
|
|
171
|
+
// Static Factory
|
|
172
|
+
// ============================================================================
|
|
173
|
+
/**
|
|
174
|
+
* Create from discovery metadata
|
|
175
|
+
*/
|
|
176
|
+
static fromDiscovery(device, port, protocol, metadata) {
|
|
177
|
+
return new VolumeFeature(device, port, {
|
|
178
|
+
volumeProtocol: protocol,
|
|
179
|
+
minVolume: metadata.minVolume ?? 0,
|
|
180
|
+
maxVolume: metadata.maxVolume ?? 100,
|
|
181
|
+
volumeStep: metadata.volumeStep ?? 5,
|
|
182
|
+
supportsMute: metadata.supportsMute ?? true,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmVhdHVyZS52b2x1bWUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9mZWF0dXJlcy9mZWF0dXJlLnZvbHVtZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFFSCxPQUFPLEVBQUUsT0FBTyxFQUF5QixNQUFNLHVCQUF1QixDQUFDO0FBa0N2RTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCxNQUFNLE9BQU8sYUFBYyxTQUFRLE9BQU87SUFDeEIsSUFBSSxHQUFHLFFBQWlCLENBQUM7SUFDekIsUUFBUSxDQUFTO0lBRWpDLGVBQWU7SUFDQyxTQUFTLENBQVM7SUFDbEIsU0FBUyxDQUFTO0lBQ2xCLFVBQVUsQ0FBUztJQUNuQixZQUFZLENBQVU7SUFFdEMsZ0JBQWdCO0lBQ04sT0FBTyxHQUFXLENBQUMsQ0FBQztJQUNwQixNQUFNLEdBQVksS0FBSyxDQUFDO0lBRWxDLHdDQUF3QztJQUM5QixnQkFBZ0IsR0FBNkIsSUFBSSxDQUFDO0lBRTVELFlBQ0UsTUFBd0IsRUFDeEIsSUFBWSxFQUNaLE9BQStCO1FBRS9CLEtBQUssQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQzdCLElBQUksQ0FBQyxRQUFRLEdBQUcsT0FBTyxFQUFFLGNBQWMsSUFBSSxTQUFTLENBQUM7UUFDckQsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLEVBQUUsU0FBUyxJQUFJLENBQUMsQ0FBQztRQUN6QyxJQUFJLENBQUMsU0FBUyxHQUFHLE9BQU8sRUFBRSxTQUFTLElBQUksR0FBRyxDQUFDO1FBQzNDLElBQUksQ0FBQyxVQUFVLEdBQUcsT0FBTyxFQUFFLFVBQVUsSUFBSSxDQUFDLENBQUM7UUFDM0MsSUFBSSxDQUFDLFlBQVksR0FBRyxPQUFPLEVBQUUsWUFBWSxJQUFJLElBQUksQ0FBQztRQUVsRCxJQUFJLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxDQUFDO1lBQzlCLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxP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
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Features Module
|
|
3
|
+
* Exports all feature classes and types
|
|
4
|
+
*/
|
|
5
|
+
export { Feature, type TDeviceReference } from './feature.abstract.js';
|
|
6
|
+
export { ScanFeature, type IScanFeatureOptions } from './feature.scan.js';
|
|
7
|
+
export { PrintFeature, type IPrintFeatureOptions } from './feature.print.js';
|
|
8
|
+
export { PlaybackFeature, type IPlaybackFeatureOptions } from './feature.playback.js';
|
|
9
|
+
export { VolumeFeature, type IVolumeFeatureOptions, type IVolumeController } from './feature.volume.js';
|
|
10
|
+
export { PowerFeature, type IPowerFeatureOptions } from './feature.power.js';
|
|
11
|
+
export { SnmpFeature, type ISnmpFeatureOptions } from './feature.snmp.js';
|
|
12
|
+
export { SwitchFeature, type ISwitchFeatureOptions } from './feature.switch.js';
|
|
13
|
+
export { SensorFeature, type ISensorFeatureOptions } from './feature.sensor.js';
|
|
14
|
+
export { LightFeature, type ILightFeatureOptions } from './feature.light.js';
|
|
15
|
+
export { CoverFeature, type ICoverFeatureOptions } from './feature.cover.js';
|
|
16
|
+
export { LockFeature, type ILockFeatureOptions } from './feature.lock.js';
|
|
17
|
+
export { FanFeature, type IFanFeatureOptions } from './feature.fan.js';
|
|
18
|
+
export { ClimateFeature, type IClimateFeatureOptions } from './feature.climate.js';
|
|
19
|
+
export { CameraFeature, type ICameraFeatureOptions } from './feature.camera.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Features Module
|
|
3
|
+
* Exports all feature classes and types
|
|
4
|
+
*/
|
|
5
|
+
// Abstract base
|
|
6
|
+
export { Feature } from './feature.abstract.js';
|
|
7
|
+
// Concrete features - Document/Infrastructure
|
|
8
|
+
export { ScanFeature } from './feature.scan.js';
|
|
9
|
+
export { PrintFeature } from './feature.print.js';
|
|
10
|
+
export { PlaybackFeature } from './feature.playback.js';
|
|
11
|
+
export { VolumeFeature } from './feature.volume.js';
|
|
12
|
+
export { PowerFeature } from './feature.power.js';
|
|
13
|
+
export { SnmpFeature } from './feature.snmp.js';
|
|
14
|
+
// Smart Home Features (protocol-agnostic: home-assistant, hue, mqtt, etc.)
|
|
15
|
+
export { SwitchFeature } from './feature.switch.js';
|
|
16
|
+
export { SensorFeature } from './feature.sensor.js';
|
|
17
|
+
export { LightFeature } from './feature.light.js';
|
|
18
|
+
export { CoverFeature } from './feature.cover.js';
|
|
19
|
+
export { LockFeature } from './feature.lock.js';
|
|
20
|
+
export { FanFeature } from './feature.fan.js';
|
|
21
|
+
export { ClimateFeature } from './feature.climate.js';
|
|
22
|
+
export { CameraFeature } from './feature.camera.js';
|
|
23
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9mZWF0dXJlcy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFFSCxnQkFBZ0I7QUFDaEIsT0FBTyxFQUFFLE9BQU8sRUFBeUIsTUFBTSx1QkFBdUIsQ0FBQztBQUV2RSw4Q0FBOEM7QUFDOUMsT0FBTyxFQUFFLFdBQVcsRUFBNEIsTUFBTSxtQkFBbUIsQ0FBQztBQUMxRSxPQUFPLEVBQUUsWUFBWSxFQUE2QixNQUFNLG9CQUFvQixDQUFDO0FBQzdFLE9BQU8sRUFBRSxlQUFlLEVBQWdDLE1BQU0sdUJBQXVCLENBQUM7QUFDdEYsT0FBTyxFQUFFLGFBQWEsRUFBc0QsTUFBTSxxQkFBcUIsQ0FBQztBQUN4RyxPQUFPLEVBQUUsWUFBWSxFQUE2QixNQUFNLG9CQUFvQixDQUFDO0FBQzdFLE9BQU8sRUFBRSxXQUFXLEVBQTRCLE1BQU0sbUJBQW1CLENBQUM7QUFFMUUsMkVBQTJFO0FBQzNFLE9BQU8sRUFBRSxhQUFhLEVBQThCLE1BQU0scUJBQXFCLENBQUM7QUFDaEYsT0FBTyxFQUFFLGFBQWEsRUFBOEIsTUFBTSxxQkFBcUIsQ0FBQztBQUNoRixPQUFPLEVBQUUsWUFBWSxFQUE2QixNQUFNLG9CQUFvQixDQUFDO0FBQzdFLE9BQU8sRUFBRSxZQUFZLEVBQTZCLE1BQU0sb0JBQW9CLENBQUM7QUFDN0UsT0FBTyxFQUFFLFdBQVcsRUFBNEIsTUFBTSxtQkFBbUIsQ0FBQztBQUMxRSxPQUFPLEVBQUUsVUFBVSxFQUEyQixNQUFNLGtCQUFrQixDQUFDO0FBQ3ZFLE9BQU8sRUFBRSxjQUFjLEVBQStCLE1BQU0sc0JBQXNCLENBQUM7QUFDbkYsT0FBTyxFQUFFLGFBQWEsRUFBOEIsTUFBTSxxQkFBcUIsQ0FBQyJ9
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP Range utility functions for network scanning
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Validates an IPv4 address
|
|
6
|
+
*/
|
|
7
|
+
export declare function isValidIp(ip: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Converts an IPv4 address to a 32-bit number
|
|
10
|
+
*/
|
|
11
|
+
export declare function ipToNumber(ip: string): number;
|
|
12
|
+
/**
|
|
13
|
+
* Converts a 32-bit number to an IPv4 address
|
|
14
|
+
*/
|
|
15
|
+
export declare function numberToIp(num: number): string;
|
|
16
|
+
/**
|
|
17
|
+
* Generates an array of IP addresses from a start to end range
|
|
18
|
+
* Excludes network address (.0) and broadcast address (.255) for /24 ranges
|
|
19
|
+
*/
|
|
20
|
+
export declare function ipRangeToIps(startIp: string, endIp: string): string[];
|
|
21
|
+
/**
|
|
22
|
+
* Parses CIDR notation and returns an array of usable host IPs
|
|
23
|
+
* Excludes network address and broadcast address
|
|
24
|
+
* Example: "192.168.1.0/24" returns 192.168.1.1 through 192.168.1.254
|
|
25
|
+
*/
|
|
26
|
+
export declare function cidrToIps(cidr: string): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Gets the local network interfaces and returns the first non-loopback IPv4 subnet
|
|
29
|
+
* Returns CIDR notation (e.g., "192.168.1.0/24")
|
|
30
|
+
*/
|
|
31
|
+
export declare function getLocalSubnet(): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Counts the number of IPs in a CIDR range (excluding network and broadcast)
|
|
34
|
+
*/
|
|
35
|
+
export declare function countIpsInCidr(cidr: string): number;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP Range utility functions for network scanning
|
|
3
|
+
*/
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
/**
|
|
6
|
+
* Validates an IPv4 address
|
|
7
|
+
*/
|
|
8
|
+
export function isValidIp(ip) {
|
|
9
|
+
const parts = ip.split('.');
|
|
10
|
+
if (parts.length !== 4)
|
|
11
|
+
return false;
|
|
12
|
+
return parts.every((part) => {
|
|
13
|
+
const num = parseInt(part, 10);
|
|
14
|
+
return !isNaN(num) && num >= 0 && num <= 255 && part === num.toString();
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Converts an IPv4 address to a 32-bit number
|
|
19
|
+
*/
|
|
20
|
+
export function ipToNumber(ip) {
|
|
21
|
+
const parts = ip.split('.').map((p) => parseInt(p, 10));
|
|
22
|
+
return ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Converts a 32-bit number to an IPv4 address
|
|
26
|
+
*/
|
|
27
|
+
export function numberToIp(num) {
|
|
28
|
+
return [
|
|
29
|
+
(num >>> 24) & 255,
|
|
30
|
+
(num >>> 16) & 255,
|
|
31
|
+
(num >>> 8) & 255,
|
|
32
|
+
num & 255,
|
|
33
|
+
].join('.');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Generates an array of IP addresses from a start to end range
|
|
37
|
+
* Excludes network address (.0) and broadcast address (.255) for /24 ranges
|
|
38
|
+
*/
|
|
39
|
+
export function ipRangeToIps(startIp, endIp) {
|
|
40
|
+
if (!isValidIp(startIp) || !isValidIp(endIp)) {
|
|
41
|
+
throw new Error(`Invalid IP address: ${!isValidIp(startIp) ? startIp : endIp}`);
|
|
42
|
+
}
|
|
43
|
+
const start = ipToNumber(startIp);
|
|
44
|
+
const end = ipToNumber(endIp);
|
|
45
|
+
if (start > end) {
|
|
46
|
+
throw new Error(`Start IP (${startIp}) must be less than or equal to end IP (${endIp})`);
|
|
47
|
+
}
|
|
48
|
+
const ips = [];
|
|
49
|
+
for (let i = start; i <= end; i++) {
|
|
50
|
+
ips.push(numberToIp(i));
|
|
51
|
+
}
|
|
52
|
+
return ips;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Parses CIDR notation and returns an array of usable host IPs
|
|
56
|
+
* Excludes network address and broadcast address
|
|
57
|
+
* Example: "192.168.1.0/24" returns 192.168.1.1 through 192.168.1.254
|
|
58
|
+
*/
|
|
59
|
+
export function cidrToIps(cidr) {
|
|
60
|
+
const match = cidr.match(/^(\d+\.\d+\.\d+\.\d+)\/(\d+)$/);
|
|
61
|
+
if (!match) {
|
|
62
|
+
throw new Error(`Invalid CIDR notation: ${cidr}`);
|
|
63
|
+
}
|
|
64
|
+
const [, networkIp, prefixStr] = match;
|
|
65
|
+
const prefix = parseInt(prefixStr, 10);
|
|
66
|
+
if (!isValidIp(networkIp)) {
|
|
67
|
+
throw new Error(`Invalid network address: ${networkIp}`);
|
|
68
|
+
}
|
|
69
|
+
if (prefix < 0 || prefix > 32) {
|
|
70
|
+
throw new Error(`Invalid prefix length: ${prefix}`);
|
|
71
|
+
}
|
|
72
|
+
// For /32, just return the single IP
|
|
73
|
+
if (prefix === 32) {
|
|
74
|
+
return [networkIp];
|
|
75
|
+
}
|
|
76
|
+
// For /31 (point-to-point), return both IPs
|
|
77
|
+
if (prefix === 31) {
|
|
78
|
+
const networkNum = ipToNumber(networkIp);
|
|
79
|
+
const mask = (0xffffffff << (32 - prefix)) >>> 0;
|
|
80
|
+
const network = (networkNum & mask) >>> 0;
|
|
81
|
+
return [numberToIp(network), numberToIp(network + 1)];
|
|
82
|
+
}
|
|
83
|
+
// Calculate network and broadcast addresses
|
|
84
|
+
const networkNum = ipToNumber(networkIp);
|
|
85
|
+
const mask = (0xffffffff << (32 - prefix)) >>> 0;
|
|
86
|
+
const network = (networkNum & mask) >>> 0;
|
|
87
|
+
const broadcast = (network | (~mask >>> 0)) >>> 0;
|
|
88
|
+
// Generate usable host IPs (exclude network and broadcast)
|
|
89
|
+
const ips = [];
|
|
90
|
+
for (let i = network + 1; i < broadcast; i++) {
|
|
91
|
+
ips.push(numberToIp(i));
|
|
92
|
+
}
|
|
93
|
+
return ips;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Gets the local network interfaces and returns the first non-loopback IPv4 subnet
|
|
97
|
+
* Returns CIDR notation (e.g., "192.168.1.0/24")
|
|
98
|
+
*/
|
|
99
|
+
export function getLocalSubnet() {
|
|
100
|
+
try {
|
|
101
|
+
const interfaces = os.networkInterfaces();
|
|
102
|
+
for (const name of Object.keys(interfaces)) {
|
|
103
|
+
const iface = interfaces[name];
|
|
104
|
+
if (!iface)
|
|
105
|
+
continue;
|
|
106
|
+
for (const info of iface) {
|
|
107
|
+
// Skip loopback and non-IPv4
|
|
108
|
+
if (info.family !== 'IPv4' || info.internal)
|
|
109
|
+
continue;
|
|
110
|
+
// Calculate the network address from IP and netmask
|
|
111
|
+
const ip = ipToNumber(info.address);
|
|
112
|
+
const mask = ipToNumber(info.netmask);
|
|
113
|
+
const network = (ip & mask) >>> 0;
|
|
114
|
+
// Calculate prefix length from netmask
|
|
115
|
+
const maskBits = info.netmask.split('.').reduce((acc, octet) => {
|
|
116
|
+
const byte = parseInt(octet, 10);
|
|
117
|
+
let bits = 0;
|
|
118
|
+
for (let i = 7; i >= 0; i--) {
|
|
119
|
+
if ((byte >> i) & 1)
|
|
120
|
+
bits++;
|
|
121
|
+
else
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
return acc + bits;
|
|
125
|
+
}, 0);
|
|
126
|
+
return `${numberToIp(network)}/${maskBits}`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Failed to get network interfaces
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Counts the number of IPs in a CIDR range (excluding network and broadcast)
|
|
137
|
+
*/
|
|
138
|
+
export function countIpsInCidr(cidr) {
|
|
139
|
+
const match = cidr.match(/^(\d+\.\d+\.\d+\.\d+)\/(\d+)$/);
|
|
140
|
+
if (!match) {
|
|
141
|
+
throw new Error(`Invalid CIDR notation: ${cidr}`);
|
|
142
|
+
}
|
|
143
|
+
const prefix = parseInt(match[2], 10);
|
|
144
|
+
if (prefix === 32)
|
|
145
|
+
return 1;
|
|
146
|
+
if (prefix === 31)
|
|
147
|
+
return 2;
|
|
148
|
+
// 2^(32-prefix) - 2 (minus network and broadcast)
|
|
149
|
+
return Math.pow(2, 32 - prefix) - 2;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVscGVycy5pcHJhbmdlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvaGVscGVycy9oZWxwZXJzLmlwcmFuZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxPQUFPLEtBQUssRUFBRSxNQUFNLElBQUksQ0FBQztBQUV6Qjs7R0FFRztBQUNILE1BQU0sVUFBVSxTQUFTLENBQUMsRUFBVTtJQUNsQyxNQUFNLEtBQUssR0FBRyxFQUFFLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQzVCLElBQUksS0FBSyxDQUFDLE1BQU0sS0FBSyxDQUFDO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFFckMsT0FBTyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUU7UUFDMUIsTUFBTSxHQUFHLEdBQUcsUUFBUSxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsQ0FBQztRQUMvQixPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLEdBQUcsSUFBSSxJQUFJLEtBQUssR0FBRyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQzFFLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVEOztHQUVHO0FBQ0gsTUFBTSxVQUFVLFVBQVUsQ0FBQyxFQUFVO0lBQ25DLE1BQU0sS0FBSyxHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDeEQsT0FBTyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FB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
|