Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/smoke co #152

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,6 @@ jobs:
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.ADDON_REPOSITORY_PAT }}
repository: 't0bst4r/home-assistant-addons'
repository: "t0bst4r/home-assistant-addons"
event-type: release-hamh
client-payload: '{ "version": "${{steps.version.outputs.package-version}}" }'
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,19 +210,19 @@ more details.

## Supported Domains / Device Types

| Domain | Represented as Device Class | Comment |
| ------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| light | OnOffLight, DimmableLight, ColorTemperatureLight, ExtendedColorLight | Depends on the supported features attribute of the device. |
| switch | OnOffPlugInUnit | |
| lock | DoorLock | |
| fan | OnOffPlugInUnit | Fans are supported in the matter specification, but they are not yet supported by Voice Assistants like Alexa, Google or Apple. |
| binary_sensor | ContactSensor, OccupancySensor | All device-classes which are not really Occupancy or Contact types, are mapped to contact sensor. Feel free to open a PR to improve this. |
| sensor | TemperatureSensor, HumiditySensor | Currently only Temperature and Humidity are supported. Feel free to open a PR to improve this. |
| cover | WindowCovering | |
| climate | Thermostat | |
| input_boolean | OnOffPlugInUnit | |
| script | OnOffPlugInUnit | |
| automation | OnOffPlugInUnit | |
| scene | OnOffPlugInUnit | |
| media_player | OnOffPlugInUnit | Media Players are not supported by most controllers. Therefore adding them as OnOff units. |
| humidifier | OnOffPlugInUnit | Matter does not support humidifiers yet. Therefore mapped to an OnOffPlugInUnit with Level Control. |
| Domain | Represented as Device Class | Comment |
| ------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| light | OnOffLight, DimmableLight, ColorTemperatureLight, ExtendedColorLight | Depends on the supported features attribute of the device. |
| switch | OnOffPlugInUnit | |
| lock | DoorLock | |
| fan | OnOffPlugInUnit | Fans are supported in the matter specification, but they are not yet supported by Voice Assistants like Alexa, Google or Apple. |
| binary_sensor | ContactSensor, OccupancySensor, WaterLeadDetector | All device-classes which are explitly implemented, are mapped to contact sensor. Feel free to open a PR to improve this. |
| sensor | TemperatureSensor, HumiditySensor | Currently only Temperature and Humidity are supported. Feel free to open a PR to improve this. |
| cover | WindowCovering | |
| climate | Thermostat | |
| input_boolean | OnOffPlugInUnit | |
| script | OnOffPlugInUnit | |
| automation | OnOffPlugInUnit | |
| scene | OnOffPlugInUnit | |
| media_player | OnOffPlugInUnit | Media Players are not supported by most controllers. Therefore adding them as OnOff units. |
| humidifier | OnOffPlugInUnit | Matter does not support humidifiers yet. Therefore mapped to an OnOffPlugInUnit with Level Control. |
38 changes: 38 additions & 0 deletions packages/backend/src/matter/behaviors/smoke-co-alarm-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { SmokeCoAlarmServer as Base } from "@matter/main/behaviors";
import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavior.js";
import { HomeAssistantEntityState } from "@home-assistant-matter-hub/common";
import { applyPatchState } from "../../utils/apply-patch-state.js";
import { SmokeCoAlarm } from "@matter/main/clusters";

export class SmokeCoAlarmServer extends Base.with("CoAlarm", "SmokeAlarm") {
override async initialize() {
await super.initialize();
const homeAssistant = await this.agent.load(HomeAssistantBehavior);
this.update(homeAssistant.entity);
this.reactTo(homeAssistant.onChange, this.update);
}

private update(state: HomeAssistantEntityState) {
applyPatchState(this.state, {
expressedState: SmokeCoAlarm.ExpressedState.Normal,
batteryAlert: SmokeCoAlarm.AlarmState.Normal,
testInProgress: false,
hardwareFaultAlert: false,
endOfServiceAlert: SmokeCoAlarm.EndOfService.Normal,
...(this.features.coAlarm
? {
smokeState: SmokeCoAlarm.AlarmState.Normal,
}
: {}),
...(this.features.smokeAlarm
? {
coState: SmokeCoAlarm.AlarmState.Normal,
}
: {}),
});
}

private isOccupied(state: HomeAssistantEntityState): boolean {
return state.state !== "off";
}
}
19 changes: 11 additions & 8 deletions packages/backend/src/matter/create-device.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@
HomeAssistantDomain,
[HomeAssistantEntityRegistry, HomeAssistantEntityState][]
> = {
[HomeAssistantDomain.binary_sensor]: [
createEntity<BinarySensorDeviceAttributes>("binary_sensor.bs1", "on", {
device_class: BinarySensorDeviceClass.GarageDoor,
}),
createEntity<BinarySensorDeviceAttributes>("binary_sensor.bs2", "on", {
device_class: BinarySensorDeviceClass.Occupancy,
}),
],
[HomeAssistantDomain.binary_sensor]: Object.values(
BinarySensorDeviceClass,
).map((device_class, idx) =>
createEntity<BinarySensorDeviceAttributes>(
`binary_sensor.bs${idx + 1}`,
"on",
{
device_class: device_class,
},
),
),
[HomeAssistantDomain.climate]: [
createEntity<ClimateDeviceAttributes>("climate.cl1", "on", {
hvac_modes: [ClimateHvacMode.heat],
Expand Down Expand Up @@ -143,7 +146,7 @@
.flatMap((d) => Object.keys(d.state)),
).sort();
const expected = Object.keys(ClusterId).sort();
expect(actual).toEqual(expected);

Check failure on line 149 in packages/backend/src/matter/create-device.test.ts

View workflow job for this annotation

GitHub Actions / main

src/matter/create-device.test.ts > createDevice > should not use any unknown clusterId

AssertionError: expected [ 'booleanState', …(15) ] to deeply equal [ 'booleanState', …(14) ] - Expected + Received Array [ "booleanState", "bridgedDeviceBasicInformation", "colorControl", "descriptor", "doorLock", "groups", "homeAssistant", "identify", "levelControl", "occupancySensing", "onOff", "relativeHumidityMeasurement", + "smokeCoAlarm", "temperatureMeasurement", "thermostat", "windowCovering", ] ❯ src/matter/create-device.test.ts:149:20
});
});

