Add basic more info for lawn mower (#17601)

* Add basic more info for lawn mower

* Change buttons layout
This commit is contained in:
Paul Bottein 2023-08-21 13:17:11 +02:00 committed by GitHub
parent ac7c354bfc
commit 5f015ac9af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 409 additions and 1 deletions

View File

@ -11,10 +11,12 @@ export type LocalizeKeys =
| `ui.card.alarm_control_panel.${string}` | `ui.card.alarm_control_panel.${string}`
| `ui.card.weather.attributes.${string}` | `ui.card.weather.attributes.${string}`
| `ui.card.weather.cardinal_direction.${string}` | `ui.card.weather.cardinal_direction.${string}`
| `ui.card.lawn_mower.actions.${string}`
| `ui.components.calendar.event.rrule.${string}` | `ui.components.calendar.event.rrule.${string}`
| `ui.components.logbook.${string}` | `ui.components.logbook.${string}`
| `ui.components.selectors.file.${string}` | `ui.components.selectors.file.${string}`
| `ui.dialogs.entity_registry.editor.${string}` | `ui.dialogs.entity_registry.editor.${string}`
| `ui.dialogs.more_info_control.lawn_mower.${string}`
| `ui.dialogs.more_info_control.vacuum.${string}` | `ui.dialogs.more_info_control.vacuum.${string}`
| `ui.dialogs.quick-bar.commands.${string}` | `ui.dialogs.quick-bar.commands.${string}`
| `ui.dialogs.unhealthy.reason.${string}` | `ui.dialogs.unhealthy.reason.${string}`

View File

@ -0,0 +1,91 @@
import "@material/mwc-button";
import { CSSResultGroup, LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators";
import { supportsFeature } from "../common/entity/supports-feature";
import {
LawnMowerEntity,
LawnMowerEntityFeature,
LawnMowerEntityState,
} from "../data/lawn_mower";
import { HomeAssistant } from "../types";
type LawnMowerAction = {
action: string;
service: string;
feature: LawnMowerEntityFeature;
};
const LAWN_MOWER_ACTIONS: Partial<
Record<LawnMowerEntityState, LawnMowerAction>
> = {
mowing: {
action: "dock",
service: "dock",
feature: LawnMowerEntityFeature.DOCK,
},
docked: {
action: "start_mowing",
service: "start_mowing",
feature: LawnMowerEntityFeature.START_MOWING,
},
paused: {
action: "resume_mowing",
service: "start_mowing",
feature: LawnMowerEntityFeature.START_MOWING,
},
};
@customElement("ha-lawn_mower-state")
class HaLawnMowerState extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: LawnMowerEntity;
public render() {
const state = this.stateObj.state;
const action = LAWN_MOWER_ACTIONS[state];
if (action && supportsFeature(this.stateObj, action.feature)) {
return html`
<mwc-button @click=${this.callService} .service=${action.service}>
${this.hass.localize(`ui.card.lawn_mower.actions.${action.action}`)}
</mwc-button>
`;
}
return html`
<mwc-button disabled>
${this.hass.formatEntityState(this.stateObj)}
</mwc-button>
`;
}
callService(ev) {
ev.stopPropagation();
const stateObj = this.stateObj;
const service = ev.target.service;
this.hass.callService("lawn_mower", service, {
entity_id: stateObj.entity_id,
});
}
static get styles(): CSSResultGroup {
return css`
mwc-button {
top: 3px;
height: 37px;
margin-right: -0.57em;
}
mwc-button[disabled] {
background-color: transparent;
color: var(--secondary-text-color);
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-lawn_mower-state": HaLawnMowerState;
}
}

42
src/data/lawn_mower.ts Normal file
View File

@ -0,0 +1,42 @@
import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
import { UNAVAILABLE } from "./entity";
export type LawnMowerEntityState = "paused" | "mowing" | "docked" | "error";
export const enum LawnMowerEntityFeature {
START_MOWING = 1,
PAUSE = 2,
DOCK = 4,
}
interface LawnMowerEntityAttributes extends HassEntityAttributeBase {
[key: string]: any;
}
export interface LawnMowerEntity extends HassEntityBase {
attributes: LawnMowerEntityAttributes;
}
export function canStartMowing(stateObj: LawnMowerEntity): boolean {
if (stateObj.state === UNAVAILABLE) {
return false;
}
return stateObj.state !== "mowing";
}
export function canPause(stateObj: LawnMowerEntity): boolean {
if (stateObj.state === UNAVAILABLE) {
return false;
}
return stateObj.state !== "paused";
}
export function canDock(stateObj: LawnMowerEntity): boolean {
if (stateObj.state === UNAVAILABLE) {
return false;
}
return stateObj.state !== "docked";
}

View File

@ -46,6 +46,7 @@ export const DOMAINS_WITH_MORE_INFO = [
"image", "image",
"input_boolean", "input_boolean",
"input_datetime", "input_datetime",
"lawn_mower",
"light", "light",
"lock", "lock",
"media_player", "media_player",

View File

@ -0,0 +1,213 @@
import { mdiHomeMapMarker, mdiPause, mdiPlay } from "@mdi/js";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
import "../../../components/entity/ha-battery-icon";
import "../../../components/ha-icon-button";
import { UNAVAILABLE } from "../../../data/entity";
import {
EntityRegistryDisplayEntry,
findBatteryChargingEntity,
findBatteryEntity,
} from "../../../data/entity_registry";
import {
LawnMowerEntity,
LawnMowerEntityFeature,
} from "../../../data/lawn_mower";
import { HomeAssistant } from "../../../types";
interface LawnMowerCommand {
translationKey: string;
icon: string;
serviceName: string;
isVisible: (stateObj: LawnMowerEntity) => boolean;
}
const LAWN_MOWER_COMMANDS: LawnMowerCommand[] = [
{
translationKey: "start_mowing",
icon: mdiPlay,
serviceName: "start_mowing",
isVisible: (stateObj) =>
supportsFeature(stateObj, LawnMowerEntityFeature.START_MOWING),
},
{
translationKey: "pause",
icon: mdiPause,
serviceName: "pause",
isVisible: (stateObj) =>
supportsFeature(stateObj, LawnMowerEntityFeature.PAUSE),
},
{
translationKey: "dock",
icon: mdiHomeMapMarker,
serviceName: "dock",
isVisible: (stateObj) =>
supportsFeature(stateObj, LawnMowerEntityFeature.DOCK),
},
];
@customElement("more-info-lawn_mower")
class MoreInfoLawnMower extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public stateObj?: LawnMowerEntity;
protected render() {
if (!this.hass || !this.stateObj) {
return nothing;
}
const stateObj = this.stateObj;
return html`
${stateObj.state !== UNAVAILABLE
? html` <div class="flex-horizontal">
<div>
<span class="status-subtitle"
>${this.hass!.localize(
"ui.dialogs.more_info_control.lawn_mower.activity"
)}:
</span>
<span>
<strong>
${computeStateDisplay(
this.hass.localize,
stateObj,
this.hass.locale,
this.hass.config,
this.hass.entities
)}
</strong>
</span>
</div>
${this._renderBattery()}
</div>`
: nothing}
${LAWN_MOWER_COMMANDS.some((item) => item.isVisible(stateObj))
? html`
<div>
<p></p>
<div class="status-subtitle">
${this.hass!.localize(
"ui.dialogs.more_info_control.lawn_mower.commands"
)}
</div>
<div class="flex-horizontal space-around">
${LAWN_MOWER_COMMANDS.filter((item) =>
item.isVisible(stateObj)
).map(
(item) => html`
<div>
<ha-icon-button
.path=${item.icon}
.entry=${item}
@click=${this.callService}
.label=${this.hass!.localize(
`ui.dialogs.more_info_control.lawn_mower.${item.translationKey}`
)}
.disabled=${stateObj.state === UNAVAILABLE}
></ha-icon-button>
</div>
`
)}
</div>
</div>
`
: ""}
`;
}
private _deviceEntities = memoizeOne(
(
deviceId: string,
entities: HomeAssistant["entities"]
): EntityRegistryDisplayEntry[] => {
const entries = Object.values(entities);
return entries.filter((entity) => entity.device_id === deviceId);
}
);
private _renderBattery() {
const stateObj = this.stateObj!;
const deviceId = this.hass.entities[stateObj.entity_id]?.device_id;
const entities = deviceId
? this._deviceEntities(deviceId, this.hass.entities)
: [];
const batteryEntity = findBatteryEntity(this.hass, entities);
const battery = batteryEntity
? this.hass.states[batteryEntity.entity_id]
: undefined;
const batteryIsBinary =
battery && computeStateDomain(battery) === "binary_sensor";
// Use device battery entity
if (battery && (batteryIsBinary || !isNaN(battery.state as any))) {
const batteryChargingEntity = findBatteryChargingEntity(
this.hass,
entities
);
const batteryCharging = batteryChargingEntity
? this.hass.states[batteryChargingEntity?.entity_id]
: undefined;
return html`
<div>
<span>
${batteryIsBinary
? ""
: `${Number(battery.state).toFixed()}${blankBeforePercent(
this.hass.locale
)}%`}
<ha-battery-icon
.hass=${this.hass}
.batteryStateObj=${battery}
.batteryChargingStateObj=${batteryCharging}
></ha-battery-icon>
</span>
</div>
`;
}
return nothing;
}
private callService(ev: CustomEvent) {
const entry = (ev.target! as any).entry as LawnMowerCommand;
this.hass.callService("lawn_mower", entry.serviceName, {
entity_id: this.stateObj!.entity_id,
});
}
static get styles(): CSSResultGroup {
return css`
:host {
line-height: 1.5;
}
.status-subtitle {
color: var(--secondary-text-color);
}
.flex-horizontal {
display: flex;
flex-direction: row;
}
.space-around {
justify-content: space-around;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"more-info-lawn_mower": MoreInfoLawnMower;
}
}

View File

@ -21,6 +21,7 @@ const LAZY_LOADED_MORE_INFO_CONTROL = {
image: () => import("./controls/more-info-image"), image: () => import("./controls/more-info-image"),
input_boolean: () => import("./controls/more-info-input_boolean"), input_boolean: () => import("./controls/more-info-input_boolean"),
input_datetime: () => import("./controls/more-info-input_datetime"), input_datetime: () => import("./controls/more-info-input_datetime"),
lawn_mower: () => import("./controls/more-info-lawn_mower"),
light: () => import("./controls/more-info-light"), light: () => import("./controls/more-info-light"),
lock: () => import("./controls/more-info-lock"), lock: () => import("./controls/more-info-lock"),
media_player: () => import("./controls/more-info-media_player"), media_player: () => import("./controls/more-info-media_player"),

View File

@ -5,15 +5,16 @@ import { stateCardType } from "../common/entity/state_card_type";
import "./state-card-alert"; import "./state-card-alert";
import "./state-card-button"; import "./state-card-button";
import "./state-card-climate"; import "./state-card-climate";
import "./state-card-humidifier";
import "./state-card-configurator"; import "./state-card-configurator";
import "./state-card-cover"; import "./state-card-cover";
import "./state-card-display"; import "./state-card-display";
import "./state-card-event"; import "./state-card-event";
import "./state-card-humidifier";
import "./state-card-input_button"; import "./state-card-input_button";
import "./state-card-input_number"; import "./state-card-input_number";
import "./state-card-input_select"; import "./state-card-input_select";
import "./state-card-input_text"; import "./state-card-input_text";
import "./state-card-lawn_mower";
import "./state-card-lock"; import "./state-card-lock";
import "./state-card-media_player"; import "./state-card-media_player";
import "./state-card-number"; import "./state-card-number";

View File

@ -0,0 +1,43 @@
import { HassEntity } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, html } from "lit";
import { customElement, property } from "lit/decorators";
import "../components/entity/state-info";
import "../components/ha-lawn_mower-state";
import { haStyle } from "../resources/styles";
import type { HomeAssistant } from "../types";
@customElement("state-card-lawn_mower")
class StateCardLawnMower extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public stateObj!: HassEntity;
@property({ type: Boolean }) public inDialog = false;
public render() {
const stateObj = this.stateObj;
return html`
<div class="horizontal justified layout">
<state-info
.hass=${this.hass}
.stateObj=${stateObj}
.inDialog=${this.inDialog}
></state-info>
<ha-lawn_mower-state
.hass=${this.hass}
.stateObj=${stateObj}
></ha-lawn_mower-state>
</div>
`;
}
static get styles(): CSSResultGroup {
return haStyle;
}
}
declare global {
interface HTMLElementTagNameMap {
"state-card-lawn_mower": StateCardLawnMower;
}
}

View File

@ -139,6 +139,13 @@
"drying": "{name} drying", "drying": "{name} drying",
"on_entity": "{name} on" "on_entity": "{name} on"
}, },
"lawn_mower": {
"actions": {
"resume_mowing": "Resume mowing",
"start_mowing": "Start mowing",
"dock": "Return to dock"
}
},
"light": { "light": {
"brightness": "Brightness", "brightness": "Brightness",
"color_temperature": "Color temperature", "color_temperature": "Color temperature",
@ -1006,6 +1013,13 @@
"target_label": "[%key:ui::dialogs::more_info_control::climate::target_label%]", "target_label": "[%key:ui::dialogs::more_info_control::climate::target_label%]",
"target": "[%key:ui::dialogs::more_info_control::climate::target%]" "target": "[%key:ui::dialogs::more_info_control::climate::target%]"
}, },
"lawn_mower": {
"activity": "Activity",
"commands": "Lawn mower commands:",
"start_mowing": "Start mowing",
"pause": "Pause",
"dock": "Return to dock"
},
"water_heater": { "water_heater": {
"target": "[%key:ui::dialogs::more_info_control::climate::target%]" "target": "[%key:ui::dialogs::more_info_control::climate::target%]"
} }