mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-17 23:54:28 +01:00
Improve mock for cover, alarm, lock, lawn mower, valve and vacuum (#29999)
This commit is contained in:
@@ -1,4 +1,15 @@
|
||||
import { MockBaseEntity } from "./base-entity";
|
||||
import type { EntityAttributes } from "./types";
|
||||
|
||||
const TRANSITION_MS = 1000;
|
||||
|
||||
const SERVICE_TO_STATE: Record<string, string> = {
|
||||
alarm_arm_home: "armed_home",
|
||||
alarm_arm_away: "armed_away",
|
||||
alarm_arm_night: "armed_night",
|
||||
alarm_arm_vacation: "armed_vacation",
|
||||
alarm_arm_custom_bypass: "armed_custom_bypass",
|
||||
};
|
||||
|
||||
export class MockAlarmControlPanelEntity extends MockBaseEntity {
|
||||
public async handleService(
|
||||
@@ -10,17 +21,45 @@ export class MockAlarmControlPanelEntity extends MockBaseEntity {
|
||||
return;
|
||||
}
|
||||
|
||||
const serviceStateMap: Record<string, string> = {
|
||||
alarm_arm_night: "armed_night",
|
||||
alarm_arm_home: "armed_home",
|
||||
alarm_arm_away: "armed_away",
|
||||
alarm_disarm: "disarmed",
|
||||
};
|
||||
this._clearTransition();
|
||||
|
||||
if (serviceStateMap[service]) {
|
||||
this.update({ state: serviceStateMap[service] });
|
||||
if (service in SERVICE_TO_STATE) {
|
||||
this._transition("arming", SERVICE_TO_STATE[service], TRANSITION_MS);
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "alarm_disarm") {
|
||||
this.update({ state: "disarmed" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "alarm_trigger") {
|
||||
this._transition("pending", "triggered", TRANSITION_MS);
|
||||
return;
|
||||
}
|
||||
|
||||
super.handleService(domain, service, data);
|
||||
}
|
||||
|
||||
protected _getCapabilityAttributes(): EntityAttributes {
|
||||
const attrs = this.attributes;
|
||||
const capabilityAttrs: EntityAttributes = {};
|
||||
|
||||
if (attrs.code_format !== undefined) {
|
||||
capabilityAttrs.code_format = attrs.code_format;
|
||||
}
|
||||
if (attrs.code_arm_required !== undefined) {
|
||||
capabilityAttrs.code_arm_required = attrs.code_arm_required;
|
||||
}
|
||||
|
||||
return capabilityAttrs;
|
||||
}
|
||||
|
||||
protected _getStateAttributes(): EntityAttributes {
|
||||
const attrs = this.attributes;
|
||||
|
||||
return {
|
||||
changed_by: attrs.changed_by ?? null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ export class MockBaseEntity {
|
||||
|
||||
public hass?: MockHassLike;
|
||||
|
||||
private _transitionTimer?: ReturnType<typeof setTimeout>;
|
||||
|
||||
static CAPABILITY_ATTRIBUTES: Set<string> = BASE_CAPABILITY_ATTRIBUTES;
|
||||
|
||||
constructor(input: EntityInput) {
|
||||
@@ -76,6 +78,28 @@ export class MockBaseEntity {
|
||||
);
|
||||
}
|
||||
|
||||
protected _transition(
|
||||
transitioning: string,
|
||||
final: string,
|
||||
duration: number,
|
||||
onComplete?: () => void
|
||||
): void {
|
||||
this._clearTransition();
|
||||
this.update({ state: transitioning });
|
||||
this._transitionTimer = setTimeout(() => {
|
||||
this._transitionTimer = undefined;
|
||||
this.update({ state: final });
|
||||
onComplete?.();
|
||||
}, duration);
|
||||
}
|
||||
|
||||
protected _clearTransition(): void {
|
||||
if (this._transitionTimer) {
|
||||
clearTimeout(this._transitionTimer);
|
||||
this._transitionTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public update(changes: {
|
||||
state?: string;
|
||||
attributes?: EntityAttributes;
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { supportsFeatureFromAttributes } from "../../common/entity/supports-feature";
|
||||
import { CoverEntityFeature } from "../../data/cover";
|
||||
import { MockBaseEntity } from "./base-entity";
|
||||
import type { EntityAttributes } from "./types";
|
||||
|
||||
const TRANSITION_STEP_MS = 400;
|
||||
const POSITION_STEP = 10;
|
||||
|
||||
export class MockCoverEntity extends MockBaseEntity {
|
||||
private _positionTimer?: ReturnType<typeof setInterval>;
|
||||
|
||||
public async handleService(
|
||||
domain: string,
|
||||
service: string,
|
||||
@@ -11,20 +19,137 @@ export class MockCoverEntity extends MockBaseEntity {
|
||||
}
|
||||
|
||||
if (service === "open_cover") {
|
||||
this.update({ state: "open" });
|
||||
this._startTransition(100);
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "close_cover") {
|
||||
this.update({ state: "closed" });
|
||||
this._startTransition(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "toggle") {
|
||||
if (this.state === "open" || this.state === "opening") {
|
||||
this._startTransition(0);
|
||||
} else {
|
||||
this._startTransition(100);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "stop_cover") {
|
||||
this._stopTransition();
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "set_cover_position") {
|
||||
this._startTransition(data.position);
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "open_cover_tilt") {
|
||||
this.update({ attributes: { current_tilt_position: 100 } });
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "close_cover_tilt") {
|
||||
this.update({ attributes: { current_tilt_position: 0 } });
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "stop_cover_tilt") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "set_cover_tilt_position") {
|
||||
this.update({
|
||||
state: data.position > 0 ? "open" : "closed",
|
||||
attributes: { current_position: data.position },
|
||||
attributes: { current_tilt_position: data.tilt_position },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "toggle_cover_tilt") {
|
||||
const currentTilt = this.attributes.current_tilt_position ?? 0;
|
||||
this.update({
|
||||
attributes: { current_tilt_position: currentTilt > 0 ? 0 : 100 },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
super.handleService(domain, service, data);
|
||||
}
|
||||
|
||||
private _startTransition(targetPosition: number): void {
|
||||
this._stopTransition();
|
||||
const hasPosition = supportsFeatureFromAttributes(
|
||||
this.attributes,
|
||||
CoverEntityFeature.SET_POSITION
|
||||
);
|
||||
|
||||
if (!hasPosition) {
|
||||
this.update({ state: targetPosition > 0 ? "open" : "closed" });
|
||||
return;
|
||||
}
|
||||
|
||||
const currentPosition = this.attributes.current_position ?? 0;
|
||||
|
||||
if (currentPosition === targetPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
const direction = targetPosition > currentPosition ? 1 : -1;
|
||||
const transitionState = direction > 0 ? "opening" : "closing";
|
||||
|
||||
this.update({ state: transitionState });
|
||||
|
||||
this._positionTimer = setInterval(() => {
|
||||
const pos = this.attributes.current_position ?? 0;
|
||||
const nextPos = pos + POSITION_STEP * direction;
|
||||
|
||||
const reachedTarget =
|
||||
direction > 0 ? nextPos >= targetPosition : nextPos <= targetPosition;
|
||||
|
||||
if (reachedTarget) {
|
||||
clearInterval(this._positionTimer);
|
||||
this._positionTimer = undefined;
|
||||
this.update({
|
||||
state: targetPosition > 0 ? "open" : "closed",
|
||||
attributes: { current_position: targetPosition },
|
||||
});
|
||||
} else {
|
||||
this.update({
|
||||
attributes: { current_position: nextPos },
|
||||
});
|
||||
}
|
||||
}, TRANSITION_STEP_MS);
|
||||
}
|
||||
|
||||
private _stopTransition(): void {
|
||||
if (this._positionTimer) {
|
||||
clearInterval(this._positionTimer);
|
||||
this._positionTimer = undefined;
|
||||
}
|
||||
|
||||
if (this.state === "opening" || this.state === "closing") {
|
||||
const pos = this.attributes.current_position ?? 0;
|
||||
this.update({ state: pos > 0 ? "open" : "closed" });
|
||||
}
|
||||
}
|
||||
|
||||
protected _getStateAttributes(): EntityAttributes {
|
||||
const attrs = this.attributes;
|
||||
const stateAttrs: EntityAttributes = {};
|
||||
|
||||
if (supportsFeatureFromAttributes(attrs, CoverEntityFeature.SET_POSITION)) {
|
||||
stateAttrs.current_position = attrs.current_position ?? null;
|
||||
}
|
||||
|
||||
if (
|
||||
supportsFeatureFromAttributes(attrs, CoverEntityFeature.SET_TILT_POSITION)
|
||||
) {
|
||||
stateAttrs.current_tilt_position = attrs.current_tilt_position ?? null;
|
||||
}
|
||||
|
||||
return stateAttrs;
|
||||
}
|
||||
}
|
||||
|
||||
34
src/fake_data/entities/lawn-mower-entity.ts
Normal file
34
src/fake_data/entities/lawn-mower-entity.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { MockBaseEntity } from "./base-entity";
|
||||
|
||||
const TRANSITION_MS = 3000;
|
||||
|
||||
export class MockLawnMowerEntity extends MockBaseEntity {
|
||||
public async handleService(
|
||||
domain: string,
|
||||
service: string,
|
||||
_data: Record<string, any>
|
||||
): Promise<void> {
|
||||
if (domain !== this.domain) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._clearTransition();
|
||||
|
||||
if (service === "start_mowing") {
|
||||
this.update({ state: "mowing" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "pause") {
|
||||
this.update({ state: "paused" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "dock") {
|
||||
this._transition("returning", "docked", TRANSITION_MS);
|
||||
return;
|
||||
}
|
||||
|
||||
super.handleService(domain, service, _data);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import { MockBaseEntity } from "./base-entity";
|
||||
import type { EntityAttributes } from "./types";
|
||||
|
||||
const TRANSITION_MS = 1000;
|
||||
|
||||
export class MockLockEntity extends MockBaseEntity {
|
||||
public async handleService(
|
||||
@@ -10,14 +13,44 @@ export class MockLockEntity extends MockBaseEntity {
|
||||
return;
|
||||
}
|
||||
|
||||
this._clearTransition();
|
||||
|
||||
if (service === "lock") {
|
||||
this.update({ state: "locked" });
|
||||
this._transition("locking", "locked", TRANSITION_MS);
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "unlock") {
|
||||
this.update({ state: "unlocked" });
|
||||
this._transition("unlocking", "unlocked", TRANSITION_MS);
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "open") {
|
||||
this._transition("opening", "open", TRANSITION_MS, () => {
|
||||
this._transition("locking", "unlocked", TRANSITION_MS);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
super.handleService(domain, service, data);
|
||||
}
|
||||
|
||||
protected _getCapabilityAttributes(): EntityAttributes {
|
||||
const attrs = this.attributes;
|
||||
const capabilityAttrs: EntityAttributes = {};
|
||||
|
||||
if (attrs.code_format !== undefined) {
|
||||
capabilityAttrs.code_format = attrs.code_format;
|
||||
}
|
||||
|
||||
return capabilityAttrs;
|
||||
}
|
||||
|
||||
protected _getStateAttributes(): EntityAttributes {
|
||||
const attrs = this.attributes;
|
||||
|
||||
return {
|
||||
changed_by: attrs.changed_by ?? null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,15 @@ import { MockFanEntity } from "./fan-entity";
|
||||
import { MockGroupEntity } from "./group-entity";
|
||||
import { MockHumidifierEntity } from "./humidifier-entity";
|
||||
import { MockInputNumberEntity } from "./input-number-entity";
|
||||
import { MockLawnMowerEntity } from "./lawn-mower-entity";
|
||||
import { MockInputSelectEntity } from "./input-select-entity";
|
||||
import { MockInputTextEntity } from "./input-text-entity";
|
||||
import { MockLightEntity } from "./light-entity";
|
||||
import { MockLockEntity } from "./lock-entity";
|
||||
import { MockMediaPlayerEntity } from "./media-player-entity";
|
||||
import { MockToggleEntity } from "./toggle-entity";
|
||||
import { MockVacuumEntity } from "./vacuum-entity";
|
||||
import { MockValveEntity } from "./valve-entity";
|
||||
import { MockWaterHeaterEntity } from "./water-heater-entity";
|
||||
|
||||
type EntityConstructor = new (input: EntityInput) => MockBaseEntity;
|
||||
@@ -29,10 +32,13 @@ const TYPES: Record<string, EntityConstructor> = {
|
||||
input_number: MockInputNumberEntity,
|
||||
input_text: MockInputTextEntity,
|
||||
input_select: MockInputSelectEntity,
|
||||
lawn_mower: MockLawnMowerEntity,
|
||||
light: MockLightEntity,
|
||||
lock: MockLockEntity,
|
||||
media_player: MockMediaPlayerEntity,
|
||||
switch: MockToggleEntity,
|
||||
vacuum: MockVacuumEntity,
|
||||
valve: MockValveEntity,
|
||||
water_heater: MockWaterHeaterEntity,
|
||||
};
|
||||
|
||||
|
||||
88
src/fake_data/entities/vacuum-entity.ts
Normal file
88
src/fake_data/entities/vacuum-entity.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { supportsFeatureFromAttributes } from "../../common/entity/supports-feature";
|
||||
import { VacuumEntityFeature } from "../../data/vacuum";
|
||||
import { MockBaseEntity } from "./base-entity";
|
||||
import type { EntityAttributes } from "./types";
|
||||
|
||||
const TRANSITION_MS = 3000;
|
||||
|
||||
export class MockVacuumEntity extends MockBaseEntity {
|
||||
public async handleService(
|
||||
domain: string,
|
||||
service: string,
|
||||
data: Record<string, any>
|
||||
): Promise<void> {
|
||||
if (domain !== this.domain) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._clearTransition();
|
||||
|
||||
if (service === "start") {
|
||||
this.update({ state: "cleaning" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "pause") {
|
||||
this.update({ state: "paused" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "stop") {
|
||||
this.update({ state: "idle" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "return_to_base") {
|
||||
this._transition("returning", "docked", TRANSITION_MS);
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "clean_spot") {
|
||||
this.update({ state: "cleaning" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "locate") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "set_fan_speed") {
|
||||
this.update({ attributes: { fan_speed: data.fan_speed } });
|
||||
return;
|
||||
}
|
||||
|
||||
super.handleService(domain, service, data);
|
||||
}
|
||||
|
||||
protected _getCapabilityAttributes(): EntityAttributes {
|
||||
const attrs = this.attributes;
|
||||
const capabilityAttrs: EntityAttributes = {};
|
||||
|
||||
if (supportsFeatureFromAttributes(attrs, VacuumEntityFeature.FAN_SPEED)) {
|
||||
capabilityAttrs.fan_speed_list = attrs.fan_speed_list;
|
||||
}
|
||||
|
||||
return capabilityAttrs;
|
||||
}
|
||||
|
||||
protected _getStateAttributes(): EntityAttributes {
|
||||
const attrs = this.attributes;
|
||||
|
||||
const stateAttrs: EntityAttributes = {};
|
||||
|
||||
if (supportsFeatureFromAttributes(attrs, VacuumEntityFeature.FAN_SPEED)) {
|
||||
stateAttrs.fan_speed = attrs.fan_speed ?? null;
|
||||
}
|
||||
|
||||
if (supportsFeatureFromAttributes(attrs, VacuumEntityFeature.BATTERY)) {
|
||||
stateAttrs.battery_level = attrs.battery_level ?? null;
|
||||
stateAttrs.battery_icon = attrs.battery_icon ?? null;
|
||||
}
|
||||
|
||||
if (supportsFeatureFromAttributes(attrs, VacuumEntityFeature.STATUS)) {
|
||||
stateAttrs.status = attrs.status ?? null;
|
||||
}
|
||||
|
||||
return stateAttrs;
|
||||
}
|
||||
}
|
||||
66
src/fake_data/entities/valve-entity.ts
Normal file
66
src/fake_data/entities/valve-entity.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { supportsFeatureFromAttributes } from "../../common/entity/supports-feature";
|
||||
import { ValveEntityFeature } from "../../data/valve";
|
||||
import { MockBaseEntity } from "./base-entity";
|
||||
import type { EntityAttributes } from "./types";
|
||||
|
||||
export class MockValveEntity extends MockBaseEntity {
|
||||
public async handleService(
|
||||
domain: string,
|
||||
service: string,
|
||||
data: Record<string, any>
|
||||
): Promise<void> {
|
||||
if (domain !== this.domain) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "open_valve") {
|
||||
this.update({
|
||||
state: "open",
|
||||
attributes: { current_position: 100 },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "close_valve") {
|
||||
this.update({
|
||||
state: "closed",
|
||||
attributes: { current_position: 0 },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "toggle") {
|
||||
if (this.state === "open") {
|
||||
this.handleService(domain, "close_valve", data);
|
||||
} else {
|
||||
this.handleService(domain, "open_valve", data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "stop_valve") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (service === "set_valve_position") {
|
||||
this.update({
|
||||
state: data.position > 0 ? "open" : "closed",
|
||||
attributes: { current_position: data.position },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
super.handleService(domain, service, data);
|
||||
}
|
||||
|
||||
protected _getStateAttributes(): EntityAttributes {
|
||||
const attrs = this.attributes;
|
||||
const stateAttrs: EntityAttributes = {};
|
||||
|
||||
if (supportsFeatureFromAttributes(attrs, ValveEntityFeature.SET_POSITION)) {
|
||||
stateAttrs.current_position = attrs.current_position ?? null;
|
||||
}
|
||||
|
||||
return stateAttrs;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user