From 2dbc4e5490d0b5f0630588f292ac1cf731644d71 Mon Sep 17 00:00:00 2001 From: t0bst4r Date: Mon, 4 Nov 2024 15:38:46 +0100 Subject: [PATCH] refactor: use custom homeAssistantBehavior for better state management --- apps/home-assistant-matter-hub/package.json | 4 +- package-lock.json | 101 +++++++++++++++--- packages/backend/package.json | 4 +- .../behaviors/basic-information-server.ts | 56 +++++----- .../matter/behaviors/boolean-state-server.ts | 27 ++--- .../color-temperature-control-server.ts | 67 ++++++------ .../extended-color-control-server.ts | 73 ++++++------- .../behaviors/humidity-measurement-server.ts | 26 ++--- .../src/matter/behaviors/identify-server.ts | 9 +- .../matter/behaviors/level-control-server.ts | 42 +++----- .../src/matter/behaviors/lock-server.ts | 55 +++++----- .../behaviors/occupancy-sensing-server.ts | 36 +++---- .../src/matter/behaviors/on-off-server.ts | 33 +++--- .../temperature-measurement-server.ts | 26 ++--- .../src/matter/behaviors/thermostat-server.ts | 8 +- .../thermostat/cooling-thermostat-server.ts | 35 ++---- .../thermostat/default-thermostat-server.ts | 24 ----- .../heating-and-cooling-thermostat-server.ts | 70 ++++-------- .../thermostat/heating-thermostat-server.ts | 36 +++---- .../thermostat/thermostat-base-server.ts | 35 +++--- .../behaviors/window-covering-server.ts | 96 ++++++++--------- packages/backend/src/matter/bridge.ts | 7 +- .../backend/src/matter/create-device.test.ts | 20 +--- packages/backend/src/matter/create-device.ts | 42 +++----- .../matter/custom-behaviors/custom-test.ts | 14 +++ .../home-assistant-behavior.ts | 29 +++++ .../matter/devices/binary-sensor-device.ts | 46 ++++---- .../devices/binary-sensor/contact-sensor.ts | 21 +--- .../devices/binary-sensor/occupancy-sensor.ts | 20 +--- .../src/matter/devices/climate-device.ts | 59 +++++----- .../src/matter/devices/cover-device.ts | 28 ++--- .../backend/src/matter/devices/fan-device.ts | 27 ++--- .../src/matter/devices/light-device.ts | 61 +++++------ .../devices/light/color-temperature-light.ts | 24 +---- .../matter/devices/light/dimmable-light.ts | 21 +--- .../devices/light/extended-color-light.ts | 24 +---- .../light/light-level-control-server.ts | 9 +- .../devices/light/on-off-light-device.ts | 18 +--- .../backend/src/matter/devices/lock-device.ts | 27 ++--- .../src/matter/devices/sensor-device.ts | 51 ++++----- .../matter/devices/sensor/humidity-sensor.ts | 20 +--- .../devices/sensor/temperature-sensor.ts | 20 +--- .../src/matter/devices/switch-device.ts | 29 ++--- packages/backend/src/matter/matter-device.ts | 44 ++------ .../backend/src/matter/mixins/ha-mixin.ts | 28 ----- .../backend/src/utils/json/device-to-json.ts | 2 +- 46 files changed, 639 insertions(+), 915 deletions(-) delete mode 100644 packages/backend/src/matter/behaviors/thermostat/default-thermostat-server.ts create mode 100644 packages/backend/src/matter/custom-behaviors/custom-test.ts create mode 100644 packages/backend/src/matter/custom-behaviors/home-assistant-behavior.ts delete mode 100644 packages/backend/src/matter/mixins/ha-mixin.ts diff --git a/apps/home-assistant-matter-hub/package.json b/apps/home-assistant-matter-hub/package.json index c9f8bb8..fd15915 100644 --- a/apps/home-assistant-matter-hub/package.json +++ b/apps/home-assistant-matter-hub/package.json @@ -49,8 +49,8 @@ "pack": "mkdir -p pack && npm pack --pack-destination pack --json | jq -r .[0].filename > pack/package-name.txt" }, "dependencies": { - "@project-chip/matter.js": "~0.10.6", - "@project-chip/matter-node.js": "~0.10.6", + "@project-chip/matter.js": "~0.11.2", + "@project-chip/matter-node.js": "~0.11.2", "ajv": "^8.17.1", "chalk": "^5.3.0", "color": "^4.2.3", diff --git a/package-lock.json b/package-lock.json index 3767ac9..f549bce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,8 +50,8 @@ "version": "3.0.0-alpha.17", "license": "Apache-2.0", "dependencies": { - "@project-chip/matter-node.js": "~0.10.6", - "@project-chip/matter.js": "~0.10.6", + "@project-chip/matter-node.js": "~0.11.2", + "@project-chip/matter.js": "~0.11.2", "ajv": "^8.17.1", "chalk": "^5.3.0", "color": "^4.2.3", @@ -3242,6 +3242,71 @@ "@lezer/lr": "^1.4.0" } }, + "node_modules/@matter/general": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@matter/general/-/general-0.11.2.tgz", + "integrity": "sha512-todBWZgYfxJK1SU6d7Foczwixzd0/5CT1LFQM2n0AgAjNGPAEfPBAnSE4uJjTrag+Subu5PalEXXFP8JeteAag==", + "dependencies": { + "@noble/curves": "^1.5.0" + } + }, + "node_modules/@matter/model": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@matter/model/-/model-0.11.2.tgz", + "integrity": "sha512-W52AhOF5Xqss9AfOUfqpD7IzK/q9LUGmZKY2CV0ztEO8YpJt2jVsCCOI/XrUeUPZJ94WgT8IyoRG1if4SVtRfw==", + "dependencies": { + "@matter/general": "0.11.2", + "@noble/curves": "^1.5.0" + } + }, + "node_modules/@matter/node": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@matter/node/-/node-0.11.2.tgz", + "integrity": "sha512-OBYciIvsMZbq4bGeN1jCGFvfdOZJurcxAWbk1B7gJ0Ufmhm+JQ7/3H42pcGNdRc0jl2iE7ElNGnfBRzZBJsmDQ==", + "dependencies": { + "@matter/general": "0.11.2", + "@matter/model": "0.11.2", + "@matter/protocol": "0.11.2", + "@matter/types": "0.11.2", + "@noble/curves": "^1.5.0" + } + }, + "node_modules/@matter/nodejs": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@matter/nodejs/-/nodejs-0.11.2.tgz", + "integrity": "sha512-Kwnm3sHXvKj4X0z9JF1J3PNQ2G+fse2MHwSfMZ2sD/l6iSuWzQ1ICpOuQ7HnJ+IhKbeVJTjYOtp4HCXhOhZgPA==", + "dependencies": { + "@matter/general": "0.11.2", + "@matter/node": "0.11.2", + "@matter/protocol": "0.11.2", + "@matter/types": "0.11.2", + "node-localstorage": "^3.0.5" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@matter/protocol": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@matter/protocol/-/protocol-0.11.2.tgz", + "integrity": "sha512-5SbpwlIwVGGRWpHbYWrsedkCIx7A4Rtf5q+W33NfX+qbwQO8po4C27a5xeKtRPJt8ZsGpGdZPJV+oC0UQlK9gg==", + "dependencies": { + "@matter/general": "0.11.2", + "@matter/model": "0.11.2", + "@matter/types": "0.11.2", + "@noble/curves": "^1.5.0" + } + }, + "node_modules/@matter/types": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@matter/types/-/types-0.11.2.tgz", + "integrity": "sha512-cO0oAXeiQxGbyJNozD9xeT3x7teS+D6FguuefFKLNBBR2HTEsXQe3+tHZe75ROrXO9qzzUbKoxFSpLzYpIjWLw==", + "dependencies": { + "@matter/general": "0.11.2", + "@matter/model": "0.11.2", + "@noble/curves": "^1.5.0" + } + }, "node_modules/@mui/base": { "version": "5.0.0-beta.58", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.58.tgz", @@ -4009,22 +4074,28 @@ } }, "node_modules/@project-chip/matter-node.js": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/@project-chip/matter-node.js/-/matter-node.js-0.10.6.tgz", - "integrity": "sha512-i0Yy0dyFiB02IxO91gBRUsr/K9qpMEAed3yFwEvm8QNCEMXh9cKyB15A0ItQS73xFSHCDJJwSMekdd1itcaIew==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@project-chip/matter-node.js/-/matter-node.js-0.11.2.tgz", + "integrity": "sha512-g4LuDPPU70MEQO7Fe9tVXPC3lyOIaKVnkIeDLWAflMYdSzWoYsQYTKhvhEsbDGyGYru580UQhGm5gOo8q0E1bg==", "dependencies": { - "@project-chip/matter.js": "0.10.6", - "node-localstorage": "^3.0.5" + "@matter/general": "0.11.2", + "@matter/nodejs": "0.11.2", + "@project-chip/matter.js": "0.11.2" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@project-chip/matter.js": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/@project-chip/matter.js/-/matter.js-0.10.6.tgz", - "integrity": "sha512-N7VKjciD4nLE/YBoQ7melMiN11scKtCawLdLHLGcKw+UGvUkbHmpdRp0YQkUR9g3G4cWRVMPaBkOkCpk/MBVaA==", - "dependencies": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@project-chip/matter.js/-/matter.js-0.11.2.tgz", + "integrity": "sha512-tG4dJptRLktketVGc/mmhje5Nwbkw/8uuM+sUKiiy13J0dmy8y6jX3vjOf7/zS+6YmG/F8oGcbpTycnjUmhi8g==", + "dependencies": { + "@matter/general": "0.11.2", + "@matter/model": "0.11.2", + "@matter/node": "0.11.2", + "@matter/protocol": "0.11.2", + "@matter/types": "0.11.2", "@noble/curves": "^1.5.0" } }, @@ -5315,7 +5386,7 @@ "version": "22.5.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==", - "dev": true, + "devOptional": true, "dependencies": { "undici-types": "~6.19.2" } @@ -12899,7 +12970,7 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14054,8 +14125,8 @@ "version": "3.0.0-alpha.17", "dependencies": { "@home-assistant-matter-hub/common": "*", - "@project-chip/matter-node.js": "~0.10.6", - "@project-chip/matter.js": "~0.10.6", + "@project-chip/matter-node.js": "~0.11.2", + "@project-chip/matter.js": "~0.11.2", "ajv": "^8.17.1", "chalk": "^5.3.0", "express": "^4.21.1", diff --git a/packages/backend/package.json b/packages/backend/package.json index 89bf703..80aa034 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "@home-assistant-matter-hub/common": "*", - "@project-chip/matter.js": "~0.10.6", - "@project-chip/matter-node.js": "~0.10.6", + "@project-chip/matter.js": "~0.11.2", + "@project-chip/matter-node.js": "~0.11.2", "ajv": "^8.17.1", "chalk": "^5.3.0", "express": "^4.21.1", diff --git a/packages/backend/src/matter/behaviors/basic-information-server.ts b/packages/backend/src/matter/behaviors/basic-information-server.ts index 682575f..82c1919 100644 --- a/packages/backend/src/matter/behaviors/basic-information-server.ts +++ b/packages/backend/src/matter/behaviors/basic-information-server.ts @@ -1,31 +1,35 @@ -import { BridgedDeviceBasicInformationServer as Base } from "@project-chip/matter.js/behavior/definitions/bridged-device-basic-information"; -import { - BridgeBasicInformation, - HomeAssistantEntityState, -} from "@home-assistant-matter-hub/common"; -import { Behavior } from "@project-chip/matter.js/behavior"; +import { BridgedDeviceBasicInformationServer as Base } from "@project-chip/matter.js/behaviors/bridged-device-basic-information"; import crypto from "node:crypto"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; +import { VendorId } from "@project-chip/matter.js/datatype"; -export class BasicInformationServer extends Base {} - -export namespace BasicInformationServer { - export function createState( - basicInformation: BridgeBasicInformation, - state: HomeAssistantEntityState, - ): Behavior.Options { - return { - vendorId: basicInformation.vendorId, - vendorName: maxLengthOrHash(basicInformation.vendorName, 32), - productName: maxLengthOrHash(basicInformation.productName, 32), - productLabel: maxLengthOrHash(basicInformation.productLabel, 64), - hardwareVersion: basicInformation.hardwareVersion, - softwareVersion: basicInformation.softwareVersion, - nodeLabel: maxLengthOrHash( - state.attributes.friendly_name ?? "Unknown Entity", - 32, - ), - reachable: true, - }; +export class BasicInformationServer extends Base { + override async initialize(): Promise { + await super.initialize(); + const homeAssistant = await this.agent.load(HomeAssistantBehavior); + const homeAssistantInfo = homeAssistant.state; + this.state.vendorId = VendorId(homeAssistantInfo.basicInformation.vendorId); + this.state.vendorName = maxLengthOrHash( + homeAssistantInfo.basicInformation.vendorName, + 32, + ); + this.state.productName = maxLengthOrHash( + homeAssistantInfo.basicInformation.productName, + 32, + ); + this.state.productLabel = maxLengthOrHash( + homeAssistantInfo.basicInformation.productLabel, + 64, + ); + this.state.hardwareVersion = + homeAssistantInfo.basicInformation.hardwareVersion; + this.state.softwareVersion = + homeAssistantInfo.basicInformation.softwareVersion; + this.state.nodeLabel = maxLengthOrHash( + homeAssistantInfo.entity.attributes.friendly_name ?? "Unknown Entity", + 32, + ); + this.state.reachable = true; } } diff --git a/packages/backend/src/matter/behaviors/boolean-state-server.ts b/packages/backend/src/matter/behaviors/boolean-state-server.ts index e2ee686..e938f24 100644 --- a/packages/backend/src/matter/behaviors/boolean-state-server.ts +++ b/packages/backend/src/matter/behaviors/boolean-state-server.ts @@ -1,13 +1,17 @@ -import { haMixin } from "../mixins/ha-mixin.js"; import { BooleanStateServer as Base } from "@project-chip/matter.js/behaviors/boolean-state"; import { HomeAssistantEntityState } from "@home-assistant-matter-hub/common"; -import { Behavior } from "@project-chip/matter.js/behavior"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; export function BooleanStateServer(inverted: boolean) { - return class Type extends haMixin("BooleanState", Base) { - override initialize(options?: {}) { - this.endpoint.entityState.subscribe(this.update.bind(this)); - return super.initialize(options); + return class Type extends Base { + override async initialize() { + super.initialize(); + const homeAssistant = await this.agent.load(HomeAssistantBehavior); + this.state.stateValue = getStateValue( + inverted, + homeAssistant.state.entity, + ); + // TODO: subscribe to HA state changes } private async update(state: HomeAssistantEntityState) { @@ -29,14 +33,3 @@ function getStateValue( const isOn = state.state !== "off"; return inverted ? !isOn : isOn; } - -export namespace BooleanStateServer { - export function createState( - inverted: boolean, - state: HomeAssistantEntityState, - ): Behavior.Options { - return { - stateValue: getStateValue(inverted, state), - }; - } -} diff --git a/packages/backend/src/matter/behaviors/color-temperature-control-server.ts b/packages/backend/src/matter/behaviors/color-temperature-control-server.ts index ed6e32b..34a5aab 100644 --- a/packages/backend/src/matter/behaviors/color-temperature-control-server.ts +++ b/packages/backend/src/matter/behaviors/color-temperature-control-server.ts @@ -1,4 +1,3 @@ -import { haMixin } from "../mixins/ha-mixin.js"; import { ColorConverter, HomeAssistantEntityState, @@ -7,15 +6,37 @@ import { } from "@home-assistant-matter-hub/common"; import { ColorControlServer as Base } from "@project-chip/matter.js/behaviors/color-control"; import { ColorControl } from "@project-chip/matter.js/cluster"; -import { Behavior } from "@project-chip/matter.js/behavior"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; -export class ColorTemperatureControlServer extends haMixin( - "ColorControl", - Base.with("ColorTemperature"), +export class ColorTemperatureControlServer extends Base.with( + "ColorTemperature", ) { - override initialize() { - super.initialize(); - this.endpoint.entityState.subscribe(this.update.bind(this)); + override async initialize() { + await super.initialize(); + + const homeAssistant = await this.agent.load(HomeAssistantBehavior); + const state = homeAssistant.state + .entity as HomeAssistantEntityState; + const minKelvin = state.attributes.min_color_temp_kelvin ?? 1500; + + const maxKelvin = state.attributes.max_color_temp_kelvin ?? 8000; + this.state.coupleColorTempToLevelMinMireds = + ColorConverter.temperatureKelvinToMireds(maxKelvin); + this.state.colorTempPhysicalMinMireds = + ColorConverter.temperatureKelvinToMireds(maxKelvin); + this.state.colorTempPhysicalMaxMireds = + ColorConverter.temperatureKelvinToMireds(minKelvin); + this.state.startUpColorTemperatureMireds = + ColorConverter.temperatureKelvinToMireds( + state.attributes.color_temp_kelvin ?? maxKelvin, + ); + if (state.attributes.color_temp_kelvin) { + this.state.colorTemperatureMireds = + ColorConverter.temperatureKelvinToMireds( + state.attributes.color_temp_kelvin, + ); + } + // TODO: subscribe to HA state changes } protected async update( @@ -41,6 +62,7 @@ export class ColorTemperatureControlServer extends haMixin( override async moveToColorTemperature( request: ColorControl.MoveToColorTemperatureRequest, ) { + const homeAssistant = this.agent.get(HomeAssistantBehavior); const targetKelvin = ColorConverter.temperatureMiredsToKelvin( request.colorTemperatureMireds, ); @@ -48,40 +70,15 @@ export class ColorTemperatureControlServer extends haMixin( ...request, transitionTime: request.transitionTime ?? 1, }); - await this.callAction( + await homeAssistant.callAction( "light", "turn_on", { color_temp_kelvin: targetKelvin, }, { - entity_id: this.entity.entity_id, + entity_id: homeAssistant.state.entity.entity_id, }, ); } } - -export namespace ColorTemperatureControlServer { - export function createState( - state: HomeAssistantEntityState, - ): Behavior.Options { - const minKelvin = state.attributes.min_color_temp_kelvin ?? 1500; - const maxKelvin = state.attributes.max_color_temp_kelvin ?? 8000; - return { - coupleColorTempToLevelMinMireds: - ColorConverter.temperatureKelvinToMireds(maxKelvin), - colorTempPhysicalMinMireds: - ColorConverter.temperatureKelvinToMireds(maxKelvin), - colorTempPhysicalMaxMireds: - ColorConverter.temperatureKelvinToMireds(minKelvin), - startUpColorTemperatureMireds: ColorConverter.temperatureKelvinToMireds( - state.attributes.color_temp_kelvin ?? maxKelvin, - ), - colorTemperatureMireds: state.attributes.color_temp_kelvin - ? ColorConverter.temperatureKelvinToMireds( - state.attributes.color_temp_kelvin, - ) - : undefined, - }; - } -} diff --git a/packages/backend/src/matter/behaviors/extended-color-control-server.ts b/packages/backend/src/matter/behaviors/extended-color-control-server.ts index 652100c..5165100 100644 --- a/packages/backend/src/matter/behaviors/extended-color-control-server.ts +++ b/packages/backend/src/matter/behaviors/extended-color-control-server.ts @@ -1,21 +1,41 @@ -import { haMixin } from "../mixins/ha-mixin.js"; import { + ColorConverter, HomeAssistantEntityState, LightDeviceAttributes, LightDeviceColorMode, } from "@home-assistant-matter-hub/common"; -import { ColorConverter } from "@home-assistant-matter-hub/common"; import { ColorControlServer as Base } from "@project-chip/matter.js/behaviors/color-control"; import { ColorControl } from "@project-chip/matter.js/cluster"; -import { Behavior } from "@project-chip/matter.js/behavior"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; -export class ExtendedColorControlServer extends haMixin( - "ColorControl", - Base.with("ColorTemperature", "HueSaturation"), +export class ExtendedColorControlServer extends Base.with( + "ColorTemperature", + "HueSaturation", ) { - override initialize() { - super.initialize(); - this.endpoint.entityState.subscribe(this.update.bind(this)); + override async initialize() { + await super.initialize(); + const homeAssistant = await this.agent.load(HomeAssistantBehavior); + const state = homeAssistant.state + .entity as HomeAssistantEntityState; + const minKelvin = state.attributes.min_color_temp_kelvin ?? 1500; + const maxKelvin = state.attributes.max_color_temp_kelvin ?? 8000; + this.state.coupleColorTempToLevelMinMireds = + ColorConverter.temperatureKelvinToMireds(maxKelvin); + this.state.colorTempPhysicalMinMireds = + ColorConverter.temperatureKelvinToMireds(maxKelvin); + this.state.colorTempPhysicalMaxMireds = + ColorConverter.temperatureKelvinToMireds(minKelvin); + this.state.startUpColorTemperatureMireds = + ColorConverter.temperatureKelvinToMireds( + state.attributes.color_temp_kelvin ?? maxKelvin, + ); + if (state.attributes.color_temp_kelvin) { + this.state.colorTemperatureMireds = + ColorConverter.temperatureKelvinToMireds( + state.attributes.color_temp_kelvin, + ); + } + // TODO: subscribe to HA state changes } protected async update( @@ -55,6 +75,7 @@ export class ExtendedColorControlServer extends haMixin( override async moveToColorTemperature( request: ColorControl.MoveToColorTemperatureRequest, ) { + const homeAssistant = this.agent.get(HomeAssistantBehavior); const targetKelvin = ColorConverter.temperatureMiredsToKelvin( request.colorTemperatureMireds, ); @@ -62,14 +83,14 @@ export class ExtendedColorControlServer extends haMixin( ...request, transitionTime: request.transitionTime ?? 1, }); - await this.callAction( + await homeAssistant.callAction( "light", "turn_on", { color_temp_kelvin: targetKelvin, }, { - entity_id: this.entity.entity_id, + entity_id: homeAssistant.state.entity.entity_id, }, ); } @@ -77,20 +98,21 @@ export class ExtendedColorControlServer extends haMixin( override async moveToHueAndSaturation( request: ColorControl.MoveToHueAndSaturationRequest, ) { + const homeAssistant = this.agent.get(HomeAssistantBehavior); await super.moveToHueAndSaturation({ ...request, transitionTime: request.transitionTime ?? 1, }); const color = ColorConverter.fromMatterHS(request.hue, request.saturation); const [hue, saturation] = ColorConverter.toHomeAssistantHS(color); - await this.callAction( + await homeAssistant.callAction( "light", "turn_on", { hs_color: [hue, saturation], }, { - entity_id: this.entity.entity_id, + entity_id: homeAssistant.state.entity.entity_id, }, ); } @@ -117,28 +139,3 @@ export class ExtendedColorControlServer extends haMixin( return undefined; } } - -export namespace ExtendedColorControlServer { - export function createState( - state: HomeAssistantEntityState, - ): Behavior.Options { - const minKelvin = state.attributes.min_color_temp_kelvin ?? 1500; - const maxKelvin = state.attributes.max_color_temp_kelvin ?? 8000; - return { - coupleColorTempToLevelMinMireds: - ColorConverter.temperatureKelvinToMireds(maxKelvin), - colorTempPhysicalMinMireds: - ColorConverter.temperatureKelvinToMireds(maxKelvin), - colorTempPhysicalMaxMireds: - ColorConverter.temperatureKelvinToMireds(minKelvin), - startUpColorTemperatureMireds: ColorConverter.temperatureKelvinToMireds( - state.attributes.color_temp_kelvin ?? maxKelvin, - ), - colorTemperatureMireds: state.attributes.color_temp_kelvin - ? ColorConverter.temperatureKelvinToMireds( - state.attributes.color_temp_kelvin, - ) - : undefined, - }; - } -} diff --git a/packages/backend/src/matter/behaviors/humidity-measurement-server.ts b/packages/backend/src/matter/behaviors/humidity-measurement-server.ts index 76d497f..328445b 100644 --- a/packages/backend/src/matter/behaviors/humidity-measurement-server.ts +++ b/packages/backend/src/matter/behaviors/humidity-measurement-server.ts @@ -3,16 +3,14 @@ import { HomeAssistantEntityState, SensorDeviceAttributes, } from "@home-assistant-matter-hub/common"; -import { Behavior } from "@project-chip/matter.js/behavior"; -import { haMixin } from "../mixins/ha-mixin.js"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; -export class HumidityMeasurementServer extends haMixin( - "HumidityMeasurement", - Base, -) { - override initialize(options?: {}) { - this.endpoint.entityState.subscribe(this.update.bind(this)); - return super.initialize(options); +export class HumidityMeasurementServer extends Base { + override async initialize() { + await super.initialize(); + const homeAssistant = await this.agent.load(HomeAssistantBehavior); + this.state.measuredValue = getHumidity(homeAssistant.state.entity); + // TODO: subscribe to HA state changes } private async update(state: HomeAssistantEntityState) { @@ -26,16 +24,6 @@ export class HumidityMeasurementServer extends haMixin( } } -export namespace HumidityMeasurementServer { - export function createState( - state: HomeAssistantEntityState, - ): Behavior.Options { - return { - measuredValue: getHumidity(state), - }; - } -} - function getHumidity({ state, }: HomeAssistantEntityState): number | null { diff --git a/packages/backend/src/matter/behaviors/identify-server.ts b/packages/backend/src/matter/behaviors/identify-server.ts index c50535e..1863624 100644 --- a/packages/backend/src/matter/behaviors/identify-server.ts +++ b/packages/backend/src/matter/behaviors/identify-server.ts @@ -1,8 +1,3 @@ -import { IdentifyServer as Base } from "@project-chip/matter.js/behavior/definitions/identify"; -import { haMixin } from "../mixins/ha-mixin.js"; +import { IdentifyServer as Base } from "@project-chip/matter.js/behaviors/identify"; -export class IdentifyServer extends haMixin("Identify", Base) { - override triggerEffect() { - this.logger.info(`Identifying ${this.entity.entity_id}`); - } -} +export class IdentifyServer extends Base {} diff --git a/packages/backend/src/matter/behaviors/level-control-server.ts b/packages/backend/src/matter/behaviors/level-control-server.ts index 52f872b..fe0ab4d 100644 --- a/packages/backend/src/matter/behaviors/level-control-server.ts +++ b/packages/backend/src/matter/behaviors/level-control-server.ts @@ -1,11 +1,10 @@ import { LevelControlServer as Base } from "@project-chip/matter.js/behaviors/level-control"; import { HomeAssistantEntityState } from "@home-assistant-matter-hub/common"; -import { haMixin } from "../mixins/ha-mixin.js"; import { LevelControl } from "@project-chip/matter.js/cluster"; -import { Behavior } from "@project-chip/matter.js/behavior"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; export interface LevelControlConfig { - getValue: (state: HomeAssistantEntityState) => number | null | undefined; + getValue: (state: HomeAssistantEntityState) => number | null; getMinValue?: (state: HomeAssistantEntityState) => number | undefined; getMaxValue?: (state: HomeAssistantEntityState) => number | undefined; moveToLevel: { @@ -15,10 +14,19 @@ export interface LevelControlConfig { } export function LevelControlServer(config: LevelControlConfig) { - return class ThisType extends haMixin("LevelControl", Base) { - override initialize() { + return class ThisType extends Base { + override async initialize() { super.initialize(); - this.endpoint.entityState.subscribe(this.update.bind(this)); + const homeAssistant = await this.agent.load(HomeAssistantBehavior); + const state = homeAssistant.state.entity; + this.state.currentLevel = config.getValue(state); + this.state.minLevel = config.getMinValue?.(state); + this.state.maxLevel = config.getMaxValue?.(state); + this.state.managedTransitionTimeHandling = false; + this.state.onTransitionTime = 1; + this.state.offTransitionTime = 1; + this.state.onOffTransitionTime = 1; + // TODO: subscribe to HA state changes } protected async update(state: HomeAssistantEntityState) { @@ -50,32 +58,16 @@ export function LevelControlServer(config: LevelControlConfig) { } private async handleMoveToLevel(request: LevelControl.MoveToLevelRequest) { + const homeAssistant = this.agent.get(HomeAssistantBehavior); const [domain, action] = config.moveToLevel.action.split("."); - await this.callAction( + await homeAssistant.callAction( domain, action, config.moveToLevel.data(request.level), { - entity_id: this.entity.entity_id, + entity_id: homeAssistant.state.entity.entity_id, }, ); } }; } - -export namespace LevelControlServer { - export function createState( - config: LevelControlConfig, - state: HomeAssistantEntityState, - ): Behavior.Options { - return { - currentLevel: config.getValue(state), - minLevel: config.getMinValue?.(state), - maxLevel: config.getMaxValue?.(state), - managedTransitionTimeHandling: false, - onTransitionTime: 1, - offTransitionTime: 1, - onOffTransitionTime: 1, - }; - } -} diff --git a/packages/backend/src/matter/behaviors/lock-server.ts b/packages/backend/src/matter/behaviors/lock-server.ts index 22a6cfd..c0f82c2 100644 --- a/packages/backend/src/matter/behaviors/lock-server.ts +++ b/packages/backend/src/matter/behaviors/lock-server.ts @@ -1,13 +1,24 @@ -import { haMixin } from "../mixins/ha-mixin.js"; -import { DoorLockServer } from "@project-chip/matter.js/behavior/definitions/door-lock"; +import { DoorLockServer as Base } from "@project-chip/matter.js/behaviors/door-lock"; import { HomeAssistantEntityState } from "@home-assistant-matter-hub/common"; -import { Behavior } from "@project-chip/matter.js/behavior"; import { DoorLock } from "@project-chip/matter.js/cluster"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; -export class LockServer extends haMixin("LockServer", DoorLockServer) { - override initialize() { - super.initialize(); - this.endpoint.entityState.subscribe(this.update.bind(this)); +export class LockServer extends Base { + override async initialize() { + await super.initialize(); + const homeAssistant = await this.agent.load(HomeAssistantBehavior); + this.state.lockState = getMatterLockState(homeAssistant.state.entity); + this.state.lockType = DoorLock.LockType.DeadBolt; + this.state.operatingMode = DoorLock.OperatingMode.Normal; + this.state.actuatorEnabled = true; + this.state.supportedOperatingModes = { + noRemoteLockUnlock: false, + normal: true, + passage: false, + privacy: false, + vacation: false, + }; + // TODO: subscribe to HA state changes } private async update(state: HomeAssistantEntityState) { @@ -22,39 +33,21 @@ export class LockServer extends haMixin("LockServer", DoorLockServer) { override async lockDoor() { super.lockDoor(); - await this.callAction("lock", "lock", undefined, { - entity_id: this.entity.entity_id, + const homeAssistant = this.agent.get(HomeAssistantBehavior); + await homeAssistant.callAction("lock", "lock", undefined, { + entity_id: homeAssistant.state.entity.entity_id, }); } override async unlockDoor() { super.unlockDoor(); - await this.callAction("lock", "unlock", undefined, { - entity_id: this.entity.entity_id, + const homeAssistant = this.agent.get(HomeAssistantBehavior); + await homeAssistant.callAction("lock", "unlock", undefined, { + entity_id: homeAssistant.state.entity.entity_id, }); } } -export namespace LockServer { - export function createState( - state: HomeAssistantEntityState, - ): Behavior.Options { - return { - lockState: getMatterLockState(state), - lockType: DoorLock.LockType.DeadBolt, - operatingMode: DoorLock.OperatingMode.Normal, - actuatorEnabled: true, - supportedOperatingModes: { - noRemoteLockUnlock: false, - normal: true, - passage: false, - privacy: false, - vacation: false, - }, - }; - } -} - function getMatterLockState(state: HomeAssistantEntityState) { return mapHAState[state.state] ?? DoorLock.LockState.NotFullyLocked; } diff --git a/packages/backend/src/matter/behaviors/occupancy-sensing-server.ts b/packages/backend/src/matter/behaviors/occupancy-sensing-server.ts index 86089cf..5fe799e 100644 --- a/packages/backend/src/matter/behaviors/occupancy-sensing-server.ts +++ b/packages/backend/src/matter/behaviors/occupancy-sensing-server.ts @@ -1,13 +1,21 @@ -import { haMixin } from "../mixins/ha-mixin.js"; import { OccupancySensingServer as Base } from "@project-chip/matter.js/behaviors/occupancy-sensing"; import { HomeAssistantEntityState } from "@home-assistant-matter-hub/common"; -import { Behavior } from "@project-chip/matter.js/behavior"; import { OccupancySensing } from "@project-chip/matter.js/cluster"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; -export class OccupancySensingServer extends haMixin("OccupancySensing", Base) { - override initialize(options?: {}) { - this.endpoint.entityState.subscribe(this.update.bind(this)); - return super.initialize(options); +export class OccupancySensingServer extends Base { + override async initialize() { + await super.initialize(); + const homeAssistant = await this.agent.load(HomeAssistantBehavior); + this.state.occupancy = { occupied: isOccupied(homeAssistant.state.entity) }; + this.state.occupancySensorType = + OccupancySensing.OccupancySensorType.PhysicalContact; + this.state.occupancySensorTypeBitmap = { + pir: false, + physicalContact: true, + ultrasonic: false, + }; + // TODO: subscribe to HA state changes } private async update(state: HomeAssistantEntityState) { @@ -24,19 +32,3 @@ export class OccupancySensingServer extends haMixin("OccupancySensing", Base) { function isOccupied(state: HomeAssistantEntityState): boolean { return state.state !== "off"; } - -export namespace OccupancySensingServer { - export function createState( - state: HomeAssistantEntityState, - ): Behavior.Options { - return { - occupancy: { occupied: isOccupied(state) }, - occupancySensorType: OccupancySensing.OccupancySensorType.PhysicalContact, - occupancySensorTypeBitmap: { - pir: false, - physicalContact: true, - ultrasonic: false, - }, - }; - } -} diff --git a/packages/backend/src/matter/behaviors/on-off-server.ts b/packages/backend/src/matter/behaviors/on-off-server.ts index 36ac7cd..63ff9e0 100644 --- a/packages/backend/src/matter/behaviors/on-off-server.ts +++ b/packages/backend/src/matter/behaviors/on-off-server.ts @@ -1,15 +1,16 @@ import { OnOffServer as Base } from "@project-chip/matter.js/behaviors/on-off"; import { HomeAssistantEntityState } from "@home-assistant-matter-hub/common"; -import { haMixin } from "../mixins/ha-mixin.js"; -import { Behavior } from "@project-chip/matter.js/behavior"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; -export class OnOffServer extends haMixin("OnOff", Base) { - override initialize() { +export class OnOffServer extends Base { + override async initialize() { super.initialize(); - this.endpoint.entityState.subscribe(this.update.bind(this)); + const homeAssistant = await this.agent.load(HomeAssistantBehavior); + this.state.onOff = homeAssistant.state.entity.state !== "off"; + // TODO: subscribe to state changes } - protected async update(state: HomeAssistantEntityState) { + private async update(state: HomeAssistantEntityState) { const current = this.endpoint.stateOf(OnOffServer); const isOn = state.state !== "off"; if (isOn !== current.onOff) { @@ -19,25 +20,17 @@ export class OnOffServer extends haMixin("OnOff", Base) { override async on() { await super.on(); - await this.callAction("homeassistant", "turn_on", undefined, { - entity_id: this.entity.entity_id, + const homeAssistant = this.agent.get(HomeAssistantBehavior); + await homeAssistant.callAction("homeassistant", "turn_on", undefined, { + entity_id: homeAssistant.state.entity.entity_id, }); } override async off() { await super.off(); - await this.callAction("homeassistant", "turn_off", undefined, { - entity_id: this.entity.entity_id, + const homeAssistant = this.agent.get(HomeAssistantBehavior); + await homeAssistant.callAction("homeassistant", "turn_off", undefined, { + entity_id: homeAssistant.state.entity.entity_id, }); } } - -export namespace OnOffServer { - export function createState( - state: HomeAssistantEntityState, - ): Behavior.Options { - return { - onOff: state.state !== "off", - }; - } -} diff --git a/packages/backend/src/matter/behaviors/temperature-measurement-server.ts b/packages/backend/src/matter/behaviors/temperature-measurement-server.ts index 1efbd69..96355e5 100644 --- a/packages/backend/src/matter/behaviors/temperature-measurement-server.ts +++ b/packages/backend/src/matter/behaviors/temperature-measurement-server.ts @@ -3,16 +3,14 @@ import { HomeAssistantEntityState, SensorDeviceAttributes, } from "@home-assistant-matter-hub/common"; -import { Behavior } from "@project-chip/matter.js/behavior"; -import { haMixin } from "../mixins/ha-mixin.js"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; -export class TemperatureMeasurementServer extends haMixin( - "TemperatureMeasurement", - Base, -) { - override initialize(options?: {}) { - this.endpoint.entityState.subscribe(this.update.bind(this)); - return super.initialize(options); +export class TemperatureMeasurementServer extends Base { + override async initialize() { + const homeAssistant = await this.agent.load(HomeAssistantBehavior); + this.state.measuredValue = getTemperature(homeAssistant.state.entity); + // this.endpoint.entityState.subscribe(this.update.bind(this)); + await super.initialize(); } private async update(state: HomeAssistantEntityState) { @@ -26,16 +24,6 @@ export class TemperatureMeasurementServer extends haMixin( } } -export namespace TemperatureMeasurementServer { - export function createState( - state: HomeAssistantEntityState, - ): Behavior.Options { - return { - measuredValue: getTemperature(state), - }; - } -} - function getTemperature({ state, attributes, diff --git a/packages/backend/src/matter/behaviors/thermostat-server.ts b/packages/backend/src/matter/behaviors/thermostat-server.ts index e1cf1b1..5fe58bf 100644 --- a/packages/backend/src/matter/behaviors/thermostat-server.ts +++ b/packages/backend/src/matter/behaviors/thermostat-server.ts @@ -1,20 +1,18 @@ import { HeatingAndCoolingThermostatServer } from "./thermostat/heating-and-cooling-thermostat-server.js"; import { HeatingThermostatServer } from "./thermostat/heating-thermostat-server.js"; import { CoolingThermostatServer } from "./thermostat/cooling-thermostat-server.js"; -import { DefaultThermostatServer } from "./thermostat/default-thermostat-server.js"; export function ThermostatServer( supportsCooling: boolean, supportsHeating: boolean, - supportsAuto: boolean, ) { - if (supportsAuto || (supportsCooling && supportsHeating)) { - return HeatingAndCoolingThermostatServer(supportsAuto); + if (supportsCooling && supportsHeating) { + return HeatingAndCoolingThermostatServer; } else if (supportsHeating) { return HeatingThermostatServer; } else if (supportsCooling) { return CoolingThermostatServer; } else { - return DefaultThermostatServer; + return undefined; } } diff --git a/packages/backend/src/matter/behaviors/thermostat/cooling-thermostat-server.ts b/packages/backend/src/matter/behaviors/thermostat/cooling-thermostat-server.ts index 4b8675b..e84fb39 100644 --- a/packages/backend/src/matter/behaviors/thermostat/cooling-thermostat-server.ts +++ b/packages/backend/src/matter/behaviors/thermostat/cooling-thermostat-server.ts @@ -1,22 +1,26 @@ -import { ThermostatServer as Base } from "@project-chip/matter.js/behavior/definitions/thermostat"; +import { ThermostatServer as Base } from "@project-chip/matter.js/behaviors/thermostat"; import { ThermostatBaseServer } from "./thermostat-base-server.js"; -import { - ClimateDeviceAttributes, - HomeAssistantEntityState, -} from "@home-assistant-matter-hub/common"; +import { HomeAssistantEntityState } from "@home-assistant-matter-hub/common"; import { Behavior } from "@project-chip/matter.js/behavior"; import { Thermostat } from "@project-chip/matter.js/cluster"; export class CoolingThermostatServer extends ThermostatBaseServer( Base.with("Cooling"), ) { - override initialize(options?: {}) { + override async initialize() { + await super.initialize(); + this.state.localTemperature = this.currentState.currentTemperature; + this.state.systemMode = this.currentState.systemMode; + this.state.occupiedCoolingSetpoint = this.currentState.targetTemperature; + this.state.minCoolSetpointLimit = this.currentState.minTemperature; + this.state.maxCoolSetpointLimit = this.currentState.maxTemperature; + this.state.controlSequenceOfOperation = + Thermostat.ControlSequenceOfOperation.CoolingOnly; this.endpoint .eventsOf(CoolingThermostatServer) .occupiedCoolingSetpoint$Changed.on( this.targetTemperatureChanged.bind(this), ); - return super.initialize(options); } protected override async update(state: HomeAssistantEntityState) { @@ -49,20 +53,3 @@ export class CoolingThermostatServer extends ThermostatBaseServer( await this.endpoint.setStateOf(CoolingThermostatServer, patch); } } - -export namespace CoolingThermostatServer { - export function createState( - entity: HomeAssistantEntityState, - ): Behavior.Options { - const state = ThermostatBaseServer.createState(entity); - return { - localTemperature: state.currentTemperature, - systemMode: state.systemMode, - occupiedCoolingSetpoint: state.targetTemperature, - minCoolSetpointLimit: state.minTemperature, - maxCoolSetpointLimit: state.maxTemperature, - controlSequenceOfOperation: - Thermostat.ControlSequenceOfOperation.CoolingOnly, - }; - } -} diff --git a/packages/backend/src/matter/behaviors/thermostat/default-thermostat-server.ts b/packages/backend/src/matter/behaviors/thermostat/default-thermostat-server.ts deleted file mode 100644 index fe15aa7..0000000 --- a/packages/backend/src/matter/behaviors/thermostat/default-thermostat-server.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ThermostatBaseServer } from "./thermostat-base-server.js"; -import { ThermostatServer as Base } from "@project-chip/matter.js/behavior/definitions/thermostat"; -import { - ClimateDeviceAttributes, - HomeAssistantEntityState, -} from "@home-assistant-matter-hub/common"; -import { Behavior } from "@project-chip/matter.js/behavior"; -import { Thermostat } from "@project-chip/matter.js/cluster"; - -export class DefaultThermostatServer extends ThermostatBaseServer(Base) {} - -export namespace DefaultThermostatServer { - export function createState( - entity: HomeAssistantEntityState, - ): Behavior.Options { - const state = ThermostatBaseServer.createState(entity); - return { - localTemperature: state.currentTemperature, - systemMode: state.systemMode, - controlSequenceOfOperation: - Thermostat.ControlSequenceOfOperation.CoolingAndHeating, - }; - } -} diff --git a/packages/backend/src/matter/behaviors/thermostat/heating-and-cooling-thermostat-server.ts b/packages/backend/src/matter/behaviors/thermostat/heating-and-cooling-thermostat-server.ts index 2060cdf..e178d99 100644 --- a/packages/backend/src/matter/behaviors/thermostat/heating-and-cooling-thermostat-server.ts +++ b/packages/backend/src/matter/behaviors/thermostat/heating-and-cooling-thermostat-server.ts @@ -1,22 +1,28 @@ -import { ThermostatServer as Base } from "@project-chip/matter.js/behavior/definitions/thermostat"; +import { ThermostatServer as Base } from "@project-chip/matter.js/behaviors/thermostat"; import { ThermostatBaseServer } from "./thermostat-base-server.js"; -import { - ClimateDeviceAttributes, - HomeAssistantEntityState, -} from "@home-assistant-matter-hub/common"; +import { HomeAssistantEntityState } from "@home-assistant-matter-hub/common"; import { Behavior } from "@project-chip/matter.js/behavior"; import { Thermostat } from "@project-chip/matter.js/cluster"; -class HeatingAndCoolingThermostatServerBase extends ThermostatBaseServer( +export class HeatingAndCoolingThermostatServer extends ThermostatBaseServer( Base.with("Heating", "Cooling"), ) { - override initialize(options?: {}) { - this.endpoint - .eventsOf(HeatingAndCoolingThermostatServerBase) - .occupiedHeatingSetpoint$Changed.on( - this.targetTemperatureChanged.bind(this), - ); - return super.initialize(options); + override async initialize() { + await super.initialize(); + this.state.localTemperature = this.currentState.currentTemperature; + this.state.systemMode = this.currentState.systemMode; + this.state.occupiedHeatingSetpoint = this.currentState.targetTemperature; + this.state.occupiedCoolingSetpoint = this.currentState.targetTemperature; + this.state.minHeatSetpointLimit = this.currentState.minTemperature; + this.state.minCoolSetpointLimit = this.currentState.minTemperature; + this.state.maxHeatSetpointLimit = this.currentState.maxTemperature; + this.state.maxCoolSetpointLimit = this.currentState.maxTemperature; + this.state.controlSequenceOfOperation = + Thermostat.ControlSequenceOfOperation.CoolingAndHeating; + + this.events.occupiedHeatingSetpoint$Changed.on( + this.targetTemperatureChanged.bind(this), + ); } protected override async update(state: HomeAssistantEntityState) { @@ -26,11 +32,11 @@ class HeatingAndCoolingThermostatServerBase extends ThermostatBaseServer( return; } const actualState = this.endpoint.stateOf( - HeatingAndCoolingThermostatServerBase, + HeatingAndCoolingThermostatServer, ); const patch: Behavior.PatchStateOf< - typeof HeatingAndCoolingThermostatServerBase + typeof HeatingAndCoolingThermostatServer > = {}; if ( expectedState.targetTemperature !== actualState.occupiedHeatingSetpoint @@ -66,38 +72,6 @@ class HeatingAndCoolingThermostatServerBase extends ThermostatBaseServer( ) { patch.maxCoolSetpointLimit = expectedState.maxTemperature; } - await this.endpoint.setStateOf( - HeatingAndCoolingThermostatServerBase, - patch, - ); + await this.endpoint.setStateOf(HeatingAndCoolingThermostatServer, patch); } } - -export function HeatingAndCoolingThermostatServer(supportsAuto: boolean) { - const result = supportsAuto - ? HeatingAndCoolingThermostatServerBase.with( - "AutoMode", - "Cooling", - "Heating", - ) - : HeatingAndCoolingThermostatServerBase; - return Object.assign(result, { - createState( - entity: HomeAssistantEntityState, - ): Behavior.Options { - const state = ThermostatBaseServer.createState(entity); - return { - localTemperature: state.currentTemperature, - systemMode: state.systemMode, - occupiedHeatingSetpoint: state.targetTemperature, - occupiedCoolingSetpoint: state.targetTemperature, - minHeatSetpointLimit: state.minTemperature, - minCoolSetpointLimit: state.minTemperature, - maxHeatSetpointLimit: state.maxTemperature, - maxCoolSetpointLimit: state.maxTemperature, - controlSequenceOfOperation: - Thermostat.ControlSequenceOfOperation.CoolingAndHeating, - }; - }, - }); -} diff --git a/packages/backend/src/matter/behaviors/thermostat/heating-thermostat-server.ts b/packages/backend/src/matter/behaviors/thermostat/heating-thermostat-server.ts index 69d9698..e230e17 100644 --- a/packages/backend/src/matter/behaviors/thermostat/heating-thermostat-server.ts +++ b/packages/backend/src/matter/behaviors/thermostat/heating-thermostat-server.ts @@ -1,22 +1,27 @@ -import { ThermostatServer as Base } from "@project-chip/matter.js/behavior/definitions/thermostat"; +import { ThermostatServer as Base } from "@project-chip/matter.js/behaviors/thermostat"; import { ThermostatBaseServer } from "./thermostat-base-server.js"; -import { - ClimateDeviceAttributes, - HomeAssistantEntityState, -} from "@home-assistant-matter-hub/common"; +import { HomeAssistantEntityState } from "@home-assistant-matter-hub/common"; import { Behavior } from "@project-chip/matter.js/behavior"; import { Thermostat } from "@project-chip/matter.js/cluster"; export class HeatingThermostatServer extends ThermostatBaseServer( Base.with("Heating"), ) { - override initialize(options?: {}) { + override async initialize() { + await super.initialize(); + this.state.localTemperature = this.currentState.currentTemperature; + this.state.systemMode = this.currentState.systemMode; + this.state.occupiedHeatingSetpoint = this.currentState.targetTemperature; + this.state.minHeatSetpointLimit = this.currentState.minTemperature; + this.state.maxHeatSetpointLimit = this.currentState.maxTemperature; + this.state.controlSequenceOfOperation = + Thermostat.ControlSequenceOfOperation.HeatingOnly; + this.endpoint .eventsOf(HeatingThermostatServer) .occupiedHeatingSetpoint$Changed.on( this.targetTemperatureChanged.bind(this), ); - return super.initialize(options); } protected override async update(state: HomeAssistantEntityState) { @@ -49,20 +54,3 @@ export class HeatingThermostatServer extends ThermostatBaseServer( await this.endpoint.setStateOf(HeatingThermostatServer, patch); } } - -export namespace HeatingThermostatServer { - export function createState( - entity: HomeAssistantEntityState, - ): Behavior.Options { - const state = ThermostatBaseServer.createState(entity); - return { - localTemperature: state.currentTemperature, - systemMode: state.systemMode, - occupiedHeatingSetpoint: state.targetTemperature, - minHeatSetpointLimit: state.minTemperature, - maxHeatSetpointLimit: state.maxTemperature, - controlSequenceOfOperation: - Thermostat.ControlSequenceOfOperation.HeatingOnly, - }; - } -} diff --git a/packages/backend/src/matter/behaviors/thermostat/thermostat-base-server.ts b/packages/backend/src/matter/behaviors/thermostat/thermostat-base-server.ts index f429687..46583ab 100644 --- a/packages/backend/src/matter/behaviors/thermostat/thermostat-base-server.ts +++ b/packages/backend/src/matter/behaviors/thermostat/thermostat-base-server.ts @@ -4,11 +4,11 @@ import { HomeAssistantEntityState, Type, } from "@home-assistant-matter-hub/common"; -import { ThermostatServer as Base } from "@project-chip/matter.js/behavior/definitions/thermostat"; +import { ThermostatServer as Base } from "@project-chip/matter.js/behaviors/thermostat"; import { Thermostat } from "@project-chip/matter.js/cluster"; -import { haMixin } from "../../mixins/ha-mixin.js"; import { Behavior } from "@project-chip/matter.js/behavior"; import _ from "lodash"; +import { HomeAssistantBehavior } from "../../custom-behaviors/home-assistant-behavior.js"; export interface ThermostatState { systemMode: Thermostat.SystemMode; @@ -19,15 +19,21 @@ export interface ThermostatState { } function createBaseServer>(type: T) { - return class ThermostatBaseServer extends haMixin("ThermostatServer", type) { - protected currentState?: ThermostatState; + return class ThermostatBaseServer extends type { + // TODO: move to internal + protected currentState!: ThermostatState; - override initialize(options?: {}) { - this.endpoint.entityState.subscribe(this.update.bind(this)); + override async initialize() { + await super.initialize(); + const homeAssistant = await this.agent.load(HomeAssistantBehavior); + this.currentState = getState( + homeAssistant.state + .entity as HomeAssistantEntityState, + ); this.endpoint .eventsOf(Base) .systemMode$Changed.on(this.systemModeChanged.bind(this)); - return super.initialize(options); + // TODO: subscribe to HA state changes } protected async update(state: HomeAssistantEntityState) { @@ -58,6 +64,7 @@ function createBaseServer>(type: T) { override async setpointRaiseLower( request: Thermostat.SetpointRaiseLowerRequest, ) { + const homeAssistant = this.agent.get(HomeAssistantBehavior); const targetTemperature = this.currentState?.targetTemperature; if (targetTemperature == null) { return; @@ -65,35 +72,37 @@ function createBaseServer>(type: T) { await super.setpointRaiseLower(request); const newTargetTemperature = targetTemperature / 100 + request.amount / 10; - await this.callAction( + await homeAssistant.callAction( "climate", "set_temperature", { temperature: newTargetTemperature }, - { entity_id: this.entity.entity_id }, + { entity_id: homeAssistant.state.entity.entity_id }, ); } protected async targetTemperatureChanged(value: number) { + const homeAssistant = this.agent.get(HomeAssistantBehavior); if (value === this.currentState?.targetTemperature) { return; } - await this.callAction( + await homeAssistant.callAction( "climate", "set_temperature", { temperature: value / 100 }, - { entity_id: this.entity.entity_id }, + { entity_id: homeAssistant.state.entity.entity_id }, ); } private async systemModeChanged(systemMode: Thermostat.SystemMode) { + const homeAssistant = this.agent.get(HomeAssistantBehavior); if (systemMode === this.currentState?.systemMode) { return; } - await this.callAction( + await homeAssistant.callAction( "climate", "set_hvac_mode", { hvac_mode: getHvacMode(systemMode) }, - { entity_id: this.entity.entity_id }, + { entity_id: homeAssistant.state.entity.entity_id }, ); } }; diff --git a/packages/backend/src/matter/behaviors/window-covering-server.ts b/packages/backend/src/matter/behaviors/window-covering-server.ts index b93dafe..75af1bf 100644 --- a/packages/backend/src/matter/behaviors/window-covering-server.ts +++ b/packages/backend/src/matter/behaviors/window-covering-server.ts @@ -1,13 +1,10 @@ -import { WindowCoveringServer as MBase } from "@project-chip/matter.js/behavior/definitions/window-covering"; -import { haMixin } from "../mixins/ha-mixin.js"; +import { WindowCoveringServer as Base } from "@project-chip/matter.js/behaviors/window-covering"; import { CoverDeviceAttributes, HomeAssistantEntityState, } from "@home-assistant-matter-hub/common"; -import { Behavior } from "@project-chip/matter.js/behavior"; import { WindowCovering } from "@project-chip/matter.js/cluster"; - -const Base = MBase.with("Lift", "PositionAwareLift", "AbsolutePosition"); +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; export interface WindowCoveringServerConfig { lift?: { @@ -19,39 +16,71 @@ export interface WindowCoveringServerConfig { } export function WindowCoveringServer(config?: WindowCoveringServerConfig) { - return class ThisType extends haMixin("WindowCovering", Base) { - override initialize(options?: {}) { - this.endpoint.entityState.subscribe(this.update.bind(this)); - return super.initialize(options); + return class ThisType extends Base.with( + "Lift", + "PositionAwareLift", + "AbsolutePosition", + ) { + override async initialize() { + await super.initialize(); + + const homeAssistant = await this.agent.load(HomeAssistantBehavior); + const initialPercentage = convertLiftValue( + (homeAssistant.state.entity.attributes as CoverDeviceAttributes) + .current_position, + config?.lift, + ); + const initialValue = initialPercentage ? initialPercentage * 100 : null; + this.state.type = WindowCovering.WindowCoveringType.Rollershade; + this.state.configStatus = { + operational: true, + onlineReserved: true, + liftPositionAware: true, + liftMovementReversed: false, + }; + this.state.targetPositionLiftPercent100ths = initialValue; + this.state.currentPositionLiftPercent100ths = initialValue; + this.state.installedOpenLimitLift = 0; + this.state.installedClosedLimitLift = 10000; + this.state.operationalStatus = { + global: WindowCovering.MovementStatus.Stopped, + lift: WindowCovering.MovementStatus.Stopped, + }; + this.state.endProductType = WindowCovering.EndProductType.RollerShade; + this.state.mode = {}; + // TODO: subscribe to HA state changes } override async upOrOpen() { await super.upOrOpen(); - await this.callAction( + const homeAssistant = this.agent.get(HomeAssistantBehavior); + await homeAssistant.callAction( "cover", "open_cover", {}, - { entity_id: this.entity.entity_id }, + { entity_id: homeAssistant.state.entity.entity_id }, ); } override async downOrClose() { await super.downOrClose(); - await this.callAction( + const homeAssistant = this.agent.get(HomeAssistantBehavior); + await homeAssistant.callAction( "cover", "close_cover", {}, - { entity_id: this.entity.entity_id }, + { entity_id: homeAssistant.state.entity.entity_id }, ); } override async stopMotion() { super.stopMotion(); - await this.callAction( + const homeAssistant = this.agent.get(HomeAssistantBehavior); + await homeAssistant.callAction( "cover", "stop_cover", {}, - { entity_id: this.entity.entity_id }, + { entity_id: homeAssistant.state.entity.entity_id }, ); } @@ -61,14 +90,15 @@ export function WindowCoveringServer(config?: WindowCoveringServerConfig) { super.goToLiftPercentage(request); const position = this.state.currentPositionLiftPercent100ths!; const targetPosition = convertLiftValue(position / 100, config?.lift); - await this.callAction( + const homeAssistant = this.agent.get(HomeAssistantBehavior); + await homeAssistant.callAction( "cover", "set_cover_position", { position: targetPosition, }, { - entity_id: this.entity.entity_id, + entity_id: homeAssistant.state.entity.entity_id, }, ); } @@ -136,35 +166,3 @@ function convertLiftValue( } return result; } - -export namespace WindowCoveringServer { - export function createState( - state: HomeAssistantEntityState, - config: WindowCoveringServerConfig | undefined, - ): Behavior.Options { - const initialPercentage = convertLiftValue( - state.attributes.current_position, - config?.lift, - ); - const initialValue = initialPercentage ? initialPercentage * 100 : null; - return { - type: WindowCovering.WindowCoveringType.Rollershade, - configStatus: { - operational: true, - onlineReserved: true, - liftPositionAware: true, - liftMovementReversed: false, - }, - targetPositionLiftPercent100ths: initialValue, - currentPositionLiftPercent100ths: initialValue, - installedOpenLimitLift: 0, - installedClosedLimitLift: 10000, - operationalStatus: { - global: WindowCovering.MovementStatus.Stopped, - lift: WindowCovering.MovementStatus.Stopped, - }, - endProductType: WindowCovering.EndProductType.RollerShade, - mode: {}, - }; - } -} diff --git a/packages/backend/src/matter/bridge.ts b/packages/backend/src/matter/bridge.ts index a54cd0a..6685502 100644 --- a/packages/backend/src/matter/bridge.ts +++ b/packages/backend/src/matter/bridge.ts @@ -92,12 +92,7 @@ export class Bridge extends ServiceBase { private async createDevices(aggregator: Endpoint) { for (const entity of this.homeAssistant.registry(this.filter)) { - const device = createDevice( - this.basicInformation, - entity, - this.log, - this.homeAssistant, - ); + const device = createDevice(this.basicInformation, entity); if (device) { await aggregator.add(device); this.matterDevices[entity.entity_id] = device; diff --git a/packages/backend/src/matter/create-device.test.ts b/packages/backend/src/matter/create-device.test.ts index 329d29d..6de97bf 100644 --- a/packages/backend/src/matter/create-device.test.ts +++ b/packages/backend/src/matter/create-device.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, Mocked, vi } from "vitest"; +import { describe, expect, it } from "vitest"; import { BinarySensorDeviceAttributes, BinarySensorDeviceClass, @@ -16,8 +16,6 @@ import { SensorDeviceClass, } from "@home-assistant-matter-hub/common"; import { createDevice } from "./create-device.js"; -import { Logger } from "winston"; -import { HomeAssistantClient } from "../home-assistant/home-assistant-client.js"; import { MatterDevice } from "./matter-device.js"; import { deviceToJson } from "../utils/json/device-to-json.js"; import _ from "lodash"; @@ -109,25 +107,11 @@ const basicInformation: BridgeBasicInformation = { hardwareVersion: 4, }; -const MockLogger = vi.fn( - () => - ({ - child: vi.fn(() => new MockLogger()), - defaultMeta: [], - }) as unknown as Mocked, -); - -const MockHomeAssistantClient = vi.fn( - () => ({}) as unknown as Mocked, -); - describe("createDevice", () => { it("should not use any unknown clusterId", () => { - const logger = new MockLogger(); - const homeAssistant = new MockHomeAssistantClient(); const entities = Object.values(testEntities).flat(); const devices = entities.map((entity) => - createDevice(basicInformation, entity, logger, homeAssistant), + createDevice(basicInformation, entity), ); const actual = _.uniq( devices diff --git a/packages/backend/src/matter/create-device.ts b/packages/backend/src/matter/create-device.ts index 898a900..12f5ab5 100644 --- a/packages/backend/src/matter/create-device.ts +++ b/packages/backend/src/matter/create-device.ts @@ -3,55 +3,45 @@ import { type HomeAssistantDomain, HomeAssistantEntityRegistryWithInitialState, } from "@home-assistant-matter-hub/common"; -import { createChildLogger } from "../logging/create-child-logger.js"; -import { MatterDevice, MatterDeviceProps } from "./matter-device.js"; +import { MatterDevice } from "./matter-device.js"; import { LightDevice } from "./devices/light-device.js"; import { SwitchDevice } from "./devices/switch-device.js"; import { LockDevice } from "./devices/lock-device.js"; import { FanDevice } from "./devices/fan-device.js"; import { BinarySensorDevice } from "./devices/binary-sensor-device.js"; import { SensorDevice } from "./devices/sensor-device.js"; -import { CoverDevice, CoverDeviceConfig } from "./devices/cover-device.js"; +import { CoverDevice } from "./devices/cover-device.js"; import { ClimateDevice } from "./devices/climate-device.js"; -import { Logger } from "winston"; -import { HomeAssistantClient } from "../home-assistant/home-assistant-client.js"; export function createDevice( basicInformation: BridgeBasicInformation, entity: HomeAssistantEntityRegistryWithInitialState, - logger: Logger, - homeAssistant: HomeAssistantClient, ): MatterDevice | undefined { const domain = entity.entity_id.split(".")[0] as HomeAssistantDomain; const factory = deviceCtrs[domain]; if (!factory) { return undefined; } - return factory(basicInformation, { - logger: createChildLogger(logger, entity.entity_id), - actions: homeAssistant, - entity, - }); + return factory(basicInformation, entity); } const deviceCtrs: Record< HomeAssistantDomain, ( basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, + registry: HomeAssistantEntityRegistryWithInitialState, ) => MatterDevice | undefined > = { - light: (b, p) => new LightDevice(b, p), - switch: (b, p) => new SwitchDevice(b, p), - lock: (b, p) => new LockDevice(b, p), - fan: (b, p) => new FanDevice(b, p), - binary_sensor: (b, p) => new BinarySensorDevice(b, p), - sensor: (b, p) => SensorDevice(b, p), - cover: (b, p) => - new CoverDevice(b, p as MatterDeviceProps), - climate: (b, p) => new ClimateDevice(b, p), - input_boolean: (b, p) => new SwitchDevice(b, p), - automation: (b, p) => new SwitchDevice(b, p), - script: (b, p) => new SwitchDevice(b, p), - scene: (b, p) => new SwitchDevice(b, p), + light: (b, r) => new LightDevice(b, r), + switch: (b, r) => new SwitchDevice(b, r), + lock: (b, r) => new LockDevice(b, r), + fan: (b, r) => new FanDevice(b, r), + binary_sensor: (b, r) => new BinarySensorDevice(b, r), + sensor: (b, r) => SensorDevice(b, r), + cover: (b, r) => new CoverDevice(b, r), + climate: (b, r) => ClimateDevice(b, r), + input_boolean: (b, r) => new SwitchDevice(b, r), + automation: (b, r) => new SwitchDevice(b, r), + script: (b, r) => new SwitchDevice(b, r), + scene: (b, r) => new SwitchDevice(b, r), }; diff --git a/packages/backend/src/matter/custom-behaviors/custom-test.ts b/packages/backend/src/matter/custom-behaviors/custom-test.ts new file mode 100644 index 0000000..11a0769 --- /dev/null +++ b/packages/backend/src/matter/custom-behaviors/custom-test.ts @@ -0,0 +1,14 @@ +import { Behavior } from "@project-chip/matter.js/behavior"; + +export class CustomBehavior extends Behavior { + static override readonly id = "myCustom"; + declare state: CustomBehavior.State; +} + +export namespace CustomBehavior { + export class State { + first!: number; + second!: string; + third!: { four: string; five: { six: number; seven: string } }; + } +} diff --git a/packages/backend/src/matter/custom-behaviors/home-assistant-behavior.ts b/packages/backend/src/matter/custom-behaviors/home-assistant-behavior.ts new file mode 100644 index 0000000..65df4ef --- /dev/null +++ b/packages/backend/src/matter/custom-behaviors/home-assistant-behavior.ts @@ -0,0 +1,29 @@ +import { Behavior } from "@project-chip/matter.js/behavior"; +import { + BridgeBasicInformation, + HomeAssistantEntityRegistry, + HomeAssistantEntityState, +} from "@home-assistant-matter-hub/common"; +import { HassServiceTarget } from "home-assistant-js-websocket/dist/types.js"; + +export class HomeAssistantBehavior extends Behavior { + static override readonly id = "homeAssistant"; + declare state: HomeAssistantBehavior.State; + + async callAction( + domain: string, + action: string, + data: object | undefined, + target: HassServiceTarget, + ) { + console.log(`${domain}.${action}: ${data} -> ${target}`); + } +} + +export namespace HomeAssistantBehavior { + export class State { + basicInformation!: BridgeBasicInformation; + registry!: HomeAssistantEntityRegistry; + entity!: HomeAssistantEntityState; + } +} diff --git a/packages/backend/src/matter/devices/binary-sensor-device.ts b/packages/backend/src/matter/devices/binary-sensor-device.ts index 20541e5..0cc00ce 100644 --- a/packages/backend/src/matter/devices/binary-sensor-device.ts +++ b/packages/backend/src/matter/devices/binary-sensor-device.ts @@ -1,20 +1,14 @@ -import { MatterDevice, MatterDeviceProps } from "../matter-device.js"; +import { MatterDevice } from "../matter-device.js"; import { BinarySensorDeviceAttributes, BinarySensorDeviceClass, BridgeBasicInformation, HomeAssistantEntityRegistryWithInitialState, + HomeAssistantEntityState, } from "@home-assistant-matter-hub/common"; -import { EndpointType } from "@project-chip/matter.js/endpoint/type"; import { Endpoint } from "@project-chip/matter.js/endpoint"; -import { - contactSensorOptions, - ContactSensorType, -} from "./binary-sensor/contact-sensor.js"; -import { - occupancySensorOptions, - OccupancySensorType, -} from "./binary-sensor/occupancy-sensor.js"; +import { ContactSensorType } from "./binary-sensor/contact-sensor.js"; +import { OccupancySensorType } from "./binary-sensor/occupancy-sensor.js"; const contactTypes: Array = [ BinarySensorDeviceClass.Door, @@ -30,28 +24,28 @@ const occupancyTypes: Array = [ ]; const defaultDeviceType = ContactSensorType; -const defaultDeviceOptions = contactSensorOptions; export class BinarySensorDevice extends MatterDevice { constructor( basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, + registry: HomeAssistantEntityRegistryWithInitialState, ) { const entity = - props.entity as HomeAssistantEntityRegistryWithInitialState; - const deviceClass = entity.initialState.attributes.device_class; + registry.initialState as HomeAssistantEntityState; + const deviceClass = entity.attributes.device_class; - let type: EndpointType, options: Endpoint.Options; - if (contactTypes.includes(deviceClass)) { - type = ContactSensorType; - options = contactSensorOptions(basicInformation, props); - } else if (occupancyTypes.includes(deviceClass)) { - type = OccupancySensorType; - options = occupancySensorOptions(basicInformation, props); - } else { - type = defaultDeviceType; - options = defaultDeviceOptions(basicInformation, props); - } - super(type, options, props); + const type = contactTypes.includes(deviceClass) + ? ContactSensorType + : occupancyTypes.includes(deviceClass) + ? OccupancySensorType + : defaultDeviceType; + const options: Endpoint.Options = { + homeAssistant: { + basicInformation, + registry: registry, + entity: entity, + }, + }; + super(type, registry.entity_id, options); } } diff --git a/packages/backend/src/matter/devices/binary-sensor/contact-sensor.ts b/packages/backend/src/matter/devices/binary-sensor/contact-sensor.ts index 3673137..34db30c 100644 --- a/packages/backend/src/matter/devices/binary-sensor/contact-sensor.ts +++ b/packages/backend/src/matter/devices/binary-sensor/contact-sensor.ts @@ -1,29 +1,12 @@ -import { MatterDeviceProps } from "../../matter-device.js"; import { ContactSensorDevice } from "@project-chip/matter.js/devices/ContactSensorDevice"; import { BasicInformationServer } from "../../behaviors/basic-information-server.js"; import { IdentifyServer } from "../../behaviors/identify-server.js"; -import { BridgeBasicInformation } from "@home-assistant-matter-hub/common"; -import { Endpoint } from "@project-chip/matter.js/endpoint"; import { BooleanStateServer } from "../../behaviors/boolean-state-server.js"; +import { HomeAssistantBehavior } from "../../custom-behaviors/home-assistant-behavior.js"; export const ContactSensorType = ContactSensorDevice.with( BasicInformationServer, IdentifyServer, + HomeAssistantBehavior, BooleanStateServer(true), ); - -export function contactSensorOptions( - basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, -): Endpoint.Options { - return { - booleanState: BooleanStateServer.createState( - true, - props.entity.initialState, - ), - bridgedDeviceBasicInformation: BasicInformationServer.createState( - basicInformation, - props.entity.initialState, - ), - }; -} diff --git a/packages/backend/src/matter/devices/binary-sensor/occupancy-sensor.ts b/packages/backend/src/matter/devices/binary-sensor/occupancy-sensor.ts index dfdc63d..cd475c5 100644 --- a/packages/backend/src/matter/devices/binary-sensor/occupancy-sensor.ts +++ b/packages/backend/src/matter/devices/binary-sensor/occupancy-sensor.ts @@ -1,28 +1,12 @@ import { OccupancySensorDevice } from "@project-chip/matter.js/devices/OccupancySensorDevice"; import { BasicInformationServer } from "../../behaviors/basic-information-server.js"; import { IdentifyServer } from "../../behaviors/identify-server.js"; -import { BridgeBasicInformation } from "@home-assistant-matter-hub/common"; -import { MatterDeviceProps } from "../../matter-device.js"; -import { Endpoint } from "@project-chip/matter.js/endpoint"; import { OccupancySensingServer } from "../../behaviors/occupancy-sensing-server.js"; +import { HomeAssistantBehavior } from "../../custom-behaviors/home-assistant-behavior.js"; export const OccupancySensorType = OccupancySensorDevice.with( BasicInformationServer, IdentifyServer, + HomeAssistantBehavior, OccupancySensingServer, ); - -export function occupancySensorOptions( - basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, -): Endpoint.Options { - return { - occupancySensing: OccupancySensingServer.createState( - props.entity.initialState, - ), - bridgedDeviceBasicInformation: BasicInformationServer.createState( - basicInformation, - props.entity.initialState, - ), - }; -} diff --git a/packages/backend/src/matter/devices/climate-device.ts b/packages/backend/src/matter/devices/climate-device.ts index 59f6600..651dea0 100644 --- a/packages/backend/src/matter/devices/climate-device.ts +++ b/packages/backend/src/matter/devices/climate-device.ts @@ -1,4 +1,4 @@ -import { MatterDevice, MatterDeviceProps } from "../matter-device.js"; +import { MatterDevice } from "../matter-device.js"; import { ThermostatDevice } from "@project-chip/matter.js/devices/ThermostatDevice"; import { BasicInformationServer } from "../behaviors/basic-information-server.js"; import { IdentifyServer } from "../behaviors/identify-server.js"; @@ -6,14 +6,17 @@ import { BridgeBasicInformation, ClimateDeviceAttributes, ClimateHvacMode, + HomeAssistantEntityRegistryWithInitialState, HomeAssistantEntityState, } from "@home-assistant-matter-hub/common"; import { ThermostatServer } from "../behaviors/thermostat-server.js"; import { Endpoint } from "@project-chip/matter.js/endpoint"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; const ClimateDeviceType = ThermostatDevice.with( BasicInformationServer, IdentifyServer, + HomeAssistantBehavior, ); const coolingModes: ClimateHvacMode[] = [ @@ -25,35 +28,29 @@ const heatingModes: ClimateHvacMode[] = [ ClimateHvacMode.heat, ]; -export class ClimateDevice extends MatterDevice { - constructor( - basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, - ) { - const entity = props.entity - .initialState as HomeAssistantEntityState; - const supportsCooling = coolingModes.some((mode) => - entity.attributes.hvac_modes.includes(mode), - ); - const supportsHeating = heatingModes.some((mode) => - entity.attributes.hvac_modes.includes(mode), - ); - const supportsAuto = entity.attributes.hvac_modes.includes( - ClimateHvacMode.auto, - ); - const thermostat = ThermostatServer( - supportsCooling, - supportsHeating, - supportsAuto, - ); - const type = ClimateDeviceType.with(thermostat); - const options: Endpoint.Options = { - bridgedDeviceBasicInformation: BasicInformationServer.createState( - basicInformation, - entity, - ), - thermostat: thermostat.createState(entity), - }; - super(type, options, props); +export function ClimateDevice( + basicInformation: BridgeBasicInformation, + registry: HomeAssistantEntityRegistryWithInitialState, +): MatterDevice | undefined { + const entity = + registry.initialState as HomeAssistantEntityState; + const supportsCooling = coolingModes.some((mode) => + entity.attributes.hvac_modes.includes(mode), + ); + const supportsHeating = heatingModes.some((mode) => + entity.attributes.hvac_modes.includes(mode), + ); + const thermostat = ThermostatServer(supportsCooling, supportsHeating); + if (!thermostat) { + return undefined; } + const type = ClimateDeviceType.with(thermostat); + const options: Endpoint.Options = { + homeAssistant: { + basicInformation, + registry, + entity: entity, + }, + }; + return new MatterDevice(type, registry.entity_id, options); } diff --git a/packages/backend/src/matter/devices/cover-device.ts b/packages/backend/src/matter/devices/cover-device.ts index 09a7014..1bb8a6b 100644 --- a/packages/backend/src/matter/devices/cover-device.ts +++ b/packages/backend/src/matter/devices/cover-device.ts @@ -1,4 +1,4 @@ -import { MatterDevice, MatterDeviceProps } from "../matter-device.js"; +import { MatterDevice } from "../matter-device.js"; import { WindowCoveringDevice } from "@project-chip/matter.js/devices/WindowCoveringDevice"; import { BasicInformationServer } from "../behaviors/basic-information-server.js"; import { IdentifyServer } from "../behaviors/identify-server.js"; @@ -6,12 +6,18 @@ import { WindowCoveringServer, WindowCoveringServerConfig, } from "../behaviors/window-covering-server.js"; -import { BridgeBasicInformation } from "@home-assistant-matter-hub/common"; +import { + BridgeBasicInformation, + HomeAssistantEntityRegistryWithInitialState, + HomeAssistantEntityState, +} from "@home-assistant-matter-hub/common"; import { Endpoint } from "@project-chip/matter.js/endpoint"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; const CoverDeviceType = WindowCoveringDevice.with( BasicInformationServer, IdentifyServer, + HomeAssistantBehavior, ); export interface CoverDeviceConfig extends WindowCoveringServerConfig {} @@ -19,20 +25,18 @@ export interface CoverDeviceConfig extends WindowCoveringServerConfig {} export class CoverDevice extends MatterDevice { constructor( basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, + registry: HomeAssistantEntityRegistryWithInitialState, ) { - const type = CoverDeviceType.with(WindowCoveringServer(props.deviceConfig)); + const entity = registry.initialState as HomeAssistantEntityState; + const type = CoverDeviceType.with(WindowCoveringServer()); const options: Endpoint.Options = { - bridgedDeviceBasicInformation: BasicInformationServer.createState( + homeAssistant: { basicInformation, - props.entity.initialState, - ), - windowCovering: WindowCoveringServer.createState( - props.entity.initialState, - props.deviceConfig, - ), + registry: registry, + entity: entity, + }, }; - super(type, options, props); + super(type, registry.entity_id, options); } } diff --git a/packages/backend/src/matter/devices/fan-device.ts b/packages/backend/src/matter/devices/fan-device.ts index a53202c..92129e6 100644 --- a/packages/backend/src/matter/devices/fan-device.ts +++ b/packages/backend/src/matter/devices/fan-device.ts @@ -1,7 +1,8 @@ -import { MatterDevice, MatterDeviceProps } from "../matter-device.js"; +import { MatterDevice } from "../matter-device.js"; import { BridgeBasicInformation, FanDeviceAttributes, + HomeAssistantEntityRegistryWithInitialState, HomeAssistantEntityState, } from "@home-assistant-matter-hub/common"; import { OnOffPlugInUnitDevice } from "@project-chip/matter.js/devices/OnOffPlugInUnitDevice"; @@ -12,6 +13,7 @@ import { LevelControlConfig, LevelControlServer, } from "../behaviors/level-control-server.js"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; const fanLevelConfig: LevelControlConfig = { getValue: (state: HomeAssistantEntityState) => { @@ -32,28 +34,21 @@ const FanDeviceType = OnOffPlugInUnitDevice.with( IdentifyServer, BasicInformationServer, OnOffServer, + HomeAssistantBehavior, LevelControlServer(fanLevelConfig), ); export class FanDevice extends MatterDevice { constructor( basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, + registry: HomeAssistantEntityRegistryWithInitialState, ) { - super( - FanDeviceType, - { - onOff: OnOffServer.createState(props.entity.initialState), - levelControl: LevelControlServer.createState( - fanLevelConfig, - props.entity.initialState, - ), - bridgedDeviceBasicInformation: BasicInformationServer.createState( - basicInformation, - props.entity.initialState, - ), + super(FanDeviceType, registry.entity_id, { + homeAssistant: { + basicInformation, + registry: registry, + entity: registry.initialState, }, - props, - ); + }); } } diff --git a/packages/backend/src/matter/devices/light-device.ts b/packages/backend/src/matter/devices/light-device.ts index be9a746..13c58ab 100644 --- a/packages/backend/src/matter/devices/light-device.ts +++ b/packages/backend/src/matter/devices/light-device.ts @@ -1,28 +1,16 @@ -import { MatterDevice, MatterDeviceProps } from "../matter-device.js"; +import { MatterDevice } from "../matter-device.js"; import { BridgeBasicInformation, HomeAssistantEntityRegistryWithInitialState, + HomeAssistantEntityState, LightDeviceAttributes, LightDeviceColorMode, } from "@home-assistant-matter-hub/common"; -import { - extendedColorLightOptions, - ExtendedColorLightType, -} from "./light/extended-color-light.js"; -import { EndpointType } from "@project-chip/matter.js/endpoint/type"; +import { ExtendedColorLightType } from "./light/extended-color-light.js"; import { Endpoint } from "@project-chip/matter.js/endpoint"; -import { - colorTemperatureLightOptions, - ColorTemperatureLightType, -} from "./light/color-temperature-light.js"; -import { - dimmableLightOptions, - DimmableLightType, -} from "./light/dimmable-light.js"; -import { - onOffLightOptions, - OnOffLightType, -} from "./light/on-off-light-device.js"; +import { ColorTemperatureLightType } from "./light/color-temperature-light.js"; +import { DimmableLightType } from "./light/dimmable-light.js"; +import { OnOffLightType } from "./light/on-off-light-device.js"; const brightnessModes: LightDeviceColorMode[] = Object.values( LightDeviceColorMode, @@ -41,12 +29,12 @@ const colorModes: LightDeviceColorMode[] = [ export class LightDevice extends MatterDevice { constructor( basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, + registry: HomeAssistantEntityRegistryWithInitialState, ) { const entity = - props.entity as HomeAssistantEntityRegistryWithInitialState; + registry.initialState as HomeAssistantEntityState; const supportedColorModes: LightDeviceColorMode[] = - entity.initialState.attributes.supported_color_modes ?? []; + entity.attributes.supported_color_modes ?? []; const supportsBrightness = supportedColorModes.some((mode) => brightnessModes.includes(mode), ); @@ -57,21 +45,20 @@ export class LightDevice extends MatterDevice { LightDeviceColorMode.COLOR_TEMP, ); - let type: EndpointType; - let options: Endpoint.Options; - if (supportsColorControl) { - type = ExtendedColorLightType; - options = extendedColorLightOptions(basicInformation, props); - } else if (supportsColorTemperature) { - type = ColorTemperatureLightType; - options = colorTemperatureLightOptions(basicInformation, props); - } else if (supportsBrightness) { - type = DimmableLightType; - options = dimmableLightOptions(basicInformation, props); - } else { - type = OnOffLightType; - options = onOffLightOptions(basicInformation, props); - } - super(type, options, props); + const type = supportsColorControl + ? ExtendedColorLightType + : supportsColorTemperature + ? ColorTemperatureLightType + : supportsBrightness + ? DimmableLightType + : OnOffLightType; + const options: Endpoint.Options = { + homeAssistant: { + basicInformation, + registry: registry, + entity: entity, + }, + }; + super(type, registry.entity_id, options); } } diff --git a/packages/backend/src/matter/devices/light/color-temperature-light.ts b/packages/backend/src/matter/devices/light/color-temperature-light.ts index 40f2a76..0b78b2b 100644 --- a/packages/backend/src/matter/devices/light/color-temperature-light.ts +++ b/packages/backend/src/matter/devices/light/color-temperature-light.ts @@ -1,36 +1,16 @@ import { ColorTemperatureLightDevice as Device } from "@project-chip/matter.js/devices/ColorTemperatureLightDevice"; -import { MatterDeviceProps } from "../../matter-device.js"; import { IdentifyServer } from "../../behaviors/identify-server.js"; import { BasicInformationServer } from "../../behaviors/basic-information-server.js"; import { OnOffServer } from "../../behaviors/on-off-server.js"; -import { BridgeBasicInformation } from "@home-assistant-matter-hub/common"; import { LightLevelControlServer } from "./light-level-control-server.js"; import { ColorTemperatureControlServer } from "../../behaviors/color-temperature-control-server.js"; -import { Endpoint } from "@project-chip/matter.js/endpoint"; +import { HomeAssistantBehavior } from "../../custom-behaviors/home-assistant-behavior.js"; export const ColorTemperatureLightType = Device.with( IdentifyServer, BasicInformationServer, + HomeAssistantBehavior, OnOffServer, LightLevelControlServer, ColorTemperatureControlServer, ); - -export function colorTemperatureLightOptions( - basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, -): Endpoint.Options { - return { - onOff: OnOffServer.createState(props.entity.initialState), - levelControl: LightLevelControlServer.createState( - props.entity.initialState, - ), - colorControl: ColorTemperatureControlServer.createState( - props.entity.initialState, - ), - bridgedDeviceBasicInformation: BasicInformationServer.createState( - basicInformation, - props.entity.initialState, - ), - }; -} diff --git a/packages/backend/src/matter/devices/light/dimmable-light.ts b/packages/backend/src/matter/devices/light/dimmable-light.ts index 4a5aa77..283e376 100644 --- a/packages/backend/src/matter/devices/light/dimmable-light.ts +++ b/packages/backend/src/matter/devices/light/dimmable-light.ts @@ -1,31 +1,14 @@ import { DimmableLightDevice as Device } from "@project-chip/matter.js/devices/DimmableLightDevice"; -import { MatterDeviceProps } from "../../matter-device.js"; import { IdentifyServer } from "../../behaviors/identify-server.js"; import { BasicInformationServer } from "../../behaviors/basic-information-server.js"; import { OnOffServer } from "../../behaviors/on-off-server.js"; -import { BridgeBasicInformation } from "@home-assistant-matter-hub/common"; import { LightLevelControlServer } from "./light-level-control-server.js"; -import { Endpoint } from "@project-chip/matter.js/endpoint"; +import { HomeAssistantBehavior } from "../../custom-behaviors/home-assistant-behavior.js"; export const DimmableLightType = Device.with( IdentifyServer, BasicInformationServer, + HomeAssistantBehavior, OnOffServer, LightLevelControlServer, ); - -export function dimmableLightOptions( - basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, -): Endpoint.Options { - return { - onOff: OnOffServer.createState(props.entity.initialState), - levelControl: LightLevelControlServer.createState( - props.entity.initialState, - ), - bridgedDeviceBasicInformation: BasicInformationServer.createState( - basicInformation, - props.entity.initialState, - ), - }; -} diff --git a/packages/backend/src/matter/devices/light/extended-color-light.ts b/packages/backend/src/matter/devices/light/extended-color-light.ts index c7ba6bb..471e229 100644 --- a/packages/backend/src/matter/devices/light/extended-color-light.ts +++ b/packages/backend/src/matter/devices/light/extended-color-light.ts @@ -1,36 +1,16 @@ import { ExtendedColorLightDevice as Device } from "@project-chip/matter.js/devices/ExtendedColorLightDevice"; -import { MatterDeviceProps } from "../../matter-device.js"; import { IdentifyServer } from "../../behaviors/identify-server.js"; import { BasicInformationServer } from "../../behaviors/basic-information-server.js"; import { OnOffServer } from "../../behaviors/on-off-server.js"; -import { BridgeBasicInformation } from "@home-assistant-matter-hub/common"; import { LightLevelControlServer } from "./light-level-control-server.js"; import { ExtendedColorControlServer } from "../../behaviors/extended-color-control-server.js"; -import { Endpoint } from "@project-chip/matter.js/endpoint"; +import { HomeAssistantBehavior } from "../../custom-behaviors/home-assistant-behavior.js"; export const ExtendedColorLightType = Device.with( IdentifyServer, BasicInformationServer, + HomeAssistantBehavior, OnOffServer, LightLevelControlServer, ExtendedColorControlServer, ); - -export function extendedColorLightOptions( - basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, -): Endpoint.Options { - return { - onOff: OnOffServer.createState(props.entity.initialState), - levelControl: LightLevelControlServer.createState( - props.entity.initialState, - ), - colorControl: ExtendedColorControlServer.createState( - props.entity.initialState, - ), - bridgedDeviceBasicInformation: BasicInformationServer.createState( - basicInformation, - props.entity.initialState, - ), - }; -} diff --git a/packages/backend/src/matter/devices/light/light-level-control-server.ts b/packages/backend/src/matter/devices/light/light-level-control-server.ts index 8229a19..d1a90e7 100644 --- a/packages/backend/src/matter/devices/light/light-level-control-server.ts +++ b/packages/backend/src/matter/devices/light/light-level-control-server.ts @@ -20,11 +20,6 @@ const lightLevelControlConfig: LevelControlConfig = { data: (brightness) => ({ brightness: (brightness / 254) * 255 }), }, }; -export const LightLevelControlServer = Object.assign( - LevelControlServer(lightLevelControlConfig), - { - createState(state: HomeAssistantEntityState) { - return LevelControlServer.createState(lightLevelControlConfig, state); - }, - }, +export const LightLevelControlServer = LevelControlServer( + lightLevelControlConfig, ); diff --git a/packages/backend/src/matter/devices/light/on-off-light-device.ts b/packages/backend/src/matter/devices/light/on-off-light-device.ts index a93ee1c..1044e48 100644 --- a/packages/backend/src/matter/devices/light/on-off-light-device.ts +++ b/packages/backend/src/matter/devices/light/on-off-light-device.ts @@ -1,26 +1,12 @@ import { OnOffLightDevice as Device } from "@project-chip/matter.js/devices/OnOffLightDevice"; -import { MatterDeviceProps } from "../../matter-device.js"; import { IdentifyServer } from "../../behaviors/identify-server.js"; import { BasicInformationServer } from "../../behaviors/basic-information-server.js"; import { OnOffServer } from "../../behaviors/on-off-server.js"; -import { BridgeBasicInformation } from "@home-assistant-matter-hub/common"; -import { Endpoint } from "@project-chip/matter.js/endpoint"; +import { HomeAssistantBehavior } from "../../custom-behaviors/home-assistant-behavior.js"; export const OnOffLightType = Device.with( IdentifyServer, BasicInformationServer, + HomeAssistantBehavior, OnOffServer, ); - -export function onOffLightOptions( - basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, -): Endpoint.Options { - return { - onOff: OnOffServer.createState(props.entity.initialState), - bridgedDeviceBasicInformation: BasicInformationServer.createState( - basicInformation, - props.entity.initialState, - ), - }; -} diff --git a/packages/backend/src/matter/devices/lock-device.ts b/packages/backend/src/matter/devices/lock-device.ts index f7b600b..fd17dbe 100644 --- a/packages/backend/src/matter/devices/lock-device.ts +++ b/packages/backend/src/matter/devices/lock-device.ts @@ -1,31 +1,32 @@ -import { MatterDevice, MatterDeviceProps } from "../matter-device.js"; +import { MatterDevice } from "../matter-device.js"; import { DoorLockDevice } from "@project-chip/matter.js/devices/DoorLockDevice"; import { BasicInformationServer } from "../behaviors/basic-information-server.js"; import { IdentifyServer } from "../behaviors/identify-server.js"; import { LockServer } from "../behaviors/lock-server.js"; -import { BridgeBasicInformation } from "@home-assistant-matter-hub/common"; +import { + BridgeBasicInformation, + HomeAssistantEntityRegistryWithInitialState, +} from "@home-assistant-matter-hub/common"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; const LockDeviceType = DoorLockDevice.with( BasicInformationServer, IdentifyServer, + HomeAssistantBehavior, LockServer, ); export class LockDevice extends MatterDevice { constructor( basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, + registry: HomeAssistantEntityRegistryWithInitialState, ) { - super( - LockDeviceType, - { - doorLock: LockServer.createState(props.entity.initialState), - bridgedDeviceBasicInformation: BasicInformationServer.createState( - basicInformation, - props.entity.initialState, - ), + super(LockDeviceType, registry.entity_id, { + homeAssistant: { + basicInformation, + registry: registry, + entity: registry.initialState, }, - props, - ); + }); } } diff --git a/packages/backend/src/matter/devices/sensor-device.ts b/packages/backend/src/matter/devices/sensor-device.ts index 12cc79f..4f424bf 100644 --- a/packages/backend/src/matter/devices/sensor-device.ts +++ b/packages/backend/src/matter/devices/sensor-device.ts @@ -1,41 +1,36 @@ -import { MatterDevice, MatterDeviceProps } from "../matter-device.js"; +import { MatterDevice } from "../matter-device.js"; import { BridgeBasicInformation, HomeAssistantEntityRegistryWithInitialState, + HomeAssistantEntityState, SensorDeviceAttributes, SensorDeviceClass, } from "@home-assistant-matter-hub/common"; -import { EndpointType } from "@project-chip/matter.js/endpoint/type"; -import { Endpoint } from "@project-chip/matter.js/endpoint"; -import { - temperatureSensorOptions, - TemperatureSensorType, -} from "./sensor/temperature-sensor.js"; -import { - humiditySensorOptions, - HumiditySensorType, -} from "./sensor/humidity-sensor.js"; +import { TemperatureSensorType } from "./sensor/temperature-sensor.js"; +import { HumiditySensorType } from "./sensor/humidity-sensor.js"; export function SensorDevice( basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, + registry: HomeAssistantEntityRegistryWithInitialState, ): MatterDevice | undefined { const entity = - props.entity as HomeAssistantEntityRegistryWithInitialState; - const deviceClass = entity.initialState.attributes.device_class; + registry.initialState as HomeAssistantEntityState; + const deviceClass = entity.attributes.device_class; + + const type = + deviceClass === SensorDeviceClass.temperature + ? TemperatureSensorType + : deviceClass === SensorDeviceClass.humidity + ? HumiditySensorType + : undefined; + + if (!type) return undefined; - let type: EndpointType | undefined; - let options: Endpoint.Options | undefined; - if (deviceClass === SensorDeviceClass.temperature) { - type = TemperatureSensorType; - options = temperatureSensorOptions(basicInformation, props); - } else if (deviceClass === SensorDeviceClass.humidity) { - type = HumiditySensorType; - options = humiditySensorOptions(basicInformation, props); - } - if (type && options) { - return new MatterDevice(type, options, props); - } else { - return undefined; - } + return new MatterDevice(type, registry.entity_id, { + homeAssistant: { + basicInformation, + registry, + entity, + }, + }); } diff --git a/packages/backend/src/matter/devices/sensor/humidity-sensor.ts b/packages/backend/src/matter/devices/sensor/humidity-sensor.ts index de0f836..a4a7e97 100644 --- a/packages/backend/src/matter/devices/sensor/humidity-sensor.ts +++ b/packages/backend/src/matter/devices/sensor/humidity-sensor.ts @@ -1,28 +1,12 @@ import { BasicInformationServer } from "../../behaviors/basic-information-server.js"; import { IdentifyServer } from "../../behaviors/identify-server.js"; -import { BridgeBasicInformation } from "@home-assistant-matter-hub/common"; -import { MatterDeviceProps } from "../../matter-device.js"; -import { Endpoint } from "@project-chip/matter.js/endpoint"; import { HumiditySensorDevice } from "@project-chip/matter.js/devices/HumiditySensorDevice"; import { HumidityMeasurementServer } from "../../behaviors/humidity-measurement-server.js"; +import { HomeAssistantBehavior } from "../../custom-behaviors/home-assistant-behavior.js"; export const HumiditySensorType = HumiditySensorDevice.with( BasicInformationServer, IdentifyServer, + HomeAssistantBehavior, HumidityMeasurementServer, ); - -export function humiditySensorOptions( - basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, -): Endpoint.Options { - return { - bridgedDeviceBasicInformation: BasicInformationServer.createState( - basicInformation, - props.entity.initialState, - ), - relativeHumidityMeasurement: HumidityMeasurementServer.createState( - props.entity.initialState, - ), - }; -} diff --git a/packages/backend/src/matter/devices/sensor/temperature-sensor.ts b/packages/backend/src/matter/devices/sensor/temperature-sensor.ts index 349f682..91d1eca 100644 --- a/packages/backend/src/matter/devices/sensor/temperature-sensor.ts +++ b/packages/backend/src/matter/devices/sensor/temperature-sensor.ts @@ -2,27 +2,11 @@ import { TemperatureSensorDevice } from "@project-chip/matter.js/devices/Tempera import { BasicInformationServer } from "../../behaviors/basic-information-server.js"; import { IdentifyServer } from "../../behaviors/identify-server.js"; import { TemperatureMeasurementServer } from "../../behaviors/temperature-measurement-server.js"; -import { BridgeBasicInformation } from "@home-assistant-matter-hub/common"; -import { MatterDeviceProps } from "../../matter-device.js"; -import { Endpoint } from "@project-chip/matter.js/endpoint"; +import { HomeAssistantBehavior } from "../../custom-behaviors/home-assistant-behavior.js"; export const TemperatureSensorType = TemperatureSensorDevice.with( BasicInformationServer, IdentifyServer, + HomeAssistantBehavior, TemperatureMeasurementServer, ); - -export function temperatureSensorOptions( - basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, -): Endpoint.Options { - return { - bridgedDeviceBasicInformation: BasicInformationServer.createState( - basicInformation, - props.entity.initialState, - ), - temperatureMeasurement: TemperatureMeasurementServer.createState( - props.entity.initialState, - ), - }; -} diff --git a/packages/backend/src/matter/devices/switch-device.ts b/packages/backend/src/matter/devices/switch-device.ts index 4d1ec2c..ed7f01f 100644 --- a/packages/backend/src/matter/devices/switch-device.ts +++ b/packages/backend/src/matter/devices/switch-device.ts @@ -1,31 +1,32 @@ -import { MatterDevice, MatterDeviceProps } from "../matter-device.js"; -import { BridgeBasicInformation } from "@home-assistant-matter-hub/common"; +import { MatterDevice } from "../matter-device.js"; +import { + BridgeBasicInformation, + HomeAssistantEntityRegistryWithInitialState, +} from "@home-assistant-matter-hub/common"; import { OnOffPlugInUnitDevice } from "@project-chip/matter.js/devices/OnOffPlugInUnitDevice"; import { OnOffServer } from "../behaviors/on-off-server.js"; import { BasicInformationServer } from "../behaviors/basic-information-server.js"; import { IdentifyServer } from "../behaviors/identify-server.js"; +import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js"; const SwitchEndpointType = OnOffPlugInUnitDevice.with( - IdentifyServer, BasicInformationServer, + IdentifyServer, + HomeAssistantBehavior, OnOffServer, ); export class SwitchDevice extends MatterDevice { constructor( basicInformation: BridgeBasicInformation, - props: MatterDeviceProps, + registry: HomeAssistantEntityRegistryWithInitialState, ) { - super( - SwitchEndpointType, - { - onOff: OnOffServer.createState(props.entity.initialState), - bridgedDeviceBasicInformation: BasicInformationServer.createState( - basicInformation, - props.entity.initialState, - ), + super(SwitchEndpointType, registry.entity_id, { + homeAssistant: { + basicInformation, + registry: registry, + entity: registry.initialState, }, - props, - ); + }); } } diff --git a/packages/backend/src/matter/matter-device.ts b/packages/backend/src/matter/matter-device.ts index 8273129..f5e0d27 100644 --- a/packages/backend/src/matter/matter-device.ts +++ b/packages/backend/src/matter/matter-device.ts @@ -1,47 +1,21 @@ -import { - HomeAssistantEntityRegistryWithInitialState, - HomeAssistantEntityState, -} from "@home-assistant-matter-hub/common"; -import { Endpoint } from "@project-chip/matter.js/endpoint"; -import { EndpointType } from "@project-chip/matter.js/endpoint/type"; -import { HomeAssistantActions } from "../home-assistant/home-assistant-actions.js"; -import { Observable, Subject } from "rxjs"; -import { Logger } from "winston"; - -export interface MatterDeviceProps { - logger: Logger; - actions: HomeAssistantActions; - entity: HomeAssistantEntityRegistryWithInitialState; - deviceConfig?: TDeviceConfig; -} +import { HomeAssistantEntityState } from "@home-assistant-matter-hub/common"; +import { Endpoint, EndpointType } from "@project-chip/matter.js/endpoint"; +import { HomeAssistantBehavior } from "./custom-behaviors/home-assistant-behavior.js"; export class MatterDevice< T extends EndpointType = EndpointType.Empty, -> extends Endpoint { - private readonly state$ = new Subject(); - get entityState(): Observable { - return this.state$.asObservable(); - } - - readonly logger: Logger; - readonly actions: HomeAssistantActions; - readonly entity: HomeAssistantEntityRegistryWithInitialState; +> extends Endpoint { + public readonly entityId: string; - constructor(type: T, options: Endpoint.Options, props: MatterDeviceProps) { + constructor(type: T, entityId: string, options: Endpoint.Options) { super(type, { - id: props.entity.entity_id.replace(/\./g, "_"), + id: entityId.replace(/\./g, "_"), ...options, }); - this.logger = props.logger; - this.actions = props.actions; - this.entity = props.entity; + this.entityId = entityId; } async update(state: HomeAssistantEntityState) { - this.logger.silly( - "Update from HomeAssistant:\n%s", - JSON.stringify(state, null, 2), - ); - this.state$.next(state); + await this.setStateOf(HomeAssistantBehavior, { entity: state }); } } diff --git a/packages/backend/src/matter/mixins/ha-mixin.ts b/packages/backend/src/matter/mixins/ha-mixin.ts deleted file mode 100644 index ea4764b..0000000 --- a/packages/backend/src/matter/mixins/ha-mixin.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Behavior } from "@project-chip/matter.js/behavior"; -import type { MatterDevice } from "../matter-device.js"; -import { createChildLogger } from "../../logging/create-child-logger.js"; -import { Type } from "@home-assistant-matter-hub/common"; - -export function haMixin>(name: string, type: T) { - return class HaMixin extends type { - override get endpoint(): MatterDevice { - return super.endpoint as MatterDevice; - } - - readonly logger = createChildLogger( - this.endpoint.logger, - `${this.entity.entity_id} / ${name}`, - ); - - get entity() { - return this.endpoint.entity; - } - - callAction = this.endpoint.actions.callAction.bind(this.endpoint.actions); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - constructor(...args: any[]) { - super(...args); - } - }; -} diff --git a/packages/backend/src/utils/json/device-to-json.ts b/packages/backend/src/utils/json/device-to-json.ts index 914d801..8dc9d22 100644 --- a/packages/backend/src/utils/json/device-to-json.ts +++ b/packages/backend/src/utils/json/device-to-json.ts @@ -3,7 +3,7 @@ import { DeviceData } from "@home-assistant-matter-hub/common"; export function deviceToJson(device: MatterDevice): DeviceData { return { - entityId: device.entity.entity_id, + entityId: device.entityId, endpointCode: device.type.deviceType.toString(16), endpointType: device.type.name, state: device.state,