Renamed Device to Browser throughout

This commit is contained in:
Thomas Lovén 2022-07-18 20:51:07 +00:00
parent 3bf2481e5b
commit acc4a15e02
18 changed files with 227 additions and 369 deletions

View File

@ -3,7 +3,7 @@ import logging
from .store import BrowserModStore
from .mod_view import async_setup_view
from .connection import async_setup_connection
from .const import DOMAIN, DATA_DEVICES, DATA_ADDERS, DATA_STORE
from .const import DOMAIN, DATA_BROWSERS, DATA_ADDERS, DATA_STORE
from .service import async_setup_services
_LOGGER = logging.getLogger(__name__)
@ -15,7 +15,7 @@ async def async_setup(hass, config):
await store.load()
hass.data[DOMAIN] = {
DATA_DEVICES: {},
DATA_BROWSERS: {},
DATA_ADDERS: {},
DATA_STORE: store,
}

View File

@ -3,7 +3,7 @@ import logging
from homeassistant.components.websocket_api import event_message
from homeassistant.helpers import device_registry, entity_registry
from .const import DOMAIN, DATA_ADDERS
from .const import DATA_BROWSERS, DOMAIN, DATA_ADDERS
from .coordinator import Coordinator
from .sensor import BrowserSensor
from .light import BrowserModLight
@ -14,13 +14,13 @@ from .camera import BrowserModCamera
_LOGGER = logging.getLogger(__name__)
class BrowserModDevice:
"""A Browser_mod device."""
class BrowserModBrowser:
"""A Browser_mod browser."""
def __init__(self, hass, deviceID):
def __init__(self, hass, browserID):
""" """
self.deviceID = deviceID
self.coordinator = Coordinator(hass, deviceID)
self.browserID = browserID
self.coordinator = Coordinator(hass, browserID)
self.entities = {}
self.data = {}
self.settings = {}
@ -38,17 +38,17 @@ class BrowserModDevice:
self.update_entities(hass)
def update_entities(self, hass):
"""Create all entities associated with the device."""
"""Create all entities associated with the browser."""
coordinator = self.coordinator
deviceID = self.deviceID
browserID = self.browserID
def _assert_browser_sensor(type, name, *properties):
if name in self.entities:
return
adder = hass.data[DOMAIN][DATA_ADDERS][type]
cls = {"sensor": BrowserSensor, "binary_sensor": BrowserBinarySensor}[type]
new = cls(coordinator, deviceID, name, *properties)
new = cls(coordinator, browserID, name, *properties)
adder([new])
self.entities[name] = new
@ -70,19 +70,19 @@ class BrowserModDevice:
if "screen" not in self.entities:
adder = hass.data[DOMAIN][DATA_ADDERS]["light"]
new = BrowserModLight(coordinator, deviceID, self)
new = BrowserModLight(coordinator, browserID, self)
adder([new])
self.entities["screen"] = new
if "player" not in self.entities:
adder = hass.data[DOMAIN][DATA_ADDERS]["media_player"]
new = BrowserModPlayer(coordinator, deviceID, self)
new = BrowserModPlayer(coordinator, browserID, self)
adder([new])
self.entities["player"] = new
if "camera" not in self.entities and self.settings.get("camera"):
adder = hass.data[DOMAIN][DATA_ADDERS]["camera"]
new = BrowserModCamera(coordinator, deviceID)
new = BrowserModCamera(coordinator, browserID)
adder([new])
self.entities["camera"] = new
if "camera" in self.entities and not self.settings.get("camera"):
@ -95,7 +95,7 @@ class BrowserModDevice:
)
def send(self, command, **kwargs):
"""Send a command to this device."""
"""Send a command to this browser."""
if self.connection is None:
return
@ -112,7 +112,7 @@ class BrowserModDevice:
)
def delete(self, hass):
"""Delete device and associated entities."""
"""Delete browser and associated entities."""
dr = device_registry.async_get(hass)
er = entity_registry.async_get(hass)
@ -121,25 +121,25 @@ class BrowserModDevice:
self.entities = {}
device = dr.async_get_device({(DOMAIN, self.deviceID)})
device = dr.async_get_device({(DOMAIN, self.browserID)})
dr.async_remove_device(device.id)
def getDevice(hass, deviceID, *, create=True):
"""Get or create device by deviceID."""
devices = hass.data[DOMAIN]["devices"]
if deviceID in devices:
return devices[deviceID]
def getBrowser(hass, browserID, *, create=True):
"""Get or create browser by browserID."""
browsers = hass.data[DOMAIN][DATA_BROWSERS]
if browserID in browsers:
return browsers[browserID]
if not create:
return None
devices[deviceID] = BrowserModDevice(hass, deviceID)
return devices[deviceID]
browsers[browserID] = BrowserModBrowser(hass, browserID)
return browsers[browserID]
def deleteDevice(hass, deviceID):
devices = hass.data[DOMAIN]["devices"]
if deviceID in devices:
devices[deviceID].delete(hass)
del devices[deviceID]
def deleteBrowser(hass, browserID):
browsers = hass.data[DOMAIN][DATA_BROWSERS]
if browserID in browsers:
browsers[browserID].delete(hass)
del browsers[browserID]

View File

