Compare commits
No commits in common. "c8e15dc9265757cb7fdb483a30361a7e3bfd79d3" and "fa3cd39674112b5d385bb72f1108e9acfad4822f" have entirely different histories.
c8e15dc926
...
fa3cd39674
@ -1,4 +0,0 @@
|
|||||||
# ...
|
|
||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
cd ./matter-bridge && npm run lint-fix && git add .
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
cd matter-bridge
|
|
||||||
exec < /dev/tty && node_modules/.bin/cz --hook || true
|
|
10
README.md
10
README.md
@ -31,16 +31,6 @@ 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
|
||||||
|
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
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: {},
|
|
||||||
};
|
|
15
matter-bridge/.eslintrc.json
Normal file
15
matter-bridge/.eslintrc.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"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": {
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"printWidth": 70,
|
"printWidth": 80
|
||||||
"useTabs": false
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: 'HA Matter Bridge'
|
name: 'HA Matter Bridge'
|
||||||
version: '0.0.3-alpha'
|
version: '0.0.2-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
|
||||||
|
3674
matter-bridge/package-lock.json
generated
3674
matter-bridge/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,11 @@
|
|||||||
{
|
{
|
||||||
"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",
|
||||||
@ -20,28 +18,11 @@
|
|||||||
"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.20.0",
|
"@typescript-eslint/eslint-plugin": "^6.19.1",
|
||||||
"@typescript-eslint/parser": "^6.20.0",
|
"@typescript-eslint/parser": "^6.19.1",
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,19 +15,14 @@ export class HAMiddleware {
|
|||||||
this.hassClient.rawClient.ws.close();
|
this.hassClient.rawClient.ws.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async callAService(
|
async callAService(domain: string, service: string, extraArgs?: unknown) {
|
||||||
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 =
|
const toDo = this.functionsToCallOnChange[event.data.entity_id];
|
||||||
this.functionsToCallOnChange[event.data.entity_id];
|
|
||||||
if (toDo) {
|
if (toDo) {
|
||||||
toDo(event);
|
toDo(event);
|
||||||
}
|
}
|
||||||
@ -36,7 +31,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);
|
||||||
@ -44,20 +39,19 @@ 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<{
|
const sorted = states.reduceRight<{ [k: string]: HassEntity }>(
|
||||||
[k: string]: HassEntity;
|
(last, current) => {
|
||||||
}>((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<{
|
async getStatesPartitionedByType(): Promise<{ [k: string]: HassEntity[] }> {
|
||||||
[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[];
|
||||||
@ -70,7 +64,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;
|
||||||
}
|
}
|
||||||
@ -85,7 +79,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);
|
||||||
|
@ -60,12 +60,7 @@ export type HassConfig = {
|
|||||||
config_source: string;
|
config_source: string;
|
||||||
recovery_mode: boolean;
|
recovery_mode: boolean;
|
||||||
safe_mode: boolean;
|
safe_mode: boolean;
|
||||||
state:
|
state: 'NOT_RUNNING' | 'STARTING' | 'RUNNING' | 'STOPPING' | 'FINAL_WRITE';
|
||||||
| '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;
|
||||||
|
@ -8,23 +8,19 @@ 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(
|
LOGGER.info('adding ', entities['light'].length, 'light devices');
|
||||||
'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();
|
||||||
|
@ -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';
|
||||||
|
@ -7,77 +7,59 @@ import {
|
|||||||
HassEntity,
|
HassEntity,
|
||||||
StateChangedEvent,
|
StateChangedEvent,
|
||||||
} from '../../../home-assistant/HAssTypes';
|
} from '../../../home-assistant/HAssTypes';
|
||||||
import {
|
import { AddHaDeviceToBridge, Bridge, HAMiddleware } from '../MapperType';
|
||||||
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 ||
|
Number(haEntity.attributes['brightness']) / 255 || null,
|
||||||
null,
|
|
||||||
onLevel: 0.1,
|
onLevel: 0.1,
|
||||||
options: {
|
options: { coupleColorTempToLevel: false, executeIfOff: false },
|
||||||
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(
|
haMiddleware.callAService('light', value ? 'turn_on' : 'turn_off', {
|
||||||
'light',
|
|
||||||
value ? 'turn_on' : 'turn_off',
|
|
||||||
{
|
|
||||||
entity_id: haEntity.entity_id,
|
entity_id: haEntity.entity_id,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
device.addCommandHandler(
|
device.addCommandHandler(
|
||||||
'identify',
|
'identify',
|
||||||
({ request: { identifyTime } }) =>
|
async ({ 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',
|
||||||
extraArgs,
|
{ entity_id: haEntity.entity_id, brightness: Number(value) }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -88,11 +70,9 @@ 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)[
|
(event.data.new_state?.attributes as never)['brightness']
|
||||||
'brightness'
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
bridge.addDevice(device, {
|
bridge.addDevice(device, {
|
||||||
|
@ -1,59 +1,46 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import {
|
import { Device, OnOffLightDevice } from '@project-chip/matter-node.js/device';
|
||||||
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 {
|
import { AddHaDeviceToBridge, Bridge, HAMiddleware } from '../MapperType';
|
||||||
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(
|
haMiddleware.callAService('light', value ? 'turn_on' : 'turn_off', {
|
||||||
'light',
|
|
||||||
value ? 'turn_on' : 'turn_off',
|
|
||||||
{
|
|
||||||
entity_id: haEntity.entity_id,
|
entity_id: haEntity.entity_id,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
device.addCommandHandler(
|
device.addCommandHandler(
|
||||||
'identify',
|
'identify',
|
||||||
({ request: { identifyTime } }) =>
|
async ({ 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,
|
||||||
@ -61,7 +48,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'],
|
||||||
|
@ -11,27 +11,25 @@ export * from './OnOffLightDevice';
|
|||||||
|
|
||||||
const LOGGER = new Logger('Lights');
|
const LOGGER = new Logger('Lights');
|
||||||
|
|
||||||
const LIGHTS_MAP_FUNCTIONS: Map<string, AddHaDeviceToBridge> =
|
const LIGHTS_MAP_FUNCTIONS: Map<string, AddHaDeviceToBridge> = new Map<
|
||||||
new Map<string, AddHaDeviceToBridge>([
|
string,
|
||||||
|
AddHaDeviceToBridge
|
||||||
|
>([
|
||||||
['onoff', addOnOffLightDevice],
|
['onoff', addOnOffLightDevice],
|
||||||
['rgb', addDimmableLightDevice],
|
['rgb', addDimmableLightDevice],
|
||||||
['brightness', addDimmableLightDevice],
|
['brightness', addDimmableLightDevice],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const LIGHTS_MAP: Map<string, Device> = new Map<string, Device>();
|
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({
|
LOGGER.info({ colormodes: entity.attributes['supported_color_modes'] });
|
||||||
colormodes: entity.attributes['supported_color_modes'],
|
const key = (entity.attributes['supported_color_modes'] as string[])[0];
|
||||||
});
|
|
||||||
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) {
|
||||||
@ -39,7 +37,7 @@ export function setLights(
|
|||||||
}
|
}
|
||||||
LIGHTS_MAP.set(
|
LIGHTS_MAP.set(
|
||||||
entity.entity_id,
|
entity.entity_id,
|
||||||
lightBuildFunction(entity, haMiddleware, bridge),
|
lightBuildFunction(entity, haMiddleware, bridge)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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 =
|
private static readonly vendorName = getParameter('vendor') || 'Jatus';
|
||||||
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,17 +106,12 @@ 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(
|
this.logger.warn('System not initialized, may cause crashes');
|
||||||
'System not initialized, may cause crashes',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
this.aggregator.addBridgedDevice(
|
this.aggregator.addBridgedDevice(device, bridgedBasicInformation);
|
||||||
device,
|
|
||||||
bridgedBasicInformation,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
@ -124,32 +119,30 @@ 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.matterServer.addCommissioningServer(this.commissioningServer);
|
||||||
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 =
|
const pairingData = this.commissioningServer.getPairingCode();
|
||||||
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() {
|
||||||
await this.matterServer.close();
|
this.matterServer.close();
|
||||||
await this.storageManager.close();
|
this.storageManager
|
||||||
|
.close()
|
||||||
|
.then(() => process.exit(0))
|
||||||
|
.catch((err) => this.logger.error(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,7 @@ import {
|
|||||||
StorageBackendDisk,
|
StorageBackendDisk,
|
||||||
StorageManager,
|
StorageManager,
|
||||||
} from '@project-chip/matter-node.js/storage';
|
} from '@project-chip/matter-node.js/storage';
|
||||||
import {
|
import { getParameter, hasParameter } from '@project-chip/matter-node.js/util';
|
||||||
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';
|
||||||
|
|
||||||
@ -19,29 +16,25 @@ export function serverSetup(): {
|
|||||||
storageManager: StorageManager;
|
storageManager: StorageManager;
|
||||||
} {
|
} {
|
||||||
if (!(MATTER_SERVER && STORAGE && STORAGE_MANAGER)) {
|
if (!(MATTER_SERVER && STORAGE && STORAGE_MANAGER)) {
|
||||||
const storageLocation =
|
const storageLocation = getParameter('store') || '/config/deviceData';
|
||||||
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 {
|
return { matterServer: MATTER_SERVER, storageManager: STORAGE_MANAGER };
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -14,25 +14,20 @@ 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) {
|
if (value === undefined) return 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) {
|
if (script === undefined) return undefined;
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return () =>
|
return () =>
|
||||||
console.log(
|
console.log(
|
||||||
`${scriptParamName}: ${execSync(script).toString().slice(0, -1)}`,
|
`${scriptParamName}: ${execSync(script).toString().slice(0, -1)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user