homebridge-switcher-laundry 1.0.0 → 1.0.3
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/README.md +27 -11
- package/config.schema.json +27 -2
- package/dist/accessory.d.ts +2 -0
- package/dist/accessory.js +19 -8
- package/dist/accessory.js.map +1 -1
- package/dist/platform.js +15 -2
- package/dist/platform.js.map +1 -1
- package/dist/switcher-power.d.ts +58 -7
- package/dist/switcher-power.js +184 -23
- package/dist/switcher-power.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Homebridge plugin that watches a **Switcher Power Plug** and tells HomeKit when a washer or dryer finishes.
|
|
4
4
|
|
|
5
|
-
Apple Home does not show live watts. Eve can. This plugin
|
|
5
|
+
Apple Home does not show live watts. Eve can. This plugin reads Switcher wattage (UDP broadcast, or TCP poll if UDP is busy) and turns power changes into sensors Home can automate.
|
|
6
6
|
|
|
7
7
|
## What you get in Home
|
|
8
8
|
|
|
9
|
-
For each configured plug:
|
|
9
|
+
For each configured plug, Home shows **one device** (named like the washer/dryer):
|
|
10
10
|
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **Clear Done** — momentary switch to close
|
|
11
|
+
- **Primary tile — Running** — occupancy; on while the machine draws sustained power
|
|
12
|
+
- **Done** (inside the device) — contact sensor that opens when a cycle finishes (use for notifications)
|
|
13
|
+
- **Clear Done** (inside the device) — momentary switch to close Done without waiting for the next cycle
|
|
14
|
+
|
|
15
|
+
This mirrors the Home Connect “appliance + sensors” layout as closely as classic HomeKit allows.
|
|
14
16
|
|
|
15
17
|
## Install
|
|
16
18
|
|
|
@@ -18,7 +20,7 @@ For each configured plug:
|
|
|
18
20
|
sudo npm install -g homebridge-switcher-laundry
|
|
19
21
|
```
|
|
20
22
|
|
|
21
|
-
Until the package is on npm
|
|
23
|
+
Until the package is on npm:
|
|
22
24
|
|
|
23
25
|
```sh
|
|
24
26
|
sudo npm install -g github:mirimalulkasif/hb-switcher-laundry
|
|
@@ -37,7 +39,8 @@ Restart Homebridge, then add **Switcher Laundry** from the Plugins page.
|
|
|
37
39
|
"devices": [
|
|
38
40
|
{
|
|
39
41
|
"name": "Washing Machine",
|
|
40
|
-
"identifier": "
|
|
42
|
+
"identifier": "192.168.68.90",
|
|
43
|
+
"deviceId": "e4eaf6",
|
|
41
44
|
"startWatts": 5,
|
|
42
45
|
"stopWatts": 2,
|
|
43
46
|
"startSeconds": 60,
|
|
@@ -51,6 +54,23 @@ Restart Homebridge, then add **Switcher Laundry** from the Plugins page.
|
|
|
51
54
|
|
|
52
55
|
`identifier` can be the Switcher device id, a static IP, or the exact device name from the Switcher app.
|
|
53
56
|
|
|
57
|
+
### Washer + dryer (two plugs)
|
|
58
|
+
|
|
59
|
+
Add **two** entries under `devices` — one per Switcher plug. Each gets its own Done / Running / Clear Done sensors in Home.
|
|
60
|
+
|
|
61
|
+
v1.0.2+ shares one UDP listener across all laundry plugs. Older builds could only hear the first plug.
|
|
62
|
+
|
|
63
|
+
### Running next to homebridge-switcher-platform
|
|
64
|
+
|
|
65
|
+
Both plugins want Switcher UDP port **20002**. If Platform is already running, Laundry falls back to **TCP polling**.
|
|
66
|
+
|
|
67
|
+
For that fallback, **every** laundry device needs:
|
|
68
|
+
|
|
69
|
+
- **`deviceId`** — hex id from Switcher Platform logs (e.g. `Found New Switcher ... | ID:e4eaf6`)
|
|
70
|
+
- **`deviceIp`** — if `identifier` is not already the plug’s IP
|
|
71
|
+
|
|
72
|
+
Missing `deviceId`/`deviceIp` on the dryer is the usual reason washer sensors move while dryer sensors stay idle.
|
|
73
|
+
|
|
54
74
|
### Calibrating thresholds
|
|
55
75
|
|
|
56
76
|
1. Run a normal wash with the plug connected.
|
|
@@ -65,8 +85,6 @@ Restart Homebridge, then add **Switcher Laundry** from the Plugins page.
|
|
|
65
85
|
2. After a real start, power stays at or below `stopWatts` for `stopSeconds` → **done**
|
|
66
86
|
3. The next sustained high-power stretch starts a new cycle and clears **done**
|
|
67
87
|
|
|
68
|
-
The plug must stay on the same LAN as Homebridge. No cloud login is required for reading wattage from Power Plug broadcasts.
|
|
69
|
-
|
|
70
88
|
## Notifications
|
|
71
89
|
|
|
72
90
|
In the Home app, create an automation:
|
|
@@ -80,8 +98,6 @@ npm install
|
|
|
80
98
|
npm test
|
|
81
99
|
```
|
|
82
100
|
|
|
83
|
-
`npm test` compiles TypeScript and runs cycle-detection and HomeKit registration tests without a physical Switcher.
|
|
84
|
-
|
|
85
101
|
## License
|
|
86
102
|
|
|
87
103
|
Apache-2.0
|
package/config.schema.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"pluginAlias": "SwitcherLaundry",
|
|
3
3
|
"pluginType": "platform",
|
|
4
4
|
"singular": true,
|
|
5
|
-
"headerDisplay": "Watches a Switcher Power Plug and exposes HomeKit sensors when a laundry cycle finishes.
|
|
5
|
+
"headerDisplay": "Watches a Switcher Power Plug and exposes HomeKit sensors when a laundry cycle finishes. Uses UDP broadcasts when free; if homebridge-switcher-platform already owns the Switcher UDP port, set deviceId so this plugin can poll watts over TCP instead.",
|
|
6
6
|
"schema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
@@ -12,6 +12,21 @@
|
|
|
12
12
|
"default": "Switcher Laundry",
|
|
13
13
|
"required": true
|
|
14
14
|
},
|
|
15
|
+
"token": {
|
|
16
|
+
"title": "Switcher token (optional)",
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "From https://switcher.co.il/GetKey/ — only needed if TCP poll login requires a token on your firmware.",
|
|
19
|
+
"x-schema-form": {
|
|
20
|
+
"type": "password"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"pollSeconds": {
|
|
24
|
+
"title": "TCP poll interval (seconds)",
|
|
25
|
+
"type": "integer",
|
|
26
|
+
"default": 10,
|
|
27
|
+
"minimum": 5,
|
|
28
|
+
"description": "Used only when UDP listen is unavailable and the plugin falls back to TCP status polling."
|
|
29
|
+
},
|
|
15
30
|
"devices": {
|
|
16
31
|
"title": "Laundry devices",
|
|
17
32
|
"type": "array",
|
|
@@ -29,7 +44,17 @@
|
|
|
29
44
|
"title": "Switcher identifier",
|
|
30
45
|
"type": "string",
|
|
31
46
|
"required": true,
|
|
32
|
-
"description": "Device id, IP address, or exact name from the Switcher app /
|
|
47
|
+
"description": "Device id, IP address, or exact name from the Switcher app / Switcher Platform logs."
|
|
48
|
+
},
|
|
49
|
+
"deviceId": {
|
|
50
|
+
"title": "Device ID (for TCP fallback)",
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Hex id from Switcher Platform logs (e.g. e4eaf6). Required for each plug if you also run homebridge-switcher-platform (UDP 20002 busy). Washer and dryer each need their own id."
|
|
53
|
+
},
|
|
54
|
+
"deviceIp": {
|
|
55
|
+
"title": "Device IP (optional)",
|
|
56
|
+
"type": "string",
|
|
57
|
+
"description": "Required with deviceId for TCP fallback when identifier is a name. Leave blank if identifier is already the plug IP."
|
|
33
58
|
},
|
|
34
59
|
"startWatts": {
|
|
35
60
|
"title": "Start threshold (W)",
|
package/dist/accessory.d.ts
CHANGED
package/dist/accessory.js
CHANGED
|
@@ -34,29 +34,40 @@ class LaundryAccessory {
|
|
|
34
34
|
this.accessory.getService(Service.AccessoryInformation)
|
|
35
35
|
?.setCharacteristic(Characteristic.Manufacturer, 'Switcher')
|
|
36
36
|
.setCharacteristic(Characteristic.Model, 'Power Plug Laundry')
|
|
37
|
-
.setCharacteristic(Characteristic.SerialNumber, device.identifier);
|
|
38
|
-
|
|
37
|
+
.setCharacteristic(Characteristic.SerialNumber, device.deviceId || device.identifier);
|
|
38
|
+
// One HomeKit device (like Home Connect): Running is the primary tile;
|
|
39
|
+
// Done + Clear Done are linked sensors inside that device.
|
|
40
|
+
this.runningService = this.accessory.getServiceById(Service.OccupancySensor, 'washer-running')
|
|
41
|
+
|| this.accessory.addService(Service.OccupancySensor, device.name, 'washer-running');
|
|
42
|
+
this.runningService.setCharacteristic(Characteristic.Name, device.name);
|
|
43
|
+
this.runningService.getCharacteristic(Characteristic.OccupancyDetected)
|
|
44
|
+
.onGet(() => this.runningOccupancy());
|
|
45
|
+
this.doneService = this.accessory.getServiceById(Service.ContactSensor, 'washer-done')
|
|
39
46
|
|| this.accessory.addService(Service.ContactSensor, `${device.name} Done`, 'washer-done');
|
|
40
47
|
this.doneService.setCharacteristic(Characteristic.Name, `${device.name} Done`);
|
|
41
48
|
this.doneService.getCharacteristic(Characteristic.ContactSensorState)
|
|
42
49
|
.onGet(() => this.doneContactState());
|
|
43
|
-
this.
|
|
44
|
-
|| this.accessory.addService(Service.OccupancySensor, `${device.name} Running`, 'washer-running');
|
|
45
|
-
this.runningService.setCharacteristic(Characteristic.Name, `${device.name} Running`);
|
|
46
|
-
this.runningService.getCharacteristic(Characteristic.OccupancyDetected)
|
|
47
|
-
.onGet(() => this.runningOccupancy());
|
|
48
|
-
this.clearService = this.accessory.getService('Clear Done')
|
|
50
|
+
this.clearService = this.accessory.getServiceById(Service.Switch, 'clear-done')
|
|
49
51
|
|| this.accessory.addService(Service.Switch, `${device.name} Clear Done`, 'clear-done');
|
|
50
52
|
this.clearService.setCharacteristic(Characteristic.Name, `${device.name} Clear Done`);
|
|
51
53
|
this.clearService.getCharacteristic(Characteristic.On)
|
|
52
54
|
.onGet(() => false)
|
|
53
55
|
.onSet((value) => this.clearDone(value));
|
|
56
|
+
this.runningService.setPrimaryService(true);
|
|
57
|
+
this.runningService.addLinkedService(this.doneService);
|
|
58
|
+
this.runningService.addLinkedService(this.clearService);
|
|
54
59
|
this.applySnapshot(this.snapshot, true);
|
|
55
60
|
this.power.on('power', (sample) => this.onPower(sample));
|
|
56
61
|
this.power.on('error', (error) => {
|
|
57
62
|
this.platform.log.error(`[${device.name}] ${error.message}`);
|
|
58
63
|
});
|
|
59
64
|
this.power.start();
|
|
65
|
+
// If no watt samples arrive, HomeKit sensors stay idle forever — surface that clearly.
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
if (this.lastWatts === 0 && this.snapshot.phase === 'idle') {
|
|
68
|
+
this.platform.log.warn(`[${device.name}] no power readings yet — check identifier/deviceId/deviceIp, or look for UDP/TCP errors above`);
|
|
69
|
+
}
|
|
70
|
+
}, 60_000);
|
|
60
71
|
}
|
|
61
72
|
stop() {
|
|
62
73
|
this.power.stop();
|
package/dist/accessory.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accessory.js","sourceRoot":"","sources":["../src/accessory.ts"],"names":[],"mappings":";;;AAEA,mCAAmE;
|
|
1
|
+
{"version":3,"file":"accessory.js","sourceRoot":"","sources":["../src/accessory.ts"],"names":[],"mappings":"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"}
|
package/dist/platform.js
CHANGED
|
@@ -14,7 +14,14 @@ class SwitcherLaundryPlatform {
|
|
|
14
14
|
cachedAccessories = [];
|
|
15
15
|
handlers = [];
|
|
16
16
|
ready = Promise.resolve();
|
|
17
|
-
constructor(log, config, api, powerFactory = (device) => new switcher_power_1.
|
|
17
|
+
constructor(log, config, api, powerFactory = (device) => new switcher_power_1.SwitcherPowerSource({
|
|
18
|
+
identifier: device.identifier,
|
|
19
|
+
deviceId: device.deviceId,
|
|
20
|
+
deviceIp: device.deviceIp,
|
|
21
|
+
token: typeof this.config.token === 'string' ? this.config.token : undefined,
|
|
22
|
+
pollSeconds: Number(this.config.pollSeconds) || 10,
|
|
23
|
+
log: (message) => this.log.debug(message),
|
|
24
|
+
})) {
|
|
18
25
|
this.log = log;
|
|
19
26
|
this.config = config;
|
|
20
27
|
this.api = api;
|
|
@@ -45,7 +52,9 @@ class SwitcherLaundryPlatform {
|
|
|
45
52
|
const uuid = this.api.hap.uuid.generate(`${settings_1.PLUGIN_NAME}:${device.identifier}`);
|
|
46
53
|
activeIds.add(uuid);
|
|
47
54
|
const existing = this.cachedAccessories.find((item) => item.UUID === uuid);
|
|
48
|
-
const
|
|
55
|
+
const category = 10 /* this.api.hap.Categories.SENSOR */;
|
|
56
|
+
const accessory = existing ?? new this.api.platformAccessory(device.name, uuid, category);
|
|
57
|
+
accessory.category = category;
|
|
49
58
|
accessory.context.identifier = device.identifier;
|
|
50
59
|
const handler = new accessory_1.LaundryAccessory(this, accessory, device, this.powerFactory(device));
|
|
51
60
|
this.handlers.push(handler);
|
|
@@ -76,9 +85,13 @@ class SwitcherLaundryPlatform {
|
|
|
76
85
|
return null;
|
|
77
86
|
}
|
|
78
87
|
const name = String(item.name ?? identifier).trim() || identifier;
|
|
88
|
+
const deviceId = String(item.deviceId ?? '').trim() || undefined;
|
|
89
|
+
const deviceIp = String(item.deviceIp ?? '').trim() || undefined;
|
|
79
90
|
return {
|
|
80
91
|
name,
|
|
81
92
|
identifier,
|
|
93
|
+
deviceId,
|
|
94
|
+
deviceIp,
|
|
82
95
|
startWatts: positiveNumber(item.startWatts, settings_1.DEFAULT_START_WATTS),
|
|
83
96
|
stopWatts: positiveNumber(item.stopWatts, settings_1.DEFAULT_STOP_WATTS),
|
|
84
97
|
startSeconds: positiveNumber(item.startSeconds, settings_1.DEFAULT_START_SECONDS),
|
package/dist/platform.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":";;;AAUA,2CAAyE;AACzE,yCAOoB;AACpB,
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
|
package/dist/switcher-power.d.ts
CHANGED
|
@@ -14,19 +14,68 @@ export interface PowerSource {
|
|
|
14
14
|
off(event: 'power', listener: (sample: PowerSample) => void): this;
|
|
15
15
|
off(event: 'error', listener: (error: Error) => void): this;
|
|
16
16
|
}
|
|
17
|
+
export interface SwitcherPowerOptions {
|
|
18
|
+
identifier: string;
|
|
19
|
+
deviceId?: string;
|
|
20
|
+
deviceIp?: string;
|
|
21
|
+
token?: string;
|
|
22
|
+
pollSeconds?: number;
|
|
23
|
+
log?: (message: string) => void;
|
|
24
|
+
}
|
|
25
|
+
interface SwitcherMessage {
|
|
26
|
+
device_id: string;
|
|
27
|
+
device_ip: string;
|
|
28
|
+
name: string;
|
|
29
|
+
type?: string;
|
|
30
|
+
state?: {
|
|
31
|
+
power?: number;
|
|
32
|
+
power_consumption?: number;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
type HubSubscriber = {
|
|
36
|
+
match: (message: SwitcherMessage) => boolean;
|
|
37
|
+
onMessage: (message: SwitcherMessage) => void;
|
|
38
|
+
onUdpFailed: (error: Error) => void;
|
|
39
|
+
};
|
|
17
40
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
41
|
+
* One UDP bind for the whole plugin. Two laundry plugs cannot each call
|
|
42
|
+
* Switcher.listen() — the second hits EADDRINUSE and never sees watts.
|
|
20
43
|
*/
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
private readonly log;
|
|
44
|
+
declare class SwitcherUdpHub {
|
|
45
|
+
private static shared?;
|
|
24
46
|
private listener?;
|
|
47
|
+
private readonly subscribers;
|
|
25
48
|
private started;
|
|
26
|
-
|
|
49
|
+
private failed;
|
|
50
|
+
private failError?;
|
|
51
|
+
static get(): SwitcherUdpHub;
|
|
52
|
+
/** Test helper — reset between unit tests. */
|
|
53
|
+
static resetForTests(): void;
|
|
54
|
+
subscribe(subscriber: HubSubscriber): void;
|
|
55
|
+
unsubscribe(subscriber: HubSubscriber): void;
|
|
56
|
+
private ensureListening;
|
|
57
|
+
private closeListener;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Prefers shared UDP broadcasts; if the Switcher UDP port is already taken
|
|
61
|
+
* (e.g. by homebridge-switcher-platform), falls back to TCP status polling.
|
|
62
|
+
*/
|
|
63
|
+
export declare class SwitcherPowerSource extends EventEmitter implements PowerSource {
|
|
64
|
+
private readonly options;
|
|
65
|
+
private pollTimer?;
|
|
66
|
+
private device?;
|
|
67
|
+
private started;
|
|
68
|
+
private readonly log;
|
|
69
|
+
private readonly pollSeconds;
|
|
70
|
+
private resolvedId?;
|
|
71
|
+
private resolvedIp?;
|
|
72
|
+
private hubSubscriber?;
|
|
73
|
+
private usingTcp;
|
|
74
|
+
constructor(options: SwitcherPowerOptions);
|
|
27
75
|
start(): void;
|
|
28
76
|
stop(): void;
|
|
29
|
-
private
|
|
77
|
+
private startTcpPoll;
|
|
78
|
+
private handleWatts;
|
|
30
79
|
}
|
|
31
80
|
/** Test double that feeds watt samples into the same PowerSource contract. */
|
|
32
81
|
export declare class FakePowerSource extends EventEmitter implements PowerSource {
|
|
@@ -34,3 +83,5 @@ export declare class FakePowerSource extends EventEmitter implements PowerSource
|
|
|
34
83
|
stop(): void;
|
|
35
84
|
push(watts: number, extras?: Partial<PowerSample>): void;
|
|
36
85
|
}
|
|
86
|
+
export declare function matchesIdentifier(identifier: string, message: SwitcherMessage): boolean;
|
|
87
|
+
export { SwitcherUdpHub };
|
package/dist/switcher-power.js
CHANGED
|
@@ -1,59 +1,205 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FakePowerSource = exports.
|
|
3
|
+
exports.SwitcherUdpHub = exports.FakePowerSource = exports.SwitcherPowerSource = void 0;
|
|
4
|
+
exports.matchesIdentifier = matchesIdentifier;
|
|
4
5
|
const node_events_1 = require("node:events");
|
|
5
6
|
// switcher-js2 ships as CommonJS without TypeScript types.
|
|
6
7
|
const Switcher = require('switcher-js2');
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* One UDP bind for the whole plugin. Two laundry plugs cannot each call
|
|
10
|
+
* Switcher.listen() — the second hits EADDRINUSE and never sees watts.
|
|
10
11
|
*/
|
|
11
|
-
class
|
|
12
|
-
|
|
13
|
-
log;
|
|
12
|
+
class SwitcherUdpHub {
|
|
13
|
+
static shared;
|
|
14
14
|
listener;
|
|
15
|
+
subscribers = new Set();
|
|
16
|
+
started = false;
|
|
17
|
+
failed = false;
|
|
18
|
+
failError;
|
|
19
|
+
static get() {
|
|
20
|
+
if (!SwitcherUdpHub.shared) {
|
|
21
|
+
SwitcherUdpHub.shared = new SwitcherUdpHub();
|
|
22
|
+
}
|
|
23
|
+
return SwitcherUdpHub.shared;
|
|
24
|
+
}
|
|
25
|
+
/** Test helper — reset between unit tests. */
|
|
26
|
+
static resetForTests() {
|
|
27
|
+
const hub = SwitcherUdpHub.shared;
|
|
28
|
+
if (hub) {
|
|
29
|
+
void hub.closeListener();
|
|
30
|
+
hub.subscribers.clear();
|
|
31
|
+
hub.started = false;
|
|
32
|
+
hub.failed = false;
|
|
33
|
+
hub.failError = undefined;
|
|
34
|
+
}
|
|
35
|
+
SwitcherUdpHub.shared = undefined;
|
|
36
|
+
}
|
|
37
|
+
subscribe(subscriber) {
|
|
38
|
+
this.subscribers.add(subscriber);
|
|
39
|
+
if (this.failed && this.failError) {
|
|
40
|
+
subscriber.onUdpFailed(this.failError);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.ensureListening();
|
|
44
|
+
}
|
|
45
|
+
unsubscribe(subscriber) {
|
|
46
|
+
this.subscribers.delete(subscriber);
|
|
47
|
+
if (this.subscribers.size === 0) {
|
|
48
|
+
void this.closeListener();
|
|
49
|
+
this.started = false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
ensureListening() {
|
|
53
|
+
if (this.started || this.failed) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.started = true;
|
|
57
|
+
try {
|
|
58
|
+
// No identifier filter — fan out to each laundry device by match().
|
|
59
|
+
this.listener = Switcher.listen((message) => {
|
|
60
|
+
// Keep library chatter at debug via first subscriber's logger if needed.
|
|
61
|
+
void message;
|
|
62
|
+
});
|
|
63
|
+
this.listener.on('error', (error) => {
|
|
64
|
+
this.failed = true;
|
|
65
|
+
this.failError = error;
|
|
66
|
+
void this.closeListener().then(() => {
|
|
67
|
+
for (const subscriber of [...this.subscribers]) {
|
|
68
|
+
subscriber.onUdpFailed(error);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
this.listener.on('message', (message) => {
|
|
73
|
+
for (const subscriber of this.subscribers) {
|
|
74
|
+
if (subscriber.match(message)) {
|
|
75
|
+
subscriber.onMessage(message);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
this.failed = true;
|
|
82
|
+
this.failError = error instanceof Error ? error : new Error(String(error));
|
|
83
|
+
for (const subscriber of [...this.subscribers]) {
|
|
84
|
+
subscriber.onUdpFailed(this.failError);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async closeListener() {
|
|
89
|
+
const closing = this.listener?.close();
|
|
90
|
+
this.listener = undefined;
|
|
91
|
+
if (closing && typeof closing.then === 'function') {
|
|
92
|
+
try {
|
|
93
|
+
await closing;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// ignore
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.SwitcherUdpHub = SwitcherUdpHub;
|
|
102
|
+
/**
|
|
103
|
+
* Prefers shared UDP broadcasts; if the Switcher UDP port is already taken
|
|
104
|
+
* (e.g. by homebridge-switcher-platform), falls back to TCP status polling.
|
|
105
|
+
*/
|
|
106
|
+
class SwitcherPowerSource extends node_events_1.EventEmitter {
|
|
107
|
+
options;
|
|
108
|
+
pollTimer;
|
|
109
|
+
device;
|
|
15
110
|
started = false;
|
|
16
|
-
|
|
111
|
+
log;
|
|
112
|
+
pollSeconds;
|
|
113
|
+
resolvedId;
|
|
114
|
+
resolvedIp;
|
|
115
|
+
hubSubscriber;
|
|
116
|
+
usingTcp = false;
|
|
117
|
+
constructor(options) {
|
|
17
118
|
super();
|
|
18
|
-
this.
|
|
19
|
-
this.log = log;
|
|
119
|
+
this.options = options;
|
|
120
|
+
this.log = options.log || (() => undefined);
|
|
121
|
+
this.pollSeconds = Math.max(5, options.pollSeconds || 10);
|
|
122
|
+
this.resolvedId = options.deviceId || (isDeviceId(options.identifier) ? options.identifier : undefined);
|
|
123
|
+
this.resolvedIp = options.deviceIp || (isIpAddress(options.identifier) ? options.identifier : undefined);
|
|
20
124
|
}
|
|
21
125
|
start() {
|
|
22
126
|
if (this.started) {
|
|
23
127
|
return;
|
|
24
128
|
}
|
|
25
129
|
this.started = true;
|
|
26
|
-
this.
|
|
27
|
-
|
|
130
|
+
this.hubSubscriber = {
|
|
131
|
+
match: (message) => matchesIdentifier(this.options.identifier, message),
|
|
132
|
+
onMessage: (message) => {
|
|
133
|
+
this.resolvedId = message.device_id;
|
|
134
|
+
this.resolvedIp = message.device_ip;
|
|
135
|
+
this.handleWatts(Number(message.state?.power_consumption), message.device_id, message.device_ip, message.name, Number(message.state?.power) === 1);
|
|
136
|
+
},
|
|
137
|
+
onUdpFailed: (error) => {
|
|
138
|
+
this.log(`UDP listen failed (${error.message}); trying TCP poll fallback`);
|
|
139
|
+
this.startTcpPoll();
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
SwitcherUdpHub.get().subscribe(this.hubSubscriber);
|
|
28
143
|
}
|
|
29
144
|
stop() {
|
|
30
145
|
if (!this.started) {
|
|
31
146
|
return;
|
|
32
147
|
}
|
|
33
148
|
this.started = false;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
149
|
+
if (this.hubSubscriber) {
|
|
150
|
+
SwitcherUdpHub.get().unsubscribe(this.hubSubscriber);
|
|
151
|
+
this.hubSubscriber = undefined;
|
|
152
|
+
}
|
|
153
|
+
if (this.pollTimer) {
|
|
154
|
+
clearInterval(this.pollTimer);
|
|
155
|
+
this.pollTimer = undefined;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
this.device?.close?.();
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// ignore
|
|
162
|
+
}
|
|
163
|
+
this.device = undefined;
|
|
164
|
+
}
|
|
165
|
+
startTcpPoll() {
|
|
166
|
+
if (this.pollTimer || this.usingTcp || !this.started) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (!this.resolvedId || !this.resolvedIp) {
|
|
170
|
+
const error = new Error(`UDP port busy for "${this.options.identifier}". Set deviceId and deviceIp on this laundry device (from Switcher Platform logs) so watts can be polled over TCP.`);
|
|
171
|
+
this.emit('error', error);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.usingTcp = true;
|
|
175
|
+
this.log(`Polling Switcher ${this.resolvedId} at ${this.resolvedIp} every ${this.pollSeconds}s over TCP`);
|
|
176
|
+
this.device = new Switcher(this.resolvedId, this.resolvedIp, (message) => this.log(message), false, 'power_plug', undefined, this.options.token);
|
|
177
|
+
const tick = () => {
|
|
178
|
+
void this.device?.status()
|
|
179
|
+
.then((status) => {
|
|
180
|
+
this.handleWatts(Number(status.power_consumption), this.resolvedId || this.options.identifier, this.resolvedIp || '0.0.0.0', this.options.identifier, Number(status.power) === 1 || status.power === 'ON');
|
|
181
|
+
})
|
|
182
|
+
.catch((error) => {
|
|
38
183
|
this.emit('error', error instanceof Error ? error : new Error(String(error)));
|
|
39
184
|
});
|
|
40
|
-
}
|
|
185
|
+
};
|
|
186
|
+
tick();
|
|
187
|
+
this.pollTimer = setInterval(tick, this.pollSeconds * 1000);
|
|
41
188
|
}
|
|
42
|
-
|
|
43
|
-
const watts = Number(message.state?.power_consumption);
|
|
189
|
+
handleWatts(watts, deviceId, deviceIp, name, on) {
|
|
44
190
|
if (!Number.isFinite(watts)) {
|
|
45
191
|
return;
|
|
46
192
|
}
|
|
47
193
|
this.emit('power', {
|
|
48
|
-
deviceId
|
|
49
|
-
deviceIp
|
|
50
|
-
name
|
|
194
|
+
deviceId,
|
|
195
|
+
deviceIp,
|
|
196
|
+
name,
|
|
51
197
|
watts,
|
|
52
|
-
on
|
|
198
|
+
on,
|
|
53
199
|
});
|
|
54
200
|
}
|
|
55
201
|
}
|
|
56
|
-
exports.
|
|
202
|
+
exports.SwitcherPowerSource = SwitcherPowerSource;
|
|
57
203
|
/** Test double that feeds watt samples into the same PowerSource contract. */
|
|
58
204
|
class FakePowerSource extends node_events_1.EventEmitter {
|
|
59
205
|
start() {
|
|
@@ -73,4 +219,19 @@ class FakePowerSource extends node_events_1.EventEmitter {
|
|
|
73
219
|
}
|
|
74
220
|
}
|
|
75
221
|
exports.FakePowerSource = FakePowerSource;
|
|
222
|
+
function matchesIdentifier(identifier, message) {
|
|
223
|
+
const needle = identifier.trim().toLowerCase();
|
|
224
|
+
if (!needle) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
return (message.device_id.toLowerCase() === needle
|
|
228
|
+
|| message.name.toLowerCase() === needle
|
|
229
|
+
|| message.device_ip === identifier.trim());
|
|
230
|
+
}
|
|
231
|
+
function isIpAddress(value) {
|
|
232
|
+
return /^\d{1,3}(?:\.\d{1,3}){3}$/.test(value);
|
|
233
|
+
}
|
|
234
|
+
function isDeviceId(value) {
|
|
235
|
+
return /^[a-f0-9]{6,}$/i.test(value);
|
|
236
|
+
}
|
|
76
237
|
//# sourceMappingURL=switcher-power.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"switcher-power.js","sourceRoot":"","sources":["../src/switcher-power.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"switcher-power.js","sourceRoot":"","sources":["../src/switcher-power.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-switcher-laundry",
|
|
3
3
|
"displayName": "Switcher Laundry",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.3",
|
|
5
5
|
"description": "Detect when a washer or dryer finishes by watching a Switcher Power Plug wattage.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "mirimalulkasif",
|