diff --git a/src/panels/lovelace/card-features/hui-valve-open-close-card-feature.ts b/src/panels/lovelace/card-features/hui-valve-open-close-card-feature.ts new file mode 100644 index 0000000000..0912dd54bb --- /dev/null +++ b/src/panels/lovelace/card-features/hui-valve-open-close-card-feature.ts @@ -0,0 +1,225 @@ +import { mdiStop, mdiValveClosed, mdiValveOpen } from "@mdi/js"; +import { html, LitElement, nothing, css } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { styleMap } from "lit/directives/style-map"; +import { computeDomain } from "../../../common/entity/compute_domain"; +import { supportsFeature } from "../../../common/entity/supports-feature"; +import { stateColorCss } from "../../../common/entity/state_color"; +import "../../../components/ha-control-button"; +import "../../../components/ha-control-button-group"; +import "../../../components/ha-svg-icon"; +import { + canClose, + canOpen, + canStop, + ValveEntityFeature, + type ValveEntity, +} from "../../../data/valve"; +import { UNAVAILABLE, UNKNOWN } from "../../../data/entity"; +import type { HomeAssistant } from "../../../types"; +import type { LovelaceCardFeature } from "../types"; +import { cardFeatureStyles } from "./common/card-feature-styles"; +import type { + ValveOpenCloseCardFeatureConfig, + LovelaceCardFeatureContext, +} from "./types"; +import "../../../components/ha-control-switch"; + +export const supportsValveOpenCloseCardFeature = ( + 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 === "valve" && + (supportsFeature(stateObj, ValveEntityFeature.OPEN) || + supportsFeature(stateObj, ValveEntityFeature.CLOSE)) + ); +}; + +@customElement("hui-valve-open-close-card-feature") +class HuiValveOpenCloseCardFeature + extends LitElement + implements LovelaceCardFeature +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public context?: LovelaceCardFeatureContext; + + @state() private _config?: ValveOpenCloseCardFeatureConfig; + + private get _stateObj() { + if (!this.hass || !this.context || !this.context.entity_id) { + return undefined; + } + return this.hass.states[this.context.entity_id!] as ValveEntity | undefined; + } + + static getStubConfig(): ValveOpenCloseCardFeatureConfig { + return { + type: "valve-open-close", + }; + } + + public setConfig(config: ValveOpenCloseCardFeatureConfig): void { + if (!config) { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + private _onOpenValve(): void { + this.hass!.callService("valve", "open_valve", { + entity_id: this._stateObj!.entity_id, + }); + } + + private _onCloseValve(): void { + this.hass!.callService("valve", "close_valve", { + entity_id: this._stateObj!.entity_id, + }); + } + + private _onOpenTap(ev): void { + ev.stopPropagation(); + this._onOpenValve(); + } + + private _onCloseTap(ev): void { + ev.stopPropagation(); + this._onCloseValve(); + } + + private _onStopTap(ev): void { + ev.stopPropagation(); + this.hass!.callService("valve", "stop_valve", { + entity_id: this._stateObj!.entity_id, + }); + } + + private _valueChanged(ev): void { + ev.stopPropagation(); + const checked = ev.target.checked as boolean; + + if (checked) { + this._onOpenValve(); + } else { + this._onCloseValve(); + } + } + + protected render() { + if ( + !this._config || + !this.hass || + !this.context || + !this._stateObj || + !supportsValveOpenCloseCardFeature(this.hass, this.context) + ) { + return nothing; + } + + // Determine colors and active states for toggle-style UI + const openColor = stateColorCss(this._stateObj, "open"); + const closedColor = stateColorCss(this._stateObj, "closed"); + const openIcon = mdiValveOpen; + const closedIcon = mdiValveClosed; + + const isOpen = + this._stateObj.state === "open" || + this._stateObj.state === "closing" || + this._stateObj.state === "opening"; + const isClosed = this._stateObj.state === "closed"; + + if ( + this._stateObj.attributes.assumed_state || + this._stateObj.state === UNKNOWN + ) { + return html` + + ${supportsFeature(this._stateObj, ValveEntityFeature.CLOSE) + ? html` + + + + ` + : nothing} + ${supportsFeature(this._stateObj, ValveEntityFeature.STOP) + ? html` + + + + ` + : nothing} + ${supportsFeature(this._stateObj, ValveEntityFeature.OPEN) + ? html` + + + + ` + : nothing} + + `; + } + + return html` + + + `; + } + + static get styles() { + return [ + cardFeatureStyles, + css` + ha-control-button.active { + --control-button-icon-color: white; + --control-button-background-color: var(--color); + --control-button-background-opacity: 1; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-valve-open-close-card-feature": HuiValveOpenCloseCardFeature; + } +} diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts index 82625917d8..d809946e90 100644 --- a/src/panels/lovelace/card-features/types.ts +++ b/src/panels/lovelace/card-features/types.ts @@ -153,6 +153,10 @@ export interface VacuumCommandsCardFeatureConfig { commands?: VacuumCommand[]; } +export interface ValveOpenCloseCardFeatureConfig { + type: "valve-open-close"; +} + export const LAWN_MOWER_COMMANDS = ["start_pause", "dock"] as const; export type LawnMowerCommand = (typeof LAWN_MOWER_COMMANDS)[number]; @@ -223,6 +227,7 @@ export type LovelaceCardFeatureConfig = | ToggleCardFeatureConfig | UpdateActionsCardFeatureConfig | VacuumCommandsCardFeatureConfig + | ValveOpenCloseCardFeatureConfig | WaterHeaterOperationModesCardFeatureConfig | AreaControlsCardFeatureConfig; 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 4a696e4cef..8fac9cef0a 100644 --- a/src/panels/lovelace/create-element/create-card-feature-element.ts +++ b/src/panels/lovelace/create-element/create-card-feature-element.ts @@ -28,6 +28,7 @@ import "../card-features/hui-target-temperature-card-feature"; import "../card-features/hui-toggle-card-feature"; import "../card-features/hui-update-actions-card-feature"; import "../card-features/hui-vacuum-commands-card-feature"; +import "../card-features/hui-valve-open-close-card-feature"; import "../card-features/hui-water-heater-operation-modes-card-feature"; import "../card-features/hui-area-controls-card-feature"; @@ -69,6 +70,7 @@ const TYPES = new Set([ "toggle", "update-actions", "vacuum-commands", + "valve-open-close", "water-heater-operation-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 afeb3ce5f6..a63690777e 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 @@ -48,6 +48,7 @@ import { supportsTargetTemperatureCardFeature } from "../../card-features/hui-ta import { supportsToggleCardFeature } from "../../card-features/hui-toggle-card-feature"; import { supportsUpdateActionsCardFeature } from "../../card-features/hui-update-actions-card-feature"; import { supportsVacuumCommandsCardFeature } from "../../card-features/hui-vacuum-commands-card-feature"; +import { supportsValveOpenCloseCardFeature } from "../../card-features/hui-valve-open-close-card-feature"; import { supportsWaterHeaterOperationModesCardFeature } from "../../card-features/hui-water-heater-operation-modes-card-feature"; import type { LovelaceCardFeatureConfig, @@ -94,6 +95,7 @@ const UI_FEATURE_TYPES = [ "toggle", "update-actions", "vacuum-commands", + "valve-open-close", "water-heater-operation-modes", ] as const satisfies readonly FeatureType[]; @@ -155,6 +157,7 @@ const SUPPORTS_FEATURE_TYPES: Record< toggle: supportsToggleCardFeature, "update-actions": supportsUpdateActionsCardFeature, "vacuum-commands": supportsVacuumCommandsCardFeature, + "valve-open-close": supportsValveOpenCloseCardFeature, "water-heater-operation-modes": supportsWaterHeaterOperationModesCardFeature, }; diff --git a/src/translations/en.json b/src/translations/en.json index f3c0c6a626..19749bc8c0 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7885,6 +7885,9 @@ "return_home": "[%key:ui::dialogs::more_info_control::vacuum::return_home%]" } }, + "valve-open-close": { + "label": "Valve open/close" + }, "climate-fan-modes": { "label": "Climate fan modes", "style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]",