@ -129,7 +129,7 @@ class BrowserPlayer extends s {
composed: true,
cancelable: false,
detail: {
entityId: (_a = window.browser_mod.deviceEntities) === null || _a === void 0 ? void 0 : _a.player,
entityId: (_a = window.browser_mod.browserEntities) === null || _a === void 0 ? void 0 : _a.player,
},
}));
}
@ -179,7 +179,7 @@ class BrowserPlayer extends s {
</ha-icon-button>
</div>
<div class="device-id">${window.browser_mod.deviceID}</div>
<div class="browser-id">${window.browser_mod.browserID}</div>
</ha-card>
`;
}
@ -196,7 +196,7 @@ class BrowserPlayer extends s {
width: 24px;
padding: 8px;
}
.device-id {
.browser-id {
opacity: 0.7;
font-size: xx-small;
margin-top: -10px;
@ -260,7 +260,7 @@ const loadLoadCardHelpers = async () => {
await ((_c = (_b = (_a = routes === null || routes === void 0 ? void 0 : routes.routes) === null || _a === void 0 ? void 0 : _a.a) === null || _b === void 0 ? void 0 : _b.load) === null || _c === void 0 ? void 0 : _c.call(_b));
};
const ID_STORAGE_KEY = "browser_mod-device-id";
const ID_STORAGE_KEY = "browser_mod-browser-id";
const ConnectionMixin = (SuperClass) => {
class BrowserModConnection extends SuperClass {
constructor() {
@ -269,7 +269,7 @@ const ConnectionMixin = (SuperClass) => {
this.connectionPromise = new Promise((resolve) => {
this._connectionResolve = resolve;
});
this.deviceEntities = {};
this.browserEntities = {};
}
LOG(...args) {
const dt = new Date();
@ -284,8 +284,8 @@ const ConnectionMixin = (SuperClass) => {
this.LOG("Command:", msg);
this.fireEvent(`command-${msg.command}`, msg);
}
else if (msg.deviceEntities) {
this.deviceEntities = msg.deviceEntities;
else if (msg.browserEntities) {
this.browserEntities = msg.browserEntities;
}
else if (msg.result) {
this.update_config(msg.result);
@ -296,7 +296,7 @@ const ConnectionMixin = (SuperClass) => {
var _a;
this.LOG("Receive:", cfg);
let update = false;
if (!this.registered && ((_a = cfg.devices) === null || _a === void 0 ? void 0 : _a[this.deviceID])) {
if (!this.registered && ((_a = cfg.browsers) === null || _a === void 0 ? void 0 : _a[this.browserID])) {
update = true;
}
this._data = cfg;
@ -314,7 +314,7 @@ const ConnectionMixin = (SuperClass) => {
// Subscribe to configuration updates
conn.subscribeMessage((msg) => this.incoming_message(msg), {
type: "browser_mod/connect",
deviceID: this.deviceID,
browserID: this.browserID,
});
// Keep connection status up to date
conn.addEventListener("disconnected", () => {
@ -332,13 +332,13 @@ const ConnectionMixin = (SuperClass) => {
var _a, _b;
return (_b = (_a = this._data) === null || _a === void 0 ? void 0 : _a.config) !== null && _b !== void 0 ? _b : {};
}
get devices() {
get browsers() {
var _a, _b;
return (_b = (_a = this._data) === null || _a === void 0 ? void 0 : _a.devices) !== null && _b !== void 0 ? _b : [];
return (_b = (_a = this._data) === null || _a === void 0 ? void 0 : _a.browsers) !== null && _b !== void 0 ? _b : [];
}
get registered() {
var _a;
return ((_a = this.devices) === null || _a === void 0 ? void 0 : _a[this.deviceID]) !== undefined;
return ((_a = this.browsers) === null || _a === void 0 ? void 0 : _a[this.browserID]) !== undefined;
}
set registered(reg) {
(async () => {
@ -347,7 +347,7 @@ const ConnectionMixin = (SuperClass) => {
return;
await this.connection.sendMessage({
type: "browser_mod/register",
deviceID: this.deviceID,
browserID: this.browserID,
});
}
else {
@ -355,7 +355,7 @@ const ConnectionMixin = (SuperClass) => {
return;
await this.connection.sendMessage({
type: "browser_mod/unregister",
deviceID: this.deviceID,
browserID: this.browserID,
});
}
})();
@ -363,14 +363,14 @@ const ConnectionMixin = (SuperClass) => {
async _reregister(newData = {}) {
await this.connection.sendMessage({
type: "browser_mod/reregister",
deviceID: this.deviceID,
data: Object.assign(Object.assign({}, this.devices[this.deviceID]), newData),
browserID: this.browserID,
data: Object.assign(Object.assign({}, this.browsers[this.browserID]), newData),
});
}
get meta() {
if (!this.registered)
return null;
return this.devices[this.deviceID].meta;
return this.browsers[this.browserID].meta;
}
set meta(value) {
this._reregister({ meta: value });
@ -378,7 +378,7 @@ const ConnectionMixin = (SuperClass) => {
get cameraEnabled() {
if (!this.registered)
return null;
return this.devices[this.deviceID].camera;
return this.browsers[this.browserID].camera;
}
set cameraEnabled(value) {
this._reregister({ camera: value });
@ -389,19 +389,19 @@ const ConnectionMixin = (SuperClass) => {
this.LOG("Send:", data);
this.connection.sendMessage({
type: "browser_mod/update",
deviceID: this.deviceID,
browserID: this.browserID,
data,
});
}
get deviceID() {
get browserID() {
if (localStorage[ID_STORAGE_KEY])
return localStorage[ID_STORAGE_KEY];
this.deviceID = "";
return this.deviceID;
this.browserID = "";
return this.browserID;
}
set deviceID(id) {
set browserID(id) {
var _a, _b;
function _createDeviceID() {
function _createBrowserID() {
var _a, _b;
const s4 = () => {
return Math.floor((1 + Math.random()) * 100000)
@ -411,21 +411,20 @@ const ConnectionMixin = (SuperClass) => {
return (_b = (_a = window.fully) === null || _a === void 0 ? void 0 : _a.getDeviceId()) !== null && _b !== void 0 ? _b : `${s4()}${s4()}-${s4()}${s4()}`;
}
if (id === "")
id = _createDeviceID();
id = _createBrowserID();
const oldID = localStorage[ID_STORAGE_KEY];
localStorage[ID_STORAGE_KEY] = id;
this.fireEvent("browser-mod-config-update");
if (((_a = this.devices) === null || _a === void 0 ? void 0 : _a[oldID]) !== undefined &&
((_b = this.devices) === null || _b === void 0 ? void 0 : _b[this.deviceID]) === undefined) {
if (((_a = this.browsers) === null || _a === void 0 ? void 0 : _a[oldID]) !== undefined &&
((_b = this.browsers) === null || _b === void 0 ? void 0 : _b[this.browserID]) === undefined) {
(async () => {
await this.connection.sendMessage({
type: "browser_mod/reregister",
deviceID: oldID,
data: Object.assign(Object.assign({}, this.devices[oldID]), { deviceID: this.deviceID }),
browserID: oldID,
data: Object.assign(Object.assign({}, this.browsers[oldID]), { browserID: this.browserID }),
});
})();
}
// TODO: Send update to backend to update device
}
}
return BrowserModConnection;
@ -1256,8 +1255,8 @@ var pjson = {
/*
TODO:
- Fix nomenclature
- Command -> Service
- Device -> Browser
x Command -> Service
x Device -> Browser
- Popups
X Basic popups
- Card-mod integration
@ -1306,13 +1305,10 @@ class BrowserMod extends ServicesMixin(PopupMixin(BrowserStateMixin(CameraMixin(
// }
// });
console.info(`%cBROWSER_MOD ${pjson.version} IS INSTALLED
%cDeviceID: ${this.deviceID}`, "color: green; font-weight: bold", "");
%cBrowserID: ${this.browserID}`, "color: green; font-weight: bold", "");
}
}
// (async () => {
// await hass_loaded();
if (!window.browser_mod)
window.browser_mod = new BrowserMod();
// })();
window.browser_mod = new BrowserMod();
export { BrowserMod };

