Basic service call framework set up

This commit is contained in:
Thomas Lovén 2022-07-17 23:07:58 +00:00
parent 26b4abaf3f
commit 3bf2481e5b
7 changed files with 279 additions and 256 deletions

View File

@ -4,6 +4,7 @@ from .store import BrowserModStore
from .mod_view import async_setup_view from .mod_view import async_setup_view
from .connection import async_setup_connection from .connection import async_setup_connection
from .const import DOMAIN, DATA_DEVICES, DATA_ADDERS, DATA_STORE from .const import DOMAIN, DATA_DEVICES, DATA_ADDERS, DATA_STORE
from .service import async_setup_services
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -32,5 +33,6 @@ async def async_setup_entry(hass, config_entry):
await async_setup_connection(hass) await async_setup_connection(hass)
await async_setup_view(hass) await async_setup_view(hass)
await async_setup_services(hass)
return True return True

View File

@ -842,16 +842,14 @@ const ServicesMixin = (SuperClass) => {
data: data:
[title: <string>] [title: <string>]
[content: <string | Lovelace Card Configuration>] [content: <string | Lovelace Card Configuration>]
[primary_action: <string>] [right_button: <string>]
[secondary_action: <string>] [right_button_action: <service call>]
[left_button: <string>]
[left_button_action: <service call>]
[dismissable: <TRUE/false>] [dismissable: <TRUE/false>]
[dismiss_action: <service call>]
[timeout: <number>] [timeout: <number>]
[callbacks: [timeout_action: <service call>]
[primary_action: <service call>]
[secondary_action: <service call>]
[timeout: <service call>]
[dismissed: <service call>]
]
Close popup Close popup
service: browser_mod.close_popup service: browser_mod.close_popup
@ -861,18 +859,37 @@ const ServicesMixin = (SuperClass) => {
data: data:
message: <string> message: <string>
*/ */
_service_action({ service, data }) { constructor() {
super();
const cmds = ["sequence", "delay", "popup", "close_popup"];
for (const service of cmds) {
this.addEventListener(`command-${service}`, (ev) => {
this._service_action({
service,
data: ev.detail,
});
});
}
}
async _service_action({ service, data }) {
let _service = service; let _service = service;
if (!_service.startsWith("browser_mod.") && _service.includes(".")) ; if (!_service.startsWith("browser_mod.") && _service.includes(".")) ;
if (_service.startsWith("browser_mod.")) { if (_service.startsWith("browser_mod.")) {
_service = _service.substring(12); _service = _service.substring(12);
} }
switch (_service) { switch (_service) {
case "sequence":
for (const a of data.sequence)
await this._service_action(a);
break;
case "delay":
await new Promise((resolve) => setTimeout(resolve, data.time));
break;
case "popup": case "popup":
const { title, content } = data, d = __rest(data, ["title", "content"]); const { title, content } = data, d = __rest(data, ["title", "content"]);
if (d.callbacks) { for (const [k, v] of Object.entries(d)) {
for (const [k, v] of Object.entries(data.callbacks)) { if (k.endsWith("_action")) {
d.callbacks[k] = () => this._service_action(v); d[k] = () => this._service_action(v);
} }
} }
this.showPopup(title, content, d); this.showPopup(title, content, d);
@ -919,7 +936,7 @@ class BrowserModPopup extends s {
}, 10); }, 10);
} }
} }
async setupDialog(title, content, { primary_action = undefined, secondary_action = undefined, dismissable = true, timeout = undefined, callbacks = undefined, } = {}) { async setupDialog(title, content, { right_button = undefined, right_button_action = undefined, left_button = undefined, left_button_action = undefined, dismissable = true, dismiss_action = undefined, timeout = undefined, timeout_action = undefined, } = {}) {
this.title = title; this.title = title;
if (content && typeof content === "object") { if (content && typeof content === "object") {
// Create a card from config in content // Create a card from config in content
@ -932,40 +949,46 @@ class BrowserModPopup extends s {
} }
else { else {
// Basic HTML content // Basic HTML content
this.card = undefined;
this.content = o(content); this.content = o(content);
} }
this.primary_action = primary_action; this.right_button = right_button;
this.secondary_action = secondary_action; this.left_button = left_button;
this.actions = primary_action === undefined ? undefined : ""; this.actions = right_button === undefined ? undefined : "";
this.dismissable = dismissable; this.dismissable = dismissable;
this.timeout = timeout; this.timeout = timeout;
this.callbacks = callbacks; this._actions = {
right_button_action,
left_button_action,
dismiss_action,
timeout_action,
};
} }
_primary() { _primary() {
var _a, _b, _c; var _a, _b, _c;
if ((_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.dismiss) if ((_a = this._actions) === null || _a === void 0 ? void 0 : _a.dismiss_action)
this.callbacks.dismiss = undefined; this._actions.dismiss_action = undefined;
this.closeDialog(); this.closeDialog();
(_c = (_b = this.callbacks) === null || _b === void 0 ? void 0 : _b.primary_action) === null || _c === void 0 ? void 0 : _c.call(_b); (_c = (_b = this._actions) === null || _b === void 0 ? void 0 : _b.right_button_action) === null || _c === void 0 ? void 0 : _c.call(_b);
} }
_secondary() { _secondary() {
var _a, _b, _c; var _a, _b, _c;
if ((_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.dismiss) if ((_a = this._actions) === null || _a === void 0 ? void 0 : _a.dismiss_action)
this.callbacks.dismiss = undefined; this._actions.dismiss_action = undefined;
this.closeDialog(); this.closeDialog();
(_c = (_b = this.callbacks) === null || _b === void 0 ? void 0 : _b.secondary_action) === null || _c === void 0 ? void 0 : _c.call(_b); (_c = (_b = this._actions) === null || _b === void 0 ? void 0 : _b.left_button_action) === null || _c === void 0 ? void 0 : _c.call(_b);
} }
_dismiss() { _dismiss() {
var _a, _b; var _a, _b;
this.closeDialog(); this.closeDialog();
(_b = (_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.dismiss) === null || _b === void 0 ? void 0 : _b.call(_a); (_b = (_a = this._actions) === null || _a === void 0 ? void 0 : _a.dismiss_action) === null || _b === void 0 ? void 0 : _b.call(_a);
} }
_timeout() { _timeout() {
var _a, _b, _c; var _a, _b, _c;
if ((_a = this.callbacks) === null || _a === void 0 ? void 0 : _a.dismiss) if ((_a = this._actions) === null || _a === void 0 ? void 0 : _a.dismiss_action)
this.callbacks.dismiss = undefined; this._actions.dismiss_action = undefined;
this.closeDialog(); this.closeDialog();
(_c = (_b = this.callbacks) === null || _b === void 0 ? void 0 : _b.timeout) === null || _c === void 0 ? void 0 : _c.call(_b); (_c = (_b = this._actions) === null || _b === void 0 ? void 0 : _b.timeout_action) === null || _c === void 0 ? void 0 : _c.call(_b);
} }
render() { render() {
if (!this.open) if (!this.open)
@ -999,20 +1022,20 @@ class BrowserModPopup extends s {
<div class="content">${this.content}</div> <div class="content">${this.content}</div>
${this.primary_action !== undefined ${this.right_button !== undefined
? $ ` ? $ `
<mwc-button <mwc-button
slot="primaryAction" slot="primaryAction"
.label=${this.primary_action} .label=${this.right_button}
@click=${this._primary} @click=${this._primary}
></mwc-button> ></mwc-button>
` `
: ""} : ""}
${this.secondary_action !== undefined ${this.left_button !== undefined
? $ ` ? $ `
<mwc-button <mwc-button
slot="secondaryAction" slot="secondaryAction"
.label=${this.secondary_action} .label=${this.left_button}
@click=${this._secondary} @click=${this._secondary}
></mwc-button> ></mwc-button>
` `
@ -1134,10 +1157,10 @@ __decorate([
], BrowserModPopup.prototype, "card", void 0); ], BrowserModPopup.prototype, "card", void 0);
__decorate([ __decorate([
e$2() e$2()
], BrowserModPopup.prototype, "primary_action", void 0); ], BrowserModPopup.prototype, "right_button", void 0);
__decorate([ __decorate([
e$2() e$2()
], BrowserModPopup.prototype, "secondary_action", void 0); ], BrowserModPopup.prototype, "left_button", void 0);
__decorate([ __decorate([
e$2() e$2()
], BrowserModPopup.prototype, "dismissable", void 0); ], BrowserModPopup.prototype, "dismissable", void 0);
@ -1232,15 +1255,20 @@ var pjson = {
/* /*
TODO: TODO:
- Fix nomenclature
- Command -> Service
- Device -> Browser
- Popups - Popups
X Basic popups X Basic popups
- Card-mod integration - Card-mod integration
X Timeout X Timeout
X Fullscreen X Fullscreen
- Motion/occupancy tracker
- Information about interaction requirement - Information about interaction requirement
- Information about fullykiosk - Information about fullykiosk
- Commands - Commands
- Framework - Rename browser_mod commands to browser_mod services
x Framework
- ll-custom handling - ll-custom handling
- Commands - Commands
x popup x popup
@ -1250,8 +1278,8 @@ var pjson = {
- lovelace-reload - lovelace-reload
- window-reload - window-reload
- screensaver - screensaver
- sequence x sequence
- delay x delay
- toast? - toast?
- Redesign services to target devices - Redesign services to target devices
- frontend editor for popup cards - frontend editor for popup cards

View File

@ -5,19 +5,73 @@ from homeassistant.helpers.entity_registry import (
async_entries_for_device, async_entries_for_device,
) )
from homeassistant.const import STATE_UNAVAILABLE from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.helpers import device_registry, area_registry
from .const import ( from .const import (
DOMAIN, DOMAIN,
DATA_DEVICES, DATA_DEVICES,
DATA_ALIASES, DATA_ALIASES,
USER_COMMANDS, USER_COMMANDS,
DATA_CONFIG,
CONFIG_DEVICES,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_services(hass):
def call_service(service, targets, data):
devices = hass.data[DOMAIN][DATA_DEVICES]
if isinstance(targets, str):
targets = [targets]
for target in targets:
if target not in devices:
continue
device = devices[target]
device.send(service, **data)
def handle_service(call):
service = call.service
data = {**call.data}
device_ids = set(data.get("device_id", []))
data.pop("device_id", None)
area_ids = set(data.get("area_id", []))
data.pop("area_id", None)
targets = data.get("target", [])
if isinstance(targets, str):
targets = [targets]
targets = set(targets)
data.pop("target", None)
dr = device_registry.async_get(hass)
for device in device_ids:
dev = dr.async_get(device)
if not dev:
continue
browserID = list(dev.identifiers)[0][1]
if browserID is None:
continue
targets.add(browserID)
for area in area_ids:
for dev in device_registry.async_entries_for_area(dr, area):
browserID = list(dev.identifiers)[0][1]
if browserID is None:
continue
targets.add(browserID)
_LOGGER.error(service)
_LOGGER.error(targets)
_LOGGER.error(data)
call_service(service, targets, data)
hass.services.async_register(DOMAIN, "test", handle_service)
hass.services.async_register(DOMAIN, "popup", handle_service)
async def setup_service(hass): async def setup_service(hass):
def handle_command(call): def handle_command(call):
command = call.data.get("command", None) command = call.data.get("command", None)
@ -49,55 +103,3 @@ async def setup_service(hass):
hass.services.async_register(DOMAIN, "command", handle_command) hass.services.async_register(DOMAIN, "command", handle_command)
for cmd in USER_COMMANDS: for cmd in USER_COMMANDS:
hass.services.async_register(DOMAIN, cmd.replace("-", "_"), command_wrapper) hass.services.async_register(DOMAIN, cmd.replace("-", "_"), command_wrapper)
async def call_service(service_call):
await async_clean_devices(hass, service_call.data)
hass.services.async_register(DOMAIN, "clean_devices", call_service)
async def async_clean_devices(hass, data):
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
entity_registry = await hass.helpers.entity_registry.async_get_registry()
device_registry = await hass.helpers.device_registry.async_get_registry()
entity_entries = async_entries_for_config_entry(
entity_registry, config_entry.entry_id
)
device_entries = [
entry
for entry in device_registry.devices.values()
if config_entry.entry_id in entry.config_entries
]
user_config = hass.data[DOMAIN][DATA_CONFIG]
devices_to_keep = []
if CONFIG_DEVICES in user_config:
for d in device_entries:
for c in user_config[CONFIG_DEVICES]:
if (DOMAIN, c) in d.identifiers:
devices_to_keep.append(d.id)
entities_to_remove = []
for e in entity_entries:
entity = hass.states.get(e.entity_id)
if entity.state != STATE_UNAVAILABLE:
continue
if e.device_id in devices_to_keep:
continue
entities_to_remove.append(e)
for e in entities_to_remove:
entity_registry.async_remove(e.entity_id)
removed = []
for d in device_entries:
if len(async_entries_for_device(entity_registry, d.id)) == 0:
removed.append(d.name)
device_registry.async_remove_device(d.id)
devices = hass.data[DOMAIN][DATA_DEVICES]
for rec in devices:
devices[rec].send("toast", message=f"Removed devices: {removed}")

View File

@ -1,134 +1,93 @@
command: test:
description: "Send a command to a browser." description: "A debugging service"
fields: target:
command: device:
description: "Command to send" integration: "browser_mod"
example: "navigate" multiple: true
deviceID: entity:
description: "(optional) List of receiving browsers" integration: "browser_mod_none"
example: '["99980b13-dabc9563", "office_computer"]' area:
commands: device:
description: "Send several commands to a browser" integration: "browser_mod"
fields: multiple: true
commands: fields: {}
description: "List of commands to send"
deviceID:
description: "(optional) List of receiving browsers"
example: '["99980b13-dabc9563", "office_computer"]'
debug:
description: "On all browsers, show a popup, and a javascript alert with the current device ID."
fields:
deviceID:
description: "(optional) List of receiving browsers"
example: '["99980b13-dabc9563", "office_computer"]'
set_theme:
description: "On all browsers, change the theme."
fields:
theme:
description: "Theme to change to"
example: '{theme: "clear_light"}'
deviceID:
description: "(optional) List of receiving browsers"
example: '["99980b13-dabc9563", "office_computer"]'
navigate:
description: "Navigate to a path on a browser."
fields:
navigation_path:
description: "Path to navigate to"
example: "/lovelace/1"
deviceID:
description: "(optional) List of receiving browsers"
example: '["99980b13-dabc9563", "office_computer"]'
more_info:
description: "Open the more info dialog of an entity on a browser."
fields:
entity_id:
description: "Entity to show more info for"
example: "camera.front_door"
deviceID:
description: "(optional) List of receiving browsers"
example: '["99980b13-dabc9563", "office_computer"]'
large:
description: "(optional) Set to true to make wider"
example: "true"
toast:
description: "Show a toast message in the bottom left on all browsers."
fields:
message:
description: "Message to show"
example: "Short message"
deviceID:
description: "(optional) List of receiving browsers"
example: '["99980b13-dabc9563", "office_computer"]'
duration:
description: "(optional) Time in milliseconds to show message for. Set to 0 for persistent display."
example: "10000"
popup: popup:
description: "Pop up a card on a browser." description: "Display a popup"
target:
device:
integration: "browser_mod"
multiple: true
entity:
integration: "browser_mod_none"
area:
device:
integration: "browser_mod"
multiple: true
fields: fields:
title: title:
description: "Name to show in popup bar" name: Title
example: "Popup example" description: "Popup title"
card: selector:
description: "YAML config for card to show" text:
deviceID: content:
description: "(optional) List of receiving browsers" name: Content
example: '["99980b13-dabc9563", "office_computer"]' required: true
large: description: "Popup content (Test or lovelace card configuration)"
description: "(optional) Set to true to make wider" selector:
example: "true" object:
hide_header: right_button:
description: "(optional) Hide header title and close button" name: Right button
example: "true" description: Text of the right button
auto_close: selector:
description: "(optional) Close popup when mouse is moved or key is pressed. Also hides header" text:
example: "true" right_button_action:
time: name: Right button action
description: "(optional) When mouse isn't moved or keys aren't pressed for this amount of seconds, reopen. Only usable with auto_close. See blackout" description: Action to perform when the right button is pressed
example: "20" selector:
object:
left_button:
name: Left button
description: Text of the left button
selector:
text:
left_button_action:
name: Left button action
description: Action to perform when left button is pressed
selector:
object:
dismissable:
name: User dismissable
description: Whether the popup can be closed by the user without action
default: true
selector:
boolean:
dismiss_action:
name: Dismiss action
description: Action to perform when popup is dismissed
selector:
object:
timeout:
name: Auto close timeout
description: Time before closing (ms)
selector:
number:
mode: box
timeout_action:
name: Timeout
description: Action to perform when popup is closed by timeout
selector:
object:
close_popup: close_popup:
description: "Close all popups on all browsers." description: "Close a popup"
fields: target:
deviceID: device:
description: "(optional) List of receiving browsers" integration: "browser_mod"
example: '["99980b13-dabc9563", "office_computer"]' multiple: true
blackout: entity:
description: "Cover screen in black until the mouse is moved or a key is pressed." integration: "browser_mod_none"
fields: area:
time: device:
description: "(optional) The blackout will turn on automatically after the specified number of seconds. It works kind of like a screensaver and will keep turning on until blackout is called again with time: -1." integration: "browser_mod"
example: "20" multiple: true
deviceID:
description: "(optional) List of receiving browsers"
example: '["99980b13-dabc9563", "office_computer"]'
no_blackout:
description: "Remove a blackout from a browser."
fields:
brightness:
description: "(optional) On a Fully Kiosk Browser Plus set the screen brightness from 0 - 255."
deviceID:
description: "(optional) List of receiving browsers"
example: '["99980b13-dabc9563", "office_computer"]'
lovelace_reload:
description: "Refresh the lovelace configuration."
fields:
deviceID:
description: "(optional) List of receiving browsers"
example: '["99980b13-dabc9563", "office_computer"]'
window_reload:
description: "Forces the browser to reload the page. Same as clicking your browser's refresh button. Note: This is not guaranteed to clear the frontend cache."
fields:
deviceID:
description: "(optional) List of receiving browsers"
example: '["99980b13-dabc9563", "office_computer"]'
delay:
description: "Do nothing for a while"
fields:
seconds:
description: "Number of seconds to delay"
example: "5"
deviceID:
description: "(optional) List of receiving browsers"
example: '["99980b13-dabc9563", "office_computer"]'
call_service:
description: ""

View File

@ -15,15 +15,20 @@ import pjson from "../../package.json";
/* /*
TODO: TODO:
- Fix nomenclature
- Command -> Service
- Device -> Browser
- Popups - Popups
X Basic popups X Basic popups
- Card-mod integration - Card-mod integration
X Timeout X Timeout
X Fullscreen X Fullscreen
- Motion/occupancy tracker
- Information about interaction requirement - Information about interaction requirement
- Information about fullykiosk - Information about fullykiosk
- Commands - Commands
- Framework - Rename browser_mod commands to browser_mod services
x Framework
- ll-custom handling - ll-custom handling
- Commands - Commands
x popup x popup
@ -33,8 +38,8 @@ import pjson from "../../package.json";
- lovelace-reload - lovelace-reload
- window-reload - window-reload
- screensaver - screensaver
- sequence x sequence
- delay x delay
- toast? - toast?
- Redesign services to target devices - Redesign services to target devices
- frontend editor for popup cards - frontend editor for popup cards

View File

@ -9,10 +9,10 @@ class BrowserModPopup extends LitElement {
@property() title; @property() title;
@property({ reflect: true }) actions; @property({ reflect: true }) actions;
@property({ reflect: true }) card; @property({ reflect: true }) card;
@property() primary_action; @property() right_button;
@property() secondary_action; @property() left_button;
@property() dismissable; @property() dismissable;
callbacks; _actions;
timeout; timeout;
_timeoutStart; _timeoutStart;
_timeoutTimer; _timeoutTimer;
@ -39,11 +39,14 @@ class BrowserModPopup extends LitElement {
title, title,
content, content,
{ {
primary_action = undefined, right_button = undefined,
secondary_action = undefined, right_button_action = undefined,
left_button = undefined,
left_button_action = undefined,
dismissable = true, dismissable = true,
dismiss_action = undefined,
timeout = undefined, timeout = undefined,
callbacks = undefined, timeout_action = undefined,
} = {} } = {}
) { ) {
this.title = title; this.title = title;
@ -57,36 +60,42 @@ class BrowserModPopup extends LitElement {
this.content = card; this.content = card;
} else { } else {
// Basic HTML content // Basic HTML content
this.card = undefined;
this.content = unsafeHTML(content); this.content = unsafeHTML(content);
} }
this.primary_action = primary_action; this.right_button = right_button;
this.secondary_action = secondary_action; this.left_button = left_button;
this.actions = primary_action === undefined ? undefined : ""; this.actions = right_button === undefined ? undefined : "";
this.dismissable = dismissable; this.dismissable = dismissable;
this.timeout = timeout; this.timeout = timeout;
this.callbacks = callbacks; this._actions = {
right_button_action,
left_button_action,
dismiss_action,
timeout_action,
};
} }
_primary() { _primary() {
if (this.callbacks?.dismiss) this.callbacks.dismiss = undefined; if (this._actions?.dismiss_action) this._actions.dismiss_action = undefined;
this.closeDialog(); this.closeDialog();
this.callbacks?.primary_action?.(); this._actions?.right_button_action?.();
} }
_secondary() { _secondary() {
if (this.callbacks?.dismiss) this.callbacks.dismiss = undefined; if (this._actions?.dismiss_action) this._actions.dismiss_action = undefined;
this.closeDialog(); this.closeDialog();
this.callbacks?.secondary_action?.(); this._actions?.left_button_action?.();
} }
_dismiss() { _dismiss() {
this.closeDialog(); this.closeDialog();
this.callbacks?.dismiss?.(); this._actions?.dismiss_action?.();
} }
_timeout() { _timeout() {
if (this.callbacks?.dismiss) this.callbacks.dismiss = undefined; if (this._actions?.dismiss_action) this._actions.dismiss_action = undefined;
this.closeDialog(); this.closeDialog();
this.callbacks?.timeout?.(); this._actions?.timeout_action?.();
} }
render() { render() {
@ -121,20 +130,20 @@ class BrowserModPopup extends LitElement {
<div class="content">${this.content}</div> <div class="content">${this.content}</div>
${this.primary_action !== undefined ${this.right_button !== undefined
? html` ? html`
<mwc-button <mwc-button
slot="primaryAction" slot="primaryAction"
.label=${this.primary_action} .label=${this.right_button}
@click=${this._primary} @click=${this._primary}
></mwc-button> ></mwc-button>
` `
: ""} : ""}
${this.secondary_action !== undefined ${this.left_button !== undefined
? html` ? html`
<mwc-button <mwc-button
slot="secondaryAction" slot="secondaryAction"
.label=${this.secondary_action} .label=${this.left_button}
@click=${this._secondary} @click=${this._secondary}
></mwc-button> ></mwc-button>
` `

View File

@ -23,16 +23,14 @@ export const ServicesMixin = (SuperClass) => {
data: data:
[title: <string>] [title: <string>]
[content: <string | Lovelace Card Configuration>] [content: <string | Lovelace Card Configuration>]
[primary_action: <string>] [right_button: <string>]
[secondary_action: <string>] [right_button_action: <service call>]
[left_button: <string>]
[left_button_action: <service call>]
[dismissable: <TRUE/false>] [dismissable: <TRUE/false>]
[dismiss_action: <service call>]
[timeout: <number>] [timeout: <number>]
[callbacks: [timeout_action: <service call>]
[primary_action: <service call>]
[secondary_action: <service call>]
[timeout: <service call>]
[dismissed: <service call>]
]
Close popup Close popup
service: browser_mod.close_popup service: browser_mod.close_popup
@ -43,7 +41,20 @@ export const ServicesMixin = (SuperClass) => {
message: <string> message: <string>
*/ */
_service_action({ service, data }) { constructor() {
super();
const cmds = ["sequence", "delay", "popup", "close_popup"];
for (const service of cmds) {
this.addEventListener(`command-${service}`, (ev) => {
this._service_action({
service,
data: ev.detail,
});
});
}
}
async _service_action({ service, data }) {
let _service: String = service; let _service: String = service;
if (!_service.startsWith("browser_mod.") && _service.includes(".")) { if (!_service.startsWith("browser_mod.") && _service.includes(".")) {
// CALL HOME ASSISTANT SERVICE // CALL HOME ASSISTANT SERVICE
@ -54,11 +65,18 @@ export const ServicesMixin = (SuperClass) => {
} }
switch (_service) { switch (_service) {
case "sequence":
for (const a of data.sequence) await this._service_action(a);
break;
case "delay":
await new Promise((resolve) => setTimeout(resolve, data.time));
break;
case "popup": case "popup":
const { title, content, ...d } = data; const { title, content, ...d } = data;
if (d.callbacks) { for (const [k, v] of Object.entries(d)) {
for (const [k, v] of Object.entries(data.callbacks)) { if (k.endsWith("_action")) {
d.callbacks[k] = () => this._service_action(v as any); d[k] = () => this._service_action(v as any);
} }
} }
this.showPopup(title, content, d); this.showPopup(title, content, d);