Compare commits

..

2 Commits

19 changed files with 3871 additions and 171 deletions

4
.husky/pre-commit Normal file
View File

@ -0,0 +1,4 @@
# ...
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
cd ./matter-bridge && npm run lint-fix && git add .

View File

@ -0,0 +1,3 @@
#!/bin/bash
cd matter-bridge
exec < /dev/tty && node_modules/.bin/cz --hook || true

View File

@ -31,6 +31,16 @@ This project serves as a proof of concept to connect HomeAssistant devices to Vo
1. Help organize the project and dependencies. 1. Help organize the project and dependencies.
2. Add integrations for other devices. 2. Add integrations for other devices.
3. Add some tests 3. Add some tests
4. Donation
<div class=donation>
<form action="https://www.paypal.com/donate" method="post" target="_top">
<input type="hidden" name="business" value="VHWQSZR2SS898" />
<input type="hidden" name="no_recurring" value="0" />
<input type="hidden" name="currency_code" value="EUR" />
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Donate with PayPal button" />
<img alt="" border="0" src="https://www.paypal.com/en_IT/i/scr/pixel.gif" width="1" height="1" />
</form>
</div>
## Known Issues and Limitations ## Known Issues and Limitations

View File

@ -0,0 +1,46 @@
module.exports = {
env: {
es2021: true,
node: true,
jest: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
overrides: [
{
env: {
node: true,
},
files: ['.eslintrc.{js,cjs}'],
parserOptions: {
sourceType: 'script',
},
},
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
arrowFunctions: true,
},
},
plugins: ['@stylistic/ts', '@typescript-eslint', 'prettier'],
rules: {
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
curly: ['error'],
'no-empty-function': [
'off',
{
allow: null,
},
],
'require-await': ['error'],
},
globals: {},
};

View File

@ -1,15 +0,0 @@
{
"env": {
"es2021": true,
"node": true
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"rules": {
}
}

View File

@ -1,7 +1,7 @@
{ {
"trailingComma": "es5",
"tabWidth": 4, "tabWidth": 4,
"semi": true, "semi": true,
"singleQuote": true, "singleQuote": true,
"printWidth": 80 "printWidth": 70,
"useTabs": false
} }

View File

@ -1,6 +1,6 @@
--- ---
name: 'HA Matter Bridge' name: 'HA Matter Bridge'
version: '0.0.2-alpha' version: '0.0.3-alpha'
slug: ha-matter-bridge slug: ha-matter-bridge
description: This project serves as a proof of concept to connect HomeAssistant devices to Voice Assistants through the Matter Protocol. description: This project serves as a proof of concept to connect HomeAssistant devices to Voice Assistants through the Matter Protocol.
init: false init: false

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,13 @@
{ {
"name": "ha-matter-bridge", "name": "ha-matter-bridge",
"version": "1.0.0",
"description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"start": "node ./build/index.js" "start": "node ./build/index.js",
"lint": "eslint .",
"lint-fix": "eslint --fix .",
"prepare": "cd .. && husky install matter-bridge/.husky",
"commit": "./node_modules/.bin/git-cz"
}, },
"author": "", "author": "",
"license": "GPL-3.0", "license": "GPL-3.0",
@ -18,11 +20,28 @@
"ws": "^8.16.0" "ws": "^8.16.0"
}, },
"devDependencies": { "devDependencies": {
"@stylistic/eslint-plugin": "^1.5.4",
"@stylistic/eslint-plugin-js": "^1.5.4",
"@stylistic/eslint-plugin-plus": "^1.5.4",
"@stylistic/eslint-plugin-ts": "^1.5.4",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/ws": "^8.5.10", "@types/ws": "^8.5.10",
"@typescript-eslint/eslint-plugin": "^6.19.1", "@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.19.1", "@typescript-eslint/parser": "^6.20.0",
"commitizen": "^4.3.0",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"husky": "^9.0.7",
"prettier": "^3.2.4",
"prettier-eslint": "^16.3.0",
"standard-version": "^9.5.0",
"typescript": "^5.3.3" "typescript": "^5.3.3"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
} }
} }

