mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
Add valve entity (#19024)
* Add valve entity * Update icon based on device class * Check assumed state first * Reset mode if entity id changes
This commit is contained in:
parent
ad543dbffb
commit
3e7fa66790
@ -28,10 +28,12 @@ import {
|
||||
mdiLockAlert,
|
||||
mdiLockClock,
|
||||
mdiLockOpen,
|
||||
mdiMeterGas,
|
||||
mdiMotionSensor,
|
||||
mdiPackage,
|
||||
mdiPackageDown,
|
||||
mdiPackageUp,
|
||||
mdiPipeValve,
|
||||
mdiPowerPlug,
|
||||
mdiPowerPlugOff,
|
||||
mdiRestart,
|
||||
@ -274,6 +276,16 @@ export const domainIconWithoutDefault = (
|
||||
: mdiPackageUp
|
||||
: mdiPackage;
|
||||
|
||||
case "valve":
|
||||
switch (stateObj?.attributes.device_class) {
|
||||
case "water":
|
||||
return mdiPipeValve;
|
||||
case "gas":
|
||||
return mdiMeterGas;
|
||||
default:
|
||||
return mdiPipeValve;
|
||||
}
|
||||
|
||||
case "water_heater":
|
||||
return compareState === "off" ? mdiWaterBoilerOff : mdiWaterBoiler;
|
||||
|
||||
|
@ -42,6 +42,8 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||
return compareState !== "standby";
|
||||
case "vacuum":
|
||||
return !["idle", "docked", "paused"].includes(compareState);
|
||||
case "valve":
|
||||
return compareState !== "closed";
|
||||
case "plant":
|
||||
return compareState === "problem";
|
||||
case "group":
|
||||
|
@ -37,6 +37,7 @@ const STATE_COLORED_DOMAIN = new Set([
|
||||
"timer",
|
||||
"update",
|
||||
"vacuum",
|
||||
"valve",
|
||||
"water_heater",
|
||||
]);
|
||||
|
||||
|
102
src/components/ha-valve-controls.ts
Normal file
102
src/components/ha-valve-controls.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { mdiStop, mdiValveClosed, mdiValveOpen } from "@mdi/js";
|
||||
import { CSSResultGroup, LitElement, html, css, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import {
|
||||
ValveEntity,
|
||||
ValveEntityFeature,
|
||||
canClose,
|
||||
canOpen,
|
||||
canStop,
|
||||
} from "../data/valve";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-icon-button";
|
||||
|
||||
@customElement("ha-valve-controls")
|
||||
class HaValveControls extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj!: ValveEntity;
|
||||
|
||||
protected render() {
|
||||
if (!this.stateObj) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="state">
|
||||
<ha-icon-button
|
||||
class=${classMap({
|
||||
hidden: !supportsFeature(this.stateObj, ValveEntityFeature.OPEN),
|
||||
})}
|
||||
.label=${this.hass.localize("ui.card.valve.open_valve")}
|
||||
@click=${this._onOpenTap}
|
||||
.disabled=${!canOpen(this.stateObj)}
|
||||
.path=${mdiValveOpen}
|
||||
>
|
||||
</ha-icon-button>
|
||||
<ha-icon-button
|
||||
class=${classMap({
|
||||
hidden: !supportsFeature(this.stateObj, ValveEntityFeature.STOP),
|
||||
})}
|
||||
.label=${this.hass.localize("ui.card.valve.stop_valve")}
|
||||
@click=${this._onStopTap}
|
||||
.disabled=${!canStop(this.stateObj)}
|
||||
.path=${mdiStop}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
class=${classMap({
|
||||
hidden: !supportsFeature(this.stateObj, ValveEntityFeature.CLOSE),
|
||||
})}
|
||||
.label=${this.hass.localize("ui.card.valve.close_valve")}
|
||||
@click=${this._onCloseTap}
|
||||
.disabled=${!canClose(this.stateObj)}
|
||||
.path=${mdiValveClosed}
|
||||
>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _onOpenTap(ev): void {
|
||||
ev.stopPropagation();
|
||||
this.hass.callService("valve", "open_valve", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _onCloseTap(ev): void {
|
||||
ev.stopPropagation();
|
||||
this.hass.callService("valve", "close_valve", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _onStopTap(ev): void {
|
||||
ev.stopPropagation();
|
||||
this.hass.callService("valve", "stop_valve", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.state {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.hidden {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-valve-controls": HaValveControls;
|
||||
}
|
||||
}
|
@ -65,7 +65,7 @@ export function canOpen(stateObj: CoverEntity) {
|
||||
return false;
|
||||
}
|
||||
const assumedState = stateObj.attributes.assumed_state === true;
|
||||
return (!isFullyOpen(stateObj) && !isOpening(stateObj)) || assumedState;
|
||||
return assumedState || (!isFullyOpen(stateObj) && !isOpening(stateObj));
|
||||
}
|
||||
|
||||
export function canClose(stateObj: CoverEntity): boolean {
|
||||
@ -73,7 +73,7 @@ export function canClose(stateObj: CoverEntity): boolean {
|
||||
return false;
|
||||
}
|
||||
const assumedState = stateObj.attributes.assumed_state === true;
|
||||
return (!isFullyClosed(stateObj) && !isClosing(stateObj)) || assumedState;
|
||||
return assumedState || (!isFullyClosed(stateObj) && !isClosing(stateObj));
|
||||
}
|
||||
|
||||
export function canStop(stateObj: CoverEntity): boolean {
|
||||
@ -85,7 +85,7 @@ export function canOpenTilt(stateObj: CoverEntity): boolean {
|
||||
return false;
|
||||
}
|
||||
const assumedState = stateObj.attributes.assumed_state === true;
|
||||
return !isFullyOpenTilt(stateObj) || assumedState;
|
||||
return assumedState || !isFullyOpenTilt(stateObj);
|
||||
}
|
||||
|
||||
export function canCloseTilt(stateObj: CoverEntity): boolean {
|
||||
@ -93,7 +93,7 @@ export function canCloseTilt(stateObj: CoverEntity): boolean {
|
||||
return false;
|
||||
}
|
||||
const assumedState = stateObj.attributes.assumed_state === true;
|
||||
return !isFullyClosedTilt(stateObj) || assumedState;
|
||||
return assumedState || !isFullyClosedTilt(stateObj);
|
||||
}
|
||||
|
||||
export function canStopTilt(stateObj: CoverEntity): boolean {
|
||||
|
@ -75,6 +75,9 @@ export const DOMAIN_ATTRIBUTES_UNITS = {
|
||||
vacuum: {
|
||||
battery_level: "%",
|
||||
},
|
||||
valve: {
|
||||
current_position: "%",
|
||||
},
|
||||
sensor: {
|
||||
battery_level: "%",
|
||||
},
|
||||
|
85
src/data/valve.ts
Normal file
85
src/data/valve.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE } from "./entity";
|
||||
import { stateActive } from "../common/entity/state_active";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export const enum ValveEntityFeature {
|
||||
OPEN = 1,
|
||||
CLOSE = 2,
|
||||
SET_POSITION = 4,
|
||||
STOP = 8,
|
||||
}
|
||||
|
||||
export function isFullyOpen(stateObj: ValveEntity) {
|
||||
if (stateObj.attributes.current_position !== undefined) {
|
||||
return stateObj.attributes.current_position === 100;
|
||||
}
|
||||
return stateObj.state === "open";
|
||||
}
|
||||
|
||||
export function isFullyClosed(stateObj: ValveEntity) {
|
||||
if (stateObj.attributes.current_position !== undefined) {
|
||||
return stateObj.attributes.current_position === 0;
|
||||
}
|
||||
return stateObj.state === "closed";
|
||||
}
|
||||
|
||||
export function isOpening(stateObj: ValveEntity) {
|
||||
return stateObj.state === "opening";
|
||||
}
|
||||
|
||||
export function isClosing(stateObj: ValveEntity) {
|
||||
return stateObj.state === "closing";
|
||||
}
|
||||
|
||||
export function canOpen(stateObj: ValveEntity) {
|
||||
if (stateObj.state === UNAVAILABLE) {
|
||||
return false;
|
||||
}
|
||||
const assumedState = stateObj.attributes.assumed_state === true;
|
||||
return assumedState || (!isFullyOpen(stateObj) && !isOpening(stateObj));
|
||||
}
|
||||
|
||||
export function canClose(stateObj: ValveEntity): boolean {
|
||||
if (stateObj.state === UNAVAILABLE) {
|
||||
return false;
|
||||
}
|
||||
const assumedState = stateObj.attributes.assumed_state === true;
|
||||
return assumedState || (!isFullyClosed(stateObj) && !isClosing(stateObj));
|
||||
}
|
||||
|
||||
export function canStop(stateObj: ValveEntity): boolean {
|
||||
return stateObj.state !== UNAVAILABLE;
|
||||
}
|
||||
|
||||
interface ValveEntityAttributes extends HassEntityAttributeBase {
|
||||
current_position?: number;
|
||||
position?: number;
|
||||
}
|
||||
|
||||
export interface ValveEntity extends HassEntityBase {
|
||||
attributes: ValveEntityAttributes;
|
||||
}
|
||||
|
||||
export function computeValvePositionStateDisplay(
|
||||
stateObj: ValveEntity,
|
||||
hass: HomeAssistant,
|
||||
position?: number
|
||||
) {
|
||||
const statePosition = stateActive(stateObj)
|
||||
? stateObj.attributes.current_position
|
||||
: undefined;
|
||||
|
||||
const currentPosition = position ?? statePosition;
|
||||
|
||||
return currentPosition && currentPosition !== 100
|
||||
? hass.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
"current_position",
|
||||
Math.round(currentPosition)
|
||||
)
|
||||
: "";
|
||||
}
|
@ -27,6 +27,7 @@ export const DOMAINS_WITH_NEW_MORE_INFO = [
|
||||
"lock",
|
||||
"siren",
|
||||
"switch",
|
||||
"valve",
|
||||
"water_heater",
|
||||
];
|
||||
/** Domains with separate more info dialog. */
|
||||
@ -61,6 +62,7 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"timer",
|
||||
"update",
|
||||
"vacuum",
|
||||
"valve",
|
||||
"water_heater",
|
||||
"weather",
|
||||
];
|
||||
|
@ -42,7 +42,9 @@ class MoreInfoCover extends LitElement {
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
if (changedProps.has("stateObj") && this.stateObj) {
|
||||
if (!this._mode) {
|
||||
const entityId = this.stateObj.entity_id;
|
||||
const oldEntityId = changedProps.get("stateObj")?.entity_id;
|
||||
if (!this._mode || entityId !== oldEntityId) {
|
||||
this._mode =
|
||||
supportsFeature(this.stateObj, CoverEntityFeature.SET_POSITION) ||
|
||||
supportsFeature(this.stateObj, CoverEntityFeature.SET_TILT_POSITION)
|
||||
|
192
src/dialogs/more-info/controls/more-info-valve.ts
Normal file
192
src/dialogs/more-info/controls/more-info-valve.ts
Normal file
@ -0,0 +1,192 @@
|
||||
import { mdiMenu, mdiSwapVertical } from "@mdi/js";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-attributes";
|
||||
import "../../../components/ha-icon-button-group";
|
||||
import "../../../components/ha-icon-button-toggle";
|
||||
import {
|
||||
ValveEntity,
|
||||
ValveEntityFeature,
|
||||
computeValvePositionStateDisplay,
|
||||
} from "../../../data/valve";
|
||||
import "../../../state-control/valve/ha-state-control-valve-buttons";
|
||||
import "../../../state-control/valve/ha-state-control-valve-position";
|
||||
import "../../../state-control/valve/ha-state-control-valve-toggle";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../components/ha-more-info-state-header";
|
||||
import { moreInfoControlStyle } from "../components/more-info-control-style";
|
||||
|
||||
type Mode = "position" | "button";
|
||||
|
||||
@customElement("more-info-valve")
|
||||
class MoreInfoValve extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: ValveEntity;
|
||||
|
||||
@state() private _mode?: Mode;
|
||||
|
||||
private _setMode(ev) {
|
||||
this._mode = ev.currentTarget.mode;
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
if (changedProps.has("stateObj") && this.stateObj) {
|
||||
const entityId = this.stateObj.entity_id;
|
||||
const oldEntityId = changedProps.get("stateObj")?.entity_id;
|
||||
if (!this._mode || entityId !== oldEntityId) {
|
||||
this._mode = supportsFeature(
|
||||
this.stateObj,
|
||||
ValveEntityFeature.SET_POSITION
|
||||
)
|
||||
? "position"
|
||||
: "button";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private get _stateOverride() {
|
||||
const stateDisplay = this.hass.formatEntityState(this.stateObj!);
|
||||
|
||||
const positionStateDisplay = computeValvePositionStateDisplay(
|
||||
this.stateObj!,
|
||||
this.hass
|
||||
);
|
||||
|
||||
if (positionStateDisplay) {
|
||||
return `${stateDisplay} ⸱ ${positionStateDisplay}`;
|
||||
}
|
||||
return stateDisplay;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const supportsPosition = supportsFeature(
|
||||
this.stateObj,
|
||||
ValveEntityFeature.SET_POSITION
|
||||
);
|
||||
|
||||
const supportsOpenClose =
|
||||
supportsFeature(this.stateObj, ValveEntityFeature.OPEN) ||
|
||||
supportsFeature(this.stateObj, ValveEntityFeature.CLOSE) ||
|
||||
supportsFeature(this.stateObj, ValveEntityFeature.STOP);
|
||||
|
||||
const supportsOpenCloseWithoutStop =
|
||||
supportsFeature(this.stateObj, ValveEntityFeature.OPEN) &&
|
||||
supportsFeature(this.stateObj, ValveEntityFeature.CLOSE) &&
|
||||
!supportsFeature(this.stateObj, ValveEntityFeature.STOP);
|
||||
|
||||
return html`
|
||||
<ha-more-info-state-header
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
.stateOverride=${this._stateOverride}
|
||||
></ha-more-info-state-header>
|
||||
<div class="controls">
|
||||
<div class="main-control">
|
||||
${
|
||||
this._mode === "position"
|
||||
? html`
|
||||
${supportsPosition
|
||||
? html`
|
||||
<ha-state-control-valve-position
|
||||
.stateObj=${this.stateObj}
|
||||
.hass=${this.hass}
|
||||
></ha-state-control-valve-position>
|
||||
`
|
||||
: nothing}
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
this._mode === "button"
|
||||
? html`
|
||||
${supportsOpenCloseWithoutStop
|
||||
? html`
|
||||
<ha-state-control-valve-toggle
|
||||
.stateObj=${this.stateObj}
|
||||
.hass=${this.hass}
|
||||
></ha-state-control-valve-toggle>
|
||||
`
|
||||
: supportsOpenClose
|
||||
? html`
|
||||
<ha-state-control-valve-buttons
|
||||
.stateObj=${this.stateObj}
|
||||
.hass=${this.hass}
|
||||
></ha-state-control-valve-buttons>
|
||||
`
|
||||
: nothing}
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
</div>
|
||||
${
|
||||
supportsPosition && supportsOpenClose
|
||||
? html`
|
||||
<ha-icon-button-group>
|
||||
<ha-icon-button-toggle
|
||||
.label=${this.hass.localize(
|
||||
`ui.dialogs.more_info_control.valve.switch_mode.position`
|
||||
)}
|
||||
.selected=${this._mode === "position"}
|
||||
.path=${mdiMenu}
|
||||
.mode=${"position"}
|
||||
@click=${this._setMode}
|
||||
></ha-icon-button-toggle>
|
||||
<ha-icon-button-toggle
|
||||
.label=${this.hass.localize(
|
||||
`ui.dialogs.more_info_control.valve.switch_mode.button`
|
||||
)}
|
||||
.selected=${this._mode === "button"}
|
||||
.path=${mdiSwapVertical}
|
||||
.mode=${"button"}
|
||||
@click=${this._setMode}
|
||||
></ha-icon-button-toggle>
|
||||
</ha-icon-button-group>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<ha-attributes
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
extra-filters="current_position,current_tilt_position"
|
||||
></ha-attributes>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
moreInfoControlStyle,
|
||||
css`
|
||||
.main-control {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.main-control > * {
|
||||
margin: 0 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-valve": MoreInfoValve;
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ const LAZY_LOADED_MORE_INFO_CONTROL = {
|
||||
timer: () => import("./controls/more-info-timer"),
|
||||
update: () => import("./controls/more-info-update"),
|
||||
vacuum: () => import("./controls/more-info-vacuum"),
|
||||
valve: () => import("./controls/more-info-valve"),
|
||||
water_heater: () => import("./controls/more-info-water_heater"),
|
||||
weather: () => import("./controls/more-info-weather"),
|
||||
};
|
||||
|
@ -251,6 +251,10 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
return this._renderStateContent(stateObj, ["state", "current_position"]);
|
||||
}
|
||||
|
||||
if (domain === "valve" && active) {
|
||||
return this._renderStateContent(stateObj, ["state", "current_position"]);
|
||||
}
|
||||
|
||||
if (domain === "humidifier") {
|
||||
return this._renderStateContent(stateObj, ["state", "current_humidity"]);
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ const LAZY_LOAD_TYPES = {
|
||||
"time-entity": () => import("../entity-rows/hui-time-entity-row"),
|
||||
"timer-entity": () => import("../entity-rows/hui-timer-entity-row"),
|
||||
"update-entity": () => import("../entity-rows/hui-update-entity-row"),
|
||||
"valve-entity": () => import("../entity-rows/hui-valve-entity-row"),
|
||||
conditional: () => import("../special-rows/hui-conditional-row"),
|
||||
"weather-entity": () => import("../entity-rows/hui-weather-entity-row"),
|
||||
divider: () => import("../special-rows/hui-divider-row"),
|
||||
@ -94,6 +95,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
|
||||
timer: "timer",
|
||||
update: "update",
|
||||
vacuum: "toggle",
|
||||
valve: "valve",
|
||||
// Temporary. Once climate is rewritten,
|
||||
// water heater should get its own row.
|
||||
water_heater: "climate",
|
||||
|
73
src/panels/lovelace/entity-rows/hui-valve-entity-row.ts
Normal file
73
src/panels/lovelace/entity-rows/hui-valve-entity-row.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../components/ha-valve-controls";
|
||||
import { ValveEntity } from "../../../data/valve";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import "../components/hui-generic-entity-row";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { EntityConfig, LovelaceRow } from "./types";
|
||||
|
||||
@customElement("hui-valve-entity-row")
|
||||
class HuiValveEntityRow extends LitElement implements LovelaceRow {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: EntityConfig;
|
||||
|
||||
public setConfig(config: EntityConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Invalid configuration");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
return hasConfigOrEntityChanged(this, changedProps);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config || !this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const stateObj = this.hass.states[this._config.entity] as ValveEntity;
|
||||
|
||||
if (!stateObj) {
|
||||
return html`
|
||||
<hui-warning>
|
||||
${createEntityNotFoundWarning(this.hass, this._config.entity)}
|
||||
</hui-warning>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||
<ha-valve-controls
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></ha-valve-controls>
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-valve-controls {
|
||||
margin-right: -0.57em;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-valve-entity-row": HuiValveEntityRow;
|
||||
}
|
||||
}
|
@ -174,6 +174,7 @@ const mainStyles = css`
|
||||
--state-switch-active-color: var(--amber-color);
|
||||
--state-update-active-color: var(--orange-color);
|
||||
--state-vacuum-active-color: var(--teal-color);
|
||||
--state-valve-active-color: var(--blue-color);
|
||||
--state-sensor-battery-high-color: var(--green-color);
|
||||
--state-sensor-battery-low-color: var(--red-color);
|
||||
--state-sensor-battery-medium-color: var(--orange-color);
|
||||
|
145
src/state-control/valve/ha-state-control-valve-buttons.ts
Normal file
145
src/state-control/valve/ha-state-control-valve-buttons.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { mdiStop, mdiValveClosed, mdiValveOpen } from "@mdi/js";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import "../../components/ha-control-button";
|
||||
import "../../components/ha-control-button-group";
|
||||
import "../../components/ha-control-slider";
|
||||
import "../../components/ha-svg-icon";
|
||||
import {
|
||||
ValveEntity,
|
||||
ValveEntityFeature,
|
||||
canClose,
|
||||
canOpen,
|
||||
canStop,
|
||||
} from "../../data/valve";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
type ValveButton = "open" | "close" | "stop" | "none";
|
||||
|
||||
export const getValveButtons = memoizeOne(
|
||||
(stateObj: ValveEntity): ValveButton[] => {
|
||||
const supportsOpen = supportsFeature(stateObj, ValveEntityFeature.OPEN);
|
||||
const supportsClose = supportsFeature(stateObj, ValveEntityFeature.CLOSE);
|
||||
const supportsStop = supportsFeature(stateObj, ValveEntityFeature.STOP);
|
||||
|
||||
const buttons: ValveButton[] = [];
|
||||
if (supportsOpen) buttons.push("open");
|
||||
if (supportsStop) buttons.push("stop");
|
||||
if (supportsClose) buttons.push("close");
|
||||
return buttons;
|
||||
}
|
||||
);
|
||||
|
||||
@customElement("ha-state-control-valve-buttons")
|
||||
export class HaStateControlValveButtons extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj!: ValveEntity;
|
||||
|
||||
private _onOpenTap(ev): void {
|
||||
ev.stopPropagation();
|
||||
this.hass!.callService("valve", "open_valve", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _onCloseTap(ev): void {
|
||||
ev.stopPropagation();
|
||||
this.hass!.callService("valve", "close_valve", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _onStopTap(ev): void {
|
||||
ev.stopPropagation();
|
||||
this.hass!.callService("valve", "stop_valve", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
protected renderButton(button: ValveButton | undefined) {
|
||||
if (button === "open") {
|
||||
return html`
|
||||
<ha-control-button
|
||||
.label=${this.hass.localize("ui.card.valve.open_valve")}
|
||||
@click=${this._onOpenTap}
|
||||
.disabled=${!canOpen(this.stateObj)}
|
||||
data-button="open"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiValveOpen}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`;
|
||||
}
|
||||
if (button === "close") {
|
||||
return html`
|
||||
<ha-control-button
|
||||
.label=${this.hass.localize("ui.card.valve.close_valve")}
|
||||
@click=${this._onCloseTap}
|
||||
.disabled=${!canClose(this.stateObj)}
|
||||
data-button="close"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiValveClosed}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`;
|
||||
}
|
||||
if (button === "stop") {
|
||||
return html`
|
||||
<ha-control-button
|
||||
.label=${this.hass.localize("ui.card.valve.stop_valve")}
|
||||
@click=${this._onStopTap}
|
||||
.disabled=${!canStop(this.stateObj)}
|
||||
data-button="stop"
|
||||
>
|
||||
<ha-svg-icon .path=${mdiStop}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const buttons = getValveButtons(this.stateObj);
|
||||
|
||||
return html`
|
||||
<ha-control-button-group vertical>
|
||||
${repeat(
|
||||
buttons,
|
||||
(button) => button,
|
||||
(button) => this.renderButton(button)
|
||||
)}
|
||||
</ha-control-button-group>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-control-button-group {
|
||||
height: 45vh;
|
||||
max-height: 320px;
|
||||
min-height: 200px;
|
||||
--control-button-group-spacing: 6px;
|
||||
--control-button-group-thickness: 100px;
|
||||
}
|
||||
ha-control-button {
|
||||
--control-button-border-radius: 18px;
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-state-control-valve-buttons": HaStateControlValveButtons;
|
||||
}
|
||||
}
|
88
src/state-control/valve/ha-state-control-valve-position.ts
Normal file
88
src/state-control/valve/ha-state-control-valve-position.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeAttributeNameDisplay } from "../../common/entity/compute_attribute_display";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import "../../components/ha-control-slider";
|
||||
import { CoverEntity } from "../../data/cover";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
import { DOMAIN_ATTRIBUTES_UNITS } from "../../data/entity_attributes";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-state-control-valve-position")
|
||||
export class HaStateControlValvePosition extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj!: CoverEntity;
|
||||
|
||||
@state() value?: number;
|
||||
|
||||
protected updated(changedProp: Map<string | number | symbol, unknown>): void {
|
||||
if (changedProp.has("stateObj")) {
|
||||
const currentPosition = this.stateObj?.attributes.current_position;
|
||||
this.value =
|
||||
currentPosition != null ? Math.round(currentPosition) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
const value = (ev.detail as any).value;
|
||||
if (isNaN(value)) return;
|
||||
|
||||
this.hass.callService("valve", "set_valve_position", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
position: value,
|
||||
});
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const color = stateColorCss(this.stateObj);
|
||||
|
||||
return html`
|
||||
<ha-control-slider
|
||||
vertical
|
||||
.value=${this.value}
|
||||
min="0"
|
||||
max="100"
|
||||
show-handle
|
||||
@value-changed=${this._valueChanged}
|
||||
.ariaLabel=${computeAttributeNameDisplay(
|
||||
this.hass.localize,
|
||||
this.stateObj,
|
||||
this.hass.entities,
|
||||
"current_position"
|
||||
)}
|
||||
style=${styleMap({
|
||||
"--control-slider-color": color,
|
||||
"--control-slider-background": color,
|
||||
})}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
.unit=${DOMAIN_ATTRIBUTES_UNITS.valve.current_position}
|
||||
.locale=${this.hass.locale}
|
||||
>
|
||||
</ha-control-slider>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-control-slider {
|
||||
height: 45vh;
|
||||
max-height: 320px;
|
||||
min-height: 200px;
|
||||
--control-slider-thickness: 100px;
|
||||
--control-slider-border-radius: 24px;
|
||||
--control-slider-color: var(--primary-color);
|
||||
--control-slider-background: var(--disabled-color);
|
||||
--control-slider-background-opacity: 0.2;
|
||||
--control-slider-tooltip-font-size: 20px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-state-control-valve-position": HaStateControlValvePosition;
|
||||
}
|
||||
}
|
167
src/state-control/valve/ha-state-control-valve-toggle.ts
Normal file
167
src/state-control/valve/ha-state-control-valve-toggle.ts
Normal file
@ -0,0 +1,167 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { domainIcon } from "../../common/entity/domain_icon";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import "../../components/ha-control-button";
|
||||
import "../../components/ha-control-switch";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import { forwardHaptic } from "../../data/haptics";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-state-control-valve-toggle")
|
||||
export class HaStateControlValveToggle extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj!: HassEntity;
|
||||
|
||||
private _valueChanged(ev) {
|
||||
const checked = ev.target.checked as boolean;
|
||||
|
||||
if (checked) {
|
||||
this._turnOn();
|
||||
} else {
|
||||
this._turnOff();
|
||||
}
|
||||
}
|
||||
|
||||
private _turnOn() {
|
||||
this._callService(true);
|
||||
}
|
||||
|
||||
private _turnOff() {
|
||||
this._callService(false);
|
||||
}
|
||||
|
||||
private async _callService(turnOn): Promise<void> {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
forwardHaptic("light");
|
||||
|
||||
await this.hass.callService(
|
||||
"valve",
|
||||
turnOn ? "open_valve" : "close_valve",
|
||||
{
|
||||
entity_id: this.stateObj.entity_id,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const onColor = stateColorCss(this.stateObj, "open");
|
||||
const offColor = stateColorCss(this.stateObj, "closed");
|
||||
|
||||
const isOn =
|
||||
this.stateObj.state === "open" ||
|
||||
this.stateObj.state === "closing" ||
|
||||
this.stateObj.state === "opening";
|
||||
const isOff = this.stateObj.state === "closed";
|
||||
|
||||
if (
|
||||
this.stateObj.attributes.assumed_state ||
|
||||
this.stateObj.state === UNKNOWN
|
||||
) {
|
||||
return html`
|
||||
<div class="buttons">
|
||||
<ha-control-button
|
||||
.label=${this.hass.localize("ui.card.valve.open_valve")}
|
||||
@click=${this._turnOn}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
class=${classMap({
|
||||
active: isOn,
|
||||
})}
|
||||
style=${styleMap({
|
||||
"--color": onColor,
|
||||
})}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${domainIcon("valve", this.stateObj, "open")}
|
||||
></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
<ha-control-button
|
||||
.label=${this.hass.localize("ui.card.valve.close_valve")}
|
||||
@click=${this._turnOff}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
class=${classMap({
|
||||
active: isOff,
|
||||
})}
|
||||
style=${styleMap({
|
||||
"--color": offColor,
|
||||
})}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${domainIcon("valve", this.stateObj, "closed")}
|
||||
></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-control-switch
|
||||
.pathOn=${domainIcon("valve", this.stateObj, "open")}
|
||||
.pathOff=${domainIcon("valve", this.stateObj, "closed")}
|
||||
vertical
|
||||
reversed
|
||||
.checked=${isOn}
|
||||
@change=${this._valueChanged}
|
||||
.ariaLabel=${isOn
|
||||
? this.hass.localize("ui.card.valve.close_valve")
|
||||
: this.hass.localize("ui.card.valve.open_valve")}
|
||||
style=${styleMap({
|
||||
"--control-switch-on-color": onColor,
|
||||
"--control-switch-off-color": offColor,
|
||||
})}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
>
|
||||
</ha-control-switch>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-control-switch {
|
||||
height: 45vh;
|
||||
max-height: 320px;
|
||||
min-height: 200px;
|
||||
--control-switch-thickness: 100px;
|
||||
--control-switch-border-radius: 24px;
|
||||
--control-switch-padding: 6px;
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100px;
|
||||
height: 45vh;
|
||||
max-height: 320px;
|
||||
min-height: 200px;
|
||||
padding: 6px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
ha-control-button {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
--control-button-border-radius: 18px;
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
ha-control-button.active {
|
||||
--control-button-icon-color: white;
|
||||
--control-button-background-color: var(--color);
|
||||
--control-button-background-opacity: 1;
|
||||
}
|
||||
ha-control-button:not(:last-child) {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-state-control-valve-toggle": HaStateControlValveToggle;
|
||||
}
|
||||
}
|
@ -255,6 +255,11 @@
|
||||
"turn_off": "Turn off"
|
||||
}
|
||||
},
|
||||
"valve": {
|
||||
"open_valve": "Open valve",
|
||||
"close_valve": "Close valve",
|
||||
"stop_valve": "Stop valve"
|
||||
},
|
||||
"water_heater": {
|
||||
"currently": "[%key:ui::card::climate::currently%]",
|
||||
"on_off": "On / off",
|
||||
@ -1094,6 +1099,12 @@
|
||||
"start_mowing": "Start mowing",
|
||||
"pause": "Pause",
|
||||
"dock": "Return to dock"
|
||||
},
|
||||
"valve": {
|
||||
"switch_mode": {
|
||||
"button": "[%key:ui::dialogs::more_info_control::valve::switch_mode::button%]",
|
||||
"position": "[%key:ui::dialogs::more_info_control::valve::switch_mode::position%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity_registry": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user