Skip to content

Commit

Permalink
feat(cover): allow tilt feature
Browse files Browse the repository at this point in the history
fixes #349
  • Loading branch information
t0bst4r committed Dec 28, 2024
1 parent 1a94ed2 commit b34d7b7
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 39 deletions.
118 changes: 93 additions & 25 deletions packages/backend/src/matter/behaviors/window-covering-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ import { HomeAssistantEntityBehavior } from "../custom-behaviors/home-assistant-
import { applyPatchState } from "../../utils/apply-patch-state.js";
import { ClusterType } from "@matter/main/types";

const FeaturedBase = Base.with("Lift", "PositionAwareLift", "AbsolutePosition");
const FeaturedBase = Base.with(
"Lift",
"PositionAwareLift",
"AbsolutePosition",
"Tilt",
"PositionAwareTilt",
);

export class WindowCoveringServerBase extends FeaturedBase {
declare state: WindowCoveringServerBase.State;
Expand All @@ -37,40 +43,71 @@ export class WindowCoveringServerBase extends FeaturedBase {
? WindowCovering.MovementStatus.Closing
: WindowCovering.MovementStatus.Stopped;

let currentLift = this.convertLiftValue(state.attributes.current_position);
if (currentLift != null) {
currentLift *= 100;
} else {
if (coverState === CoverDeviceState.open) {
currentLift = 0;
} else if (state.state === "closed") {
currentLift = 100_00;
}
}
const currentLift = this.getCurrentPosition(
state.attributes.current_position,
coverState,
);
const currentTilt = this.getCurrentPosition(
state.attributes.current_tilt_position,
coverState,
);

applyPatchState<WindowCoveringServerBase.State>(this.state, {
type: WindowCovering.WindowCoveringType.Rollershade,
endProductType: WindowCovering.EndProductType.RollerShade,
operationalStatus: {
global: movementStatus,
lift: movementStatus,
...(this.features.lift ? { lift: movementStatus } : {}),
...(this.features.tilt ? { tilt: movementStatus } : {}),
},
...(this.features.absolutePosition
...(this.features.absolutePosition && this.features.lift
? {
installedOpenLimitLift: 0,
installedCloseLimit: 100_00,
installedClosedLimitLift: 100_00,
currentPositionLift: currentLift,
}
: {}),
...(this.features.absolutePosition && this.features.tilt
? {
installedOpenLimitTilt: 0,
installedClosedLimitTilt: 100_00,
currentPositionTilt: currentLift,
}
: {}),
...(this.features.positionAwareLift
? {
currentPositionLiftPercent100ths: currentLift,
targetPositionLiftPercent100ths:
this.state.targetPositionLiftPercent100ths ?? currentLift,
}
: {}),
...(this.features.positionAwareTilt
? {
currentPositionTiltPercent100ths: currentTilt,
targetPositionTiltPercent100ths:
this.state.targetPositionTiltPercent100ths ?? currentTilt,
}
: {}),
});
}

private getCurrentPosition(
percentage: number | undefined,
coverState: CoverDeviceState,
) {
let currentLift = this.invertValue(percentage);
if (currentLift != null) {
currentLift *= 100;
} else {
if (coverState === CoverDeviceState.open) {
currentLift = 0;
} else if (coverState === CoverDeviceState.closed) {
currentLift = 100_00;
}
}
return currentLift;
}

override async handleMovement(
type: MovementType,
_: boolean,
Expand All @@ -79,36 +116,47 @@ export class WindowCoveringServerBase extends FeaturedBase {
) {
if (type === MovementType.Lift) {
if (targetPercent100ths != null && this.features.absolutePosition) {
await this.handleGoToPosition(targetPercent100ths);
await this.handleGoToLiftPosition(targetPercent100ths);
} else if (
direction === MovementDirection.Close ||
(targetPercent100ths != null && targetPercent100ths > 0)
) {
await this.handleLiftClose();
} else if (direction === MovementDirection.Open) {
await this.handleLiftOpen();
}
} else if (type === MovementType.Tilt) {
if (targetPercent100ths != null && this.features.absolutePosition) {
await this.handleGoToTiltPosition(targetPercent100ths);
} else if (
direction === MovementDirection.Close ||
(targetPercent100ths != null && targetPercent100ths > 0)
) {
await this.handleClose();
await this.handleTiltClose();
} else if (direction === MovementDirection.Open) {
await this.handleOpen();
await this.handleTiltOpen();
}
}
}
override async handleStopMovement() {
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
await homeAssistant.callAction("cover.stop_cover");
}
private async handleOpen() {

private async handleLiftOpen() {
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
await homeAssistant.callAction("cover.open_cover");
}
private async handleClose() {
private async handleLiftClose() {
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
await homeAssistant.callAction("cover.close_cover");
}

private async handleGoToPosition(targetPercent100ths: number) {
private async handleGoToLiftPosition(targetPercent100ths: number) {
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
const attributes = homeAssistant.entity.state
.attributes as CoverDeviceAttributes;
const currentPosition = attributes.current_position;
const targetPosition = this.convertLiftValue(targetPercent100ths / 100);
const targetPosition = this.invertValue(targetPercent100ths / 100);
if (targetPosition == null || targetPosition === currentPosition) {
return;
}
Expand All @@ -117,9 +165,29 @@ export class WindowCoveringServerBase extends FeaturedBase {
});
}

private convertLiftValue(
percentage: number | undefined | null,
): number | null {
private async handleTiltOpen() {
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
await homeAssistant.callAction("cover.open_cover_tilt");
}
private async handleTiltClose() {
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
await homeAssistant.callAction("cover.close_cover_tilt");
}
private async handleGoToTiltPosition(targetPercent100ths: number) {
const homeAssistant = this.agent.get(HomeAssistantEntityBehavior);
const attributes = homeAssistant.entity.state
.attributes as CoverDeviceAttributes;
const currentPosition = attributes.current_tilt_position;
const targetPosition = this.invertValue(targetPercent100ths / 100);
if (targetPosition == null || targetPosition === currentPosition) {
return;
}
await homeAssistant.callAction("cover.set_cover_tilt_position", {
tilt_position: targetPosition,
});
}

private invertValue(percentage: number | undefined | null): number | null {
if (percentage == null) {
return null;
}
Expand Down
42 changes: 31 additions & 11 deletions packages/backend/src/matter/devices/cover-device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,39 @@ import {
} from "@home-assistant-matter-hub/common";
import { testBit } from "../../utils/test-bit.js";
import { EndpointType } from "@matter/main";
import { FeatureSelection } from "../../utils/feature-selection.js";
import { WindowCovering } from "@matter/main/clusters";

const CoverDeviceType = (supportedFeatures: number) => {
const features: FeatureSelection<WindowCovering.Complete> = new Set();
if (testBit(supportedFeatures, CoverSupportedFeatures.support_open)) {
features.add("Lift");
features.add("PositionAwareLift");
if (
testBit(supportedFeatures, CoverSupportedFeatures.support_set_position)
) {
features.add("AbsolutePosition");
}
}

if (testBit(supportedFeatures, CoverSupportedFeatures.support_open_tilt)) {
features.add("Tilt");
features.add("PositionAwareTilt");
if (
testBit(
supportedFeatures,
CoverSupportedFeatures.support_set_tilt_position,
)
) {
features.add("AbsolutePosition");
}
}

const CoverDeviceType = (positionAwareLift: boolean) => {
const windowCoveringServer = positionAwareLift
? WindowCoveringServer.with("Lift", "PositionAwareLift", "AbsolutePosition")
: WindowCoveringServer.with("Lift", "PositionAwareLift");
return WindowCoveringDevice.with(
BasicInformationServer,
IdentifyServer,
HomeAssistantEntityBehavior,
windowCoveringServer,
WindowCoveringServer.with(...features),

Check failure on line 44 in packages/backend/src/matter/devices/cover-device.ts

View workflow job for this annotation

GitHub Actions / main

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

Error: Feature combination { lift: false, tilt: false } is disallowed by the Matter specification ❯ ClusterComposer.reject ../../node_modules/@matter/types/src/cluster/mutation/ClusterComposer.ts:149:15 ❯ ClusterComposer.compose ../../node_modules/@matter/types/src/cluster/mutation/ClusterComposer.ts:56:26 ❯ Function.withFeatures ../../node_modules/@matter/node/src/behavior/cluster/ClusterBehavior.ts:113:62 ❯ Function.with ../../node_modules/@matter/node/src/behavior/cluster/ClusterBehavior.ts:127:21 ❯ CoverDeviceType src/matter/devices/cover-device.ts:44:26 ❯ CoverDevice src/matter/devices/cover-device.ts:53:10 ❯ Module.createDevice src/matter/bridge/create-device.ts:33:10 ❯ src/matter/bridge/create-device.test.ts:133:7 ❯ src/matter/bridge/create-device.test.ts:132:30 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { format: 'Function<format>' }
);
};

Expand All @@ -27,10 +50,7 @@ export function CoverDevice(
): EndpointType {
const attributes = homeAssistantEntity.entity.state
.attributes as CoverDeviceAttributes;
const supportedFeatures = attributes.supported_features ?? 0;
const positionAwareLift = testBit(
supportedFeatures,
CoverSupportedFeatures.support_set_position,
);
return CoverDeviceType(positionAwareLift).set({ homeAssistantEntity });
return CoverDeviceType(attributes.supported_features ?? 0).set({
homeAssistantEntity,
});
}
6 changes: 3 additions & 3 deletions packages/backend/src/utils/feature-selection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ClusterType } from "@matter/main/types";

export type FeatureSelection<T extends ClusterType> = Capitalize<
string & keyof T["features"]
>[];
export type FeatureSelection<T extends ClusterType> = Set<
Capitalize<string & keyof T["features"]>
>;
1 change: 1 addition & 0 deletions packages/common/src/domains/cover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum CoverDeviceState {
}
export interface CoverDeviceAttributes {
current_position?: number;
current_tilt_position?: number;
supported_features?: number;
}

Expand Down

0 comments on commit b34d7b7

Please sign in to comment.