View File

@ -15,14 +15,19 @@ export class HAMiddleware {
this.hassClient.rawClient.ws.close(); this.hassClient.rawClient.ws.close();
} }
async callAService(domain: string, service: string, extraArgs?: unknown) { async callAService(
domain: string,
service: string,
extraArgs?: unknown,
) {
await this.hassClient.callService(domain, service, extraArgs); await this.hassClient.callService(domain, service, extraArgs);
} }
subscribe() { subscribe() {
this.hassClient.on('state_changed', (event) => { this.hassClient.on('state_changed', (event) => {
this.logger.debug(JSON.stringify(event)); this.logger.debug(JSON.stringify(event));
const toDo = this.functionsToCallOnChange[event.data.entity_id]; const toDo =
this.functionsToCallOnChange[event.data.entity_id];
if (toDo) { if (toDo) {
toDo(event); toDo(event);
} }
@ -31,7 +36,7 @@ export class HAMiddleware {
subscribeToDevice( subscribeToDevice(
deviceId: string, deviceId: string,
fn: (event: StateChangedEvent) => void fn: (event: StateChangedEvent) => void,
) { ) {
this.functionsToCallOnChange[deviceId] = fn; this.functionsToCallOnChange[deviceId] = fn;
this.logger.debug(this.functionsToCallOnChange); this.logger.debug(this.functionsToCallOnChange);
@ -39,19 +44,20 @@ export class HAMiddleware {
async getStates(): Promise<{ [k: string]: HassEntity }> { async getStates(): Promise<{ [k: string]: HassEntity }> {
const states = await this.hassClient.getStates(); const states = await this.hassClient.getStates();
const sorted = states.reduceRight<{ [k: string]: HassEntity }>( const sorted = states.reduceRight<{
(last, current) => { [k: string]: HassEntity;
}>((last, current) => {
last[current['entity_id']] = current; last[current['entity_id']] = current;
return last; return last;
}, }, {});
{}
);
this.logger.debug(JSON.stringify({ getStates: sorted })); this.logger.debug(JSON.stringify({ getStates: sorted }));
this.entities = sorted; this.entities = sorted;
return this.entities; return this.entities;
} }
async getStatesPartitionedByType(): Promise<{ [k: string]: HassEntity[] }> { async getStatesPartitionedByType(): Promise<{
[k: string]: HassEntity[];
}> {
const states = await this.getStates(); const states = await this.getStates();
const toReturn = Object.keys(states).reduceRight<{ const toReturn = Object.keys(states).reduceRight<{
[k: string]: HassEntity[]; [k: string]: HassEntity[];
@ -64,7 +70,7 @@ export class HAMiddleware {
return prev; return prev;
}, {}); }, {});
this.logger.debug( this.logger.debug(
JSON.stringify({ getStatesPartitionedByType: toReturn }) JSON.stringify({ getStatesPartitionedByType: toReturn }),
); );
return toReturn; return toReturn;
} }
@ -79,7 +85,7 @@ export class HAMiddleware {
} }
public static async getInstance( public static async getInstance(
callerOptions?: Partial<HassWsOptions> | undefined callerOptions?: Partial<HassWsOptions> | undefined,
): Promise<HAMiddleware> { ): Promise<HAMiddleware> {
if (!HAMiddleware.instance) { if (!HAMiddleware.instance) {
const client = await hass(callerOptions); const client = await hass(callerOptions);

View File

@ -60,7 +60,12 @@ export type HassConfig = {
config_source: string; config_source: string;
recovery_mode: boolean; recovery_mode: boolean;
safe_mode: boolean; safe_mode: boolean;
state: 'NOT_RUNNING' | 'STARTING' | 'RUNNING' | 'STOPPING' | 'FINAL_WRITE'; state:
| 'NOT_RUNNING'
| 'STARTING'
| 'RUNNING'
| 'STOPPING'
| 'FINAL_WRITE';
external_url: string | null; external_url: string | null;
internal_url: string | null; internal_url: string | null;
currency: string; currency: string;

View File

@ -8,19 +8,23 @@ const LOGGER = new Logger('Mapper');
async function setHasEntities( async function setHasEntities(
haMiddleware: HAMiddleware, haMiddleware: HAMiddleware,
bridge: Bridge bridge: Bridge,
): Promise<void> { ): Promise<void> {
const entities = await haMiddleware.getStatesPartitionedByType(); const entities = await haMiddleware.getStatesPartitionedByType();
LOGGER.info({ entities }); LOGGER.info({ entities });
if (entities['light']) { if (entities['light']) {
LOGGER.info('adding ', entities['light'].length, 'light devices'); LOGGER.info(
'adding ',
entities['light'].length,
'light devices',
);
setLights(entities['light'], haMiddleware, bridge); setLights(entities['light'], haMiddleware, bridge);
} }
} }
export async function addAllDevicesToBridge( export async function addAllDevicesToBridge(
haMiddleware: HAMiddleware, haMiddleware: HAMiddleware,
bridge: Bridge bridge: Bridge,
): Promise<void> { ): Promise<void> {
await setHasEntities(haMiddleware, bridge); await setHasEntities(haMiddleware, bridge);
haMiddleware.subscribe(); haMiddleware.subscribe();

View File

@ -7,7 +7,7 @@ import { Bridge } from '../../matter';
export type AddHaDeviceToBridge = ( export type AddHaDeviceToBridge = (
haEntity: HassEntity, haEntity: HassEntity,
haMiddleware: HAMiddleware, haMiddleware: HAMiddleware,
bridge: Bridge bridge: Bridge,
) => Device; ) => Device;
export { HAMiddleware } from '../../home-assistant/HAmiddleware'; export { HAMiddleware } from '../../home-assistant/HAmiddleware';

View File

@ -7,59 +7,77 @@ import {
HassEntity, HassEntity,
StateChangedEvent, StateChangedEvent,
} from '../../../home-assistant/HAssTypes'; } from '../../../home-assistant/HAssTypes';
import { AddHaDeviceToBridge, Bridge, HAMiddleware } from '../MapperType'; import {
AddHaDeviceToBridge,
Bridge,
HAMiddleware,
} from '../MapperType';
import { Logger } from '@project-chip/matter-node.js/log'; import { Logger } from '@project-chip/matter-node.js/log';
const LOGGER = new Logger('DimmableLight'); const LOGGER = new Logger('DimmableLight');
export const addDimmableLightDevice: AddHaDeviceToBridge = ( export const addDimmableLightDevice: AddHaDeviceToBridge = (
haEntity: HassEntity, haEntity: HassEntity,
haMiddleware: HAMiddleware, haMiddleware: HAMiddleware,
bridge: Bridge bridge: Bridge,
): Device => { ): Device => {
LOGGER.debug( LOGGER.debug(
`Building device ${haEntity.entity_id} \n ${JSON.stringify({ `Building device ${haEntity.entity_id} \n ${JSON.stringify({
haEntity, haEntity,
})}` })}`,
); );
const device = new DimmableLightDevice( const device = new DimmableLightDevice(
{ onOff: haEntity.state === 'on' }, { onOff: haEntity.state === 'on' },
{ {
currentLevel: currentLevel:
Number(haEntity.attributes['brightness']) / 255 || null, Number(haEntity.attributes['brightness']) / 255 ||
null,
onLevel: 0.1, onLevel: 0.1,
options: { coupleColorTempToLevel: false, executeIfOff: false }, options: {
} coupleColorTempToLevel: false,
executeIfOff: false,
},
},
); );
const serialFromId = MD5(haEntity.entity_id).toString(); const serialFromId = MD5(haEntity.entity_id).toString();
device.addOnOffListener((value, oldValue) => { device.addOnOffListener((value, oldValue) => {
LOGGER.debug( LOGGER.debug(
`OnOff Event for device ${haEntity.entity_id}, ${JSON.stringify({ `OnOff Event for device ${haEntity.entity_id}, ${JSON.stringify(
{
value, value,
oldValue, oldValue,
})}` },
)}`,
); );
if (value !== oldValue) { if (value !== oldValue) {
haMiddleware.callAService('light', value ? 'turn_on' : 'turn_off', { haMiddleware.callAService(
'light',
value ? 'turn_on' : 'turn_off',
{
entity_id: haEntity.entity_id, entity_id: haEntity.entity_id,
}); },
);
} }
}); });
device.addCommandHandler( device.addCommandHandler(
'identify', 'identify',
async ({ request: { identifyTime } }) => ({ request: { identifyTime } }) =>
LOGGER.info( LOGGER.info(
`Identify called for OnOffDevice ${haEntity.attributes['friendly_name']} with id: ${serialFromId} and identifyTime: ${identifyTime}` `Identify called for OnOffDevice ${haEntity.attributes['friendly_name']} with id: ${serialFromId} and identifyTime: ${identifyTime}`,
) ),
); );
device.addCurrentLevelListener((value) => { device.addCurrentLevelListener((value) => {
LOGGER.debug( LOGGER.debug(
`CurrentLevel Event for device ${haEntity.entity_id} value: ${value}` `CurrentLevel Event for device ${haEntity.entity_id} value: ${value}`,
); );
let extraArgs = { entity_id: haEntity.entity_id } as object;
if (Number(value) > 0) {
extraArgs = { ...extraArgs, brightness: Number(value) };
}
haMiddleware.callAService( haMiddleware.callAService(
'light', 'light',
Number(value) > 0 ? 'turn_on' : 'turn_off', Number(value) > 0 ? 'turn_on' : 'turn_off',
{ entity_id: haEntity.entity_id, brightness: Number(value) } extraArgs,
); );
}); });
@ -70,9 +88,11 @@ export const addDimmableLightDevice: AddHaDeviceToBridge = (
LOGGER.debug(JSON.stringify(event)); LOGGER.debug(JSON.stringify(event));
device.setOnOff(event.data.new_state?.state === 'on'); device.setOnOff(event.data.new_state?.state === 'on');
device.setCurrentLevel( device.setCurrentLevel(
(event.data.new_state?.attributes as never)['brightness'] (event.data.new_state?.attributes as never)[
'brightness'
],
); );
} },
); );
bridge.addDevice(device, { bridge.addDevice(device, {

View File

@ -1,46 +1,59 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { Device, OnOffLightDevice } from '@project-chip/matter-node.js/device'; import {
Device,
OnOffLightDevice,
} from '@project-chip/matter-node.js/device';
import { MD5 } from 'crypto-js'; import { MD5 } from 'crypto-js';
import { import {
HassEntity, HassEntity,
StateChangedEvent, StateChangedEvent,
} from '../../../home-assistant/HAssTypes'; } from '../../../home-assistant/HAssTypes';
import { AddHaDeviceToBridge, Bridge, HAMiddleware } from '../MapperType'; import {
AddHaDeviceToBridge,
Bridge,
HAMiddleware,
} from '../MapperType';
import { Logger } from '@project-chip/matter-node.js/log'; import { Logger } from '@project-chip/matter-node.js/log';
const LOGGER = new Logger('OnOffLight'); const LOGGER = new Logger('OnOffLight');
export const addOnOffLightDevice: AddHaDeviceToBridge = ( export const addOnOffLightDevice: AddHaDeviceToBridge = (
haEntity: HassEntity, haEntity: HassEntity,
haMiddleware: HAMiddleware, haMiddleware: HAMiddleware,
bridge: Bridge bridge: Bridge,
): Device => { ): Device => {
LOGGER.debug( LOGGER.debug(
`Building device ${haEntity.entity_id} \n ${JSON.stringify({ `Building device ${haEntity.entity_id} \n ${JSON.stringify({
haEntity, haEntity,
})}` })}`,
); );
const device = new OnOffLightDevice(); const device = new OnOffLightDevice();
const serialFromId = MD5(haEntity.entity_id).toString(); const serialFromId = MD5(haEntity.entity_id).toString();
device.addOnOffListener((value, oldValue) => { device.addOnOffListener((value, oldValue) => {
LOGGER.debug( LOGGER.debug(
`OnOff Event for device ${haEntity.entity_id}, ${JSON.stringify({ `OnOff Event for device ${haEntity.entity_id}, ${JSON.stringify(
{
value, value,
oldValue, oldValue,
})}` },
)}`,
); );
if (value !== oldValue) { if (value !== oldValue) {
haMiddleware.callAService('light', value ? 'turn_on' : 'turn_off', { haMiddleware.callAService(
'light',
value ? 'turn_on' : 'turn_off',
{
entity_id: haEntity.entity_id, entity_id: haEntity.entity_id,
}); },
);
} }
}); });
device.addCommandHandler( device.addCommandHandler(
'identify', 'identify',
async ({ request: { identifyTime } }) => ({ request: { identifyTime } }) =>
LOGGER.info( LOGGER.info(
`Identify called for OnOffDevice ${haEntity.attributes['friendly_name']} with id: ${serialFromId} and identifyTime: ${identifyTime}` `Identify called for OnOffDevice ${haEntity.attributes['friendly_name']} with id: ${serialFromId} and identifyTime: ${identifyTime}`,
) ),
); );
haMiddleware.subscribeToDevice( haMiddleware.subscribeToDevice(
haEntity.entity_id, haEntity.entity_id,
@ -48,7 +61,7 @@ export const addOnOffLightDevice: AddHaDeviceToBridge = (
LOGGER.debug(`Event for device ${haEntity.entity_id}`); LOGGER.debug(`Event for device ${haEntity.entity_id}`);
LOGGER.debug(JSON.stringify(event)); LOGGER.debug(JSON.stringify(event));
device.setOnOff(event.data.new_state?.state === 'on'); device.setOnOff(event.data.new_state?.state === 'on');
} },
); );
bridge.addDevice(device, { bridge.addDevice(device, {
nodeLabel: haEntity.attributes['friendly_name'], nodeLabel: haEntity.attributes['friendly_name'],

View File

@ -11,10 +11,8 @@ export * from './OnOffLightDevice';
const LOGGER = new Logger('Lights'); const LOGGER = new Logger('Lights');
const LIGHTS_MAP_FUNCTIONS: Map<string, AddHaDeviceToBridge> = new Map< const LIGHTS_MAP_FUNCTIONS: Map<string, AddHaDeviceToBridge> =
string, new Map<string, AddHaDeviceToBridge>([
AddHaDeviceToBridge
>([
['onoff', addOnOffLightDevice], ['onoff', addOnOffLightDevice],
['rgb', addDimmableLightDevice], ['rgb', addDimmableLightDevice],
['brightness', addDimmableLightDevice], ['brightness', addDimmableLightDevice],
@ -25,11 +23,15 @@ const LIGHTS_MAP: Map<string, Device> = new Map<string, Device>();
export function setLights( export function setLights(
lights: HassEntity[], lights: HassEntity[],
haMiddleware: HAMiddleware, haMiddleware: HAMiddleware,
bridge: Bridge bridge: Bridge,
) { ) {
lights.forEach((entity) => { lights.forEach((entity) => {
LOGGER.info({ colormodes: entity.attributes['supported_color_modes'] }); LOGGER.info({
const key = (entity.attributes['supported_color_modes'] as string[])[0]; colormodes: entity.attributes['supported_color_modes'],
});
const key = (
entity.attributes['supported_color_modes'] as string[]
)[0];
LOGGER.info({ key }); LOGGER.info({ key });
const lightBuildFunction = LIGHTS_MAP_FUNCTIONS.get(key); const lightBuildFunction = LIGHTS_MAP_FUNCTIONS.get(key);
if (!lightBuildFunction) { if (!lightBuildFunction) {
@ -37,7 +39,7 @@ export function setLights(
} }
LIGHTS_MAP.set( LIGHTS_MAP.set(
entity.entity_id, entity.entity_id,
lightBuildFunction(entity, haMiddleware, bridge) lightBuildFunction(entity, haMiddleware, bridge),
); );
}); });
} }

View File

@ -23,10 +23,10 @@ export class Bridge {
private static readonly deviceName = private static readonly deviceName =
getParameter('name') || 'Matter Bridge'; getParameter('name') || 'Matter Bridge';
private static readonly deviceType = DeviceTypes.AGGREGATOR.code; private static readonly deviceType = DeviceTypes.AGGREGATOR.code;
private static readonly vendorName = getParameter('vendor') || 'Jatus'; private static readonly vendorName =
getParameter('vendor') || 'Jatus';
private static readonly productName = 'HomeAssistant'; private static readonly productName = 'HomeAssistant';
private static readonly port = getIntParameter('port') ?? 5540; private static readonly port = getIntParameter('port') ?? 5540;
private ready = false;
private matterServer: MatterServer; private matterServer: MatterServer;
private static instance: Bridge; private static instance: Bridge;
@ -37,7 +37,7 @@ export class Bridge {
private constructor( private constructor(
matterServer: MatterServer, matterServer: MatterServer,
storageManager: StorageManager storageManager: StorageManager,
) { ) {
this.matterServer = matterServer; this.matterServer = matterServer;
this.storageManager = storageManager; this.storageManager = storageManager;
@ -47,7 +47,7 @@ export class Bridge {
public static getInstance( public static getInstance(
matterServer: MatterServer, matterServer: MatterServer,
storageManager: StorageManager storageManager: StorageManager,
): Bridge { ): Bridge {
if (!Bridge.instance) { if (!Bridge.instance) {
this.instance = new Bridge(matterServer, storageManager); this.instance = new Bridge(matterServer, storageManager);
@ -106,12 +106,17 @@ export class Bridge {
device: Device | ComposedDevice, device: Device | ComposedDevice,
bridgedBasicInformation?: AttributeInitialValues< bridgedBasicInformation?: AttributeInitialValues<
typeof BridgedDeviceBasicInformationCluster.attributes typeof BridgedDeviceBasicInformationCluster.attributes
> >,
) { ) {
if (!this.commissioningServer?.isCommissioned()) { if (!this.commissioningServer?.isCommissioned()) {
this.logger.warn('System not initialized, may cause crashes'); this.logger.warn(
'System not initialized, may cause crashes',
);
} }
this.aggregator.addBridgedDevice(device, bridgedBasicInformation); this.aggregator.addBridgedDevice(
device,
bridgedBasicInformation,
);
} }
async start() { async start() {
@ -119,30 +124,32 @@ export class Bridge {
this.commissioningServer = this.commissioningServer =
await this.setupContextAndCommissioningServer(); await this.setupContextAndCommissioningServer();
this.commissioningServer.addDevice(this.aggregator); this.commissioningServer.addDevice(this.aggregator);
this.matterServer.addCommissioningServer(this.commissioningServer); this.matterServer.addCommissioningServer(
this.commissioningServer,
);
await this.matterServer.start(); await this.matterServer.start();
this.logger.info('Listening'); this.logger.info('Listening');
if (!this.commissioningServer.isCommissioned()) { if (!this.commissioningServer.isCommissioned()) {
const pairingData = this.commissioningServer.getPairingCode(); const pairingData =
this.commissioningServer.getPairingCode();
const { qrPairingCode, manualPairingCode } = pairingData; const { qrPairingCode, manualPairingCode } = pairingData;
console.log(QrCode.get(qrPairingCode)); console.log(QrCode.get(qrPairingCode));
this.logger.info( this.logger.info(
`QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}` `QR Code URL: https://project-chip.github.io/connectedhomeip/qrcode.html?data=${qrPairingCode}`,
);
this.logger.info(
`Manual pairing code: ${manualPairingCode}`,
); );
this.logger.info(`Manual pairing code: ${manualPairingCode}`);
} else { } else {
this.logger.info( this.logger.info(
'Device is already commissioned. Waiting for controllers to connect ...' 'Device is already commissioned. Waiting for controllers to connect ...',
); );
} }
} }
async stop() { async stop() {
this.matterServer.close(); await this.matterServer.close();
this.storageManager await this.storageManager.close();
.close()
.then(() => process.exit(0))
.catch((err) => this.logger.error(err));
} }
} }

View File

@ -3,7 +3,10 @@ import {
StorageBackendDisk, StorageBackendDisk,
StorageManager, StorageManager,
} from '@project-chip/matter-node.js/storage'; } from '@project-chip/matter-node.js/storage';
import { getParameter, hasParameter } from '@project-chip/matter-node.js/util'; import {
getParameter,
hasParameter,
} from '@project-chip/matter-node.js/util';
export { Bridge } from './Bridge'; export { Bridge } from './Bridge';
import { Bridge } from './Bridge'; import { Bridge } from './Bridge';
@ -16,25 +19,29 @@ export function serverSetup(): {
storageManager: StorageManager; storageManager: StorageManager;
} { } {
if (!(MATTER_SERVER && STORAGE && STORAGE_MANAGER)) { if (!(MATTER_SERVER && STORAGE && STORAGE_MANAGER)) {
const storageLocation = getParameter('store') || '/config/deviceData'; const storageLocation =
getParameter('store') || '/config/deviceData';
STORAGE = new StorageBackendDisk( STORAGE = new StorageBackendDisk(
storageLocation, storageLocation,
hasParameter('clearstorage') hasParameter('clearstorage'),
); );
STORAGE_MANAGER = new StorageManager(STORAGE); STORAGE_MANAGER = new StorageManager(STORAGE);
MATTER_SERVER = new MatterServer(STORAGE_MANAGER, { MATTER_SERVER = new MatterServer(STORAGE_MANAGER, {
mdnsInterface: getParameter('netinterface'), mdnsInterface: getParameter('netinterface'),
}); });
} }
return { matterServer: MATTER_SERVER, storageManager: STORAGE_MANAGER }; return {
matterServer: MATTER_SERVER,
storageManager: STORAGE_MANAGER,
};
} }
export function getBridge(): Bridge { export function getBridge(): Bridge {
const serverData = serverSetup(); const serverData = serverSetup();
const bridge = Bridge.getInstance( const bridge = Bridge.getInstance(
serverData.matterServer, serverData.matterServer,
serverData.storageManager serverData.storageManager,
); );
return bridge; return bridge;
} }

View File

@ -14,20 +14,25 @@ export function hasParameter(name: string) {
export function getIntParameter(name: string) { export function getIntParameter(name: string) {
const value = getParameter(name); const value = getParameter(name);
if (value === undefined) return undefined; if (value === undefined) {
return undefined;
}
const intValue = parseInt(value, 10); const intValue = parseInt(value, 10);
if (isNaN(intValue)) if (isNaN(intValue)) {
throw new ValidationError( throw new ValidationError(
`Invalid value for parameter ${name}: ${value} is not a number` `Invalid value for parameter ${name}: ${value} is not a number`,
); );
}
return intValue; return intValue;
} }
export function commandExecutor(scriptParamName: string) { export function commandExecutor(scriptParamName: string) {
const script = getParameter(scriptParamName); const script = getParameter(scriptParamName);
if (script === undefined) return undefined; if (script === undefined) {
return undefined;
}
return () => return () =>
console.log( console.log(
`${scriptParamName}: ${execSync(script).toString().slice(0, -1)}` `${scriptParamName}: ${execSync(script).toString().slice(0, -1)}`,
); );
} }