View File

@ -90,28 +90,28 @@ loadDevTools().then(() => {
return;
window.browser_mod.registered = !window.browser_mod.registered;
}
changeDeviceID(ev) {
window.browser_mod.deviceID = ev.target.value;
changeBrowserID(ev) {
window.browser_mod.browserID = ev.target.value;
}
toggleCameraEnabled() {
window.browser_mod.cameraEnabled = !window.browser_mod.cameraEnabled;
}
unregister_device(ev) {
const deviceID = ev.currentTarget.deviceID;
unregister_browser(ev) {
const browserID = ev.currentTarget.browserID;
const unregisterCallback = () => {
console.log(deviceID, window.browser_mod.deviceID);
if (deviceID === window.browser_mod.deviceID) {
console.log(browserID, window.browser_mod.browserID);
if (browserID === window.browser_mod.browserID) {
console.log("Unregister self");
window.browser_mod.registered = false;
}
else {
window.browser_mod.connection.sendMessage({
type: "browser_mod/unregister",
deviceID,
browserID,
});
}
};
window.browser_mod.showPopup("Unregister device", `Are you sure you want to unregister device ${deviceID}?`, {
window.browser_mod.showPopup("Unregister browser", `Are you sure you want to unregister browser ${browserID}?`, {
primary_action: "Yes",
secondary_action: "No",
callbacks: {
@ -176,21 +176,21 @@ loadDevTools().then(() => {
</ha-settings-row>
<ha-settings-row>
<span slot="heading">DeviceID</span>
<span slot="heading">BrowserID</span>
<span slot="description"
>A unique identifier for this browser-device
combination.</span
>
<ha-textfield
.value=${(_c = window.browser_mod) === null || _c === void 0 ? void 0 : _c.deviceID}
@change=${this.changeDeviceID}
.value=${(_c = window.browser_mod) === null || _c === void 0 ? void 0 : _c.browserID}
@change=${this.changeBrowserID}
></ha-textfield>
</ha-settings-row>
<ha-settings-row>
<span slot="heading">Enable camera</span>
<span slot="description"
>Get camera input from this device (hardware
>Get camera input from this browser (hardware
dependent)</span
>
<ha-switch
@ -201,20 +201,20 @@ loadDevTools().then(() => {
</div>
</ha-card>
<ha-card header="Registered devices" outlined>
<ha-card header="Registered browsers" outlined>
<div class="card-content">
${Object.keys(window.browser_mod.devices).map((d) => $ ` <ha-settings-row>
${Object.keys(window.browser_mod.browsers).map((d) => $ ` <ha-settings-row>
<span slot="heading"> ${d} </span>
<span slot="description">
Last connected:
<ha-relative-time
.hass=${this.hass}
.datetime=${window.browser_mod.devices[d].last_seen}
.datetime=${window.browser_mod.browsers[d].last_seen}
></ha-relative-time>
</span>
<ha-icon-button
.deviceID=${d}
@click=${this.unregister_device}
.browserID=${d}
@click=${this.unregister_browser}
>
<ha-icon .icon=${"mdi:delete"}></ha-icon>
</ha-icon-button>

View File

@ -21,13 +21,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class BrowserModCamera(BrowserModEntity, Camera):
def __init__(self, coordinator, deviceID):
BrowserModEntity.__init__(self, coordinator, deviceID, None)
def __init__(self, coordinator, browserID):
BrowserModEntity.__init__(self, coordinator, browserID, None)
Camera.__init__(self)
@property
def unique_id(self):
return f"{self.deviceID}-camera"
return f"{self.browserID}-camera"
@property
def entity_registry_visible_default(self):

View File

@ -18,7 +18,7 @@ from .const import (
DOMAIN,
)
from .device import getDevice, deleteDevice
from .browser import getBrowser, deleteBrowser
_LOGGER = logging.getLogger(__name__)
@ -27,12 +27,12 @@ async def async_setup_connection(hass):
@websocket_api.websocket_command(
{
vol.Required("type"): WS_CONNECT,
vol.Required("deviceID"): str,
vol.Required("browserID"): str,
}
)
@websocket_api.async_response
async def handle_connect(hass, connection, msg):
deviceID = msg["deviceID"]
browserID = msg["browserID"]
store = hass.data[DOMAIN]["store"]
def listener(data):
@ -41,93 +41,93 @@ async def async_setup_connection(hass):
connection.subscriptions[msg["id"]] = store.add_listener(listener)
connection.send_result(msg["id"])
if store.get_device(deviceID).enabled:
dev = getDevice(hass, deviceID)
dev.update_settings(hass, store.get_device(deviceID).asdict())
if store.get_browser(browserID).enabled:
dev = getBrowser(hass, browserID)
dev.update_settings(hass, store.get_browser(browserID).asdict())
dev.connection = (connection, msg["id"])
await store.set_device(
deviceID, last_seen=datetime.now(tz=timezone.utc).isoformat()
await store.set_browser(
browserID, last_seen=datetime.now(tz=timezone.utc).isoformat()
)
listener(store.asdict())
@websocket_api.websocket_command(
{
vol.Required("type"): WS_REGISTER,
vol.Required("deviceID"): str,
vol.Required("browserID"): str,
}
)
@websocket_api.async_response
async def handle_register(hass, connection, msg):
deviceID = msg["deviceID"]
browserID = msg["browserID"]
store = hass.data[DOMAIN]["store"]
await store.set_device(deviceID, enabled=True)
await store.set_browser(browserID, enabled=True)
connection.send_result(msg["id"])
@websocket_api.websocket_command(
{
vol.Required("type"): WS_UNREGISTER,
vol.Required("deviceID"): str,
vol.Required("browserID"): str,
}
)
@websocket_api.async_response
async def handle_unregister(hass, connection, msg):
deviceID = msg["deviceID"]
browserID = msg["browserID"]
store = hass.data[DOMAIN]["store"]
deleteDevice(hass, deviceID)
await store.delete_device(deviceID)
deleteBrowser(hass, browserID)
await store.delete_browser(browserID)
connection.send_result(msg["id"])
@websocket_api.websocket_command(
{
vol.Required("type"): WS_REREGISTER,
vol.Required("deviceID"): str,
vol.Required("browserID"): str,
vol.Required("data"): dict,
}
)
@websocket_api.async_response
async def handle_reregister(hass, connection, msg):
deviceID = msg["deviceID"]
browserID = msg["browserID"]
store = hass.data[DOMAIN]["store"]
data = msg["data"]
del data["last_seen"]
deviceSettings = {}
browserSettings = {}
if "deviceID" in data:
newDeviceID = data["deviceID"]
del data["deviceID"]
if "browserID" in data:
newBrowserID = data["browserID"]
del data["browserID"]
oldDeviceSettings = store.get_device(deviceID)
if oldDeviceSettings:
deviceSettings = oldDeviceSettings.asdict()
await store.delete_device(deviceID)
oldBrowserSetting = store.get_browser(browserID)
if oldBrowserSetting:
browserSettings = oldBrowserSetting.asdict()
await store.delete_browser(browserID)
deleteDevice(hass, deviceID)
deleteBrowser(hass, browserID)
deviceID = newDeviceID
browserID = newBrowserID
if (dev := getDevice(hass, deviceID, create=False)) is not None:
if (dev := getBrowser(hass, browserID, create=False)) is not None:
dev.update_settings(hass, data)
deviceSettings.update(data)
await store.set_device(deviceID, **deviceSettings)
browserSettings.update(data)
await store.set_browser(browserID, **browserSettings)
@websocket_api.websocket_command(
{
vol.Required("type"): WS_UPDATE,
vol.Required("deviceID"): str,
vol.Required("browserID"): str,
vol.Optional("data"): dict,
}
)
@websocket_api.async_response
async def handle_update(hass, connection, msg):
deviceID = msg["deviceID"]
browserID = msg["browserID"]
store = hass.data[DOMAIN]["store"]
if store.get_device(deviceID).enabled:
dev = getDevice(hass, deviceID)
if store.get_browser(browserID).enabled:
dev = getBrowser(hass, browserID)
dev.update(hass, msg.get("data", {}))
async_register_command(hass, handle_connect)

View File

@ -3,7 +3,7 @@ DOMAIN = "browser_mod"
FRONTEND_SCRIPT_URL = "/browser_mod.js"
SETTINGS_PANEL_URL = "/browser_mod_panel.js"
DATA_DEVICES = "devices"
DATA_BROWSERS = "browsers"
DATA_ADDERS = "adders"
DATA_STORE = "store"
DATA_ALIASES = "aliases"

View File

@ -8,10 +8,10 @@ _LOGGER = logging.getLogger(__name__)
class Coordinator(DataUpdateCoordinator):
def __init__(self, hass, deviceID):
def __init__(self, hass, browserID):
super().__init__(
hass,
_LOGGER,
name="Browser Mod Coordinator",
)
self.deviceID = deviceID
self.browserID = browserID

View File

@ -11,9 +11,9 @@ _LOGGER = logging.getLogger(__name__)
class BrowserModEntity(CoordinatorEntity):
def __init__(self, coordinator, deviceID, name):
def __init__(self, coordinator, browserID, name):
super().__init__(coordinator)
self.deviceID = deviceID
self.browserID = browserID
self._name = name
@property
@ -26,8 +26,8 @@ class BrowserModEntity(CoordinatorEntity):
if ip := self._data.get("browser", {}).get("ip_address"):
config_url = {"configuration_url": f"http://{ip}:2323"}
return {
"identifiers": {(DOMAIN, self.deviceID)},
"name": self.deviceID,
"identifiers": {(DOMAIN, self.browserID)},
"name": self.browserID,
"manufacturer": "Browser Mod",
**config_url,
}
@ -36,7 +36,7 @@ class BrowserModEntity(CoordinatorEntity):
def extra_state_attributes(self):
return {
"type": "browser_mod",
"deviceID": self.deviceID,
"browserID": self.browserID,
}
@property
@ -53,4 +53,4 @@ class BrowserModEntity(CoordinatorEntity):
@property
def unique_id(self):
return f"{self.deviceID}-{self._name.replace(' ','_')}"
return f"{self.browserID}-{self._name.replace(' ','_')}"

View File

@ -15,10 +15,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class BrowserModLight(BrowserModEntity, LightEntity):
def __init__(self, coordinator, deviceID, device):
BrowserModEntity.__init__(self, coordinator, deviceID, "Screen")
def __init__(self, coordinator, browserID, browser):
BrowserModEntity.__init__(self, coordinator, browserID, "Screen")
LightEntity.__init__(self)
self.device = device
self.browser = browser
@property
def entity_registry_visible_default(self):
@ -41,7 +41,7 @@ class BrowserModLight(BrowserModEntity, LightEntity):
return self._data.get("screen_brightness", 1)
def turn_on(self, **kwargs):
self.device.send("screen_on", **kwargs)
self.browser.send("screen_on", **kwargs)
def turn_off(self, **kwargs):
self.device.send("screen_off")
self.browser.send("screen_off")

View File

@ -39,14 +39,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class BrowserModPlayer(BrowserModEntity, MediaPlayerEntity):
def __init__(self, coordinator, deviceID, device):
BrowserModEntity.__init__(self, coordinator, deviceID, None)
def __init__(self, coordinator, browserID, browser):
BrowserModEntity.__init__(self, coordinator, browserID, None)
MediaPlayerEntity.__init__(self)
self.device = device
self.browser = browser
@property
def unique_id(self):
return f"{self.deviceID}-player"
return f"{self.browserID}-player"
@property
def entity_registry_visible_default(self):
@ -83,10 +83,10 @@ class BrowserModPlayer(BrowserModEntity, MediaPlayerEntity):
return self._data.get("player", {}).get("muted", False)
def set_volume_level(self, volume):
self.device.send("player-set-volume", volume_level=volume)
self.browser.send("player-set-volume", volume_level=volume)
def mute_volume(self, mute):
self.device.send("player-mute", mute=mute)
self.browser.send("player-mute", mute=mute)
async def async_play_media(self, media_type, media_id, **kwargs):
if media_source.is_media_source_id(media_id):
@ -97,7 +97,7 @@ class BrowserModPlayer(BrowserModEntity, MediaPlayerEntity):
media_id = play_item.url
if media_type in (MEDIA_TYPE_URL, MEDIA_TYPE_MUSIC):
media_id = async_process_play_media_url(self.hass, media_id)
self.device.send("player-play", media_content_id=media_id)
self.browser.send("player-play", media_content_id=media_id)
async def async_browse_media(self, media_content_type=None, media_content_id=None):
"""Implement the websocket media browsing helper."""
@ -108,10 +108,10 @@ class BrowserModPlayer(BrowserModEntity, MediaPlayerEntity):
)
def media_play(self):
self.device.send("player-play")
self.browser.send("player-play")
def media_pause(self):
self.device.send("player-pause")
self.browser.send("player-pause")
def media_stop(self):
self.device.send("player-stop")
self.browser.send("player-stop")

View File

@ -18,13 +18,13 @@ class BrowserSensor(BrowserModEntity, SensorEntity):
def __init__(
self,
coordinator,
deviceID,
browserID,
parameter,
name,
unit_of_measurement=None,
device_class=None,
):
super().__init__(coordinator, deviceID, name)
super().__init__(coordinator, browserID, name)
self.parameter = parameter
self._device_class = device_class
self._unit_of_measurement = unit_of_measurement

View File

@ -1,17 +1,10 @@
import logging
from homeassistant.helpers.entity_registry import (
async_entries_for_config_entry,
async_entries_for_device,
)
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.helpers import device_registry, area_registry
from homeassistant.helpers import device_registry
from .const import (
DOMAIN,
DATA_DEVICES,
DATA_ALIASES,
USER_COMMANDS,
DATA_BROWSERS,
)
_LOGGER = logging.getLogger(__name__)
@ -20,29 +13,31 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_services(hass):
def call_service(service, targets, data):
devices = hass.data[DOMAIN][DATA_DEVICES]
browsers = hass.data[DOMAIN][DATA_BROWSERS]
if isinstance(targets, str):
targets = [targets]
# If no targets were specified, send to all browsers
if len(targets) == 0:
targets = browsers.keys()
for target in targets:
if target not in devices:
if target not in browsers:
continue
device = devices[target]
device.send(service, **data)
browser = browsers[target]
browser.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)
browsers = data.pop("browser_id", [])
if isinstance(browsers, str):
browsers = [browsers]
browsers = set(browsers)
device_ids = set(data.pop("device_id", []))
area_ids = set(data.pop("area_id", []))
dr = device_registry.async_get(hass)
@ -53,53 +48,16 @@ async def async_setup_services(hass):
browserID = list(dev.identifiers)[0][1]
if browserID is None:
continue
targets.add(browserID)
browsers.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)
browsers.add(browserID)
_LOGGER.error(service)
_LOGGER.error(targets)
_LOGGER.error(data)
call_service(service, targets, data)
call_service(service, browsers, data)
hass.services.async_register(DOMAIN, "test", handle_service)
hass.services.async_register(DOMAIN, "popup", handle_service)
async def setup_service(hass):
def handle_command(call):
command = call.data.get("command", None)
if not command:
return
targets = call.data.get("deviceID", None)
if isinstance(targets, str):
targets = [targets]
devices = hass.data[DOMAIN][DATA_DEVICES]
aliases = hass.data[DOMAIN][DATA_ALIASES]
if not targets:
targets = devices.keys()
targets = [aliases.get(t, t) for t in targets]
data = dict(call.data)
del data["command"]
for t in targets:
if t in devices:
devices[t].send(command, **data)
def command_wrapper(call):
command = call.service.replace("_", "-")
call.data = dict(call.data)
call.data["command"] = command
handle_command(call)
hass.services.async_register(DOMAIN, "command", handle_command)
for cmd in USER_COMMANDS:
hass.services.async_register(DOMAIN, cmd.replace("-", "_"), command_wrapper)

View File

@ -10,7 +10,7 @@ _LOGGER = logging.getLogger(__name__)
@attr.s
class DeviceStoreData:
class BrowserStoreData:
last_seen = attr.ib(type=int, default=0)
enabled = attr.ib(type=bool, default=False)
camera = attr.ib(type=bool, default=False)
@ -26,17 +26,19 @@ class DeviceStoreData:
@attr.s
class ConfigStoreData:
devices = attr.ib(type=dict[str:DeviceStoreData], factory=dict)
browsers = attr.ib(type=dict[str:BrowserStoreData], factory=dict)
version = attr.ib(type=str, default="2.0")
@classmethod
def from_dict(cls, data={}):
devices = {k: DeviceStoreData.from_dict(v) for k, v in data["devices"].items()}
browsers = {
k: BrowserStoreData.from_dict(v) for k, v in data["browsers"].items()
}
return cls(
**(
data
| {
"devices": devices,
"browsers": browsers,
}
)
)
@ -83,15 +85,15 @@ class BrowserModStore:
return remove_listener
def get_device(self, deviceID):
return self.data.devices.get(deviceID, DeviceStoreData())
def get_browser(self, browserID):
return self.data.browsers.get(browserID, BrowserStoreData())
async def set_device(self, deviceID, **data):
device = self.data.devices.get(deviceID, DeviceStoreData())
device.__dict__.update(data)
self.data.devices[deviceID] = device
async def set_browser(self, browserID, **data):
browser = self.data.browsers.get(browserID, BrowserStoreData())
browser.__dict__.update(data)
self.data.browsers[browserID] = browser
await self.updated()
async def delete_device(self, deviceID):
del self.data.devices[deviceID]
async def delete_browser(self, browserID):
del self.data.browsers[browserID]
await self.updated()

View File

@ -14,32 +14,32 @@ loadDevTools().then(() => {
if (!window.browser_mod?.connected) return;
window.browser_mod.registered = !window.browser_mod.registered;
}
changeDeviceID(ev) {
window.browser_mod.deviceID = ev.target.value;
changeBrowserID(ev) {
window.browser_mod.browserID = ev.target.value;
}
toggleCameraEnabled() {
window.browser_mod.cameraEnabled = !window.browser_mod.cameraEnabled;
}
unregister_device(ev) {
const deviceID = ev.currentTarget.deviceID;
unregister_browser(ev) {
const browserID = ev.currentTarget.browserID;
const unregisterCallback = () => {
console.log(deviceID, window.browser_mod.deviceID);
if (deviceID === window.browser_mod.deviceID) {
console.log(browserID, window.browser_mod.browserID);
if (browserID === window.browser_mod.browserID) {
console.log("Unregister self");
window.browser_mod.registered = false;
} else {
window.browser_mod.connection.sendMessage({
type: "browser_mod/unregister",
deviceID,
browserID,
});
}
};
window.browser_mod.showPopup(
"Unregister device",
`Are you sure you want to unregister device ${deviceID}?`,
"Unregister browser",
`Are you sure you want to unregister browser ${browserID}?`,
{
primary_action: "Yes",
secondary_action: "No",
@ -109,21 +109,21 @@ loadDevTools().then(() => {
</ha-settings-row>
<ha-settings-row>
<span slot="heading">DeviceID</span>
<span slot="heading">BrowserID</span>
<span slot="description"
>A unique identifier for this browser-device
combination.</span
>
<ha-textfield
.value=${window.browser_mod?.deviceID}
@change=${this.changeDeviceID}
.value=${window.browser_mod?.browserID}
@change=${this.changeBrowserID}
></ha-textfield>
</ha-settings-row>
<ha-settings-row>
<span slot="heading">Enable camera</span>
<span slot="description"
>Get camera input from this device (hardware
>Get camera input from this browser (hardware
dependent)</span
>
<ha-switch
@ -134,21 +134,21 @@ loadDevTools().then(() => {
</div>
</ha-card>
<ha-card header="Registered devices" outlined>
<ha-card header="Registered browsers" outlined>
<div class="card-content">
${Object.keys(window.browser_mod.devices).map(
${Object.keys(window.browser_mod.browsers).map(
(d) => html` <ha-settings-row>
<span slot="heading"> ${d} </span>
<span slot="description">
Last connected:
<ha-relative-time
.hass=${this.hass}
.datetime=${window.browser_mod.devices[d].last_seen}
.datetime=${window.browser_mod.browsers[d].last_seen}
></ha-relative-time>
</span>
<ha-icon-button
.deviceID=${d}
@click=${this.unregister_device}
.browserID=${d}
@click=${this.unregister_browser}
>
<ha-icon .icon=${"mdi:delete"}></ha-icon>
</ha-icon-button>

View File

@ -47,7 +47,7 @@ class BrowserPlayer extends LitElement {
composed: true,
cancelable: false,
detail: {
entityId: window.browser_mod.deviceEntities?.player,
entityId: window.browser_mod.browserEntities?.player,
},
})
);
@ -98,7 +98,7 @@ class BrowserPlayer extends LitElement {
</ha-icon-button>
</div>
<div class="device-id">${window.browser_mod.deviceID}</div>
<div class="browser-id">${window.browser_mod.browserID}</div>
</ha-card>
`;
}
@ -116,7 +116,7 @@ class BrowserPlayer extends LitElement {
width: 24px;
padding: 8px;
}
.device-id {
.browser-id {
opacity: 0.7;
font-size: xx-small;
margin-top: -10px;

View File

@ -1,6 +1,6 @@
import { hass, provideHass } from "../helpers";
const ID_STORAGE_KEY = "browser_mod-device-id";
const ID_STORAGE_KEY = "browser_mod-browser-id";
export const ConnectionMixin = (SuperClass) => {
class BrowserModConnection extends SuperClass {
@ -12,7 +12,7 @@ export const ConnectionMixin = (SuperClass) => {
public connectionPromise = new Promise((resolve) => {
this._connectionResolve = resolve;
});
public deviceEntities = {};
public browserEntities = {};
LOG(...args) {
const dt = new Date();
@ -27,8 +27,8 @@ export const ConnectionMixin = (SuperClass) => {
if (msg.command) {
this.LOG("Command:", msg);
this.fireEvent(`command-${msg.command}`, msg);
} else if (msg.deviceEntities) {
this.deviceEntities = msg.deviceEntities;
} else if (msg.browserEntities) {
this.browserEntities = msg.browserEntities;
} else if (msg.result) {
this.update_config(msg.result);
}
@ -39,7 +39,7 @@ export const ConnectionMixin = (SuperClass) => {
this.LOG("Receive:", cfg);
let update = false;
if (!this.registered && cfg.devices?.[this.deviceID]) {
if (!this.registered && cfg.browsers?.[this.browserID]) {
update = true;
}
this._data = cfg;
@ -61,7 +61,7 @@ export const ConnectionMixin = (SuperClass) => {
// Subscribe to configuration updates
conn.subscribeMessage((msg) => this.incoming_message(msg), {
type: "browser_mod/connect",
deviceID: this.deviceID,
browserID: this.browserID,
});
// Keep connection status up to date
@ -82,12 +82,12 @@ export const ConnectionMixin = (SuperClass) => {
return this._data?.config ?? {};
}
get devices() {
return this._data?.devices ?? [];
get browsers() {
return this._data?.browsers ?? [];
}
get registered() {
return this.devices?.[this.deviceID] !== undefined;
return this.browsers?.[this.browserID] !== undefined;
}
set registered(reg) {
@ -96,13 +96,13 @@ export const ConnectionMixin = (SuperClass) => {
if (this.registered) return;
await this.connection.sendMessage({
type: "browser_mod/register",
deviceID: this.deviceID,
browserID: this.browserID,
});
} else {
if (!this.registered) return;
await this.connection.sendMessage({
type: "browser_mod/unregister",
deviceID: this.deviceID,
browserID: this.browserID,
});
}
})();
@ -111,9 +111,9 @@ export const ConnectionMixin = (SuperClass) => {
private async _reregister(newData = {}) {
await this.connection.sendMessage({
type: "browser_mod/reregister",
deviceID: this.deviceID,
browserID: this.browserID,
data: {
...this.devices[this.deviceID],
...this.browsers[this.browserID],
...newData,
},
});
@ -121,7 +121,7 @@ export const ConnectionMixin = (SuperClass) => {
get meta() {
if (!this.registered) return null;
return this.devices[this.deviceID].meta;
return this.browsers[this.browserID].meta;
}
set meta(value) {
this._reregister({ meta: value });
@ -129,7 +129,7 @@ export const ConnectionMixin = (SuperClass) => {
get cameraEnabled() {
if (!this.registered) return null;
return this.devices[this.deviceID].camera;
return this.browsers[this.browserID].camera;
}
set cameraEnabled(value) {
this._reregister({ camera: value });
@ -143,18 +143,18 @@ export const ConnectionMixin = (SuperClass) => {
this.connection.sendMessage({
type: "browser_mod/update",
deviceID: this.deviceID,
browserID: this.browserID,
data,
});
}
get deviceID() {
get browserID() {
if (localStorage[ID_STORAGE_KEY]) return localStorage[ID_STORAGE_KEY];
this.deviceID = "";
return this.deviceID;
this.browserID = "";
return this.browserID;
}
set deviceID(id) {
function _createDeviceID() {
set browserID(id) {
function _createBrowserID() {
const s4 = () => {
return Math.floor((1 + Math.random()) * 100000)
.toString(16)
@ -163,29 +163,27 @@ export const ConnectionMixin = (SuperClass) => {
return window.fully?.getDeviceId() ?? `${s4()}${s4()}-${s4()}${s4()}`;
}
if (id === "") id = _createDeviceID();
if (id === "") id = _createBrowserID();
const oldID = localStorage[ID_STORAGE_KEY];
localStorage[ID_STORAGE_KEY] = id;
this.fireEvent("browser-mod-config-update");
if (
this.devices?.[oldID] !== undefined &&
this.devices?.[this.deviceID] === undefined
this.browsers?.[oldID] !== undefined &&
this.browsers?.[this.browserID] === undefined
) {
(async () => {
await this.connection.sendMessage({
type: "browser_mod/reregister",
deviceID: oldID,
browserID: oldID,
data: {
...this.devices[oldID],
deviceID: this.deviceID,
...this.browsers[oldID],
browserID: this.browserID,
},
});
})();
}
// TODO: Send update to backend to update device
}
}

View File

@ -16,8 +16,8 @@ import pjson from "../../package.json";
/*
TODO:
- Fix nomenclature
- Command -> Service
- Device -> Browser
x Command -> Service
x Device -> Browser
- Popups
X Basic popups
- Card-mod integration
@ -81,107 +81,11 @@ export class BrowserMod extends ServicesMixin(
console.info(
`%cBROWSER_MOD ${pjson.version} IS INSTALLED
%cDeviceID: ${this.deviceID}`,
%cBrowserID: ${this.browserID}`,
"color: green; font-weight: bold",
""
);
}
// async msg_callback(msg) {
// const handlers = {
// update: (msg) => this.update(msg),
// debug: (msg) => this.debug(msg),
// play: (msg) => this.player_play(msg.media_content_id),
// pause: (msg) => this.player_pause(),
// stop: (msg) => this.player_stop(),
// "set-volume": (msg) => this.player_set_volume(msg.volume_level),
// mute: (msg) => this.player_mute(msg.mute),
// toast: (msg) => this.do_toast(msg.message, msg.duration),
// popup: (msg) => this.do_popup(msg),
// "close-popup": (msg) => this.do_close_popup(),
// "more-info": (msg) => this.do_more_info(msg.entity_id, msg.large),
// navigate: (msg) => this.do_navigate(msg.navigation_path),
// "set-theme": (msg) => this.set_theme(msg),
// "lovelace-reload": (msg) => this.lovelace_reload(msg),
// "window-reload": () => window.location.reload(),
// blackout: (msg) =>
// this.do_blackout(msg.time ? parseInt(msg.time) : undefined),
// "no-blackout": (msg) => {
// if (msg.brightness && this.isFully) {
// (window as any).fully.setScreenBrightness(msg.brightness);
// }
// this.no_blackout();
// },
// "call-service": (msg) => this.call_service(msg),
// commands: async (msg) => {
// for (const m of msg.commands) {
// await this.msg_callback(m);
// }
// },
// delay: async (msg) =>
// await new Promise((resolve) => {
// window.setTimeout(resolve, msg.seconds * 1000);
// }),
// };
// await handlers[msg.command.replace("_", "-")](msg);
// }
// debug(msg) {
// popUp(`deviceID`, { type: "markdown", content: `# ${deviceID}` });
// alert(deviceID);
// }
// set_theme(msg) {
// if (!msg.theme) msg.theme = "default";
// fireEvent("settheme", { theme: msg.theme }, ha_element());
// }
// lovelace_reload(msg) {
// const ll = lovelace_view();
// if (ll) fireEvent("config-refresh", {}, ll);
// }
// call_service(msg) {
// const _replaceThis = (data) => {
// if (data === "this") return deviceID;
// if (Array.isArray(data)) return data.map(_replaceThis);
// if (data.constructor == Object) {
// for (const key in data) data[key] = _replaceThis(data[key]);
// }
// return data;
// };
// const [domain, service] = msg.service.split(".", 2);
// let service_data = _replaceThis(
// JSON.parse(JSON.stringify(msg.service_data))
// );
// this.hass.callService(domain, service, service_data);
// }
// // update(msg = null) {
// // if (msg) {
// // if (msg.name) {
// // this.entity_id = msg.name.toLowerCase();
// // }
// // if (msg.camera && !this.isFully) {
// // this.setup_camera();
// // }
// // this.config = { ...this.config, ...msg };
// // }
// // this.player_update();
// // this.fully_update();
// // this.screen_update();
// // this.sensor_update();
// // }
}
// (async () => {
// await hass_loaded();
if (!window.browser_mod) window.browser_mod = new BrowserMod();
// })();