matterbridge-homey 0.2.0 → 0.3.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 CHANGED
@@ -4,6 +4,16 @@ 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.3.0] - 2026-10-28
8
+
9
+ ### Added
10
+
11
+ - [platform]: Devices added to Homey are exposed right away, without a Matterbridge restart. The new `exposeNewDevices` setting (default on) turns this off.
12
+
13
+ ### Fixed
14
+
15
+ - [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.
16
+
7
17
  ## [0.2.0] - 2026-10-18
8
18
 
9
19
  ### Added
package/README.md CHANGED
@@ -36,6 +36,7 @@ The key is shown only once, so copy it right away.
36
36
  | `outletList` | These devices are exposed as plugs, whatever their Homey class. |
37
37
  | `capabilityBlackList` | These Homey capabilities are never exposed, e.g. `meter_power`. |
38
38
  | `deviceCapabilityBlackList` | Capabilities not exposed for one device: the device name (or ID) and its list of capabilities, e.g. `{ "Multisensor": ["alarm_occupancy"] }`. |
39
+ | `exposeNewDevices` | Expose devices added to Homey right away, without a restart (default on). The lists above still apply. When off, new devices are exposed after a restart. |
39
40
  | `debug` | Log the homey-api debug output. |
40
41
  | `unregisterOnShutdown` | Remove all devices from the controllers on shutdown. For development only. |
41
42
 
@@ -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 need a Matterbridge restart; the frontend asks for it. Deleted devices are removed right away.
117
+ - New Homey devices are exposed right away and deleted devices are removed right away. With `exposeNewDevices` off, new devices need a Matterbridge restart; the frontend asks for it. To keep new devices out entirely, use the white list.
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
 
package/dist/module.js CHANGED
@@ -54,6 +54,9 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
54
54
  homey;
55
55
  bridgedDevices = new Map();
56
56
  zones = [];
57
+ newDeviceIds = new Set();
58
+ areDevicesConfigured = false;
59
+ deviceChanges = Promise.resolve();
57
60
  constructor(matterbridge, log, config, homey) {
58
61
  super(matterbridge, log, config);
59
62
  this.config = config;
@@ -73,20 +76,26 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
73
76
  this.config.outletList = isValidArray(config.outletList) ? config.outletList : [];
74
77
  this.config.capabilityBlackList = isValidArray(config.capabilityBlackList) ? config.capabilityBlackList : [];
75
78
  this.config.deviceCapabilityBlackList = isValidObject(config.deviceCapabilityBlackList) ? config.deviceCapabilityBlackList : {};
79
+ this.config.exposeNewDevices ??= true;
76
80
  this.config.debug ??= false;
77
81
  this.config.unregisterOnShutdown ??= false;
78
82
  this.homey =
79
83
  homey ?? new HomeyApiConnection(config.address, config.token, 15000, config.debug ? (message) => this.log.debug(`[homey-api] ${message}`) : undefined);
80
84
  this.homey.on('device.create', (device) => {
81
- this.log.notice(`New Homey device ${CYAN}${device.name}${nf}: restart Matterbridge to expose it`);
82
- if (this.isConfigured)
83
- this.wssSendRestartRequired();
85
+ if (!this.config.exposeNewDevices) {
86
+ this.log.notice(`New Homey device ${CYAN}${device.name}${nf}: restart Matterbridge to expose it`);
87
+ if (this.isConfigured)
88
+ this.wssSendRestartRequired();
89
+ return;
90
+ }
91
+ this.log.notice(`New Homey device ${CYAN}${device.name}${nf}`);
92
+ fireAndForget(this.serialize(async () => this.addNewDevice(device)), this.log, `addition of ${device.name}`);
84
93
  });
85
94
  this.homey.on('device.update', (device) => {
86
- fireAndForget(this.updateDevice(device), this.log, `update of ${device.name}`);
95
+ fireAndForget(this.serialize(async () => this.updateDevice(device)), this.log, `update of ${device.name}`);
87
96
  });
88
97
  this.homey.on('device.delete', (device) => {
89
- fireAndForget(this.removeDevice(device.id), this.log, `removal of ${device.id}`);
98
+ fireAndForget(this.serialize(async () => this.removeDevice(device.id)), this.log, `removal of ${device.id}`);
90
99
  });
91
100
  this.homey.on('disconnected', () => {
92
101
  this.log.warn('Disconnected from Homey');
@@ -113,22 +122,27 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
113
122
  throw new Error(`Unable to connect to Homey at ${this.config.address}: ${getErrorMessage(error)}`, { cause: error });
114
123
  }
115
124
  this.log.notice(`Connected to Homey ${CYAN}${this.homey.homeyId ?? ''}${nf}`);
116
- const [devices, zones] = await Promise.all([this.homey.getDevices(), this.homey.getZones()]);
117
- this.zones = zones;
118
- this.log.info(`Found ${CYAN}${devices.length}${nf} devices in ${CYAN}${zones.length}${nf} zones`);
119
- const names = getUniqueDeviceNames(devices, zones);
120
- for (const device of devices.toSorted((a, b) => a.name.localeCompare(b.name))) {
121
- await this.addDevice(device, names.get(device.id) ?? device.name, zones);
122
- }
125
+ await this.serialize(async () => {
126
+ const [devices, zones] = await Promise.all([this.homey.getDevices(), this.homey.getZones()]);
127
+ this.zones = zones;
128
+ this.log.info(`Found ${CYAN}${devices.length}${nf} devices in ${CYAN}${zones.length}${nf} zones`);
129
+ const names = getUniqueDeviceNames(devices, zones);
130
+ for (const device of devices.toSorted((a, b) => a.name.localeCompare(b.name))) {
131
+ await this.addDevice(device, names.get(device.id) ?? device.name, zones);
132
+ }
133
+ });
123
134
  this.log.info(`Started platform ${idn}${this.config.name}${rs}${nf} with ${CYAN}${this.bridgedDevices.size}${nf} devices`);
124
135
  }
125
136
  async onConfigure() {
126
137
  await super.onConfigure();
127
138
  this.log.info(`Configuring platform ${idn}${this.config.name}${rs}${nf}...`);
128
- for (const bridgedDevice of this.bridgedDevices.values()) {
129
- await bridgedDevice.applyAll();
130
- bridgedDevice.subscribe();
131
- }
139
+ await this.serialize(async () => {
140
+ for (const bridgedDevice of this.bridgedDevices.values()) {
141
+ await bridgedDevice.applyAll();
142
+ bridgedDevice.subscribe();
143
+ }
144
+ this.areDevicesConfigured = true;
145
+ });
132
146
  this.log.info(`Configured platform ${idn}${this.config.name}${rs}${nf}`);
133
147
  }
134
148
  async onChangeLoggerLevel(logLevel) {
@@ -203,10 +217,33 @@ 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
+ const [devices, zones] = await Promise.all([this.homey.getDevices(), this.homey.getZones()]);
224
+ this.zones = zones;
225
+ const names = getUniqueDeviceNames([...devices.filter((other) => other.id !== device.id), device], zones);
226
+ for (const [deviceId, bridged] of this.bridgedDevices)
227
+ await bridged.setName(names.get(deviceId) ?? bridged.device.name);
228
+ await this.addDevice(device, names.get(device.id) ?? device.name, zones);
229
+ const bridgedDevice = this.bridgedDevices.get(device.id);
230
+ if (!bridgedDevice) {
231
+ this.newDeviceIds.add(device.id);
232
+ return;
233
+ }
234
+ this.newDeviceIds.delete(device.id);
235
+ if (!this.areDevicesConfigured)
236
+ return;
237
+ await bridgedDevice.applyAll();
238
+ bridgedDevice.subscribe();
239
+ }
206
240
  async updateDevice(device) {
207
241
  const bridgedDevice = this.bridgedDevices.get(device.id);
208
- if (!bridgedDevice)
242
+ if (!bridgedDevice) {
243
+ if (this.newDeviceIds.has(device.id))
244
+ await this.addNewDevice(device);
209
245
  return;
246
+ }
210
247
  const isRenamed = device.name !== bridgedDevice.homeyName;
211
248
  await bridgedDevice.update(device);
212
249
  if (!isRenamed)
@@ -216,6 +253,7 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
216
253
  await bridged.setName(names.get(deviceId) ?? bridged.device.name);
217
254
  }
218
255
  async removeDevice(deviceId) {
256
+ this.newDeviceIds.delete(deviceId);
219
257
  const bridgedDevice = this.bridgedDevices.get(deviceId);
220
258
  if (!bridgedDevice)
221
259
  return;
@@ -224,4 +262,10 @@ export class HomeyPlatform extends MatterbridgeDynamicPlatform {
224
262
  this.bridgedDevices.delete(deviceId);
225
263
  await this.unregisterDevice(bridgedDevice.endpoint);
226
264
  }
265
+ async serialize(change) {
266
+ const result = this.deviceChanges.then(change);
267
+ this.deviceChanges = result.catch(() => {
268
+ });
269
+ await result;
270
+ }
227
271
  }
@@ -10,6 +10,7 @@
10
10
  "outletList": [],
11
11
  "capabilityBlackList": [],
12
12
  "deviceCapabilityBlackList": {},
13
+ "exposeNewDevices": true,
13
14
  "debug": false,
14
15
  "unregisterOnShutdown": false
15
16
  }
@@ -104,6 +104,12 @@
104
104
  "selectDeviceEntityFrom": "name"
105
105
  }
106
106
  },
107
+ "exposeNewDevices": {
108
+ "title": "Expose New Devices",
109
+ "description": "Expose the devices added to Homey right away, without restarting Matterbridge. The white list, black list and zone white list still apply. When off, new devices are exposed after the next restart.",
110
+ "type": "boolean",
111
+ "default": true
112
+ },
107
113
  "debug": {
108
114
  "title": "Enable Debug",
109
115
  "description": "Enable debug mode for the plugin. Only for debugging purposes, not recommended for production use.",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "matterbridge-homey",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "matterbridge-homey",
9
- "version": "0.2.0",
9
+ "version": "0.3.0",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "homey-api": "3.20.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge-homey",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Matterbridge plugin that exposes Athom Homey Pro devices to Matter",
5
5
  "author": "Stefan Schweiger",
6
6
  "license": "Apache-2.0",