matterbridge-homey 0.2.0 → 0.4.0
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/CHANGELOG.md +22 -0
- package/README.md +36 -15
- package/dist/features.js +13 -14
- package/dist/module.js +82 -18
- package/matterbridge-homey.config.json +1 -0
- package/matterbridge-homey.schema.json +7 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.4.0] - 2026-10-28
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- [platform]: The `newDevices` setting replaces `exposeNewDevices` of 0.3.0: new Homey devices can be added to the white list or the black list automatically, also the devices added while Matterbridge was stopped. The default applies the lists as they are and exposes new devices right away.
|
|
12
|
+
- [build]: The plugin is published to npm by a GitHub workflow when a GitHub release is created.
|
|
13
|
+
- [test]: The tests use the test utilities of Matterbridge 3.11 (`matterbridge/test-utils`).
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- [devices]: Plugs have the Lighting feature of the OnOff cluster, and smoke and CO alarms without a battery have a wired power source, as Matter 1.4 requires. Matterbridge 3.11 warned about both.
|
|
18
|
+
|
|
19
|
+
## [0.3.0] - 2026-10-28
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- [platform]: Devices added to Homey are exposed right away, without a Matterbridge restart. The new `exposeNewDevices` setting (default on) turns this off.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- [platform]: Homey device changes are applied one at a time, so a device that changes while the plugin starts or adds it isn't registered twice.
|
|
28
|
+
|
|
7
29
|
## [0.2.0] - 2026-10-18
|
|
8
30
|
|
|
9
31
|
### Added
|
package/README.md
CHANGED
|
@@ -25,19 +25,20 @@ The key is shown only once, so copy it right away.
|
|
|
25
25
|
|
|
26
26
|
## Configuration
|
|
27
27
|
|
|
28
|
-
| Setting | Description
|
|
29
|
-
| --------------------------- |
|
|
30
|
-
| `address` | Local address of the Homey, e.g. `http://192.168.1.50`. `https://192-168-1-50.homey.homeylocal.com` also works, unless your router blocks DNS rebinding.
|
|
31
|
-
| `token` | The API key.
|
|
32
|
-
| `whiteList` | Only these devices are exposed (names or Homey device IDs). Empty exposes all supported devices. The frontend offers a device picker after the first start.
|
|
33
|
-
| `blackList` | These devices are never exposed.
|
|
34
|
-
| `zoneWhiteList` | Only devices in these Homey zones are exposed, including sub-zones. Empty exposes all zones.
|
|
35
|
-
| `lightList` | These devices are exposed as lights, whatever their Homey class.
|
|
36
|
-
| `outletList` | These devices are exposed as plugs, whatever their Homey class.
|
|
37
|
-
| `capabilityBlackList` | These Homey capabilities are never exposed, e.g. `meter_power`.
|
|
38
|
-
| `deviceCapabilityBlackList` | Capabilities not exposed for one device: the device name (or ID) and its list of capabilities, e.g. `{ "Multisensor": ["alarm_occupancy"] }`.
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
28
|
+
| Setting | Description |
|
|
29
|
+
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `address` | Local address of the Homey, e.g. `http://192.168.1.50`. `https://192-168-1-50.homey.homeylocal.com` also works, unless your router blocks DNS rebinding. |
|
|
31
|
+
| `token` | The API key. |
|
|
32
|
+
| `whiteList` | Only these devices are exposed (names or Homey device IDs). Empty exposes all supported devices. The frontend offers a device picker after the first start. |
|
|
33
|
+
| `blackList` | These devices are never exposed. |
|
|
34
|
+
| `zoneWhiteList` | Only devices in these Homey zones are exposed, including sub-zones. Empty exposes all zones. |
|
|
35
|
+
| `lightList` | These devices are exposed as lights, whatever their Homey class. |
|
|
36
|
+
| `outletList` | These devices are exposed as plugs, whatever their Homey class. |
|
|
37
|
+
| `capabilityBlackList` | These Homey capabilities are never exposed, e.g. `meter_power`. |
|
|
38
|
+
| `deviceCapabilityBlackList` | Capabilities not exposed for one device: the device name (or ID) and its list of capabilities, e.g. `{ "Multisensor": ["alarm_occupancy"] }`. |
|
|
39
|
+
| `newDevices` | What happens to devices added to Homey, also while Matterbridge is stopped: `default` applies the lists as they are, `whiteList` adds them to the white list (when it isn't empty), `blackList` adds them to the black list. |
|
|
40
|
+
| `debug` | Log the homey-api debug output. |
|
|
41
|
+
| `unregisterOnShutdown` | Remove all devices from the controllers on shutdown. For development only. |
|
|
41
42
|
|
|
42
43
|
## Supported devices
|
|
43
44
|
|
|
@@ -113,7 +114,8 @@ The white and black lists accept Homey device IDs as well as names. Entries by n
|
|
|
113
114
|
|
|
114
115
|
### Limitations
|
|
115
116
|
|
|
116
|
-
- New Homey devices
|
|
117
|
+
- New Homey devices are exposed right away, unless the lists or `newDevices` keep them out, and deleted devices are removed right away. `newDevices` adds a device to a list by its name: like the other entries by name, it stops matching when the device is renamed.
|
|
118
|
+
- A new device is exposed with the capabilities Homey reports for it. Capabilities that its app adds later appear after a restart.
|
|
117
119
|
- Devices with the same name get their zone appended, e.g. `Ceiling light Kitchen`.
|
|
118
120
|
- Not supported yet: buttons and remotes, media players and TVs (only their `onoff`), window covering tilt, plant moisture, `level_aqi`.
|
|
119
121
|
|
|
@@ -137,7 +139,16 @@ Uploading a newer tarball replaces the installed version. Test builds are marked
|
|
|
137
139
|
|
|
138
140
|
## Development
|
|
139
141
|
|
|
140
|
-
The plugin follows the [Matterbridge plugin template](https://github.com/Luligu/matterbridge-plugin-template). Matterbridge must never be a dependency of the plugin:
|
|
142
|
+
The plugin follows the [Matterbridge plugin template](https://github.com/Luligu/matterbridge-plugin-template). Matterbridge must never be a dependency of the plugin: clone and build Matterbridge next to the plugin and link it, like the CI does (see the [development guide](https://matterbridge.io/README-DEV.html)):
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
git clone --depth 1 --single-branch --no-tags https://github.com/Luligu/matterbridge.git ../matterbridge
|
|
146
|
+
(cd ../matterbridge && npm install && npm run build && npm link)
|
|
147
|
+
npm install
|
|
148
|
+
npm run link
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
A Matterbridge installed with `npm install -g matterbridge` builds the plugin, but the tests and the typecheck fail: its test utilities need Vitest, which only a clone installs. Pull and rebuild the clone to update it. The tests need Matterbridge 3.11 or later.
|
|
141
152
|
|
|
142
153
|
| Script | Purpose |
|
|
143
154
|
| ----------------------- | ------------------------------- |
|
|
@@ -158,6 +169,16 @@ Source layout:
|
|
|
158
169
|
|
|
159
170
|
The tests run the platform against a Matter server node with a fake Homey. `vitest/homey.real.test.ts` runs it against your own Homey, read-only (every write is refused): put `HOMEY_ADDRESS`, `HOMEY_TOKEN` and `HOMEY_REAL_TEST=1` in `.env` (ignored by git) and run `npm run test -- vitest/homey.real.test.ts`.
|
|
160
171
|
|
|
172
|
+
### Publishing
|
|
173
|
+
|
|
174
|
+
The [Publish to npm](.github/workflows/publish.yml) workflow publishes the plugin when a GitHub release is created: it builds Matterbridge, runs the build, typecheck, lint and tests, and publishes with [npm trusted publishing](https://docs.npmjs.com/trusted-publishers), so neither an npm login nor a local Matterbridge is needed.
|
|
175
|
+
|
|
176
|
+
1. `npm version minor --no-git-tag-version --ignore-scripts` (or `patch`), and move the `[Unreleased]` changes in `CHANGELOG.md` under the new version and date.
|
|
177
|
+
2. Commit and push.
|
|
178
|
+
3. `node scripts/create-release.mjs`: creates the GitHub release from the changelog (needs the [GitHub CLI](https://cli.github.com)), which starts the workflow.
|
|
179
|
+
|
|
180
|
+
Once, before the first release from the workflow: on npmjs.com, add the trusted publisher in the settings of the package (GitHub Actions, repository `stefan-schweiger/matterbridge-homey`, workflow `publish.yml`).
|
|
181
|
+
|
|
161
182
|
## License
|
|
162
183
|
|
|
163
184
|
Apache-2.0
|
package/dist/features.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { airQualitySensor, colorTemperatureLight, contactSensor, dimmableLight, dimmablePlugInUnit, doorLock, electricalSensor, extendedColorLight, fan, humiditySensor, lightSensor, occupancySensor, onOffLight, onOffPlugInUnit, pressureSensor, rainSensor, roboticVacuumCleaner, smokeCoAlarm, temperatureSensor, thermostat, waterLeakDetector, windowCovering, } from 'matterbridge';
|
|
1
|
+
import { airQualitySensor, colorTemperatureLight, contactSensor, dimmableLight, dimmablePlugInUnit, doorLock, electricalSensor, extendedColorLight, fan, humiditySensor, lightSensor, occupancySensor, onOffLight, onOffPlugInUnit, powerSource, pressureSensor, rainSensor, roboticVacuumCleaner, smokeCoAlarm, temperatureSensor, thermostat, waterLeakDetector, windowCovering, } from 'matterbridge';
|
|
2
2
|
import { MatterbridgeRvcCleanModeServer, MatterbridgeRvcOperationalStateServer, MatterbridgeRvcRunModeServer } from 'matterbridge/devices';
|
|
3
3
|
import { AirQuality, BooleanState, CarbonDioxideConcentrationMeasurement, CarbonMonoxideConcentrationMeasurement, ColorControl, DoorLock, ElectricalEnergyMeasurement, ElectricalPowerMeasurement, FanControl, IlluminanceMeasurement, LevelControl, OccupancySensing, OnOff, Pm10ConcentrationMeasurement, Pm25ConcentrationMeasurement, PressureMeasurement, RelativeHumidityMeasurement, RvcCleanMode, RvcOperationalState, RvcRunMode, SmokeCoAlarm, TemperatureMeasurement, Thermostat, TotalVolatileOrganicCompoundsConcentrationMeasurement, WindowCovering, } from 'matterbridge/matter/clusters';
|
|
4
4
|
import { kelvinToRGB, miredToKelvin, rgbColorToHslColor, xyToHsl } from 'matterbridge/utils';
|
|
@@ -119,7 +119,7 @@ function lightFeature(device) {
|
|
|
119
119
|
deviceTypes: [deviceType],
|
|
120
120
|
clusters,
|
|
121
121
|
build: (context) => {
|
|
122
|
-
buildOnOff(context, 'onoff'
|
|
122
|
+
buildOnOff(context, 'onoff');
|
|
123
123
|
if (clusters.includes('LevelControl'))
|
|
124
124
|
buildLevelControl(context);
|
|
125
125
|
if (hasHueSaturation)
|
|
@@ -137,19 +137,16 @@ function plugFeature(device, capabilityId) {
|
|
|
137
137
|
deviceTypes: [isDimmable ? dimmablePlugInUnit : onOffPlugInUnit],
|
|
138
138
|
clusters: isDimmable ? ['OnOff', 'LevelControl'] : ['OnOff'],
|
|
139
139
|
build: (context) => {
|
|
140
|
-
buildOnOff(context, capabilityId
|
|
140
|
+
buildOnOff(context, capabilityId);
|
|
141
141
|
if (isDimmable)
|
|
142
142
|
buildLevelControl(context);
|
|
143
143
|
},
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
-
function buildOnOff(context, capabilityId
|
|
146
|
+
function buildOnOff(context, capabilityId) {
|
|
147
147
|
const { device, endpoint } = context;
|
|
148
148
|
const isOn = context.value(capabilityId) === true;
|
|
149
|
-
|
|
150
|
-
endpoint.createDefaultOnOffClusterServer(isOn);
|
|
151
|
-
else
|
|
152
|
-
endpoint.createOnOffClusterServer(isOn);
|
|
149
|
+
endpoint.createDefaultOnOffClusterServer(isOn);
|
|
153
150
|
if (hasCapability(device, capabilityId)) {
|
|
154
151
|
endpoint.addCommandHandler('on', () => context.set(capabilityId, true));
|
|
155
152
|
endpoint.addCommandHandler('off', () => context.set(capabilityId, false));
|
|
@@ -578,18 +575,17 @@ function vacuumFeature(device) {
|
|
|
578
575
|
else if (request.newMode === vacuumIdleMode)
|
|
579
576
|
stop('stopped', true);
|
|
580
577
|
});
|
|
578
|
+
const isInState = (state, ...states) => states.includes(state);
|
|
581
579
|
endpoint.addCommandHandler('RvcOperationalState.pause', ({ attributes }) => {
|
|
582
|
-
if (attributes.operationalState
|
|
580
|
+
if (isInState(attributes.operationalState, OperationalState.Running, OperationalState.SeekingCharger))
|
|
583
581
|
stop('stopped', false);
|
|
584
582
|
});
|
|
585
583
|
endpoint.addCommandHandler('RvcOperationalState.resume', ({ attributes }) => {
|
|
586
|
-
if (attributes.operationalState
|
|
584
|
+
if (isInState(attributes.operationalState, OperationalState.Paused))
|
|
587
585
|
start();
|
|
588
586
|
});
|
|
589
587
|
endpoint.addCommandHandler('RvcOperationalState.goHome', ({ attributes }) => {
|
|
590
|
-
if (attributes.operationalState
|
|
591
|
-
attributes.operationalState === OperationalState.Running ||
|
|
592
|
-
attributes.operationalState === OperationalState.Paused)
|
|
588
|
+
if (isInState(attributes.operationalState, OperationalState.Stopped, OperationalState.Running, OperationalState.Paused))
|
|
593
589
|
stop('docked', true);
|
|
594
590
|
});
|
|
595
591
|
if (cleanModeIds.length > 0) {
|
|
@@ -719,11 +715,12 @@ function booleanStateFeature(capabilityId, deviceType, toState) {
|
|
|
719
715
|
function smokeCoAlarmFeature(device) {
|
|
720
716
|
const hasSmoke = hasCapability(device, 'alarm_smoke');
|
|
721
717
|
const hasCo = hasCapability(device, 'alarm_co');
|
|
718
|
+
const hasBattery = hasCapability(device, 'measure_battery') || hasCapability(device, 'alarm_battery');
|
|
722
719
|
const alarmState = (value) => (value === true ? SmokeCoAlarm.AlarmState.Critical : SmokeCoAlarm.AlarmState.Normal);
|
|
723
720
|
return {
|
|
724
721
|
name: 'smokecoalarm',
|
|
725
722
|
capabilities: ['alarm_smoke', 'alarm_co'],
|
|
726
|
-
deviceTypes: [smokeCoAlarm],
|
|
723
|
+
deviceTypes: [smokeCoAlarm, powerSource],
|
|
727
724
|
clusters: ['SmokeCoAlarm'],
|
|
728
725
|
build: (context) => {
|
|
729
726
|
const { endpoint } = context;
|
|
@@ -733,6 +730,8 @@ function smokeCoAlarmFeature(device) {
|
|
|
733
730
|
endpoint.createSmokeOnlySmokeCOAlarmClusterServer(alarmState(context.value('alarm_smoke')));
|
|
734
731
|
else
|
|
735
732
|
endpoint.createCoOnlySmokeCOAlarmClusterServer(alarmState(context.value('alarm_co')));
|
|
733
|
+
if (!hasBattery)
|
|
734
|
+
endpoint.createDefaultPowerSourceWiredClusterServer();
|
|
736
735
|
if (hasSmoke) {
|
|
737
736
|
context.bind('alarm_smoke', async (value) => {
|
|
738
737
|
await endpoint.updateAttribute(SmokeCoAlarm, 'smokeState', alarmState(value), endpoint.log);
|
package/dist/module.js
CHANGED
|
@@ -54,6 +54,10 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
|
|
|
54
54
|
homey;
|
|
55
55
|
bridgedDevices = new Map();
|
|
56
56
|
zones = [];
|
|
57
|
+
newDeviceIds = new Set();
|
|
58
|
+
knownDeviceIds = new Set();
|
|
59
|
+
areDevicesConfigured = false;
|
|
60
|
+
deviceChanges = Promise.resolve();
|
|
57
61
|
constructor(matterbridge, log, config, homey) {
|
|
58
62
|
super(matterbridge, log, config);
|
|
59
63
|
this.config = config;
|
|
@@ -73,20 +77,20 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
|
|
|
73
77
|
this.config.outletList = isValidArray(config.outletList) ? config.outletList : [];
|
|
74
78
|
this.config.capabilityBlackList = isValidArray(config.capabilityBlackList) ? config.capabilityBlackList : [];
|
|
75
79
|
this.config.deviceCapabilityBlackList = isValidObject(config.deviceCapabilityBlackList) ? config.deviceCapabilityBlackList : {};
|
|
80
|
+
this.config.newDevices = config.newDevices === 'whiteList' || config.newDevices === 'blackList' ? config.newDevices : 'default';
|
|
76
81
|
this.config.debug ??= false;
|
|
77
82
|
this.config.unregisterOnShutdown ??= false;
|
|
78
83
|
this.homey =
|
|
79
84
|
homey ?? new HomeyApiConnection(config.address, config.token, 15000, config.debug ? (message) => this.log.debug(`[homey-api] ${message}`) : undefined);
|
|
80
85
|
this.homey.on('device.create', (device) => {
|
|
81
|
-
this.log.notice(`New Homey device ${CYAN}${device.name}${nf}
|
|
82
|
-
|
|
83
|
-
this.wssSendRestartRequired();
|
|
86
|
+
this.log.notice(`New Homey device ${CYAN}${device.name}${nf}`);
|
|
87
|
+
fireAndForget(this.serialize(async () => this.addNewDevice(device)), this.log, `addition of ${device.name}`);
|
|
84
88
|
});
|
|
85
89
|
this.homey.on('device.update', (device) => {
|
|
86
|
-
fireAndForget(this.updateDevice(device), this.log, `update of ${device.name}`);
|
|
90
|
+
fireAndForget(this.serialize(async () => this.updateDevice(device)), this.log, `update of ${device.name}`);
|
|
87
91
|
});
|
|
88
92
|
this.homey.on('device.delete', (device) => {
|
|
89
|
-
fireAndForget(this.removeDevice(device.id), this.log, `removal of ${device.id}`);
|
|
93
|
+
fireAndForget(this.serialize(async () => this.removeDevice(device.id)), this.log, `removal of ${device.id}`);
|
|
90
94
|
});
|
|
91
95
|
this.homey.on('disconnected', () => {
|
|
92
96
|
this.log.warn('Disconnected from Homey');
|
|
@@ -113,22 +117,30 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
|
|
|
113
117
|
throw new Error(`Unable to connect to Homey at ${this.config.address}: ${getErrorMessage(error)}`, { cause: error });
|
|
114
118
|
}
|
|
115
119
|
this.log.notice(`Connected to Homey ${CYAN}${this.homey.homeyId ?? ''}${nf}`);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
await this.
|
|
122
|
-
|
|
120
|
+
await this.serialize(async () => {
|
|
121
|
+
const [devices, zones] = await Promise.all([this.homey.getDevices(), this.homey.getZones()]);
|
|
122
|
+
this.zones = zones;
|
|
123
|
+
this.log.info(`Found ${CYAN}${devices.length}${nf} devices in ${CYAN}${zones.length}${nf} zones`);
|
|
124
|
+
const names = getUniqueDeviceNames(devices, zones);
|
|
125
|
+
const knownDeviceIds = await this.context?.get('knownDeviceIds');
|
|
126
|
+
for (const device of devices.toSorted((a, b) => a.name.localeCompare(b.name))) {
|
|
127
|
+
await this.addDevice(device, names.get(device.id) ?? device.name, zones, knownDeviceIds?.includes(device.id) === false);
|
|
128
|
+
this.knownDeviceIds.add(device.id);
|
|
129
|
+
}
|
|
130
|
+
await this.saveKnownDevices();
|
|
131
|
+
});
|
|
123
132
|
this.log.info(`Started platform ${idn}${this.config.name}${rs}${nf} with ${CYAN}${this.bridgedDevices.size}${nf} devices`);
|
|
124
133
|
}
|
|
125
134
|
async onConfigure() {
|
|
126
135
|
await super.onConfigure();
|
|
127
136
|
this.log.info(`Configuring platform ${idn}${this.config.name}${rs}${nf}...`);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
137
|
+
await this.serialize(async () => {
|
|
138
|
+
for (const bridgedDevice of this.bridgedDevices.values()) {
|
|
139
|
+
await bridgedDevice.applyAll();
|
|
140
|
+
bridgedDevice.subscribe();
|
|
141
|
+
}
|
|
142
|
+
this.areDevicesConfigured = true;
|
|
143
|
+
});
|
|
132
144
|
this.log.info(`Configured platform ${idn}${this.config.name}${rs}${nf}`);
|
|
133
145
|
}
|
|
134
146
|
async onChangeLoggerLevel(logLevel) {
|
|
@@ -156,7 +168,7 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
|
|
|
156
168
|
this.bridgedDevices.clear();
|
|
157
169
|
this.log.info(`Shut down platform ${idn}${this.config.name}${rs}${nf}`);
|
|
158
170
|
}
|
|
159
|
-
async addDevice(device, name, zones) {
|
|
171
|
+
async addDevice(device, name, zones, isNew = false) {
|
|
160
172
|
const serial = getSerialNumber(device.id);
|
|
161
173
|
const keys = [name, device.name, device.id, serial];
|
|
162
174
|
const isListed = (list) => keys.some((key) => list.includes(key));
|
|
@@ -176,6 +188,8 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
|
|
|
176
188
|
for (const entity of entities)
|
|
177
189
|
this.setSelectEntity(entity.name, entity.description, 'component');
|
|
178
190
|
this.setSelectDevice(serial, name, configUrl, 'hub', entities);
|
|
191
|
+
if (isNew)
|
|
192
|
+
this.listNewDevice(keys);
|
|
179
193
|
if (!this.validateDevice(keys))
|
|
180
194
|
return;
|
|
181
195
|
const excludedCapabilities = new Set([...this.config.capabilityBlackList, ...keys.flatMap((key) => this.config.deviceCapabilityBlackList[key] ?? [])]);
|
|
@@ -203,10 +217,37 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
|
|
|
203
217
|
inspectError(this.log, `Failed to register device ${name}`, error);
|
|
204
218
|
}
|
|
205
219
|
}
|
|
220
|
+
async addNewDevice(device) {
|
|
221
|
+
if (this.bridgedDevices.has(device.id))
|
|
222
|
+
return;
|
|
223
|
+
if (!this.knownDeviceIds.has(device.id)) {
|
|
224
|
+
this.knownDeviceIds.add(device.id);
|
|
225
|
+
await this.saveKnownDevices();
|
|
226
|
+
}
|
|
227
|
+
const [devices, zones] = await Promise.all([this.homey.getDevices(), this.homey.getZones()]);
|
|
228
|
+
this.zones = zones;
|
|
229
|
+
const names = getUniqueDeviceNames([...devices.filter((other) => other.id !== device.id), device], zones);
|
|
230
|
+
for (const [deviceId, bridged] of this.bridgedDevices)
|
|
231
|
+
await bridged.setName(names.get(deviceId) ?? bridged.device.name);
|
|
232
|
+
await this.addDevice(device, names.get(device.id) ?? device.name, zones, true);
|
|
233
|
+
const bridgedDevice = this.bridgedDevices.get(device.id);
|
|
234
|
+
if (!bridgedDevice) {
|
|
235
|
+
this.newDeviceIds.add(device.id);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this.newDeviceIds.delete(device.id);
|
|
239
|
+
if (!this.areDevicesConfigured)
|
|
240
|
+
return;
|
|
241
|
+
await bridgedDevice.applyAll();
|
|
242
|
+
bridgedDevice.subscribe();
|
|
243
|
+
}
|
|
206
244
|
async updateDevice(device) {
|
|
207
245
|
const bridgedDevice = this.bridgedDevices.get(device.id);
|
|
208
|
-
if (!bridgedDevice)
|
|
246
|
+
if (!bridgedDevice) {
|
|
247
|
+
if (this.newDeviceIds.has(device.id))
|
|
248
|
+
await this.addNewDevice(device);
|
|
209
249
|
return;
|
|
250
|
+
}
|
|
210
251
|
const isRenamed = device.name !== bridgedDevice.homeyName;
|
|
211
252
|
await bridgedDevice.update(device);
|
|
212
253
|
if (!isRenamed)
|
|
@@ -216,6 +257,9 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
|
|
|
216
257
|
await bridged.setName(names.get(deviceId) ?? bridged.device.name);
|
|
217
258
|
}
|
|
218
259
|
async removeDevice(deviceId) {
|
|
260
|
+
this.newDeviceIds.delete(deviceId);
|
|
261
|
+
if (this.knownDeviceIds.delete(deviceId))
|
|
262
|
+
await this.saveKnownDevices();
|
|
219
263
|
const bridgedDevice = this.bridgedDevices.get(deviceId);
|
|
220
264
|
if (!bridgedDevice)
|
|
221
265
|
return;
|
|
@@ -224,4 +268,24 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
|
|
|
224
268
|
this.bridgedDevices.delete(deviceId);
|
|
225
269
|
await this.unregisterDevice(bridgedDevice.endpoint);
|
|
226
270
|
}
|
|
271
|
+
listNewDevice(keys) {
|
|
272
|
+
const listName = this.config.newDevices;
|
|
273
|
+
if (listName === 'default' || (listName === 'whiteList' && this.config.whiteList.length === 0))
|
|
274
|
+
return;
|
|
275
|
+
const list = this.config[listName];
|
|
276
|
+
if (keys.some((key) => list.includes(key)))
|
|
277
|
+
return;
|
|
278
|
+
list.push(keys[0]);
|
|
279
|
+
this.log.notice(`Added the new device ${CYAN}${keys[0]}${nf} to the ${listName === 'whiteList' ? 'white' : 'black'} list`);
|
|
280
|
+
this.saveConfig(this.config);
|
|
281
|
+
}
|
|
282
|
+
async saveKnownDevices() {
|
|
283
|
+
await this.context?.set('knownDeviceIds', [...this.knownDeviceIds]);
|
|
284
|
+
}
|
|
285
|
+
async serialize(change) {
|
|
286
|
+
const result = this.deviceChanges.then(change);
|
|
287
|
+
this.deviceChanges = result.catch(() => {
|
|
288
|
+
});
|
|
289
|
+
await result;
|
|
290
|
+
}
|
|
227
291
|
}
|
|
@@ -104,6 +104,13 @@
|
|
|
104
104
|
"selectDeviceEntityFrom": "name"
|
|
105
105
|
}
|
|
106
106
|
},
|
|
107
|
+
"newDevices": {
|
|
108
|
+
"title": "New Devices",
|
|
109
|
+
"description": "What happens to the devices added to Homey, also while Matterbridge is stopped. default: the lists apply as they are, so with an empty white list new devices are exposed right away. whiteList: new devices are added to the white list, so they are exposed (only when the white list isn't empty). blackList: new devices are added to the black list, so they aren't exposed until you remove them from it.",
|
|
110
|
+
"type": "string",
|
|
111
|
+
"enum": ["default", "whiteList", "blackList"],
|
|
112
|
+
"default": "default"
|
|
113
|
+
},
|
|
107
114
|
"debug": {
|
|
108
115
|
"title": "Enable Debug",
|
|
109
116
|
"description": "Enable debug mode for the plugin. Only for debugging purposes, not recommended for production use.",
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge-homey",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "matterbridge-homey",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.4.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"homey-api": "3.20.0"
|