base refactoring #1
@ -30,6 +30,7 @@ Once the `.env` file is configured, run the project with `docker-compose up` for
|
||||
|
||||
1. Help organize the project and dependencies.
|
||||
2. Add integrations for other devices.
|
||||
3. Add some tests
|
||||
|
||||
## Known Issues and Limitations
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Logger } from '@project-chip/matter-node.js/log';
|
||||
import { HassEntity, HassEvent } from './HAssTypes';
|
||||
import { HassEntity, StateChangedEvent } from './HAssTypes';
|
||||
import hass, { HassApi, HassWsOptions } from 'homeassistant-ws';
|
||||
|
||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
||||
@ -12,7 +12,7 @@ export class HAMiddleware {
|
||||
private requestFulfilled: boolean = true;
|
||||
private entities: { [k: string]: HassEntity } = {};
|
||||
private functionsToCallOnChange: {
|
||||
[k: string]: ((data: HassEvent['data']) => void) | undefined;
|
||||
[k: string]: ((data: StateChangedEvent) => void) | undefined;
|
||||
} = {};
|
||||
|
||||
async waitCompletition(): Promise<void> {
|
||||
@ -34,17 +34,17 @@ export class HAMiddleware {
|
||||
}
|
||||
|
||||
subscribe() {
|
||||
this.hassClient.on('state_changed', (stateChangedEvent) => {
|
||||
this.logger.debug(stateChangedEvent.data);
|
||||
this.hassClient.on('state_changed', (event) => {
|
||||
this.logger.debug(event);
|
||||
const toDo =
|
||||
this.functionsToCallOnChange[stateChangedEvent.data.entity_id];
|
||||
this.functionsToCallOnChange[event.data.entity_id];
|
||||
if (toDo) {
|
||||
toDo(stateChangedEvent.data);
|
||||
toDo(event.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
subscrieToDevice(deviceId: string, fn: (data: HassEvent['data']) => void) {
|
||||
subscrieToDevice(deviceId: string, fn: (event: StateChangedEvent) => void) {
|
||||
this.functionsToCallOnChange[deviceId] = fn;
|
||||
this.logger.debug(this.functionsToCallOnChange);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Bridge, HAMiddleware, addAllDevicesToBridge } from './matter/devices';
|
||||
import { Bridge, HAMiddleware, addAllDevicesToBridge } from './matter';
|
||||
import { serverSetup } from './matter/server';
|
||||
import { Logger } from '@project-chip/matter-node.js/log';
|
||||
|
||||
|
57
src/matter/Mapper.ts
Normal file
57
src/matter/Mapper.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { addDimmerableLightDevice, addOnOffLightDevice } from './devices/lights';
|
||||
import { HassEntity } from '../HA/HAssTypes';
|
||||
import { Bridge, HAMiddleware } from '.';
|
||||
import { Logger } from '@project-chip/matter-node.js/log';
|
||||
|
||||
const LOGGER = new Logger('Mapper');
|
||||
|
||||
|
||||
const lightsMap: Map<
|
||||
string,
|
||||
(haEntity: HassEntity, haMiddleware: HAMiddleware, bridge: Bridge) => void
|
||||
> = new Map<
|
||||
string,
|
||||
(haEntity: HassEntity, haMiddleware: HAMiddleware, bridge: Bridge) => void
|
||||
>([
|
||||
['onoff', addOnOffLightDevice],
|
||||
['rgb', addDimmerableLightDevice],
|
||||
['brightness', addDimmerableLightDevice],
|
||||
]);
|
||||
|
||||
function setLights(
|
||||
lights: HassEntity[],
|
||||
haMiddleware: HAMiddleware,
|
||||
bridge: Bridge
|
||||
) {
|
||||
lights.forEach((entity) => {
|
||||
LOGGER.info({ colormodes: entity.attributes['supported_color_modes'] });
|
||||
const key = (entity.attributes['supported_color_modes'] as string[])[0];
|
||||
LOGGER.info({ key });
|
||||
const lightBuildFunction = lightsMap.get(key);
|
||||
if (!lightBuildFunction) {
|
||||
throw new Error('Missing ' + key);
|
||||
}
|
||||
return lightBuildFunction(entity, haMiddleware, bridge);
|
||||
});
|
||||
}
|
||||
|
||||
async function setHasEnties(
|
||||
haMiddleware: HAMiddleware,
|
||||
bridge: Bridge
|
||||
): Promise<void> {
|
||||
const entities = await haMiddleware.getStatesPartitionedByType();
|
||||
LOGGER.info({ entities });
|
||||
if (entities['light']) {
|
||||
LOGGER.info('adding ', entities['light'].length, 'light devices');
|
||||
setLights(entities['light'], haMiddleware, bridge);
|
||||
}
|
||||
}
|
||||
|
||||
export async function addAllDevicesToBridge(
|
||||
haMiddleware: HAMiddleware,
|
||||
bridge: Bridge
|
||||
): Promise<void> {
|
||||
await setHasEnties(haMiddleware, bridge);
|
||||
haMiddleware.subscribe();
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
Device,
|
||||
DimmableLightDevice,
|
||||
OnOffLightDevice,
|
||||
} from '@project-chip/matter.js/device';
|
||||
import { HassEntity, HassEvent } from './HAssTypes';
|
||||
import { Bridge, HAMiddleware } from '.';
|
||||
import { MD5 } from 'crypto-js';
|
||||
import { Logger } from '@project-chip/matter-node.js/log';
|
||||
|
||||
const DEVICE_ENTITY_MAP: {
|
||||
[k: string]: { haEntity: HassEntity; device: Device };
|
||||
} = {};
|
||||
|
||||
const LOGGER = new Logger('Mapper');
|
||||
|
||||
function addRGBLightDeviceToMap(
|
||||
haEntity: HassEntity,
|
||||
haMiddleware: HAMiddleware,
|
||||
bridge: Bridge
|
||||
): void {
|
||||
const device = new DimmableLightDevice();
|
||||
const serialFromId = MD5(haEntity.entity_id).toString();
|
||||
|
||||
device.addOnOffListener((value, oldValue) => {
|
||||
if (value !== oldValue) {
|
||||
haMiddleware.callAService('light', value ? 'turn_on' : 'turn_off', {
|
||||
entity_id: haEntity.entity_id,
|
||||
});
|
||||
}
|
||||
});
|
||||
device.addCurrentLevelListener((value) => {
|
||||
haMiddleware.callAService(
|
||||
'light',
|
||||
Number(value) > 0 ? 'turn_on' : 'turn_off',
|
||||
{ entity_id: haEntity.entity_id, brightness: Number(value) }
|
||||
);
|
||||
});
|
||||
bridge.addDevice(device, {
|
||||
nodeLabel: haEntity.attributes['friendly_name'],
|
||||
reachable: true,
|
||||
serialNumber: serialFromId,
|
||||
});
|
||||
|
||||
DEVICE_ENTITY_MAP[haEntity.entity_id] = { haEntity, device };
|
||||
}
|
||||
|
||||
function addimmerableLightDeviceToMap(
|
||||
haEntity: HassEntity,
|
||||
haMiddleware: HAMiddleware,
|
||||
bridge: Bridge
|
||||
): void {
|
||||
const device = new DimmableLightDevice();
|
||||
const serialFromId = MD5(haEntity.entity_id).toString();
|
||||
device.addOnOffListener((value, oldValue) => {
|
||||
if (value !== oldValue) {
|
||||
haMiddleware.callAService('light', value ? 'turn_on' : 'turn_off', {
|
||||
entity_id: haEntity.entity_id,
|
||||
});
|
||||
}
|
||||
});
|
||||
device.addCommandHandler(
|
||||
'identify',
|
||||
async ({ request: { identifyTime } }) =>
|
||||
LOGGER.info(
|
||||
`Identify called for OnOffDevice ${haEntity.attributes['friendly_name']} with id: ${serialFromId} and identifyTime: ${identifyTime}`
|
||||
)
|
||||
);
|
||||
|
||||
device.addCurrentLevelListener((value) => {
|
||||
haMiddleware.callAService(
|
||||
'light',
|
||||
Number(value) > 0 ? 'turn_on' : 'turn_off',
|
||||
{ entity_id: haEntity.entity_id, brightness: Number(value) }
|
||||
);
|
||||
});
|
||||
haMiddleware.subscrieToDevice(
|
||||
haEntity.entity_id,
|
||||
(data: HassEvent['data']) => {
|
||||
device.setOnOff((data.new_state as any).state === 'on');
|
||||
device.setCurrentLevel(
|
||||
(data.new_state as any)['attributes']['brightness']
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
bridge.addDevice(device, {
|
||||
nodeLabel: haEntity.attributes['friendly_name'],
|
||||
reachable: true,
|
||||
serialNumber: serialFromId,
|
||||
});
|
||||
|
||||
DEVICE_ENTITY_MAP[haEntity.entity_id] = { haEntity, device };
|
||||
}
|
||||
|
||||
function addOnOffLightDeviceToMap(
|
||||
haEntity: HassEntity,
|
||||
haMiddleware: HAMiddleware,
|
||||
bridge: Bridge
|
||||
) {
|
||||
const device = new OnOffLightDevice();
|
||||
const serialFromId = MD5(haEntity.entity_id).toString();
|
||||
device.addOnOffListener((value, oldValue) => {
|
||||
if (value !== oldValue) {
|
||||
haMiddleware.callAService('light', value ? 'turn_on' : 'turn_off', {
|
||||
entity_id: haEntity.entity_id,
|
||||
});
|
||||
}
|
||||
});
|
||||
device.addCommandHandler(
|
||||
'identify',
|
||||
async ({ request: { identifyTime } }) =>
|
||||
LOGGER.info(
|
||||
`Identify called for OnOffDevice ${haEntity.attributes['friendly_name']} with id: ${serialFromId} and identifyTime: ${identifyTime}`
|
||||
)
|
||||
);
|
||||
haMiddleware.subscrieToDevice(
|
||||
haEntity.entity_id,
|
||||
(data: HassEvent['data']) => {
|
||||
device.setOnOff((data.new_state as any).state === 'on');
|
||||
}
|
||||
);
|
||||
bridge.addDevice(device, {
|
||||
nodeLabel: haEntity.attributes['friendly_name'],
|
||||
reachable: true,
|
||||
serialNumber: serialFromId,
|
||||
});
|
||||
DEVICE_ENTITY_MAP[haEntity.entity_id] = { haEntity, device };
|
||||
}
|
||||
|
||||
const lightsMap: Map<
|
||||
string,
|
||||
(haEntity: HassEntity, haMiddleware: HAMiddleware, bridge: Bridge) => void
|
||||
> = new Map<
|
||||
string,
|
||||
(haEntity: HassEntity, haMiddleware: HAMiddleware, bridge: Bridge) => void
|
||||
>([
|
||||
['onoff', addOnOffLightDeviceToMap],
|
||||
['rgb', addRGBLightDeviceToMap],
|
||||
['brightness', addimmerableLightDeviceToMap],
|
||||
]);
|
||||
|
||||
function setLights(
|
||||
lights: HassEntity[],
|
||||
haMiddleware: HAMiddleware,
|
||||
bridge: Bridge
|
||||
) {
|
||||
lights.forEach((entity) => {
|
||||
LOGGER.info({ colormodes: entity.attributes['supported_color_modes'] });
|
||||
const key = (entity.attributes['supported_color_modes'] as string[])[0];
|
||||
LOGGER.info({ key });
|
||||
const lightBuildFunction = lightsMap.get(key);
|
||||
if (!lightBuildFunction) {
|
||||
throw new Error('Missing ' + key);
|
||||
}
|
||||
return lightBuildFunction(entity, haMiddleware, bridge);
|
||||
});
|
||||
}
|
||||
|
||||
async function setHasEnties(
|
||||
haMiddleware: HAMiddleware,
|
||||
bridge: Bridge
|
||||
): Promise<void> {
|
||||
const entities = await haMiddleware.getStatesPartitionedByType();
|
||||
LOGGER.info({ entities });
|
||||
if (entities['light']) {
|
||||
LOGGER.info('adding ', entities['light'].length, 'light devices');
|
||||
setLights(entities['light'], haMiddleware, bridge);
|
||||
}
|
||||
}
|
||||
|
||||
export async function addAllDevicesToBridge(
|
||||
haMiddleware: HAMiddleware,
|
||||
bridge: Bridge
|
||||
): Promise<void> {
|
||||
await setHasEnties(haMiddleware, bridge);
|
||||
haMiddleware.subscribe();
|
||||
}
|
7
src/matter/devices/MapperType.ts
Normal file
7
src/matter/devices/MapperType.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Device } from "@project-chip/matter-node.js/device";
|
||||
import { HAMiddleware, Bridge } from "..";
|
||||
import { HassEntity } from "../../HA/HAssTypes";
|
||||
|
||||
export type AddHaDeviceToBridge = (haEntity: HassEntity,
|
||||
haMiddleware: HAMiddleware,
|
||||
bridge: Bridge) => Device
|
@ -1,3 +0,0 @@
|
||||
export * from './Bridge';
|
||||
export * from './HAmiddleware';
|
||||
export * from './Mapper';
|
49
src/matter/devices/lights/DimmerableLightDevice.ts
Normal file
49
src/matter/devices/lights/DimmerableLightDevice.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { Device, DimmableLightDevice } from "@project-chip/matter-node.js/device";
|
||||
import { MD5 } from "crypto-js";
|
||||
import { HAMiddleware, Bridge } from "../..";
|
||||
import { HassEntity, StateChangedEvent } from "../../../HA/HAssTypes";
|
||||
import { AddHaDeviceToBridge } from "../MapperType";
|
||||
import { Logger } from "@project-chip/matter-node.js/log";
|
||||
|
||||
const LOGGER = new Logger('DimmableLight');
|
||||
export const addDimmerableLightDevice: AddHaDeviceToBridge = (haEntity: HassEntity, haMiddleware: HAMiddleware, bridge: Bridge): Device => {
|
||||
const device = new DimmableLightDevice();
|
||||
const serialFromId = MD5(haEntity.entity_id).toString();
|
||||
device.addOnOffListener((value, oldValue) => {
|
||||
if (value !== oldValue) {
|
||||
haMiddleware.callAService('light', value ? 'turn_on' : 'turn_off', {
|
||||
entity_id: haEntity.entity_id,
|
||||
});
|
||||
}
|
||||
});
|
||||
device.addCommandHandler(
|
||||
'identify',
|
||||
async ({ request: { identifyTime } }) =>
|
||||
LOGGER.info(
|
||||
`Identify called for OnOffDevice ${haEntity.attributes['friendly_name']} with id: ${serialFromId} and identifyTime: ${identifyTime}`
|
||||
)
|
||||
);
|
||||
|
||||
device.addCurrentLevelListener((value) => {
|
||||
haMiddleware.callAService(
|
||||
'light',
|
||||
Number(value) > 0 ? 'turn_on' : 'turn_off',
|
||||
{ entity_id: haEntity.entity_id, brightness: Number(value) }
|
||||
);
|
||||
});
|
||||
haMiddleware.subscrieToDevice(
|
||||
haEntity.entity_id,
|
||||
(event: StateChangedEvent) => {
|
||||
device.setOnOff(event.data.new_state?.state === 'on');
|
||||
device.setCurrentLevel(
|
||||
(event.data.new_state?.attributes as never)['brightness']);
|
||||
}
|
||||
);
|
||||
|
||||
bridge.addDevice(device, {
|
||||
nodeLabel: haEntity.attributes['friendly_name'],
|
||||
reachable: true,
|
||||
serialNumber: serialFromId,
|
||||
});
|
||||
return device;
|
||||
}
|
39
src/matter/devices/lights/OnOffLightDevice.ts
Normal file
39
src/matter/devices/lights/OnOffLightDevice.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Device, OnOffLightDevice } from "@project-chip/matter-node.js/device";
|
||||
import { MD5 } from "crypto-js";
|
||||
import { HAMiddleware, Bridge } from "../..";
|
||||
import { HassEntity, StateChangedEvent } from "../../../HA/HAssTypes";
|
||||
import { AddHaDeviceToBridge } from "../MapperType";
|
||||
import { Logger } from "@project-chip/matter-node.js/log";
|
||||
|
||||
const LOGGER = new Logger('OnOfflIght');
|
||||
export const addOnOffLightDevice: AddHaDeviceToBridge = (haEntity: HassEntity, haMiddleware: HAMiddleware, bridge: Bridge): Device => {
|
||||
const device = new OnOffLightDevice();
|
||||
const serialFromId = MD5(haEntity.entity_id).toString();
|
||||
device.addOnOffListener((value, oldValue) => {
|
||||
if (value !== oldValue) {
|
||||
haMiddleware.callAService('light', value ? 'turn_on' : 'turn_off', {
|
||||
entity_id: haEntity.entity_id,
|
||||
});
|
||||
}
|
||||
});
|
||||
device.addCommandHandler(
|
||||
'identify',
|
||||
async ({ request: { identifyTime } }) =>
|
||||
LOGGER.info(
|
||||
`Identify called for OnOffDevice ${haEntity.attributes['friendly_name']} with id: ${serialFromId} and identifyTime: ${identifyTime}`
|
||||
)
|
||||
);
|
||||
haMiddleware.subscrieToDevice(
|
||||
haEntity.entity_id,
|
||||
(event: StateChangedEvent) => {
|
||||
device.setOnOff(event.data.new_state?.state === 'on');
|
||||
}
|
||||
);
|
||||
bridge.addDevice(device, {
|
||||
nodeLabel: haEntity.attributes['friendly_name'],
|
||||
reachable: true,
|
||||
serialNumber: serialFromId,
|
||||
});
|
||||
return device;
|
||||
}
|
2
src/matter/devices/lights/index.ts
Normal file
2
src/matter/devices/lights/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './DimmerableLightDevice';
|
||||
export * from './OnOffLightDevice';
|
3
src/matter/index.ts
Normal file
3
src/matter/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './server/Bridge';
|
||||
export * from '../HA/HAmiddleware';
|
||||
export * from './Mapper';
|
Loading…
Reference in New Issue
Block a user