Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@

# Standalone
/.envrc
/.matter*
/.matter*
# Generated mapping test reports
/tests/mappings/artifacts/
2 changes: 1 addition & 1 deletion .homeycompose/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"name": "Emile Nijssen",
"email": "emile@athom.nl"
},
"support": "mailto:emile@athom.nl",
"support": "mailto:apps@athom.nl",
"source": "https://github.com/athombv/com.athom.matter-bridge",
"api": {
"startPairing": {
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ storage, including multiple fabrics, rollback, cancellation, expiry, competing
platforms, and restart. A disk fixture generated by Matter.js 0.15.6 verifies that
existing fabrics and endpoint identities survive the upgrade and that both original
controller credentials still allow control without pairing again. Device mapping
tests initialize lights, sockets, thermostats, air conditioners, locks, covers,
and sensors against the current Matter model. They also test the settings panel with mocked Homey responses
tests verify discovery, values, subscriptions, commands, rejection, and lifecycle behavior
for lights, sockets, thermostats, air conditioners, locks, covers, and sensors.
Run `npm run test:mappings` for these tests, or `npm run test:mappings:report` to generate
a coverage report. The optional Node 24 backend comparison and its pinned Homey OS reference
are documented in [the mapping test guide](tests/mappings/README.md).
The tests also exercise the settings panel with mocked Homey responses
and `homey-api` loading, `createAppAPI`, discovery, capability subscriptions, and
reconnection against a local HTTP/Socket.IO fixture. Allow local multicast for the
Matter integration test. No physical devices or existing Homey pairings are used.
Expand All @@ -63,6 +67,8 @@ npm audit --omit=dev
homey app install
```

Use the normal install command so the CLI packages production dependencies and excludes tests.
`--skip-build` packages the source directory directly in Homey CLI 4.4.4 and bypasses that preparation.
Do not use `--clean` when updating an existing installation. Acceptance testing on
Homey requires adding a second external platform, verifying both control the same
selected devices, and repeating those checks after an app restart.
Expand Down
2 changes: 1 addition & 1 deletion app.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"name": "Emile Nijssen",
"email": "emile@athom.nl"
},
"support": "mailto:emile@athom.nl",
"support": "mailto:apps@athom.nl",
"source": "https://github.com/athombv/com.athom.matter-bridge",
"api": {
"startPairing": {
Expand Down
44 changes: 31 additions & 13 deletions lib/MatterBridgeApp.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import crypto from 'node:crypto';

import Homey from 'homey';
import PQueue from 'p-queue';
import { HomeyAPI } from 'homey-api';
import MatterBridgeServer from './MatterBridgeServer.mjs'
import { MatterBridgeServer } from './MatterBridgeServer.mjs'

export default class MatterBridgeApp extends Homey.App {

#selectionQueue = new PQueue({ concurrency: 1 });

async onInit() {
this.log('Starting Matter Bridge Server...');

Expand Down Expand Up @@ -110,6 +113,15 @@ export default class MatterBridgeApp extends Homey.App {
iconOverride: device.iconOverride,
isSelected: enabledDeviceIds.has(device.id),
};
const support = MatterBridgeServer.getDeviceSupport(device);
const describeCapability = (id) => {
return { id, title: device.capabilitiesObj[id].title ?? id.replaceAll('_', ' ') };
};
deviceObj.support = {
canShare: support.canShare,
supportedCapabilities: support.supportedCapabilities.map(describeCapability),
unsupportedCapabilities: support.unsupportedCapabilities.map(describeCapability),
};

// Add Zone Name
const zone = await device.getZone();
Expand All @@ -132,25 +144,31 @@ export default class MatterBridgeApp extends Homey.App {
}

async onAPIEnableDevice({ deviceId }) {
const enabledDeviceIds = await this.getEnabledDeviceIds();
if (!enabledDeviceIds.has(deviceId)) {
await this.#selectionQueue.add(async () => {
const enabledDeviceIds = await this.getEnabledDeviceIds();

if (enabledDeviceIds.has(deviceId)) {
return;
}

await this.server.enableDevice(deviceId);
enabledDeviceIds.add(deviceId);
await this.homey.settings.set('enabledDeviceIds', Array.from(enabledDeviceIds));
this.server.enableDevice(deviceId).catch(err => {
this.log(`Error enabling device ${deviceId}:`, err);
});
}
});
}

async onAPIDisableDevice({ deviceId }) {
const enabledDeviceIds = await this.getEnabledDeviceIds();
if (enabledDeviceIds.has(deviceId)) {
await this.#selectionQueue.add(async () => {
const enabledDeviceIds = await this.getEnabledDeviceIds();

if (!enabledDeviceIds.has(deviceId)) {
return;
}

await this.server.disableDevice(deviceId);
enabledDeviceIds.delete(deviceId);
await this.homey.settings.set('enabledDeviceIds', Array.from(enabledDeviceIds));
this.server.disableDevice(deviceId).catch(err => {
this.log(`Error disabling device ${deviceId}:`, err);
});
}
});
}

}
Loading
Loading