Expand Down
62 changes: 36 additions & 26 deletions packages/backend/src/matter/devices/binary-sensor-device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,55 +8,65 @@ import { HomeAssistantBehavior } from "../custom-behaviors/home-assistant-behavi
import {
ContactSensorDevice,
OccupancySensorDevice,
WaterLeakDetectorDevice,
SmokeCoAlarmDevice,
} from "@matter/main/devices";
import { BasicInformationServer } from "../behaviors/basic-information-server.js";
import { IdentifyServer } from "../behaviors/identify-server.js";
import { BooleanStateServer } from "../behaviors/boolean-state-server.js";
import { OccupancySensingServer } from "../behaviors/occupancy-sensing-server.js";
import { EndpointType } from "@matter/main";
import { SmokeCoAlarmServer } from "../behaviors/smoke-co-alarm-server.js";

const ContactSensorType = ContactSensorDevice.with(
BasicInformationServer,
IdentifyServer,
HomeAssistantBehavior,
BooleanStateServer,
BooleanStateServer.set({ config: { inverted: true } }),
);

const OccupancySensorType = OccupancySensorDevice.with(
BasicInformationServer,
IdentifyServer,
HomeAssistantBehavior,
OccupancySensingServer,
);
const WaterLeakDetectorType = WaterLeakDetectorDevice.with(
BasicInformationServer,
IdentifyServer,
HomeAssistantBehavior,
BooleanStateServer,
);
const SmokeCoAlarmType = SmokeCoAlarmDevice.with(
BasicInformationServer,
IdentifyServer,
HomeAssistantBehavior,
SmokeCoAlarmServer,
);

const contactTypes: Array<BinarySensorDeviceClass | undefined> = [
BinarySensorDeviceClass.Door,
BinarySensorDeviceClass.Window,
BinarySensorDeviceClass.GarageDoor,
BinarySensorDeviceClass.Lock,
];
const occupancyTypes: Array<BinarySensorDeviceClass | undefined> = [
BinarySensorDeviceClass.Occupancy,
BinarySensorDeviceClass.Motion,
BinarySensorDeviceClass.Moving,
BinarySensorDeviceClass.Presence,
];
const deviceClasses: Partial<Record<BinarySensorDeviceClass, EndpointType>> = {
[BinarySensorDeviceClass.Occupancy]: OccupancySensorType,
[BinarySensorDeviceClass.Motion]: OccupancySensorType,
[BinarySensorDeviceClass.Moving]: OccupancySensorType,
[BinarySensorDeviceClass.Presence]: OccupancySensorType,

[BinarySensorDeviceClass.Door]: ContactSensorType,
[BinarySensorDeviceClass.Window]: ContactSensorType,
[BinarySensorDeviceClass.GarageDoor]: ContactSensorType,
[BinarySensorDeviceClass.Lock]: ContactSensorType,

[BinarySensorDeviceClass.Moisture]: WaterLeakDetectorType,
[BinarySensorDeviceClass.Smoke]: SmokeCoAlarmType,
};

const defaultDeviceType = ContactSensorType;

export function BinarySensorDevice(homeAssistant: HomeAssistantBehavior.State) {
const entity =
homeAssistant.entity as HomeAssistantEntityState<BinarySensorDeviceAttributes>;
const deviceClass = entity.attributes.device_class;

if (contactTypes.includes(deviceClass)) {
return new MatterDevice(ContactSensorType, homeAssistant, {
booleanState: { config: { inverted: true } },
});
} else if (occupancyTypes.includes(deviceClass)) {
return new MatterDevice(OccupancySensorType, homeAssistant);
} else {
return new MatterDevice(defaultDeviceType, homeAssistant, {
booleanState: { config: { inverted: true } },
});
}
const type =
deviceClass && deviceClasses[deviceClass]
? deviceClasses[deviceClass]
: defaultDeviceType;
return new MatterDevice(type, homeAssistant);
}
Loading