diff --git a/src/data/vacuum.ts b/src/data/vacuum.ts
index 0ad0738d1b..9a5b963b86 100644
--- a/src/data/vacuum.ts
+++ b/src/data/vacuum.ts
@@ -2,21 +2,63 @@ import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
+import { UNAVAILABLE } from "./entity";
-export const VACUUM_SUPPORT_PAUSE = 4;
-export const VACUUM_SUPPORT_STOP = 8;
-export const VACUUM_SUPPORT_RETURN_HOME = 16;
-export const VACUUM_SUPPORT_FAN_SPEED = 32;
-export const VACUUM_SUPPORT_BATTERY = 64;
-export const VACUUM_SUPPORT_STATUS = 128;
-export const VACUUM_SUPPORT_LOCATE = 512;
-export const VACUUM_SUPPORT_CLEAN_SPOT = 1024;
-export const VACUUM_SUPPORT_START = 8192;
+export type VacuumEntityState =
+ | "on"
+ | "off"
+ | "cleaning"
+ | "docked"
+ | "idle"
+ | "paused"
+ | "returning"
+ | "error";
-export type VacuumEntity = HassEntityBase & {
- attributes: HassEntityAttributeBase & {
- battery_level: number;
- fan_speed: any;
- [key: string]: any;
- };
-};
+export const enum VacuumEntityFeature {
+ TURN_ON = 1,
+ TURN_OFF = 2,
+ PAUSE = 4,
+ STOP = 8,
+ RETURN_HOME = 16,
+ FAN_SPEED = 32,
+ BATTERY = 64,
+ STATUS = 128,
+ SEND_COMMAND = 256,
+ LOCATE = 512,
+ CLEAN_SPOT = 1024,
+ MAP = 2048,
+ STATE = 4096,
+ START = 8192,
+}
+
+interface VacuumEntityAttributes extends HassEntityAttributeBase {
+ battery_level?: number;
+ fan_speed?: any;
+ [key: string]: any;
+}
+
+export interface VacuumEntity extends HassEntityBase {
+ attributes: VacuumEntityAttributes;
+}
+
+export function isCleaning(stateObj: VacuumEntity): boolean {
+ return ["cleaning", "on"].includes(stateObj.state);
+}
+
+export function canStart(stateObj: VacuumEntity): boolean {
+ if (stateObj.state === UNAVAILABLE) {
+ return false;
+ }
+ return !isCleaning(stateObj);
+}
+
+export function canStop(stateObj: VacuumEntity): boolean {
+ return !["docked", "off"].includes(stateObj.state);
+}
+
+export function canReturnHome(stateObj: VacuumEntity): boolean {
+ if (stateObj.state === UNAVAILABLE) {
+ return false;
+ }
+ return stateObj.state !== "returning";
+}
diff --git a/src/dialogs/more-info/controls/more-info-vacuum.ts b/src/dialogs/more-info/controls/more-info-vacuum.ts
index e53c4e9118..5a183712d6 100644
--- a/src/dialogs/more-info/controls/more-info-vacuum.ts
+++ b/src/dialogs/more-info/controls/more-info-vacuum.ts
@@ -18,18 +18,7 @@ import "../../../components/ha-icon";
import "../../../components/ha-icon-button";
import "../../../components/ha-select";
import { UNAVAILABLE } from "../../../data/entity";
-import {
- VacuumEntity,
- VACUUM_SUPPORT_BATTERY,
- VACUUM_SUPPORT_CLEAN_SPOT,
- VACUUM_SUPPORT_FAN_SPEED,
- VACUUM_SUPPORT_LOCATE,
- VACUUM_SUPPORT_PAUSE,
- VACUUM_SUPPORT_RETURN_HOME,
- VACUUM_SUPPORT_START,
- VACUUM_SUPPORT_STATUS,
- VACUUM_SUPPORT_STOP,
-} from "../../../data/vacuum";
+import { VacuumEntity, VacuumEntityFeature } from "../../../data/vacuum";
import { HomeAssistant } from "../../../types";
interface VacuumCommand {
@@ -44,7 +33,8 @@ const VACUUM_COMMANDS: VacuumCommand[] = [
translationKey: "start",
icon: mdiPlay,
serviceName: "start",
- isVisible: (stateObj) => supportsFeature(stateObj, VACUUM_SUPPORT_START),
+ isVisible: (stateObj) =>
+ supportsFeature(stateObj, VacuumEntityFeature.START),
},
{
translationKey: "pause",
@@ -52,8 +42,8 @@ const VACUUM_COMMANDS: VacuumCommand[] = [
serviceName: "pause",
isVisible: (stateObj) =>
// We need also to check if Start is supported because if not we show play-pause
- supportsFeature(stateObj, VACUUM_SUPPORT_START) &&
- supportsFeature(stateObj, VACUUM_SUPPORT_PAUSE),
+ supportsFeature(stateObj, VacuumEntityFeature.START) &&
+ supportsFeature(stateObj, VacuumEntityFeature.PAUSE),
},
{
translationKey: "start_pause",
@@ -61,34 +51,36 @@ const VACUUM_COMMANDS: VacuumCommand[] = [
serviceName: "start_pause",
isVisible: (stateObj) =>
// If start is supported, we don't show this button
- !supportsFeature(stateObj, VACUUM_SUPPORT_START) &&
- supportsFeature(stateObj, VACUUM_SUPPORT_PAUSE),
+ !supportsFeature(stateObj, VacuumEntityFeature.START) &&
+ supportsFeature(stateObj, VacuumEntityFeature.PAUSE),
},
{
translationKey: "stop",
icon: mdiStop,
serviceName: "stop",
- isVisible: (stateObj) => supportsFeature(stateObj, VACUUM_SUPPORT_STOP),
+ isVisible: (stateObj) =>
+ supportsFeature(stateObj, VacuumEntityFeature.STOP),
},
{
translationKey: "clean_spot",
icon: mdiTargetVariant,
serviceName: "clean_spot",
isVisible: (stateObj) =>
- supportsFeature(stateObj, VACUUM_SUPPORT_CLEAN_SPOT),
+ supportsFeature(stateObj, VacuumEntityFeature.CLEAN_SPOT),
},
{
translationKey: "locate",
icon: mdiMapMarker,
serviceName: "locate",
- isVisible: (stateObj) => supportsFeature(stateObj, VACUUM_SUPPORT_LOCATE),
+ isVisible: (stateObj) =>
+ supportsFeature(stateObj, VacuumEntityFeature.LOCATE),
},
{
translationKey: "return_home",
icon: mdiHomeMapMarker,
serviceName: "return_to_base",
isVisible: (stateObj) =>
- supportsFeature(stateObj, VACUUM_SUPPORT_RETURN_HOME),
+ supportsFeature(stateObj, VacuumEntityFeature.RETURN_HOME),
},
];
@@ -111,7 +103,7 @@ class MoreInfoVacuum extends LitElement {
return html`
${stateObj.state !== UNAVAILABLE
? html`
- ${supportsFeature(stateObj, VACUUM_SUPPORT_STATUS)
+ ${supportsFeature(stateObj, VacuumEntityFeature.STATUS)
? html`
`
: ""}
- ${supportsFeature(stateObj, VACUUM_SUPPORT_BATTERY) &&
+ ${supportsFeature(stateObj, VacuumEntityFeature.BATTERY) &&
stateObj.attributes.battery_level
? html`
@@ -177,7 +169,7 @@ class MoreInfoVacuum extends LitElement {
`
: ""}
- ${supportsFeature(stateObj, VACUUM_SUPPORT_FAN_SPEED)
+ ${supportsFeature(stateObj, VacuumEntityFeature.FAN_SPEED)
? html`
diff --git a/src/panels/lovelace/create-element/create-tile-extra-element.ts b/src/panels/lovelace/create-element/create-tile-extra-element.ts
index e023cdc5f7..25321d149f 100644
--- a/src/panels/lovelace/create-element/create-tile-extra-element.ts
+++ b/src/panels/lovelace/create-element/create-tile-extra-element.ts
@@ -6,11 +6,13 @@ import {
import "../tile-extra/hui-cover-open-close-tile-extra";
import "../tile-extra/hui-cover-tilt-tile-extra";
import "../tile-extra/hui-light-brightness-tile-extra";
+import "../tile-extra/hui-vacuum-commands-tile-extra";
const TYPES: Set
= new Set([
"cover-open-close",
"cover-tilt",
"light-brightness",
+ "vacuum-commands",
]);
export const createTileExtraElement = (config: LovelaceTileExtraConfig) =>
diff --git a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts
index e03e130296..7ac30fb4e6 100644
--- a/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-tile-card-editor.ts
@@ -20,7 +20,10 @@ import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
import type { TileCardConfig } from "../../cards/types";
-import { LovelaceTileExtraConfig } from "../../tile-extra/types";
+import {
+ LovelaceTileExtraConfig,
+ LovelaceTileExtraContext,
+} from "../../tile-extra/types";
import type { LovelaceCardEditor } from "../../types";
import "../hui-sub-element-editor";
import { actionConfigStruct } from "../structs/action-struct";
@@ -129,6 +132,10 @@ export class HuiTileCardEditor
] as const
);
+ private _context = memoizeOne(
+ (entity_id?: string): LovelaceTileExtraContext => ({ entity_id })
+ );
+
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
@@ -149,6 +156,7 @@ export class HuiTileCardEditor
diff --git a/src/panels/lovelace/editor/config-elements/hui-tile-card-extras-editor.ts b/src/panels/lovelace/editor/config-elements/hui-tile-card-extras-editor.ts
index bc5dbed4ac..24e75cf2aa 100644
--- a/src/panels/lovelace/editor/config-elements/hui-tile-card-extras-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-tile-card-extras-editor.ts
@@ -33,6 +33,7 @@ const EXTRAS_TYPE: LovelaceTileExtraConfig["type"][] = [
"cover-open-close",
"cover-tilt",
"light-brightness",
+ "vacuum-commands",
];
declare global {
@@ -227,7 +228,7 @@ export class HuiTileCardExtrasEditor extends LitElement {
let newExtra: LovelaceTileExtraConfig;
if (elClass && elClass.getStubConfig) {
- newExtra = await elClass.getStubConfig(this.hass!);
+ newExtra = await elClass.getStubConfig(this.hass!, this.stateObj);
} else {
newExtra = { type: value } as LovelaceTileExtraConfig;
}
diff --git a/src/panels/lovelace/editor/config-elements/hui-vacuum-commands-tile-extra-editor.ts b/src/panels/lovelace/editor/config-elements/hui-vacuum-commands-tile-extra-editor.ts
new file mode 100644
index 0000000000..8613ac9dc9
--- /dev/null
+++ b/src/panels/lovelace/editor/config-elements/hui-vacuum-commands-tile-extra-editor.ts
@@ -0,0 +1,101 @@
+import { HassEntity } from "home-assistant-js-websocket";
+import { html, LitElement, TemplateResult } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import memoizeOne from "memoize-one";
+import { fireEvent } from "../../../../common/dom/fire_event";
+import type { LocalizeFunc } from "../../../../common/translations/localize";
+import type { SchemaUnion } from "../../../../components/ha-form/types";
+import type { HomeAssistant } from "../../../../types";
+import { supportsVacuumCommand } from "../../tile-extra/hui-vacuum-commands-tile-extra";
+import {
+ LovelaceTileExtraContext,
+ VacuumCommandsTileExtraConfig,
+ VACUUM_COMMANDS,
+} from "../../tile-extra/types";
+import type { LovelaceTileExtraEditor } from "../../types";
+
+@customElement("hui-vacuum-commands-tile-extra-editor")
+export class HuiVacuumCommandsTileExtraEditor
+ extends LitElement
+ implements LovelaceTileExtraEditor
+{
+ @property({ attribute: false }) public hass?: HomeAssistant;
+
+ @property({ attribute: false }) public context?: LovelaceTileExtraContext;
+
+ @state() private _config?: VacuumCommandsTileExtraConfig;
+
+ public setConfig(config: VacuumCommandsTileExtraConfig): void {
+ this._config = config;
+ }
+
+ private _schema = memoizeOne(
+ (localize: LocalizeFunc, stateObj?: HassEntity) =>
+ [
+ {
+ name: "commands",
+ selector: {
+ select: {
+ multiple: true,
+ options: VACUUM_COMMANDS.filter(
+ (command) =>
+ stateObj && supportsVacuumCommand(stateObj, command)
+ ).map((command) => ({
+ value: command,
+ label: `${localize(
+ `ui.panel.lovelace.editor.card.tile.extras.types.vacuum-commands.commands_list.${command}`
+ )}`,
+ })),
+ },
+ },
+ },
+ ] as const
+ );
+
+ protected render(): TemplateResult {
+ if (!this.hass || !this._config) {
+ return html``;
+ }
+
+ const stateObj = this.context?.entity_id
+ ? this.hass.states[this.context?.entity_id]
+ : undefined;
+
+ const schema = this._schema(this.hass.localize, stateObj);
+
+ return html`
+
+ `;
+ }
+
+ private _valueChanged(ev: CustomEvent): void {
+ fireEvent(this, "config-changed", { config: ev.detail.value });
+ }
+
+ private _computeLabelCallback = (
+ schema: SchemaUnion>
+ ) => {
+ switch (schema.name) {
+ case "commands":
+ return this.hass!.localize(
+ `ui.panel.lovelace.editor.card.tile.extras.types.vacuum-commands.${schema.name}`
+ );
+ default:
+ return this.hass!.localize(
+ `ui.panel.lovelace.editor.card.generic.${schema.name}`
+ );
+ }
+ };
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-vacuum-commands-tile-extra-editor": HuiVacuumCommandsTileExtraEditor;
+ }
+}
diff --git a/src/panels/lovelace/editor/hui-element-editor.ts b/src/panels/lovelace/editor/hui-element-editor.ts
index bfa97f75bd..7bf4a80c6d 100644
--- a/src/panels/lovelace/editor/hui-element-editor.ts
+++ b/src/panels/lovelace/editor/hui-element-editor.ts
@@ -57,11 +57,13 @@ export interface UIConfigChangedEvent extends Event {
};
}
-export abstract class HuiElementEditor extends LitElement {
+export abstract class HuiElementEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public lovelace?: LovelaceConfig;
+ @property({ attribute: false }) public context?: C;
+
@state() private _yaml?: string;
@state() private _config?: T;
@@ -275,6 +277,9 @@ export abstract class HuiElementEditor extends LitElement {
) {
this._configElement.lovelace = this.lovelace;
}
+ if (this._configElement && changedProperties.has("context")) {
+ this._configElement.context = this.context;
+ }
}
private _handleUIConfigChanged(ev: UIConfigChangedEvent) {
@@ -328,6 +333,7 @@ export abstract class HuiElementEditor extends LitElement {
if ("lovelace" in configElement) {
configElement.lovelace = this.lovelace;
}
+ configElement.context = this.context;
configElement.addEventListener("config-changed", (ev) =>
this._handleUIConfigChanged(ev as UIConfigChangedEvent)
);
diff --git a/src/panels/lovelace/editor/hui-sub-element-editor.ts b/src/panels/lovelace/editor/hui-sub-element-editor.ts
index 2d46415ffa..a8510541d6 100644
--- a/src/panels/lovelace/editor/hui-sub-element-editor.ts
+++ b/src/panels/lovelace/editor/hui-sub-element-editor.ts
@@ -25,6 +25,8 @@ export class HuiSubElementEditor extends LitElement {
@property({ attribute: false }) public config!: SubElementEditorConfig;
+ @property({ attribute: false }) public context?: any;
+
@state() private _guiModeAvailable = true;
@state() private _guiMode = true;
@@ -66,6 +68,7 @@ export class HuiSubElementEditor extends LitElement {
class="editor"
.hass=${this.hass}
.value=${this.config.elementConfig}
+ .context=${this.context}
@config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
>
@@ -76,6 +79,7 @@ export class HuiSubElementEditor extends LitElement {
class="editor"
.hass=${this.hass}
.value=${this.config.elementConfig}
+ .context=${this.context}
@config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
>
@@ -86,6 +90,7 @@ export class HuiSubElementEditor extends LitElement {
class="editor"
.hass=${this.hass}
.value=${this.config.elementConfig}
+ .context=${this.context}
@config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
>
diff --git a/src/panels/lovelace/editor/tile-extra/hui-tile-extra-element-editor.ts b/src/panels/lovelace/editor/tile-extra/hui-tile-extra-element-editor.ts
index 046a4d2d14..95300a78d2 100644
--- a/src/panels/lovelace/editor/tile-extra/hui-tile-extra-element-editor.ts
+++ b/src/panels/lovelace/editor/tile-extra/hui-tile-extra-element-editor.ts
@@ -1,11 +1,17 @@
import { customElement } from "lit/decorators";
import { getTileExtraElementClass } from "../../create-element/create-tile-extra-element";
-import { LovelaceTileExtraConfig } from "../../tile-extra/types";
+import {
+ LovelaceTileExtraConfig,
+ LovelaceTileExtraContext,
+} from "../../tile-extra/types";
import type { LovelaceTileExtraEditor } from "../../types";
import { HuiElementEditor } from "../hui-element-editor";
@customElement("hui-tile-extra-element-editor")
-export class HuiTileExtraElementEditor extends HuiElementEditor {
+export class HuiTileExtraElementEditor extends HuiElementEditor<
+ LovelaceTileExtraConfig,
+ LovelaceTileExtraContext
+> {
protected async getConfigElement(): Promise<
LovelaceTileExtraEditor | undefined
> {
diff --git a/src/panels/lovelace/tile-extra/hui-vacuum-commands-tile-extra.ts b/src/panels/lovelace/tile-extra/hui-vacuum-commands-tile-extra.ts
new file mode 100644
index 0000000000..9a2c7e9191
--- /dev/null
+++ b/src/panels/lovelace/tile-extra/hui-vacuum-commands-tile-extra.ts
@@ -0,0 +1,220 @@
+import {
+ mdiHomeMapMarker,
+ mdiMapMarker,
+ mdiPause,
+ mdiPlay,
+ mdiPlayPause,
+ mdiStop,
+ mdiTargetVariant,
+} from "@mdi/js";
+import { HassEntity } from "home-assistant-js-websocket";
+import { css, html, LitElement, TemplateResult } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { supportsFeature } from "../../../common/entity/supports-feature";
+import "../../../components/tile/ha-tile-button";
+import { UNAVAILABLE } from "../../../data/entity";
+import {
+ canReturnHome,
+ canStart,
+ canStop,
+ isCleaning,
+ VacuumEntity,
+ VacuumEntityFeature,
+} from "../../../data/vacuum";
+import { HomeAssistant } from "../../../types";
+import { LovelaceTileExtra, LovelaceTileExtraEditor } from "../types";
+import {
+ VacuumCommand,
+ VacuumCommandsTileExtraConfig,
+ VACUUM_COMMANDS,
+} from "./types";
+
+interface VacuumButton {
+ translationKey: string;
+ icon: string;
+ serviceName: string;
+ disabled?: boolean;
+}
+
+export const VACUUM_COMMANDS_FEATURES: Record<
+ VacuumCommand,
+ VacuumEntityFeature[]
+> = {
+ start_pause: [VacuumEntityFeature.PAUSE],
+ stop: [VacuumEntityFeature.STOP],
+ clean_spot: [VacuumEntityFeature.CLEAN_SPOT],
+ locate: [VacuumEntityFeature.LOCATE],
+ return_home: [VacuumEntityFeature.RETURN_HOME],
+};
+
+export const supportsVacuumCommand = (
+ stateObj: HassEntity,
+ command: VacuumCommand
+): boolean =>
+ VACUUM_COMMANDS_FEATURES[command].some((feature) =>
+ supportsFeature(stateObj, feature)
+ );
+
+export const VACUUM_COMMANDS_BUTTONS: Record<
+ VacuumCommand,
+ (stateObj: VacuumEntity) => VacuumButton
+> = {
+ start_pause: (stateObj) => {
+ const startPauseOnly =
+ !supportsFeature(stateObj, VacuumEntityFeature.START) &&
+ supportsFeature(stateObj, VacuumEntityFeature.PAUSE);
+
+ if (startPauseOnly) {
+ return {
+ translationKey: "start_pause",
+ icon: mdiPlayPause,
+ serviceName: "start_pause",
+ };
+ }
+ const canPause =
+ isCleaning(stateObj) &&
+ supportsFeature(stateObj, VacuumEntityFeature.PAUSE);
+
+ return canPause
+ ? {
+ translationKey: "pause",
+ icon: mdiPause,
+ serviceName: "pause",
+ }
+ : {
+ translationKey: "start",
+ icon: mdiPlay,
+ serviceName: "start",
+ disabled: canStart(stateObj),
+ };
+ },
+ stop: (stateObj) => ({
+ translationKey: "stop",
+ icon: mdiStop,
+ serviceName: "stop",
+ disabled: !canStop(stateObj),
+ }),
+ clean_spot: () => ({
+ translationKey: "clean_spot",
+ icon: mdiTargetVariant,
+ serviceName: "clean_spot",
+ }),
+ locate: () => ({
+ translationKey: "locate",
+ icon: mdiMapMarker,
+ serviceName: "locate",
+ }),
+ return_home: (stateObj) => ({
+ translationKey: "return_home",
+ icon: mdiHomeMapMarker,
+ serviceName: "return_to_base",
+ disabled: !canReturnHome(stateObj),
+ }),
+};
+
+@customElement("hui-vacuum-commands-tile-extra")
+class HuiVacuumCommandTileExtra
+ extends LitElement
+ implements LovelaceTileExtra
+{
+ @property({ attribute: false }) public hass?: HomeAssistant;
+
+ @property({ attribute: false }) public stateObj?: HassEntity;
+
+ @state() private _config?: VacuumCommandsTileExtraConfig;
+
+ static getStubConfig(
+ _,
+ stateObj?: HassEntity
+ ): VacuumCommandsTileExtraConfig {
+ return {
+ type: "vacuum-commands",
+ commands: stateObj
+ ? VACUUM_COMMANDS.filter((c) =>
+ supportsVacuumCommand(stateObj, c)
+ ).slice(0, 3)
+ : [],
+ };
+ }
+
+ public static async getConfigElement(): Promise {
+ await import(
+ "../editor/config-elements/hui-vacuum-commands-tile-extra-editor"
+ );
+ return document.createElement("hui-vacuum-commands-tile-extra-editor");
+ }
+
+ public setConfig(config: VacuumCommandsTileExtraConfig): void {
+ if (!config) {
+ throw new Error("Invalid configuration");
+ }
+ this._config = config;
+ }
+
+ private _onCommandTap(ev): void {
+ ev.stopPropagation();
+ const entry = (ev.target! as any).entry as VacuumButton;
+ this.hass!.callService("vacuum", entry.serviceName, {
+ entity_id: this.stateObj!.entity_id,
+ });
+ }
+
+ protected render(): TemplateResult {
+ if (!this._config || !this.hass || !this.stateObj) {
+ return html``;
+ }
+
+ const stateObj = this.stateObj as VacuumEntity;
+
+ return html`
+
+ ${VACUUM_COMMANDS.filter(
+ (command) =>
+ supportsVacuumCommand(stateObj, command) &&
+ this._config?.commands?.includes(command)
+ ).map((command) => {
+ const button = VACUUM_COMMANDS_BUTTONS[command](stateObj);
+ return html`
+
+
+
+ `;
+ })}
+
+ `;
+ }
+
+ static get styles() {
+ return css`
+ .container {
+ display: flex;
+ flex-direction: row;
+ padding: 0 12px 12px 12px;
+ width: auto;
+ }
+ ha-tile-button {
+ flex: 1;
+ }
+ ha-tile-button:not(:last-child) {
+ margin-right: 12px;
+ margin-inline-end: 12px;
+ margin-inline-start: initial;
+ direction: var(--direction);
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-vacuum-commands-tile-extra": HuiVacuumCommandTileExtra;
+ }
+}
diff --git a/src/panels/lovelace/tile-extra/tile-extras.ts b/src/panels/lovelace/tile-extra/tile-extras.ts
index be2029d086..2f8cb7d05b 100644
--- a/src/panels/lovelace/tile-extra/tile-extras.ts
+++ b/src/panels/lovelace/tile-extra/tile-extras.ts
@@ -3,7 +3,8 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { CoverEntityFeature } from "../../../data/cover";
import { lightSupportsBrightness } from "../../../data/light";
-import { LovelaceTileExtraConfig } from "./types";
+import { supportsVacuumCommand } from "./hui-vacuum-commands-tile-extra";
+import { LovelaceTileExtraConfig, VACUUM_COMMANDS } from "./types";
type TileExtraType = LovelaceTileExtraConfig["type"];
export type SupportsTileExtra = (stateObj: HassEntity) => boolean;
@@ -20,9 +21,12 @@ const TILE_EXTRAS_SUPPORT: Record = {
"light-brightness": (stateObj) =>
computeDomain(stateObj.entity_id) === "light" &&
lightSupportsBrightness(stateObj),
+ "vacuum-commands": (stateObj) =>
+ computeDomain(stateObj.entity_id) === "vacuum" &&
+ VACUUM_COMMANDS.some((c) => supportsVacuumCommand(stateObj, c)),
};
-const TILE_EXTRAS_EDITABLE: Set = new Set([]);
+const TILE_EXTRAS_EDITABLE: Set = new Set(["vacuum-commands"]);
export const supportsTileExtra = (
stateObj: HassEntity,
diff --git a/src/panels/lovelace/tile-extra/types.ts b/src/panels/lovelace/tile-extra/types.ts
index 57d0897096..53d117f465 100644
--- a/src/panels/lovelace/tile-extra/types.ts
+++ b/src/panels/lovelace/tile-extra/types.ts
@@ -10,7 +10,27 @@ export interface LightBrightnessTileExtraConfig {
type: "light-brightness";
}
+export const VACUUM_COMMANDS = [
+ "start_pause",
+ "stop",
+ "clean_spot",
+ "locate",
+ "return_home",
+] as const;
+
+export type VacuumCommand = typeof VACUUM_COMMANDS[number];
+
+export interface VacuumCommandsTileExtraConfig {
+ type: "vacuum-commands";
+ commands?: VacuumCommand[];
+}
+
export type LovelaceTileExtraConfig =
| CoverOpenCloseTileExtraConfig
| CoverTiltTileExtraConfig
- | LightBrightnessTileExtraConfig;
+ | LightBrightnessTileExtraConfig
+ | VacuumCommandsTileExtraConfig;
+
+export type LovelaceTileExtraContext = {
+ entity_id?: string;
+};
diff --git a/src/panels/lovelace/types.ts b/src/panels/lovelace/types.ts
index bb656818d5..55a39b536c 100644
--- a/src/panels/lovelace/types.ts
+++ b/src/panels/lovelace/types.ts
@@ -88,9 +88,10 @@ export interface LovelaceRowEditor extends LovelaceGenericElementEditor {
setConfig(config: LovelaceRowConfig): void;
}
-export interface LovelaceGenericElementEditor extends HTMLElement {
+export interface LovelaceGenericElementEditor extends HTMLElement {
hass?: HomeAssistant;
lovelace?: LovelaceConfig;
+ context?: C;
setConfig(config: any): void;
focusYamlEditor?: () => void;
}
@@ -104,7 +105,10 @@ export interface LovelaceTileExtra extends HTMLElement {
export interface LovelaceTileExtraConstructor
extends Constructor {
getConfigElement?: () => LovelaceTileExtraEditor;
- getStubConfig?: (hass: HomeAssistant) => LovelaceTileExtraConfig;
+ getStubConfig?: (
+ hass: HomeAssistant,
+ stateObj?: HassEntity
+ ) => LovelaceTileExtraConfig;
}
export interface LovelaceTileExtraEditor extends LovelaceGenericElementEditor {
diff --git a/src/translations/en.json b/src/translations/en.json
index ea11f54a27..1ba64f531b 100755
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -4237,6 +4237,17 @@
},
"light-brightness": {
"label": "Light brightness"
+ },
+ "vacuum-commands": {
+ "label": "Vacuum commands",
+ "commands": "Commands",
+ "commands_list": {
+ "start_pause": "[%key:ui::dialogs::more_info_control::vacuum::start_pause%]",
+ "stop": "[%key:ui::dialogs::more_info_control::vacuum::stop%]",
+ "clean_spot": "[%key:ui::dialogs::more_info_control::vacuum::clean_spot%]",
+ "locate": "[%key:ui::dialogs::more_info_control::vacuum::locate%]",
+ "return_home": "[%key:ui::dialogs::more_info_control::vacuum::return_home%]"
+ }
}
}
}