diff --git a/src/data/fan.ts b/src/data/fan.ts index 18d6814e8d..a47237473c 100644 --- a/src/data/fan.ts +++ b/src/data/fan.ts @@ -34,6 +34,8 @@ export interface FanEntity extends HassEntityBase { attributes: FanEntityAttributes; } +export type FanDirection = "forward" | "reverse"; + export type FanSpeed = "off" | "low" | "medium" | "high" | "on"; export const FAN_SPEEDS: Partial> = { diff --git a/src/panels/lovelace/card-features/hui-fan-direction-card-feature.ts b/src/panels/lovelace/card-features/hui-fan-direction-card-feature.ts new file mode 100644 index 0000000000..35fc4230fa --- /dev/null +++ b/src/panels/lovelace/card-features/hui-fan-direction-card-feature.ts @@ -0,0 +1,151 @@ +import type { PropertyValues, TemplateResult } from "lit"; +import { html, LitElement } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { computeDomain } from "../../../common/entity/compute_domain"; +import { supportsFeature } from "../../../common/entity/supports-feature"; +import "../../../components/ha-attribute-icon"; +import "../../../components/ha-control-select"; +import type { ControlSelectOption } from "../../../components/ha-control-select"; +import { UNAVAILABLE } from "../../../data/entity"; +import type { FanEntity, FanDirection } from "../../../data/fan"; +import { FanEntityFeature } from "../../../data/fan"; +import type { HomeAssistant } from "../../../types"; +import type { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; +import type { + FanDirectionCardFeatureConfig, + LovelaceCardFeatureContext, +} from "./types"; + +export const supportsFanDirectionCardFeature = ( + hass: HomeAssistant, + context: LovelaceCardFeatureContext +) => { + const stateObj = context.entity_id + ? hass.states[context.entity_id] + : undefined; + if (!stateObj) return false; + const domain = computeDomain(stateObj.entity_id); + return ( + domain === "fan" && supportsFeature(stateObj, FanEntityFeature.DIRECTION) + ); +}; + +@customElement("hui-fan-direction-card-feature") +class HuiFanDirectionCardFeature + extends LitElement + implements LovelaceCardFeature +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public context?: LovelaceCardFeatureContext; + + @state() private _config?: FanDirectionCardFeatureConfig; + + @state() _currentDirection?: FanDirection; + + private get _stateObj() { + if (!this.hass || !this.context || !this.context.entity_id) { + return undefined; + } + return this.hass.states[this.context.entity_id!] as FanEntity | undefined; + } + + static getStubConfig(): FanDirectionCardFeatureConfig { + return { + type: "fan-direction", + }; + } + + public setConfig(config: FanDirectionCardFeatureConfig): void { + if (!config) { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + protected willUpdate(changedProp: PropertyValues): void { + if ( + (changedProp.has("hass") || changedProp.has("context")) && + this._stateObj + ) { + const oldHass = changedProp.get("hass") as HomeAssistant | undefined; + const oldStateObj = oldHass?.states[this.context!.entity_id!]; + if (oldStateObj !== this._stateObj) { + this._currentDirection = this._stateObj.attributes + .direction as FanDirection; + } + } + } + + private async _valueChanged(ev: CustomEvent) { + const newDirection = (ev.detail as any).value as FanDirection; + + if (newDirection === this._stateObj!.attributes.direction) return; + + const oldDirection = this._stateObj!.attributes.direction as FanDirection; + this._currentDirection = newDirection; + + try { + await this._setDirection(newDirection); + } catch (_err) { + this._currentDirection = oldDirection; + } + } + + private async _setDirection(direction: string) { + await this.hass!.callService("fan", "set_direction", { + entity_id: this._stateObj!.entity_id, + direction: direction, + }); + } + + protected render(): TemplateResult | null { + if ( + !this._config || + !this.hass || + !this.context || + !this._stateObj || + !supportsFanDirectionCardFeature(this.hass, this.context) + ) { + return null; + } + + const stateObj = this._stateObj; + const FAN_DIRECTION_MAP: FanDirection[] = ["forward", "reverse"]; + + const options = FAN_DIRECTION_MAP.map((direction) => ({ + value: direction, + label: this.hass!.localize(`ui.card.fan.${direction}`), + icon: html``, + })); + + return html` + + + `; + } + + static get styles() { + return cardFeatureStyles; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-fan-direction-card-feature": HuiFanDirectionCardFeature; + } +} diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts index c937170aff..82625917d8 100644 --- a/src/panels/lovelace/card-features/types.ts +++ b/src/panels/lovelace/card-features/types.ts @@ -43,6 +43,10 @@ export interface MediaPlayerVolumeSliderCardFeatureConfig { type: "media-player-volume-slider"; } +export interface FanDirectionCardFeatureConfig { + type: "fan-direction"; +} + export interface FanPresetModesCardFeatureConfig { type: "fan-preset-modes"; style?: "dropdown" | "icons"; @@ -201,6 +205,7 @@ export type LovelaceCardFeatureConfig = | CoverPositionCardFeatureConfig | CoverTiltPositionCardFeatureConfig | CoverTiltCardFeatureConfig + | FanDirectionCardFeatureConfig | FanPresetModesCardFeatureConfig | FanSpeedCardFeatureConfig | HumidifierToggleCardFeatureConfig diff --git a/src/panels/lovelace/create-element/create-card-feature-element.ts b/src/panels/lovelace/create-element/create-card-feature-element.ts index fa5a006b44..4a696e4cef 100644 --- a/src/panels/lovelace/create-element/create-card-feature-element.ts +++ b/src/panels/lovelace/create-element/create-card-feature-element.ts @@ -10,6 +10,7 @@ import "../card-features/hui-cover-open-close-card-feature"; import "../card-features/hui-cover-position-card-feature"; import "../card-features/hui-cover-tilt-card-feature"; import "../card-features/hui-cover-tilt-position-card-feature"; +import "../card-features/hui-fan-direction-card-feature"; import "../card-features/hui-fan-preset-modes-card-feature"; import "../card-features/hui-fan-speed-card-feature"; import "../card-features/hui-humidifier-modes-card-feature"; @@ -50,6 +51,7 @@ const TYPES = new Set([ "cover-position", "cover-tilt-position", "cover-tilt", + "fan-direction", "fan-preset-modes", "fan-speed", "humidifier-modes", diff --git a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts index 273aaa390f..afeb3ce5f6 100644 --- a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts @@ -30,6 +30,7 @@ import { supportsCoverOpenCloseCardFeature } from "../../card-features/hui-cover import { supportsCoverPositionCardFeature } from "../../card-features/hui-cover-position-card-feature"; import { supportsCoverTiltCardFeature } from "../../card-features/hui-cover-tilt-card-feature"; import { supportsCoverTiltPositionCardFeature } from "../../card-features/hui-cover-tilt-position-card-feature"; +import { supportsFanDirectionCardFeature } from "../../card-features/hui-fan-direction-card-feature"; import { supportsFanPresetModesCardFeature } from "../../card-features/hui-fan-preset-modes-card-feature"; import { supportsFanSpeedCardFeature } from "../../card-features/hui-fan-speed-card-feature"; import { supportsHumidifierModesCardFeature } from "../../card-features/hui-humidifier-modes-card-feature"; @@ -75,6 +76,7 @@ const UI_FEATURE_TYPES = [ "cover-position", "cover-tilt-position", "cover-tilt", + "fan-direction", "fan-preset-modes", "fan-speed", "humidifier-modes", @@ -135,6 +137,7 @@ const SUPPORTS_FEATURE_TYPES: Record< "cover-position": supportsCoverPositionCardFeature, "cover-tilt-position": supportsCoverTiltPositionCardFeature, "cover-tilt": supportsCoverTiltCardFeature, + "fan-direction": supportsFanDirectionCardFeature, "fan-preset-modes": supportsFanPresetModesCardFeature, "fan-speed": supportsFanSpeedCardFeature, "humidifier-modes": supportsHumidifierModesCardFeature, diff --git a/src/translations/en.json b/src/translations/en.json index d289ecf2f1..e831f61d7d 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7835,6 +7835,9 @@ "cover-tilt-position": { "label": "Cover tilt position" }, + "fan-direction": { + "label": "Fan direction" + }, "fan-speed": { "label": "Fan speed" },