Compare commits
No commits in common. "1dddaa9bcca470f57768c39e37822dda3c4e74ab" and "5ecf24d50151b827f13c9e3ac34ef691f53deb35" have entirely different histories.
1dddaa9bcc
...
5ecf24d501
@ -260,7 +260,6 @@ data:
|
|||||||
[left_button_action: <service call>]
|
[left_button_action: <service call>]
|
||||||
[dismissable: <TRUE/false>]
|
[dismissable: <TRUE/false>]
|
||||||
[dismiss_action: <service call>]
|
[dismiss_action: <service call>]
|
||||||
[autoclose: <true/FALSE>]
|
|
||||||
[timeout: <number>]
|
[timeout: <number>]
|
||||||
[timeout_action: <service call>]
|
[timeout_action: <service call>]
|
||||||
[style: <string>]
|
[style: <string>]
|
||||||
@ -273,7 +272,6 @@ If `size` is `wide` or `fullscreen` the card will be displayed wider or covering
|
|||||||
`right_button` and `left_button` specify the text of two action buttons. \
|
`right_button` and `left_button` specify the text of two action buttons. \
|
||||||
When either action button is clicked, the dialog is closed and the service specified as `right_button_action` or `left_button_action` is called. \
|
When either action button is clicked, the dialog is closed and the service specified as `right_button_action` or `left_button_action` is called. \
|
||||||
If `dismissable` is false, the dialog cannot be closed by the user without clicking either action button. If it is true and the dialog is dismissed, `dismiss_action` is called. \
|
If `dismissable` is false, the dialog cannot be closed by the user without clicking either action button. If it is true and the dialog is dismissed, `dismiss_action` is called. \
|
||||||
If `autoclose` is true the dialog will close automatically when the mouse, screen or keyboard is touched, at which point `dismiss_action` will be called. \
|
|
||||||
If `timeout` is specified the dialog will close automatically after `timeout` milliseconds, at which point `timeout_action` will be called. \
|
If `timeout` is specified the dialog will close automatically after `timeout` milliseconds, at which point `timeout_action` will be called. \
|
||||||
Finally, `style` lets you specify some CSS styles to apply to the dialog itself (to style a card in the dialog check out [card-mod](https://github.com/thomasloven/lovelace-card-mod))
|
Finally, `style` lets you specify some CSS styles to apply to the dialog itself (to style a card in the dialog check out [card-mod](https://github.com/thomasloven/lovelace-card-mod))
|
||||||
|
|
||||||
|
@ -25,8 +25,11 @@ async def async_setup(hass, config):
|
|||||||
|
|
||||||
async def async_setup_entry(hass, config_entry):
|
async def async_setup_entry(hass, config_entry):
|
||||||
|
|
||||||
for domain in ["sensor", "binary_sensor", "light", "media_player", "camera"]:
|
await hass.config_entries.async_forward_entry_setup(config_entry, "sensor")
|
||||||
await hass.config_entries.async_forward_entry_setup(config_entry, domain)
|
await hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor")
|
||||||
|
await hass.config_entries.async_forward_entry_setup(config_entry, "light")
|
||||||
|
await hass.config_entries.async_forward_entry_setup(config_entry, "media_player")
|
||||||
|
await hass.config_entries.async_forward_entry_setup(config_entry, "camera")
|
||||||
|
|
||||||
await async_setup_connection(hass)
|
await async_setup_connection(hass)
|
||||||
await async_setup_view(hass)
|
await async_setup_view(hass)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from homeassistant.components.binary_sensor import BinarySensorEntity
|
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||||
|
|
||||||
from .const import DATA_BROWSERS, DOMAIN, DATA_ADDERS
|
from .const import DOMAIN, DATA_ADDERS
|
||||||
from .entities import BrowserModEntity
|
from .helpers import BrowserModEntity
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
@ -22,7 +22,10 @@ class BrowserBinarySensor(BrowserModEntity, BinarySensorEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
return self._data.get(DATA_BROWSERS, {}).get(self.parameter, None)
|
data = self._data
|
||||||
|
data = data.get("browser", {})
|
||||||
|
data = data.get(self.parameter, None)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class ActivityBinarySensor(BrowserModEntity, BinarySensorEntity):
|
class ActivityBinarySensor(BrowserModEntity, BinarySensorEntity):
|
||||||
@ -44,4 +47,6 @@ class ActivityBinarySensor(BrowserModEntity, BinarySensorEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
return self._data.get("activity", False)
|
data = self._data
|
||||||
|
data = data.get("activity", False)
|
||||||
|
return data
|
||||||
|
@ -2,9 +2,9 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.websocket_api import event_message
|
from homeassistant.components.websocket_api import event_message
|
||||||
from homeassistant.helpers import device_registry, entity_registry
|
from homeassistant.helpers import device_registry, entity_registry
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
|
||||||
|
|
||||||
from .const import DATA_BROWSERS, DOMAIN, DATA_ADDERS
|
from .const import DATA_BROWSERS, DOMAIN, DATA_ADDERS
|
||||||
|
from .coordinator import Coordinator
|
||||||
from .sensor import BrowserSensor
|
from .sensor import BrowserSensor
|
||||||
from .light import BrowserModLight
|
from .light import BrowserModLight
|
||||||
from .binary_sensor import BrowserBinarySensor, ActivityBinarySensor
|
from .binary_sensor import BrowserBinarySensor, ActivityBinarySensor
|
||||||
@ -14,23 +14,11 @@ from .camera import BrowserModCamera
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Coordinator(DataUpdateCoordinator):
|
|
||||||
def __init__(self, hass, browserID):
|
|
||||||
super().__init__(
|
|
||||||
hass,
|
|
||||||
_LOGGER,
|
|
||||||
name="Browser Mod Coordinator",
|
|
||||||
)
|
|
||||||
self.browserID = browserID
|
|
||||||
|
|
||||||
|
|
||||||
class BrowserModBrowser:
|
class BrowserModBrowser:
|
||||||
"""A Browser_mod browser.
|
"""A Browser_mod browser."""
|
||||||
Handles the Home Assistant device corresponding to a registered Browser.
|
|
||||||
Creates and updates entities based on available data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, hass, browserID):
|
def __init__(self, hass, browserID):
|
||||||
|
""" """
|
||||||
self.browserID = browserID
|
self.browserID = browserID
|
||||||
self.coordinator = Coordinator(hass, browserID)
|
self.coordinator = Coordinator(hass, browserID)
|
||||||
self.entities = {}
|
self.entities = {}
|
||||||
@ -41,13 +29,11 @@ class BrowserModBrowser:
|
|||||||
self.update_entities(hass)
|
self.update_entities(hass)
|
||||||
|
|
||||||
def update(self, hass, newData):
|
def update(self, hass, newData):
|
||||||
"""Update state of all related entities."""
|
|
||||||
self.data.update(newData)
|
self.data.update(newData)
|
||||||
self.update_entities(hass)
|
self.update_entities(hass)
|
||||||
self.coordinator.async_set_updated_data(self.data)
|
self.coordinator.async_set_updated_data(self.data)
|
||||||
|
|
||||||
def update_settings(self, hass, settings):
|
def update_settings(self, hass, settings):
|
||||||
"""Update Browser settings and entities if needed."""
|
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.update_entities(hass)
|
self.update_entities(hass)
|
||||||
|
|
||||||
@ -58,7 +44,6 @@ class BrowserModBrowser:
|
|||||||
browserID = self.browserID
|
browserID = self.browserID
|
||||||
|
|
||||||
def _assert_browser_sensor(type, name, *properties):
|
def _assert_browser_sensor(type, name, *properties):
|
||||||
"""Create a browser state sensor if it does not already exist"""
|
|
||||||
if name in self.entities:
|
if name in self.entities:
|
||||||
return
|
return
|
||||||
adder = hass.data[DOMAIN][DATA_ADDERS][type]
|
adder = hass.data[DOMAIN][DATA_ADDERS][type]
|
||||||
@ -73,7 +58,6 @@ class BrowserModBrowser:
|
|||||||
_assert_browser_sensor("sensor", "currentUser", "Browser user")
|
_assert_browser_sensor("sensor", "currentUser", "Browser user")
|
||||||
_assert_browser_sensor("sensor", "width", "Browser width", "px")
|
_assert_browser_sensor("sensor", "width", "Browser width", "px")
|
||||||
_assert_browser_sensor("sensor", "height", "Browser height", "px")
|
_assert_browser_sensor("sensor", "height", "Browser height", "px")
|
||||||
# Don't create battery sensor unless battery level is reported
|
|
||||||
if self.data.get("browser", {}).get("battery_level", None) is not None:
|
if self.data.get("browser", {}).get("battery_level", None) is not None:
|
||||||
_assert_browser_sensor(
|
_assert_browser_sensor(
|
||||||
"sensor", "battery_level", "Browser battery", "%", "battery"
|
"sensor", "battery_level", "Browser battery", "%", "battery"
|
||||||
@ -81,7 +65,6 @@ class BrowserModBrowser:
|
|||||||
|
|
||||||
_assert_browser_sensor("binary_sensor", "darkMode", "Browser dark mode")
|
_assert_browser_sensor("binary_sensor", "darkMode", "Browser dark mode")
|
||||||
_assert_browser_sensor("binary_sensor", "fullyKiosk", "Browser FullyKiosk")
|
_assert_browser_sensor("binary_sensor", "fullyKiosk", "Browser FullyKiosk")
|
||||||
# Don't create a charging sensor unless charging state is reported
|
|
||||||
if self.data.get("browser", {}).get("charging", None) is not None:
|
if self.data.get("browser", {}).get("charging", None) is not None:
|
||||||
_assert_browser_sensor("binary_sensor", "charging", "Browser charging")
|
_assert_browser_sensor("binary_sensor", "charging", "Browser charging")
|
||||||
|
|
||||||
@ -134,7 +117,7 @@ class BrowserModBrowser:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def delete(self, hass):
|
def delete(self, hass):
|
||||||
"""Delete the device and associated entities."""
|
"""Delete browser and associated entities."""
|
||||||
dr = device_registry.async_get(hass)
|
dr = device_registry.async_get(hass)
|
||||||
er = entity_registry.async_get(hass)
|
er = entity_registry.async_get(hass)
|
||||||
|
|
||||||
@ -148,15 +131,12 @@ class BrowserModBrowser:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def connection(self):
|
def connection(self):
|
||||||
"""The current websocket connections for this Browser."""
|
|
||||||
return self._connections
|
return self._connections
|
||||||
|
|
||||||
def open_connection(self, connection, cid):
|
def open_connection(self, connection, cid):
|
||||||
"""Add a websocket connection."""
|
|
||||||
self._connections.append((connection, cid))
|
self._connections.append((connection, cid))
|
||||||
|
|
||||||
def close_connection(self, connection):
|
def close_connection(self, connection):
|
||||||
"""Close a websocket connection."""
|
|
||||||
self._connections = list(
|
self._connections = list(
|
||||||
filter(lambda v: v[0] != connection, self._connections)
|
filter(lambda v: v[0] != connection, self._connections)
|
||||||
)
|
)
|
||||||
@ -176,17 +156,7 @@ def getBrowser(hass, browserID, *, create=True):
|
|||||||
|
|
||||||
|
|
||||||
def deleteBrowser(hass, browserID):
|
def deleteBrowser(hass, browserID):
|
||||||
"""Delete a browser by BrowserID."""
|
|
||||||
browsers = hass.data[DOMAIN][DATA_BROWSERS]
|
browsers = hass.data[DOMAIN][DATA_BROWSERS]
|
||||||
if browserID in browsers:
|
if browserID in browsers:
|
||||||
browsers[browserID].delete(hass)
|
browsers[browserID].delete(hass)
|
||||||
del browsers[browserID]
|
del browsers[browserID]
|
||||||
|
|
||||||
|
|
||||||
def getBrowserByConnection(hass, connection):
|
|
||||||
"""Get the browser that has a given connection open."""
|
|
||||||
browsers = hass.data[DOMAIN][DATA_BROWSERS]
|
|
||||||
|
|
||||||
for k, v in browsers.items():
|
|
||||||
if any([c[0] == connection for c in v.connection]):
|
|
||||||
return v
|
|
||||||
|
@ -427,7 +427,7 @@ const ConnectionMixin = (SuperClass) => {
|
|||||||
}
|
}
|
||||||
async _reregister(newData = {}) {
|
async _reregister(newData = {}) {
|
||||||
await this.connection.sendMessage({
|
await this.connection.sendMessage({
|
||||||
type: "browser_mod/register",
|
type: "browser_mod/reregister",
|
||||||
browserID: this.browserID,
|
browserID: this.browserID,
|
||||||
data: Object.assign(Object.assign({}, this.browsers[this.browserID]), newData),
|
data: Object.assign(Object.assign({}, this.browsers[this.browserID]), newData),
|
||||||
});
|
});
|
||||||
@ -519,7 +519,7 @@ const ConnectionMixin = (SuperClass) => {
|
|||||||
((_b = this.browsers) === null || _b === void 0 ? void 0 : _b[this.browserID]) === undefined) {
|
((_b = this.browsers) === null || _b === void 0 ? void 0 : _b[this.browserID]) === undefined) {
|
||||||
(async () => {
|
(async () => {
|
||||||
await this.connection.sendMessage({
|
await this.connection.sendMessage({
|
||||||
type: "browser_mod/register",
|
type: "browser_mod/reregister",
|
||||||
browserID: oldID,
|
browserID: oldID,
|
||||||
data: Object.assign(Object.assign({}, this.browsers[oldID]), { browserID: this.browserID }),
|
data: Object.assign(Object.assign({}, this.browsers[oldID]), { browserID: this.browserID }),
|
||||||
});
|
});
|
||||||
@ -954,7 +954,6 @@ const ServicesMixin = (SuperClass) => {
|
|||||||
[left_button_action: <service call>]
|
[left_button_action: <service call>]
|
||||||
[dismissable: <TRUE/false>]
|
[dismissable: <TRUE/false>]
|
||||||
[dismiss_action: <service call>]
|
[dismiss_action: <service call>]
|
||||||
[autoclose: <true/FALSE>]
|
|
||||||
[timeout: <number>]
|
[timeout: <number>]
|
||||||
[timeout_action: <service call>]
|
[timeout_action: <service call>]
|
||||||
[style: <string>]
|
[style: <string>]
|
||||||
@ -1083,22 +1082,19 @@ const ActivityMixin = (SuperClass) => {
|
|||||||
this.activityTriggered = false;
|
this.activityTriggered = false;
|
||||||
this._activityCooldown = 15000;
|
this._activityCooldown = 15000;
|
||||||
for (const ev of ["pointerdown", "pointermove", "keydown"]) {
|
for (const ev of ["pointerdown", "pointermove", "keydown"]) {
|
||||||
window.addEventListener(ev, () => this.activityTrigger(true));
|
window.addEventListener(ev, () => this.activityTrigger());
|
||||||
}
|
}
|
||||||
this.addEventListener("fully-update", () => {
|
this.addEventListener("fully-update", () => {
|
||||||
this.activityTrigger();
|
this.activityTrigger();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
activityTrigger(touched = false) {
|
activityTrigger() {
|
||||||
if (!this.activityTriggered) {
|
if (!this.activityTriggered) {
|
||||||
this.sendUpdate({
|
this.sendUpdate({
|
||||||
activity: true,
|
activity: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.activityTriggered = true;
|
this.activityTriggered = true;
|
||||||
if (touched) {
|
|
||||||
this.fireEvent("browser-mod-activity");
|
|
||||||
}
|
|
||||||
clearTimeout(this._activityTimeout);
|
clearTimeout(this._activityTimeout);
|
||||||
this._activityTimeout = setTimeout(() => this.activityReset(), this._activityCooldown);
|
this._activityTimeout = setTimeout(() => this.activityReset(), this._activityCooldown);
|
||||||
}
|
}
|
||||||
@ -1128,18 +1124,18 @@ const t={ATTRIBUTE:1,CHILD:2,PROPERTY:3,BOOLEAN_ATTRIBUTE:4,EVENT:5,ELEMENT:6},e
|
|||||||
*/class e extends i{constructor(i){if(super(i),this.it=w,i.type!==t.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(r){if(r===w||null==r)return this.ft=void 0,this.it=r;if(r===b)return r;if("string"!=typeof r)throw Error(this.constructor.directiveName+"() called with a non-string value");if(r===this.it)return this.ft;this.it=r;const s=[r];return s.raw=s,this.ft={_$litType$:this.constructor.resultType,strings:s,values:[]}}}e.directiveName="unsafeHTML",e.resultType=1;const o=e$1(e);
|
*/class e extends i{constructor(i){if(super(i),this.it=w,i.type!==t.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(r){if(r===w||null==r)return this.ft=void 0,this.it=r;if(r===b)return r;if("string"!=typeof r)throw Error(this.constructor.directiveName+"() called with a non-string value");if(r===this.it)return this.ft;this.it=r;const s=[r];return s.raw=s,this.ft={_$litType$:this.constructor.resultType,strings:s,values:[]}}}e.directiveName="unsafeHTML",e.resultType=1;const o=e$1(e);
|
||||||
|
|
||||||
class BrowserModPopup extends s {
|
class BrowserModPopup extends s {
|
||||||
|
closedCallback() {
|
||||||
|
var _a;
|
||||||
|
(_a = this._resolveClosed) === null || _a === void 0 ? void 0 : _a.call(this);
|
||||||
|
this._resolveClosed = undefined;
|
||||||
|
}
|
||||||
async closeDialog() {
|
async closeDialog() {
|
||||||
this.open = false;
|
this.open = false;
|
||||||
|
await new Promise((resolve) => (this._resolveClosed = resolve));
|
||||||
clearInterval(this._timeoutTimer);
|
clearInterval(this._timeoutTimer);
|
||||||
if (this._autocloseListener) {
|
|
||||||
window.browser_mod.removeEventListener("browser-mod-activity", this._autocloseListener);
|
|
||||||
this._autocloseListener = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
openDialog() {
|
openDialog() {
|
||||||
var _a;
|
|
||||||
this.open = true;
|
this.open = true;
|
||||||
(_a = this.dialog) === null || _a === void 0 ? void 0 : _a.show();
|
|
||||||
if (this.timeout) {
|
if (this.timeout) {
|
||||||
this._timeoutStart = new Date().getTime();
|
this._timeoutStart = new Date().getTime();
|
||||||
this._timeoutTimer = setInterval(() => {
|
this._timeoutTimer = setInterval(() => {
|
||||||
@ -1150,13 +1146,8 @@ class BrowserModPopup extends s {
|
|||||||
this._timeout();
|
this._timeout();
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
this._autocloseListener = undefined;
|
|
||||||
if (this._autoclose) {
|
|
||||||
this._autocloseListener = this._dismiss.bind(this);
|
|
||||||
window.browser_mod.addEventListener("browser-mod-activity", this._autocloseListener);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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, size = undefined, style = undefined, autoclose = false, } = {}) {
|
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, size = undefined, style = 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
|
||||||
@ -1186,7 +1177,6 @@ class BrowserModPopup extends s {
|
|||||||
this.wide = size === "wide" ? "" : undefined;
|
this.wide = size === "wide" ? "" : undefined;
|
||||||
this.fullscreen = size === "fullscreen" ? "" : undefined;
|
this.fullscreen = size === "fullscreen" ? "" : undefined;
|
||||||
this._style = style;
|
this._style = style;
|
||||||
this._autoclose = autoclose;
|
|
||||||
}
|
}
|
||||||
async _primary() {
|
async _primary() {
|
||||||
var _a, _b, _c;
|
var _a, _b, _c;
|
||||||
@ -1220,6 +1210,7 @@ class BrowserModPopup extends s {
|
|||||||
return $ `
|
return $ `
|
||||||
<ha-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
|
@closed=${this.closedCallback}
|
||||||
.heading=${this.title !== undefined}
|
.heading=${this.title !== undefined}
|
||||||
?hideActions=${this.actions === undefined}
|
?hideActions=${this.actions === undefined}
|
||||||
.scrimClickAction=${this.dismissable ? this._dismiss : ""}
|
.scrimClickAction=${this.dismissable ? this._dismiss : ""}
|
||||||
@ -1408,9 +1399,6 @@ __decorate([
|
|||||||
__decorate([
|
__decorate([
|
||||||
e$2()
|
e$2()
|
||||||
], BrowserModPopup.prototype, "_style", void 0);
|
], BrowserModPopup.prototype, "_style", void 0);
|
||||||
__decorate([
|
|
||||||
i$1("ha-dialog")
|
|
||||||
], BrowserModPopup.prototype, "dialog", void 0);
|
|
||||||
if (!customElements.get("browser-mod-popup"))
|
if (!customElements.get("browser-mod-popup"))
|
||||||
customElements.define("browser-mod-popup", BrowserModPopup);
|
customElements.define("browser-mod-popup", BrowserModPopup);
|
||||||
const PopupMixin = (SuperClass) => {
|
const PopupMixin = (SuperClass) => {
|
||||||
@ -2074,25 +2062,12 @@ const BrowserIDMixin = (SuperClass) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async recall_id() {
|
|
||||||
// If the connection is still open, but the BrowserID has disappeared - recall it from the backend
|
|
||||||
// This happens e.g. when the frontend cache is reset in the Compainon app
|
|
||||||
if (!this.connection)
|
|
||||||
return;
|
|
||||||
const recalledID = await this.connection.sendMessagePromise({
|
|
||||||
type: "browser_mod/recall_id",
|
|
||||||
});
|
|
||||||
if (recalledID) {
|
|
||||||
localStorage[ID_STORAGE_KEY] = recalledID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get browserID() {
|
get browserID() {
|
||||||
if (document.querySelector("hc-main"))
|
if (document.querySelector("hc-main"))
|
||||||
return "CAST";
|
return "CAST";
|
||||||
if (localStorage[ID_STORAGE_KEY])
|
if (localStorage[ID_STORAGE_KEY])
|
||||||
return localStorage[ID_STORAGE_KEY];
|
return localStorage[ID_STORAGE_KEY];
|
||||||
this.browserID = "";
|
this.browserID = "";
|
||||||
this.recall_id();
|
|
||||||
return this.browserID;
|
return this.browserID;
|
||||||
}
|
}
|
||||||
set browserID(id) {
|
set browserID(id) {
|
||||||
@ -2137,20 +2112,16 @@ const BrowserIDMixin = (SuperClass) => {
|
|||||||
x ll-custom handling
|
x ll-custom handling
|
||||||
- Commands
|
- Commands
|
||||||
x popup
|
x popup
|
||||||
x Auto-close
|
|
||||||
x close_popup
|
x close_popup
|
||||||
x more-info
|
x more-info
|
||||||
x navigate
|
x navigate
|
||||||
- lovelace-reload?
|
- lovelace-reload?
|
||||||
- Not needed
|
|
||||||
x window-reload
|
x window-reload
|
||||||
- screensaver ?
|
- screensaver ?
|
||||||
- Refer to automations instead
|
|
||||||
x sequence
|
x sequence
|
||||||
x delay
|
x delay
|
||||||
x javascript eval
|
x javascript eval
|
||||||
- toast?
|
- toast?
|
||||||
- Replaced with popups with timeout
|
|
||||||
x Redesign services to target devices
|
x Redesign services to target devices
|
||||||
x frontend editor for popup cards
|
x frontend editor for popup cards
|
||||||
- also screensavers
|
- also screensavers
|
||||||
@ -2168,8 +2139,7 @@ const BrowserIDMixin = (SuperClass) => {
|
|||||||
- Video player?
|
- Video player?
|
||||||
- Media_seek
|
- Media_seek
|
||||||
- Screensavers
|
- Screensavers
|
||||||
x IMPORTANT: FIX DEFAULT HIDING OF ENTITIES
|
- IMPORTANT: FIX DEFAULT HIDING OF ENTITIES
|
||||||
- NOFIX. Home Assistant bug
|
|
||||||
X Check functionality with CAST - may need to add frontend part as a lovelace resource
|
X Check functionality with CAST - may need to add frontend part as a lovelace resource
|
||||||
*/
|
*/
|
||||||
class BrowserMod extends ServicesMixin(PopupMixin(ActivityMixin(BrowserStateMixin(CameraMixin(MediaPlayerMixin(ScreenSaverMixin(AutoSettingsMixin(FullyMixin(RequireInteractMixin(ConnectionMixin(BrowserIDMixin(EventTarget)))))))))))) {
|
class BrowserMod extends ServicesMixin(PopupMixin(ActivityMixin(BrowserStateMixin(CameraMixin(MediaPlayerMixin(ScreenSaverMixin(AutoSettingsMixin(FullyMixin(RequireInteractMixin(ConnectionMixin(BrowserIDMixin(EventTarget)))))))))))) {
|
||||||
|
@ -2,7 +2,7 @@ import base64
|
|||||||
|
|
||||||
from homeassistant.components.camera import Camera
|
from homeassistant.components.camera import Camera
|
||||||
|
|
||||||
from .entities import BrowserModEntity
|
from .helpers import BrowserModEntity
|
||||||
from .const import DOMAIN, DATA_ADDERS
|
from .const import DOMAIN, DATA_ADDERS
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -10,19 +10,15 @@ from homeassistant.components.websocket_api import (
|
|||||||
from homeassistant.components import websocket_api
|
from homeassistant.components import websocket_api
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
BROWSER_ID,
|
|
||||||
DATA_STORE,
|
|
||||||
WS_CONNECT,
|
WS_CONNECT,
|
||||||
WS_LOG,
|
|
||||||
WS_RECALL_ID,
|
|
||||||
WS_REGISTER,
|
WS_REGISTER,
|
||||||
WS_SETTINGS,
|
|
||||||
WS_UNREGISTER,
|
WS_UNREGISTER,
|
||||||
|
WS_REREGISTER,
|
||||||
WS_UPDATE,
|
WS_UPDATE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .browser import getBrowser, deleteBrowser, getBrowserByConnection
|
from .browser import getBrowser, deleteBrowser
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -36,74 +32,44 @@ async def async_setup_connection(hass):
|
|||||||
)
|
)
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
async def handle_connect(hass, connection, msg):
|
async def handle_connect(hass, connection, msg):
|
||||||
"""Connect to Browser Mod and subscribe to settings updates."""
|
browserID = msg["browserID"]
|
||||||
browserID = msg[BROWSER_ID]
|
store = hass.data[DOMAIN]["store"]
|
||||||
store = hass.data[DOMAIN][DATA_STORE]
|
|
||||||
|
|
||||||
def send_update(data):
|
def listener(data):
|
||||||
connection.send_message(event_message(msg["id"], {"result": data}))
|
connection.send_message(event_message(msg["id"], {"result": data}))
|
||||||
|
|
||||||
store_listener = store.add_listener(send_update)
|
store_listener = store.add_listener(listener)
|
||||||
|
|
||||||
def close_connection():
|
def unsubscriber():
|
||||||
store_listener()
|
store_listener()
|
||||||
dev = getBrowser(hass, browserID, create=False)
|
dev = getBrowser(hass, browserID, create=False)
|
||||||
if dev:
|
if dev:
|
||||||
dev.close_connection(connection)
|
dev.close_connection(connection)
|
||||||
|
|
||||||
connection.subscriptions[msg["id"]] = close_connection
|
connection.subscriptions[msg["id"]] = unsubscriber
|
||||||
connection.send_result(msg["id"])
|
connection.send_result(msg["id"])
|
||||||
|
|
||||||
if store.get_browser(browserID).registered:
|
if store.get_browser(browserID).enabled:
|
||||||
dev = getBrowser(hass, browserID)
|
dev = getBrowser(hass, browserID)
|
||||||
dev.update_settings(hass, store.get_browser(browserID).asdict())
|
dev.update_settings(hass, store.get_browser(browserID).asdict())
|
||||||
dev.open_connection(connection, msg["id"])
|
dev.open_connection(connection, msg["id"])
|
||||||
await store.set_browser(
|
await store.set_browser(
|
||||||
browserID, last_seen=datetime.now(tz=timezone.utc).isoformat()
|
browserID, last_seen=datetime.now(tz=timezone.utc).isoformat()
|
||||||
)
|
)
|
||||||
send_update(store.asdict())
|
listener(store.asdict())
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
vol.Required("type"): WS_REGISTER,
|
vol.Required("type"): WS_REGISTER,
|
||||||
vol.Required("browserID"): str,
|
vol.Required("browserID"): str,
|
||||||
vol.Optional("data"): dict,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
async def handle_register(hass, connection, msg):
|
async def handle_register(hass, connection, msg):
|
||||||
"""Register a Browser."""
|
browserID = msg["browserID"]
|
||||||
browserID = msg[BROWSER_ID]
|
store = hass.data[DOMAIN]["store"]
|
||||||
store = hass.data[DOMAIN][DATA_STORE]
|
await store.set_browser(browserID, enabled=True)
|
||||||
|
connection.send_result(msg["id"])
|
||||||
browserSettings = {"registered": True}
|
|
||||||
data = msg.get("data", {})
|
|
||||||
if "last_seen" in data:
|
|
||||||
del data["last_seen"]
|
|
||||||
if BROWSER_ID in data:
|
|
||||||
# Change ID of registered browser
|
|
||||||
newBrowserID = data[BROWSER_ID]
|
|
||||||
del data[BROWSER_ID]
|
|
||||||
|
|
||||||
# Copy data from old browser and delete it from store
|
|
||||||
if oldBrowserSettings := store.get_browser(browserID):
|
|
||||||
browserSettings = oldBrowserSettings.asdict()
|
|
||||||
await store.delete_browser(browserID)
|
|
||||||
|
|
||||||
# Delete the old Browser device
|
|
||||||
deleteBrowser(hass, browserID)
|
|
||||||
|
|
||||||
# Use the new browserID from now on
|
|
||||||
browserID = newBrowserID
|
|
||||||
|
|
||||||
# Create and/or update Browser device
|
|
||||||
dev = getBrowser(hass, browserID)
|
|
||||||
dev.update_settings(hass, data)
|
|
||||||
|
|
||||||
# Create or update store data
|
|
||||||
if data is not None:
|
|
||||||
browserSettings.update(data)
|
|
||||||
await store.set_browser(browserID, **browserSettings)
|
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
@ -113,15 +79,49 @@ async def async_setup_connection(hass):
|
|||||||
)
|
)
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
async def handle_unregister(hass, connection, msg):
|
async def handle_unregister(hass, connection, msg):
|
||||||
"""Unregister a Browser."""
|
browserID = msg["browserID"]
|
||||||
browserID = msg[BROWSER_ID]
|
store = hass.data[DOMAIN]["store"]
|
||||||
store = hass.data[DOMAIN][DATA_STORE]
|
|
||||||
|
|
||||||
deleteBrowser(hass, browserID)
|
deleteBrowser(hass, browserID)
|
||||||
await store.delete_browser(browserID)
|
await store.delete_browser(browserID)
|
||||||
|
|
||||||
connection.send_result(msg["id"])
|
connection.send_result(msg["id"])
|
||||||
|
|
||||||
|
@websocket_api.websocket_command(
|
||||||
|
{
|
||||||
|
vol.Required("type"): WS_REREGISTER,
|
||||||
|
vol.Required("browserID"): str,
|
||||||
|
vol.Required("data"): dict,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@websocket_api.async_response
|
||||||
|
async def handle_reregister(hass, connection, msg):
|
||||||
|
browserID = msg["browserID"]
|
||||||
|
store = hass.data[DOMAIN]["store"]
|
||||||
|
|
||||||
|
data = msg["data"]
|
||||||
|
del data["last_seen"]
|
||||||
|
browserSettings = {}
|
||||||
|
|
||||||
|
if "browserID" in data:
|
||||||
|
newBrowserID = data["browserID"]
|
||||||
|
del data["browserID"]
|
||||||
|
|
||||||
|
oldBrowserSetting = store.get_browser(browserID)
|
||||||
|
if oldBrowserSetting:
|
||||||
|
browserSettings = oldBrowserSetting.asdict()
|
||||||
|
await store.delete_browser(browserID)
|
||||||
|
|
||||||
|
deleteBrowser(hass, browserID)
|
||||||
|
|
||||||
|
browserID = newBrowserID
|
||||||
|
|
||||||
|
if (dev := getBrowser(hass, browserID, create=False)) is not None:
|
||||||
|
dev.update_settings(hass, data)
|
||||||
|
|
||||||
|
browserSettings.update(data)
|
||||||
|
await store.set_browser(browserID, **browserSettings)
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
vol.Required("type"): WS_UPDATE,
|
vol.Required("type"): WS_UPDATE,
|
||||||
@ -131,17 +131,16 @@ async def async_setup_connection(hass):
|
|||||||
)
|
)
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
async def handle_update(hass, connection, msg):
|
async def handle_update(hass, connection, msg):
|
||||||
"""Receive state updates from a Browser."""
|
browserID = msg["browserID"]
|
||||||
browserID = msg[BROWSER_ID]
|
store = hass.data[DOMAIN]["store"]
|
||||||
store = hass.data[DOMAIN][DATA_STORE]
|
|
||||||
|
|
||||||
if store.get_browser(browserID).registered:
|
if store.get_browser(browserID).enabled:
|
||||||
dev = getBrowser(hass, browserID)
|
dev = getBrowser(hass, browserID)
|
||||||
dev.update(hass, msg.get("data", {}))
|
dev.update(hass, msg.get("data", {}))
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
vol.Required("type"): WS_SETTINGS,
|
vol.Required("type"): "browser_mod/settings",
|
||||||
vol.Required("key"): str,
|
vol.Required("key"): str,
|
||||||
vol.Optional("value"): vol.Any(int, str, bool, list, object, None),
|
vol.Optional("value"): vol.Any(int, str, bool, list, object, None),
|
||||||
vol.Optional("user"): str,
|
vol.Optional("user"): str,
|
||||||
@ -149,8 +148,7 @@ async def async_setup_connection(hass):
|
|||||||
)
|
)
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
async def handle_settings(hass, connection, msg):
|
async def handle_settings(hass, connection, msg):
|
||||||
"""Change user or global settings."""
|
store = hass.data[DOMAIN]["store"]
|
||||||
store = hass.data[DOMAIN][DATA_STORE]
|
|
||||||
if "user" in msg:
|
if "user" in msg:
|
||||||
# Set user setting
|
# Set user setting
|
||||||
await store.set_user_settings(
|
await store.set_user_settings(
|
||||||
@ -161,34 +159,9 @@ async def async_setup_connection(hass):
|
|||||||
await store.set_global_settings(**{msg["key"]: msg.get("value", None)})
|
await store.set_global_settings(**{msg["key"]: msg.get("value", None)})
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
|
||||||
{
|
|
||||||
vol.Required("type"): WS_RECALL_ID,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
def handle_recall_id(hass, connection, msg):
|
|
||||||
"""Recall browserID of Browser with the current connection."""
|
|
||||||
dev = getBrowserByConnection(hass, connection)
|
|
||||||
if dev:
|
|
||||||
connection.send_message(
|
|
||||||
websocket_api.result_message(msg["id"], dev.browserID)
|
|
||||||
)
|
|
||||||
connection.send_message(websocket_api.result_message(msg["id"], None))
|
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
|
||||||
{
|
|
||||||
vol.Required("type"): WS_LOG,
|
|
||||||
vol.Required("message"): str,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
def handle_log(hass, connection, msg):
|
|
||||||
"""Print a debug message."""
|
|
||||||
_LOGGER.info(f"LOG MESSAGE: {msg['message']}")
|
|
||||||
|
|
||||||
async_register_command(hass, handle_connect)
|
async_register_command(hass, handle_connect)
|
||||||
async_register_command(hass, handle_register)
|
async_register_command(hass, handle_register)
|
||||||
async_register_command(hass, handle_unregister)
|
async_register_command(hass, handle_unregister)
|
||||||
|
async_register_command(hass, handle_reregister)
|
||||||
async_register_command(hass, handle_update)
|
async_register_command(hass, handle_update)
|
||||||
async_register_command(hass, handle_settings)
|
async_register_command(hass, handle_settings)
|
||||||
async_register_command(hass, handle_recall_id)
|
|
||||||
async_register_command(hass, handle_log)
|
|
||||||
|
@ -1,31 +1,42 @@
|
|||||||
DOMAIN = "browser_mod"
|
DOMAIN = "browser_mod"
|
||||||
|
|
||||||
BROWSER_ID = "browserID"
|
|
||||||
|
|
||||||
FRONTEND_SCRIPT_URL = "/browser_mod.js"
|
FRONTEND_SCRIPT_URL = "/browser_mod.js"
|
||||||
SETTINGS_PANEL_URL = "/browser_mod_panel.js"
|
SETTINGS_PANEL_URL = "/browser_mod_panel.js"
|
||||||
|
|
||||||
DATA_BROWSERS = "browsers"
|
DATA_BROWSERS = "browsers"
|
||||||
DATA_ADDERS = "adders"
|
DATA_ADDERS = "adders"
|
||||||
DATA_STORE = "store"
|
DATA_STORE = "store"
|
||||||
|
DATA_ALIASES = "aliases"
|
||||||
|
DATA_CONFIG = "config"
|
||||||
|
DATA_SETUP_COMPLETE = "setup_complete"
|
||||||
|
|
||||||
|
CONFIG_DEVICES = "devices"
|
||||||
|
CONFIG_PREFIX = "prefix"
|
||||||
|
CONFIG_DISABLE = "disable"
|
||||||
|
CONFIG_DISABLE_ALL = "all"
|
||||||
|
|
||||||
WS_ROOT = DOMAIN
|
WS_ROOT = DOMAIN
|
||||||
WS_CONNECT = f"{WS_ROOT}/connect"
|
WS_CONNECT = "{}/connect".format(WS_ROOT)
|
||||||
|
WS_UPDATE = "{}/update".format(WS_ROOT)
|
||||||
|
WS_CAMERA = "{}/camera".format(WS_ROOT)
|
||||||
|
|
||||||
WS_REGISTER = f"{WS_ROOT}/register"
|
WS_REGISTER = f"{WS_ROOT}/register"
|
||||||
WS_UNREGISTER = f"{WS_ROOT}/unregister"
|
WS_UNREGISTER = f"{WS_ROOT}/unregister"
|
||||||
WS_UPDATE = f"{WS_ROOT}/update"
|
WS_REREGISTER = f"{WS_ROOT}/reregister"
|
||||||
WS_SETTINGS = f"{WS_ROOT}/settings"
|
|
||||||
WS_RECALL_ID = f"{WS_ROOT}/recall_id"
|
|
||||||
WS_LOG = f"{WS_ROOT}/log"
|
|
||||||
|
|
||||||
BROWSER_MOD_SERVICES = [
|
USER_COMMANDS = [
|
||||||
"sequence",
|
"debug",
|
||||||
"delay",
|
|
||||||
"popup",
|
"popup",
|
||||||
"more_info",
|
"close-popup",
|
||||||
"close_popup",
|
|
||||||
"navigate",
|
"navigate",
|
||||||
"refresh",
|
"more-info",
|
||||||
"console",
|
"set-theme",
|
||||||
"javascript",
|
"lovelace-reload",
|
||||||
|
"window-reload",
|
||||||
|
"blackout",
|
||||||
|
"no-blackout",
|
||||||
|
"toast",
|
||||||
|
"commands",
|
||||||
|
"call_service",
|
||||||
|
"delay",
|
||||||
]
|
]
|
||||||
|
17
custom_components/browser_mod/coordinator.py
Normal file
17
custom_components/browser_mod/coordinator.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
DataUpdateCoordinator,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Coordinator(DataUpdateCoordinator):
|
||||||
|
def __init__(self, hass, browserID):
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name="Browser Mod Coordinator",
|
||||||
|
)
|
||||||
|
self.browserID = browserID
|
@ -1,6 +1,6 @@
|
|||||||
from homeassistant.components.light import LightEntity, ColorMode
|
from homeassistant.components.light import LightEntity, ColorMode
|
||||||
|
|
||||||
from .entities import BrowserModEntity
|
from .helpers import BrowserModEntity
|
||||||
from .const import DOMAIN, DATA_ADDERS
|
from .const import DOMAIN, DATA_ADDERS
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ from homeassistant.const import (
|
|||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .entities import BrowserModEntity
|
from .helpers import BrowserModEntity
|
||||||
from .const import DOMAIN, DATA_ADDERS
|
from .const import DOMAIN, DATA_ADDERS
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,18 +9,12 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
async def async_setup_view(hass):
|
async def async_setup_view(hass):
|
||||||
|
|
||||||
# Serve the Browser Mod controller and add it as extra_module_url
|
|
||||||
hass.http.register_static_path(
|
hass.http.register_static_path(
|
||||||
FRONTEND_SCRIPT_URL,
|
FRONTEND_SCRIPT_URL,
|
||||||
hass.config.path("custom_components/browser_mod/browser_mod.js"),
|
hass.config.path("custom_components/browser_mod/browser_mod.js"),
|
||||||
)
|
)
|
||||||
add_extra_js_url(hass, FRONTEND_SCRIPT_URL)
|
add_extra_js_url(hass, FRONTEND_SCRIPT_URL)
|
||||||
|
|
||||||
# Serve the Browser Mod Settings panel and register it as a panel
|
|
||||||
hass.http.register_static_path(
|
|
||||||
SETTINGS_PANEL_URL,
|
|
||||||
hass.config.path("custom_components/browser_mod/browser_mod_panel.js"),
|
|
||||||
)
|
|
||||||
hass.components.frontend.async_register_built_in_panel(
|
hass.components.frontend.async_register_built_in_panel(
|
||||||
component_name="custom",
|
component_name="custom",
|
||||||
sidebar_title="Browser Mod",
|
sidebar_title="Browser Mod",
|
||||||
@ -34,6 +28,10 @@ async def async_setup_view(hass):
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
hass.http.register_static_path(
|
||||||
|
SETTINGS_PANEL_URL,
|
||||||
|
hass.config.path("custom_components/browser_mod/browser_mod_panel.js"),
|
||||||
|
)
|
||||||
|
|
||||||
# Also load Browser Mod as a lovelace resource so it's accessible to Cast
|
# Also load Browser Mod as a lovelace resource so it's accessible to Cast
|
||||||
resources = hass.data["lovelace"]["resources"]
|
resources = hass.data["lovelace"]["resources"]
|
||||||
@ -41,17 +39,14 @@ async def async_setup_view(hass):
|
|||||||
if not resources.loaded:
|
if not resources.loaded:
|
||||||
await resources.async_load()
|
await resources.async_load()
|
||||||
resources.loaded = True
|
resources.loaded = True
|
||||||
|
|
||||||
frontend_added = False
|
frontend_added = False
|
||||||
for r in resources.async_items():
|
for r in resources.async_items():
|
||||||
if r["url"].startswith(FRONTEND_SCRIPT_URL):
|
if r["url"].startswith(FRONTEND_SCRIPT_URL):
|
||||||
frontend_added = True
|
frontend_added = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# While going through the resources, also preload card-mod if it is found
|
# While going through the resources, also preload card-mod if it is found
|
||||||
if "card-mod.js" in r["url"]:
|
if "card-mod.js" in r["url"]:
|
||||||
add_extra_js_url(hass, r["url"])
|
add_extra_js_url(hass, r["url"])
|
||||||
|
|
||||||
if not frontend_added:
|
if not frontend_added:
|
||||||
await resources.async_create_item(
|
await resources.async_create_item(
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from homeassistant.components.sensor import SensorEntity
|
from homeassistant.components.sensor import SensorEntity
|
||||||
|
|
||||||
from .const import DOMAIN, DATA_ADDERS
|
from .const import DOMAIN, DATA_ADDERS
|
||||||
from .entities import BrowserModEntity
|
from .helpers import BrowserModEntity
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
@ -48,15 +48,12 @@ class BrowserSensor(BrowserModEntity, SensorEntity):
|
|||||||
@property
|
@property
|
||||||
def extra_state_attributes(self):
|
def extra_state_attributes(self):
|
||||||
retval = super().extra_state_attributes
|
retval = super().extra_state_attributes
|
||||||
|
|
||||||
if self.parameter == "currentUser":
|
if self.parameter == "currentUser":
|
||||||
retval["userData"] = self._data.get("browser", {}).get("userData")
|
retval["userData"] = self._data.get("browser", {}).get("userData")
|
||||||
|
|
||||||
if self.parameter == "path":
|
if self.parameter == "path":
|
||||||
retval["pathSegments"] = (
|
retval["pathSegments"] = (
|
||||||
self._data.get("browser", {}).get("path", "").split("/")
|
self._data.get("browser", {}).get("path", "").split("/")
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.parameter == "userAgent":
|
if self.parameter == "userAgent":
|
||||||
retval["userAgent"] = self._data.get("browser", {}).get("userAgent")
|
retval["userAgent"] = self._data.get("browser", {}).get("userAgent")
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import logging
|
|||||||
from homeassistant.helpers import device_registry
|
from homeassistant.helpers import device_registry
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
BROWSER_MOD_SERVICES,
|
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
DATA_BROWSERS,
|
DATA_BROWSERS,
|
||||||
)
|
)
|
||||||
@ -60,5 +59,12 @@ async def async_setup_services(hass):
|
|||||||
|
|
||||||
call_service(service, browsers, data)
|
call_service(service, browsers, data)
|
||||||
|
|
||||||
for service in BROWSER_MOD_SERVICES:
|
hass.services.async_register(DOMAIN, "sequence", handle_service)
|
||||||
hass.services.async_register(DOMAIN, service, handle_service)
|
hass.services.async_register(DOMAIN, "delay", handle_service)
|
||||||
|
hass.services.async_register(DOMAIN, "popup", handle_service)
|
||||||
|
hass.services.async_register(DOMAIN, "more_info", handle_service)
|
||||||
|
hass.services.async_register(DOMAIN, "close_popup", handle_service)
|
||||||
|
hass.services.async_register(DOMAIN, "navigate", handle_service)
|
||||||
|
hass.services.async_register(DOMAIN, "refresh", handle_service)
|
||||||
|
hass.services.async_register(DOMAIN, "console", handle_service)
|
||||||
|
hass.services.async_register(DOMAIN, "javascript", handle_service)
|
||||||
|
@ -101,12 +101,6 @@ popup:
|
|||||||
description: Action to perform when popup is dismissed
|
description: Action to perform when popup is dismissed
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
autoclose:
|
|
||||||
name: Auto close
|
|
||||||
description: Close the popup automatically on mouse, pointer or keyboard activity
|
|
||||||
default: false
|
|
||||||
selector:
|
|
||||||
boolean:
|
|
||||||
timeout:
|
timeout:
|
||||||
name: Auto close timeout
|
name: Auto close timeout
|
||||||
description: Time before closing (ms)
|
description: Time before closing (ms)
|
||||||
|
@ -30,7 +30,7 @@ class SettingsStoreData:
|
|||||||
@attr.s
|
@attr.s
|
||||||
class BrowserStoreData:
|
class BrowserStoreData:
|
||||||
last_seen = attr.ib(type=int, default=0)
|
last_seen = attr.ib(type=int, default=0)
|
||||||
registered = attr.ib(type=bool, default=False)
|
enabled = attr.ib(type=bool, default=False)
|
||||||
camera = attr.ib(type=bool, default=False)
|
camera = attr.ib(type=bool, default=False)
|
||||||
settings = attr.ib(type=SettingsStoreData, factory=SettingsStoreData)
|
settings = attr.ib(type=SettingsStoreData, factory=SettingsStoreData)
|
||||||
meta = attr.ib(type=str, default="default")
|
meta = attr.ib(type=str, default="default")
|
||||||
|
@ -6,23 +6,20 @@ export const ActivityMixin = (SuperClass) => {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
for (const ev of ["pointerdown", "pointermove", "keydown"]) {
|
for (const ev of ["pointerdown", "pointermove", "keydown"]) {
|
||||||
window.addEventListener(ev, () => this.activityTrigger(true));
|
window.addEventListener(ev, () => this.activityTrigger());
|
||||||
}
|
}
|
||||||
this.addEventListener("fully-update", () => {
|
this.addEventListener("fully-update", () => {
|
||||||
this.activityTrigger();
|
this.activityTrigger();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
activityTrigger(touched = false) {
|
activityTrigger() {
|
||||||
if (!this.activityTriggered) {
|
if (!this.activityTriggered) {
|
||||||
this.sendUpdate({
|
this.sendUpdate({
|
||||||
activity: true,
|
activity: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.activityTriggered = true;
|
this.activityTriggered = true;
|
||||||
if (touched) {
|
|
||||||
this.fireEvent("browser-mod-activity");
|
|
||||||
}
|
|
||||||
clearTimeout(this._activityTimeout);
|
clearTimeout(this._activityTimeout);
|
||||||
this._activityTimeout = setTimeout(
|
this._activityTimeout = setTimeout(
|
||||||
() => this.activityReset(),
|
() => this.activityReset(),
|
||||||
|
@ -20,23 +20,10 @@ export const BrowserIDMixin = (SuperClass) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async recall_id() {
|
|
||||||
// If the connection is still open, but the BrowserID has disappeared - recall it from the backend
|
|
||||||
// This happens e.g. when the frontend cache is reset in the Compainon app
|
|
||||||
if (!this.connection) return;
|
|
||||||
const recalledID = await this.connection.sendMessagePromise({
|
|
||||||
type: "browser_mod/recall_id",
|
|
||||||
});
|
|
||||||
if (recalledID) {
|
|
||||||
localStorage[ID_STORAGE_KEY] = recalledID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get browserID() {
|
get browserID() {
|
||||||
if (document.querySelector("hc-main")) return "CAST";
|
if (document.querySelector("hc-main")) return "CAST";
|
||||||
if (localStorage[ID_STORAGE_KEY]) return localStorage[ID_STORAGE_KEY];
|
if (localStorage[ID_STORAGE_KEY]) return localStorage[ID_STORAGE_KEY];
|
||||||
this.browserID = "";
|
this.browserID = "";
|
||||||
this.recall_id();
|
|
||||||
return this.browserID;
|
return this.browserID;
|
||||||
}
|
}
|
||||||
set browserID(id) {
|
set browserID(id) {
|
||||||
|
@ -16,11 +16,6 @@ export const ConnectionMixin = (SuperClass) => {
|
|||||||
return;
|
return;
|
||||||
const dt = new Date();
|
const dt = new Date();
|
||||||
console.log(`${dt.toLocaleTimeString()}`, ...args);
|
console.log(`${dt.toLocaleTimeString()}`, ...args);
|
||||||
|
|
||||||
this.connection.sendMessage({
|
|
||||||
type: "browser_mod/log",
|
|
||||||
message: args[0],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fireEvent(event, detail = undefined) {
|
private fireEvent(event, detail = undefined) {
|
||||||
@ -114,7 +109,7 @@ export const ConnectionMixin = (SuperClass) => {
|
|||||||
|
|
||||||
private async _reregister(newData = {}) {
|
private async _reregister(newData = {}) {
|
||||||
await this.connection.sendMessage({
|
await this.connection.sendMessage({
|
||||||
type: "browser_mod/register",
|
type: "browser_mod/reregister",
|
||||||
browserID: this.browserID,
|
browserID: this.browserID,
|
||||||
data: {
|
data: {
|
||||||
...this.browsers[this.browserID],
|
...this.browsers[this.browserID],
|
||||||
@ -215,7 +210,7 @@ export const ConnectionMixin = (SuperClass) => {
|
|||||||
) {
|
) {
|
||||||
(async () => {
|
(async () => {
|
||||||
await this.connection.sendMessage({
|
await this.connection.sendMessage({
|
||||||
type: "browser_mod/register",
|
type: "browser_mod/reregister",
|
||||||
browserID: oldID,
|
browserID: oldID,
|
||||||
data: {
|
data: {
|
||||||
...this.browsers[oldID],
|
...this.browsers[oldID],
|
||||||
|
@ -39,20 +39,16 @@ import { BrowserIDMixin } from "./browserID";
|
|||||||
x ll-custom handling
|
x ll-custom handling
|
||||||
- Commands
|
- Commands
|
||||||
x popup
|
x popup
|
||||||
x Auto-close
|
|
||||||
x close_popup
|
x close_popup
|
||||||
x more-info
|
x more-info
|
||||||
x navigate
|
x navigate
|
||||||
- lovelace-reload?
|
- lovelace-reload?
|
||||||
- Not needed
|
|
||||||
x window-reload
|
x window-reload
|
||||||
- screensaver ?
|
- screensaver ?
|
||||||
- Refer to automations instead
|
|
||||||
x sequence
|
x sequence
|
||||||
x delay
|
x delay
|
||||||
x javascript eval
|
x javascript eval
|
||||||
- toast?
|
- toast?
|
||||||
- Replaced with popups with timeout
|
|
||||||
x Redesign services to target devices
|
x Redesign services to target devices
|
||||||
x frontend editor for popup cards
|
x frontend editor for popup cards
|
||||||
- also screensavers
|
- also screensavers
|
||||||
@ -70,8 +66,7 @@ import { BrowserIDMixin } from "./browserID";
|
|||||||
- Video player?
|
- Video player?
|
||||||
- Media_seek
|
- Media_seek
|
||||||
- Screensavers
|
- Screensavers
|
||||||
x IMPORTANT: FIX DEFAULT HIDING OF ENTITIES
|
- IMPORTANT: FIX DEFAULT HIDING OF ENTITIES
|
||||||
- NOFIX. Home Assistant bug
|
|
||||||
X Check functionality with CAST - may need to add frontend part as a lovelace resource
|
X Check functionality with CAST - may need to add frontend part as a lovelace resource
|
||||||
*/
|
*/
|
||||||
export class BrowserMod extends ServicesMixin(
|
export class BrowserMod extends ServicesMixin(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { LitElement, html, css } from "lit";
|
import { LitElement, html, css } from "lit";
|
||||||
import { property, query } from "lit/decorators.js";
|
import { property } from "lit/decorators.js";
|
||||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||||
import { provideHass, loadLoadCardHelpers, hass_base_el } from "../helpers";
|
import { provideHass, loadLoadCardHelpers, hass_base_el } from "../helpers";
|
||||||
|
|
||||||
@ -16,31 +16,27 @@ class BrowserModPopup extends LitElement {
|
|||||||
@property() dismissable;
|
@property() dismissable;
|
||||||
@property({ reflect: true }) wide;
|
@property({ reflect: true }) wide;
|
||||||
@property({ reflect: true }) fullscreen;
|
@property({ reflect: true }) fullscreen;
|
||||||
@property() _style;
|
|
||||||
@query("ha-dialog") dialog: any;
|
|
||||||
_autoclose;
|
|
||||||
_autocloseListener;
|
|
||||||
_actions;
|
_actions;
|
||||||
timeout;
|
timeout;
|
||||||
_timeoutStart;
|
_timeoutStart;
|
||||||
_timeoutTimer;
|
_timeoutTimer;
|
||||||
|
@property() _style;
|
||||||
|
|
||||||
_resolveClosed;
|
_resolveClosed;
|
||||||
|
|
||||||
|
closedCallback() {
|
||||||
|
this._resolveClosed?.();
|
||||||
|
this._resolveClosed = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
async closeDialog() {
|
async closeDialog() {
|
||||||
this.open = false;
|
this.open = false;
|
||||||
|
await new Promise((resolve) => (this._resolveClosed = resolve));
|
||||||
clearInterval(this._timeoutTimer);
|
clearInterval(this._timeoutTimer);
|
||||||
if (this._autocloseListener) {
|
|
||||||
window.browser_mod.removeEventListener(
|
|
||||||
"browser-mod-activity",
|
|
||||||
this._autocloseListener
|
|
||||||
);
|
|
||||||
this._autocloseListener = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openDialog() {
|
openDialog() {
|
||||||
this.open = true;
|
this.open = true;
|
||||||
this.dialog?.show();
|
|
||||||
if (this.timeout) {
|
if (this.timeout) {
|
||||||
this._timeoutStart = new Date().getTime();
|
this._timeoutStart = new Date().getTime();
|
||||||
this._timeoutTimer = setInterval(() => {
|
this._timeoutTimer = setInterval(() => {
|
||||||
@ -50,14 +46,6 @@ class BrowserModPopup extends LitElement {
|
|||||||
if (ellapsed >= this.timeout) this._timeout();
|
if (ellapsed >= this.timeout) this._timeout();
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
this._autocloseListener = undefined;
|
|
||||||
if (this._autoclose) {
|
|
||||||
this._autocloseListener = this._dismiss.bind(this);
|
|
||||||
window.browser_mod.addEventListener(
|
|
||||||
"browser-mod-activity",
|
|
||||||
this._autocloseListener
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setupDialog(
|
async setupDialog(
|
||||||
@ -74,7 +62,6 @@ class BrowserModPopup extends LitElement {
|
|||||||
timeout_action = undefined,
|
timeout_action = undefined,
|
||||||
size = undefined,
|
size = undefined,
|
||||||
style = undefined,
|
style = undefined,
|
||||||
autoclose = false,
|
|
||||||
} = {}
|
} = {}
|
||||||
) {
|
) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
@ -107,7 +94,6 @@ class BrowserModPopup extends LitElement {
|
|||||||
this.wide = size === "wide" ? "" : undefined;
|
this.wide = size === "wide" ? "" : undefined;
|
||||||
this.fullscreen = size === "fullscreen" ? "" : undefined;
|
this.fullscreen = size === "fullscreen" ? "" : undefined;
|
||||||
this._style = style;
|
this._style = style;
|
||||||
this._autoclose = autoclose;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _primary() {
|
async _primary() {
|
||||||
@ -136,6 +122,7 @@ class BrowserModPopup extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<ha-dialog
|
<ha-dialog
|
||||||
open
|
open
|
||||||
|
@closed=${this.closedCallback}
|
||||||
.heading=${this.title !== undefined}
|
.heading=${this.title !== undefined}
|
||||||
?hideActions=${this.actions === undefined}
|
?hideActions=${this.actions === undefined}
|
||||||
.scrimClickAction=${this.dismissable ? this._dismiss : ""}
|
.scrimClickAction=${this.dismissable ? this._dismiss : ""}
|
||||||
|
@ -30,7 +30,6 @@ export const ServicesMixin = (SuperClass) => {
|
|||||||
[left_button_action: <service call>]
|
[left_button_action: <service call>]
|
||||||
[dismissable: <TRUE/false>]
|
[dismissable: <TRUE/false>]
|
||||||
[dismiss_action: <service call>]
|
[dismiss_action: <service call>]
|
||||||
[autoclose: <true/FALSE>]
|
|
||||||
[timeout: <number>]
|
[timeout: <number>]
|
||||||
[timeout_action: <service call>]
|
[timeout_action: <service call>]
|
||||||
[style: <string>]
|
[style: <string>]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user