Added camera functionality
This commit is contained in:
parent
466a5eb5e7
commit
e4a65f3077
@ -3,14 +3,7 @@ import logging
|
|||||||
from .store import BrowserModStore
|
from .store import BrowserModStore
|
||||||
from .mod_view import async_setup_view
|
from .mod_view import async_setup_view
|
||||||
from .connection import async_setup_connection
|
from .connection import async_setup_connection
|
||||||
from .const import (
|
from .const import DOMAIN, DATA_DEVICES, DATA_ADDERS, DATA_STORE
|
||||||
DOMAIN,
|
|
||||||
DATA_DEVICES,
|
|
||||||
DATA_ADDERS,
|
|
||||||
DATA_STORE
|
|
||||||
)
|
|
||||||
|
|
||||||
from .coordinator import Coordinator
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -35,13 +28,9 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
await hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor")
|
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, "light")
|
||||||
await hass.config_entries.async_forward_entry_setup(config_entry, "media_player")
|
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)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
for component in COMPONENTS:
|
|
||||||
hass.async_create_task(
|
|
||||||
hass.config_entries.async_forward_entry_setup(config_entry, component)
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
from homeassistant.components.binary_sensor import BinarySensorEntity
|
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||||
|
|
||||||
from .const import DOMAIN, DATA_ADDERS
|
from .const import DOMAIN, DATA_ADDERS
|
||||||
from .helpers import BrowserModEntity2
|
from .helpers import BrowserModEntity
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config_entry, async_add_entities, discoveryInfo = None):
|
async def async_setup_platform(
|
||||||
|
hass, config_entry, async_add_entities, discoveryInfo=None
|
||||||
|
):
|
||||||
hass.data[DOMAIN][DATA_ADDERS]["binary_sensor"] = async_add_entities
|
hass.data[DOMAIN][DATA_ADDERS]["binary_sensor"] = async_add_entities
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
await async_setup_platform(hass, {}, async_add_entities)
|
await async_setup_platform(hass, {}, async_add_entities)
|
||||||
|
|
||||||
|
|
||||||
class BrowserBinarySensor(BrowserModEntity2, BinarySensorEntity):
|
class BrowserBinarySensor(BrowserModEntity, BinarySensorEntity):
|
||||||
|
|
||||||
def __init__(self, coordinator, deviceID, parameter, name):
|
def __init__(self, coordinator, deviceID, parameter, name):
|
||||||
super().__init__(coordinator, deviceID, name)
|
super().__init__(coordinator, deviceID, name)
|
||||||
self.parameter = parameter
|
self.parameter = parameter
|
||||||
|
@ -732,19 +732,28 @@ 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),
|
||||||
|
});
|
||||||
|
}
|
||||||
get meta() {
|
get meta() {
|
||||||
if (!this.registered)
|
if (!this.registered)
|
||||||
return null;
|
return null;
|
||||||
return this.devices[this.deviceID].meta;
|
return this.devices[this.deviceID].meta;
|
||||||
}
|
}
|
||||||
set meta(value) {
|
set meta(value) {
|
||||||
(async () => {
|
this._reregister({ meta: value });
|
||||||
await this.connection.sendMessage({
|
}
|
||||||
type: "browser_mod/reregister",
|
get cameraEnabled() {
|
||||||
deviceID: this.deviceID,
|
if (!this.registered)
|
||||||
data: Object.assign(Object.assign({}, this.devices[this.deviceID]), { meta: value })
|
return null;
|
||||||
});
|
return this.devices[this.deviceID].camera;
|
||||||
})();
|
}
|
||||||
|
set cameraEnabled(value) {
|
||||||
|
this._reregister({ camera: value });
|
||||||
}
|
}
|
||||||
sendUpdate(data) {
|
sendUpdate(data) {
|
||||||
if (!this.connected || !this.registered)
|
if (!this.connected || !this.registered)
|
||||||
@ -923,6 +932,65 @@ const MediaPlayerMixin = (SuperClass) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CameraMixin = (SuperClass) => {
|
||||||
|
return class CameraMixinClass extends SuperClass {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._framerate = 2;
|
||||||
|
window.addEventListener("pointerdown", () => {
|
||||||
|
this._setup_camera();
|
||||||
|
}, { once: true });
|
||||||
|
}
|
||||||
|
async _setup_camera() {
|
||||||
|
if (this._video)
|
||||||
|
return;
|
||||||
|
await this.connectionPromise;
|
||||||
|
if (!this.cameraEnabled)
|
||||||
|
return;
|
||||||
|
const video = (this._video = document.createElement("video"));
|
||||||
|
video.autoplay = true;
|
||||||
|
video.playsInline = true;
|
||||||
|
video.style.display = "none";
|
||||||
|
const canvas = (this._canvas = document.createElement("canvas"));
|
||||||
|
canvas.style.display = "none";
|
||||||
|
document.body.appendChild(video);
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
if (!navigator.mediaDevices)
|
||||||
|
return;
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
video: true,
|
||||||
|
audio: false,
|
||||||
|
});
|
||||||
|
video.srcObject = stream;
|
||||||
|
video.play();
|
||||||
|
this.update_camera();
|
||||||
|
}
|
||||||
|
async update_camera() {
|
||||||
|
var _a;
|
||||||
|
if (!this.cameraEnabled) {
|
||||||
|
const stream = (_a = this._video) === null || _a === void 0 ? void 0 : _a.srcObject;
|
||||||
|
if (stream) {
|
||||||
|
stream.getTracks().forEach((t) => t.stop());
|
||||||
|
this._video.scrObject = undefined;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const video = this._video;
|
||||||
|
const width = video.videoWidth;
|
||||||
|
const height = video.videoHeight;
|
||||||
|
this._canvas.width = width;
|
||||||
|
this._canvas.height = height;
|
||||||
|
const context = this._canvas.getContext("2d");
|
||||||
|
context.drawImage(video, 0, 0, width, height);
|
||||||
|
this.sendUpdate({
|
||||||
|
camera: this._canvas.toDataURL("image/jpeg"),
|
||||||
|
});
|
||||||
|
const interval = Math.round(1000 / this._framerate);
|
||||||
|
setTimeout(() => this.update_camera(), interval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
var name = "browser_mod";
|
var name = "browser_mod";
|
||||||
var version = "2.0.0b0";
|
var version = "2.0.0b0";
|
||||||
var description = "";
|
var description = "";
|
||||||
@ -970,7 +1038,7 @@ var pjson = {
|
|||||||
// FullyKioskMixin,
|
// FullyKioskMixin,
|
||||||
// BrowserModMediaPlayerMixin,
|
// BrowserModMediaPlayerMixin,
|
||||||
// ]) {
|
// ]) {
|
||||||
class BrowserMod extends MediaPlayerMixin(ScreenSaverMixin(ConnectionMixin(EventTarget))) {
|
class BrowserMod extends CameraMixin(MediaPlayerMixin(ScreenSaverMixin(ConnectionMixin(EventTarget)))) {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.entity_id = deviceID.replace("-", "_");
|
this.entity_id = deviceID.replace("-", "_");
|
||||||
|
@ -90,6 +90,9 @@ loadDevTools().then(() => {
|
|||||||
changeDeviceID(ev) {
|
changeDeviceID(ev) {
|
||||||
window.browser_mod.deviceID = ev.target.value;
|
window.browser_mod.deviceID = ev.target.value;
|
||||||
}
|
}
|
||||||
|
toggleCameraEnabled() {
|
||||||
|
window.browser_mod.cameraEnabled = !window.browser_mod.cameraEnabled;
|
||||||
|
}
|
||||||
unregister_device(ev) {
|
unregister_device(ev) {
|
||||||
const deviceID = ev.currentTarget.deviceID;
|
const deviceID = ev.currentTarget.deviceID;
|
||||||
if (deviceID === window.browser_mod.deviceID)
|
if (deviceID === window.browser_mod.deviceID)
|
||||||
@ -104,7 +107,7 @@ loadDevTools().then(() => {
|
|||||||
window.browser_mod.addEventListener("browser-mod-config-update", () => this.requestUpdate());
|
window.browser_mod.addEventListener("browser-mod-config-update", () => this.requestUpdate());
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
var _a, _b, _c;
|
var _a, _b, _c, _d;
|
||||||
return $ `
|
return $ `
|
||||||
<ha-app-layout>
|
<ha-app-layout>
|
||||||
<app-header slot="header" fixed>
|
<app-header slot="header" fixed>
|
||||||
@ -137,7 +140,13 @@ loadDevTools().then(() => {
|
|||||||
></ha-icon>
|
></ha-icon>
|
||||||
`}
|
`}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="card-content">Browser-mod not connected.</div>
|
<div class="card-content">
|
||||||
|
<p>Settings that apply to this browser.</p>
|
||||||
|
<p>
|
||||||
|
It is strongly recommended to refresh your browser window
|
||||||
|
after any change to those settings.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<ha-settings-row>
|
<ha-settings-row>
|
||||||
<span slot="heading">Enable</span>
|
<span slot="heading">Enable</span>
|
||||||
@ -168,7 +177,10 @@ loadDevTools().then(() => {
|
|||||||
>Get camera input from this device (hardware
|
>Get camera input from this device (hardware
|
||||||
dependent)</span
|
dependent)</span
|
||||||
>
|
>
|
||||||
<ha-switch> </ha-switch>
|
<ha-switch
|
||||||
|
.checked=${(_d = window.browser_mod) === null || _d === void 0 ? void 0 : _d.cameraEnabled}
|
||||||
|
@change=${this.toggleCameraEnabled}
|
||||||
|
></ha-switch>
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
@ -184,7 +196,10 @@ loadDevTools().then(() => {
|
|||||||
.datetime=${window.browser_mod.devices[d].last_seen}
|
.datetime=${window.browser_mod.devices[d].last_seen}
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
</span>
|
</span>
|
||||||
<ha-icon-button .deviceID=${d} @click=${this.unregister_device}>
|
<ha-icon-button
|
||||||
|
.deviceID=${d}
|
||||||
|
@click=${this.unregister_device}
|
||||||
|
>
|
||||||
<ha-icon .icon=${"mdi:delete"}></ha-icon>
|
<ha-icon .icon=${"mdi:delete"}></ha-icon>
|
||||||
</ha-icon-button>
|
</ha-icon-button>
|
||||||
<ha-icon-button>
|
<ha-icon-button>
|
||||||
|
@ -1,45 +1,40 @@
|
|||||||
from datetime import datetime
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
from homeassistant.components.camera import Camera
|
from homeassistant.components.camera import Camera
|
||||||
|
|
||||||
from .helpers import setup_platform, BrowserModEntity
|
from .helpers import BrowserModEntity
|
||||||
|
from .const import DOMAIN, DATA_ADDERS
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
PLATFORM = "camera"
|
|
||||||
|
|
||||||
LOGGER = logging.Logger(__name__)
|
LOGGER = logging.Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
async def async_setup_platform(
|
||||||
return setup_platform(hass, config, async_add_devices, PLATFORM, BrowserModCamera)
|
hass, config_entry, async_add_entities, discoveryInfo=None
|
||||||
|
):
|
||||||
|
hass.data[DOMAIN][DATA_ADDERS]["camera"] = async_add_entities
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
await async_setup_platform(hass, {}, async_add_entities)
|
await async_setup_platform(hass, {}, async_add_entities)
|
||||||
|
|
||||||
|
|
||||||
class BrowserModCamera(Camera, BrowserModEntity):
|
class BrowserModCamera(BrowserModEntity, Camera):
|
||||||
domain = PLATFORM
|
def __init__(self, coordinator, deviceID):
|
||||||
|
BrowserModEntity.__init__(self, coordinator, deviceID, None)
|
||||||
def __init__(self, hass, connection, deviceID, alias=None):
|
|
||||||
Camera.__init__(self)
|
Camera.__init__(self)
|
||||||
BrowserModEntity.__init__(self, hass, connection, deviceID, alias)
|
|
||||||
self.last_seen = None
|
|
||||||
|
|
||||||
def updated(self):
|
|
||||||
if self.last_seen is None or (datetime.now() - self.last_seen).seconds > 15:
|
|
||||||
self.last_seen = datetime.now()
|
|
||||||
self.schedule_update_ha_state()
|
|
||||||
|
|
||||||
def camera_image(self, width=None, height=None):
|
|
||||||
return base64.b64decode(self.data.split(",")[-1])
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self):
|
def unique_id(self):
|
||||||
return {
|
return f"{self.deviceID}-camera"
|
||||||
"type": "browser_mod",
|
|
||||||
"deviceID": self.deviceID,
|
@property
|
||||||
"last_seen": self.last_seen,
|
def entity_registry_visible_default(self):
|
||||||
}
|
return True
|
||||||
|
|
||||||
|
def camera_image(self, width=None, height=None):
|
||||||
|
if "camera" not in self._data:
|
||||||
|
LOGGER.error(self._data)
|
||||||
|
return None
|
||||||
|
return base64.b64decode(self._data["camera"].split(",")[-1])
|
||||||
|
@ -15,7 +15,7 @@ from .const import WS_CONNECT, WS_REGISTER, WS_UNREGISTER, WS_REREGISTER, WS_UPD
|
|||||||
from .helpers import get_devices, create_entity, get_config, is_setup_complete
|
from .helpers import get_devices, create_entity, get_config, is_setup_complete
|
||||||
|
|
||||||
from .coordinator import Coordinator
|
from .coordinator import Coordinator
|
||||||
from .device import getDevice
|
from .device import getDevice, deleteDevice
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -40,6 +40,7 @@ async def async_setup_connection(hass):
|
|||||||
|
|
||||||
if store.get_device(deviceID).enabled:
|
if store.get_device(deviceID).enabled:
|
||||||
dev = getDevice(hass, deviceID)
|
dev = getDevice(hass, deviceID)
|
||||||
|
dev.update_settings(hass, store.get_device(deviceID).asdict())
|
||||||
dev.connection = (connection, msg["id"])
|
dev.connection = (connection, msg["id"])
|
||||||
await store.set_device(deviceID,
|
await store.set_device(deviceID,
|
||||||
last_seen=datetime.now(
|
last_seen=datetime.now(
|
||||||
@ -75,12 +76,8 @@ async def async_setup_connection(hass):
|
|||||||
async def handle_unregister(hass, connection, msg):
|
async def handle_unregister(hass, connection, msg):
|
||||||
deviceID = msg["deviceID"]
|
deviceID = msg["deviceID"]
|
||||||
store = hass.data[DOMAIN]["store"]
|
store = hass.data[DOMAIN]["store"]
|
||||||
devices = hass.data[DOMAIN]["devices"]
|
|
||||||
|
|
||||||
if deviceID in devices:
|
|
||||||
devices[deviceID].delete(hass)
|
|
||||||
del devices[deviceID]
|
|
||||||
|
|
||||||
|
deleteDevice(hass, deviceID)
|
||||||
await store.delete_device(deviceID)
|
await store.delete_device(deviceID)
|
||||||
|
|
||||||
connection.send_result(msg["id"])
|
connection.send_result(msg["id"])
|
||||||
@ -96,28 +93,29 @@ async def async_setup_connection(hass):
|
|||||||
async def handle_reregister(hass, connection, msg):
|
async def handle_reregister(hass, connection, msg):
|
||||||
deviceID = msg["deviceID"]
|
deviceID = msg["deviceID"]
|
||||||
store = hass.data[DOMAIN]["store"]
|
store = hass.data[DOMAIN]["store"]
|
||||||
devices = hass.data[DOMAIN]["devices"]
|
|
||||||
|
|
||||||
data = msg["data"]
|
data = msg["data"]
|
||||||
del data["last_seen"]
|
del data["last_seen"]
|
||||||
device = {}
|
deviceSettings = {}
|
||||||
|
|
||||||
if "deviceID" in data:
|
if "deviceID" in data:
|
||||||
newDeviceID = data["deviceID"]
|
newDeviceID = data["deviceID"]
|
||||||
del data["deviceID"]
|
del data["deviceID"]
|
||||||
|
|
||||||
oldDevice = store.get_device(deviceID)
|
oldDeviceSettings = store.get_device(deviceID)
|
||||||
if oldDevice:
|
if oldDeviceSettings:
|
||||||
device = oldDevice.asdict()
|
deviceSettings = oldDeviceSettings.asdict()
|
||||||
await store.delete_device(deviceID)
|
await store.delete_device(deviceID)
|
||||||
|
|
||||||
if deviceID in devices:
|
deleteDevice(hass, deviceID)
|
||||||
devices[deviceID].delete(hass)
|
|
||||||
del devices[deviceID]
|
|
||||||
|
|
||||||
deviceID = newDeviceID
|
deviceID = newDeviceID
|
||||||
|
|
||||||
device.update(data)
|
if (dev := getDevice(hass, deviceID, create=False)) is not None:
|
||||||
await store.set_device(deviceID, **device)
|
dev.update_settings(hass, data)
|
||||||
|
|
||||||
|
deviceSettings.update(data)
|
||||||
|
await store.set_device(deviceID, **deviceSettings)
|
||||||
|
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
|
@ -9,29 +9,32 @@ from .sensor import BrowserSensor
|
|||||||
from .light import BrowserModLight
|
from .light import BrowserModLight
|
||||||
from .binary_sensor import BrowserBinarySensor
|
from .binary_sensor import BrowserBinarySensor
|
||||||
from .media_player import BrowserModPlayer
|
from .media_player import BrowserModPlayer
|
||||||
|
from .camera import BrowserModCamera
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
BROWSER_SENSORS = {
|
|
||||||
"battery_level", ()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BrowserModDevice:
|
class BrowserModDevice:
|
||||||
""" A Browser_mod device. """
|
"""A Browser_mod device."""
|
||||||
|
|
||||||
def __init__(self, hass, deviceID):
|
def __init__(self, hass, deviceID):
|
||||||
""" """
|
""" """
|
||||||
self.deviceID = deviceID
|
self.deviceID = deviceID
|
||||||
self.coordinator = Coordinator(hass, deviceID)
|
self.coordinator = Coordinator(hass, deviceID)
|
||||||
self.entities = []
|
self.entities = []
|
||||||
|
self.camera_entity = None
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.setup_sensors(hass)
|
self.setup_sensors(hass)
|
||||||
self.connection = None
|
self.connection = None
|
||||||
|
|
||||||
|
def update_settings(self, hass, settings):
|
||||||
|
if settings.get("camera", False) and self.camera_entity is None:
|
||||||
|
self.add_camera(hass)
|
||||||
|
elif self.camera_entity and not settings.get("camera", False):
|
||||||
|
self.remove_camera(hass)
|
||||||
|
|
||||||
def setup_sensors(self, hass):
|
def setup_sensors(self, hass):
|
||||||
""" Create all entities associated with the device. """
|
"""Create all entities associated with the device."""
|
||||||
|
|
||||||
coordinator = self.coordinator
|
coordinator = self.coordinator
|
||||||
deviceID = self.deviceID
|
deviceID = self.deviceID
|
||||||
@ -70,9 +73,30 @@ class BrowserModDevice:
|
|||||||
adder(new)
|
adder(new)
|
||||||
self.entities += new
|
self.entities += new
|
||||||
|
|
||||||
|
def add_camera(self, hass):
|
||||||
|
if self.camera_entity is not None:
|
||||||
|
return
|
||||||
|
coordinator = self.coordinator
|
||||||
|
deviceID = self.deviceID
|
||||||
|
adder = hass.data[DOMAIN][DATA_ADDERS]["camera"]
|
||||||
|
self.camera_entity = BrowserModCamera(coordinator, deviceID)
|
||||||
|
adder([self.camera_entity])
|
||||||
|
self.entities.append(self.camera_entity)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def remove_camera(self, hass):
|
||||||
|
if self.camera_entity is None:
|
||||||
|
return
|
||||||
|
er = entity_registry.async_get(hass)
|
||||||
|
er.async_remove(self.camera_entity.entity_id)
|
||||||
|
self.entities.remove(self.camera_entity)
|
||||||
|
self.camera_entity = None
|
||||||
|
pass
|
||||||
|
|
||||||
def send(self, command, **kwargs):
|
def send(self, command, **kwargs):
|
||||||
""" Send a command to this device. """
|
"""Send a command to this device."""
|
||||||
if self.connection is None: return
|
if self.connection is None:
|
||||||
|
return
|
||||||
|
|
||||||
connection, cid = self.connection
|
connection, cid = self.connection
|
||||||
|
|
||||||
@ -87,22 +111,35 @@ class BrowserModDevice:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def delete(self, hass):
|
def delete(self, hass):
|
||||||
""" Delete device and associated entities. """
|
"""Delete device 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)
|
||||||
|
|
||||||
for e in self.entities:
|
for e in self.entities:
|
||||||
er.async_remove(e.entity_id)
|
er.async_remove(e.entity_id)
|
||||||
|
|
||||||
|
self.entities = []
|
||||||
|
self.camera_entity = None
|
||||||
|
|
||||||
device = dr.async_get_device({(DOMAIN, self.deviceID)})
|
device = dr.async_get_device({(DOMAIN, self.deviceID)})
|
||||||
dr.async_remove_device(device.id)
|
dr.async_remove_device(device.id)
|
||||||
|
|
||||||
|
|
||||||
def getDevice(hass, deviceID):
|
def getDevice(hass, deviceID, *, create=True):
|
||||||
""" Get or create device by deviceID. """
|
"""Get or create device by deviceID."""
|
||||||
devices = hass.data[DOMAIN]["devices"]
|
devices = hass.data[DOMAIN]["devices"]
|
||||||
if deviceID in devices:
|
if deviceID in devices:
|
||||||
return devices[deviceID]
|
return devices[deviceID]
|
||||||
|
|
||||||
|
if not create:
|
||||||
|
return None
|
||||||
|
|
||||||
devices[deviceID] = BrowserModDevice(hass, deviceID)
|
devices[deviceID] = BrowserModDevice(hass, deviceID)
|
||||||
return devices[deviceID]
|
return devices[deviceID]
|
||||||
|
|
||||||
|
|
||||||
|
def deleteDevice(hass, deviceID):
|
||||||
|
devices = hass.data[DOMAIN]["devices"]
|
||||||
|
if deviceID in devices:
|
||||||
|
devices["deviceID"].delete()
|
||||||
|
del devices["deviceID"]
|
||||||
|
@ -72,8 +72,8 @@ def setup_platform(hass, config, async_add_devices, platform, cls):
|
|||||||
def is_setup_complete(hass):
|
def is_setup_complete(hass):
|
||||||
return hass.data[DOMAIN][DATA_SETUP_COMPLETE]
|
return hass.data[DOMAIN][DATA_SETUP_COMPLETE]
|
||||||
|
|
||||||
class BrowserModEntity2(CoordinatorEntity):
|
|
||||||
|
|
||||||
|
class BrowserModEntity(CoordinatorEntity):
|
||||||
def __init__(self, coordinator, deviceID, name):
|
def __init__(self, coordinator, deviceID, name):
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self.deviceID = deviceID
|
self.deviceID = deviceID
|
||||||
@ -101,55 +101,15 @@ class BrowserModEntity2(CoordinatorEntity):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_entity_name(self):
|
def has_entity_name(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entity_registry_visible_default(self):
|
def entity_registry_visible_default(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
return f"{self.deviceID}-{self._name.replace(' ','_')}"
|
return f"{self.deviceID}-{self._name.replace(' ','_')}"
|
||||||
|
|
||||||
|
|
||||||
class BrowserModEntity(Entity):
|
|
||||||
def __init__(self, hass, connection, deviceID, alias=None):
|
|
||||||
self.hass = hass
|
|
||||||
self.connection = connection
|
|
||||||
self.deviceID = deviceID
|
|
||||||
self._data = {}
|
|
||||||
self._alias = alias
|
|
||||||
prefix = hass.data[DOMAIN][DATA_CONFIG].get(CONFIG_PREFIX, "")
|
|
||||||
self.entity_id = async_generate_entity_id(
|
|
||||||
self.domain + ".{}", alias or f"{prefix}{deviceID}", hass=hass
|
|
||||||
)
|
|
||||||
|
|
||||||
def updated(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self):
|
|
||||||
return {
|
|
||||||
"identifiers": {(DOMAIN, self.deviceID)},
|
|
||||||
"name": self._alias or self.deviceID,
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
return f"{self.domain}-{self.deviceID}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def data(self):
|
|
||||||
return self._data
|
|
||||||
|
|
||||||
@data.setter
|
|
||||||
def data(self, data):
|
|
||||||
self._data = data
|
|
||||||
self.updated()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_id(self):
|
|
||||||
return self.deviceID
|
|
||||||
|
|
||||||
def send(self, command, **kwargs):
|
|
||||||
self.connection.send(command, **kwargs)
|
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
from homeassistant.components.light import LightEntity, ColorMode
|
from homeassistant.components.light import LightEntity, ColorMode
|
||||||
|
|
||||||
from .helpers import setup_platform, BrowserModEntity2
|
from .helpers import setup_platform, BrowserModEntity
|
||||||
from .const import DOMAIN, DATA_ADDERS
|
from .const import DOMAIN, DATA_ADDERS
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config_entry, async_add_entities, discoveryInfo = None):
|
async def async_setup_platform(
|
||||||
|
hass, config_entry, async_add_entities, discoveryInfo=None
|
||||||
|
):
|
||||||
hass.data[DOMAIN][DATA_ADDERS]["light"] = async_add_entities
|
hass.data[DOMAIN][DATA_ADDERS]["light"] = async_add_entities
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
await async_setup_platform(hass, {}, async_add_entities)
|
await async_setup_platform(hass, {}, async_add_entities)
|
||||||
|
|
||||||
|
|
||||||
class BrowserModLight(BrowserModEntity2, LightEntity):
|
class BrowserModLight(BrowserModEntity, LightEntity):
|
||||||
|
|
||||||
def __init__(self, coordinator, deviceID, device):
|
def __init__(self, coordinator, deviceID, device):
|
||||||
super().__init__(coordinator, deviceID, "Screen")
|
BrowserModEntity.__init__(self, coordinator, deviceID, "Screen")
|
||||||
|
LightEntity.__init__(self)
|
||||||
self.device = device
|
self.device = device
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -28,6 +31,7 @@ class BrowserModLight(BrowserModEntity2, LightEntity):
|
|||||||
@property
|
@property
|
||||||
def supported_color_modes(self):
|
def supported_color_modes(self):
|
||||||
return {ColorMode.BRIGHTNESS}
|
return {ColorMode.BRIGHTNESS}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color_mode(self):
|
def color_mode(self):
|
||||||
return ColorMode.BRIGHTNESS
|
return ColorMode.BRIGHTNESS
|
||||||
|
@ -25,27 +25,34 @@ from homeassistant.const import (
|
|||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .helpers import BrowserModEntity2
|
from .helpers import BrowserModEntity
|
||||||
from .const import DOMAIN, DATA_ADDERS
|
from .const import DOMAIN, DATA_ADDERS
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config_entry, async_add_entities, discoveryInfo = None):
|
async def async_setup_platform(
|
||||||
|
hass, config_entry, async_add_entities, discoveryInfo=None
|
||||||
|
):
|
||||||
hass.data[DOMAIN][DATA_ADDERS]["media_player"] = async_add_entities
|
hass.data[DOMAIN][DATA_ADDERS]["media_player"] = async_add_entities
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
await async_setup_platform(hass, {}, async_add_entities)
|
await async_setup_platform(hass, {}, async_add_entities)
|
||||||
|
|
||||||
|
|
||||||
class BrowserModPlayer(BrowserModEntity2, MediaPlayerEntity):
|
class BrowserModPlayer(BrowserModEntity, MediaPlayerEntity):
|
||||||
|
|
||||||
def __init__(self, coordinator, deviceID, device):
|
def __init__(self, coordinator, deviceID, device):
|
||||||
super().__init__(coordinator, deviceID, None)
|
BrowserModEntity.__init__(self, coordinator, deviceID, None)
|
||||||
|
MediaPlayerEntity.__init__(self)
|
||||||
self.device = device
|
self.device = device
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
return f"{self.deviceID}-player"
|
return f"{self.deviceID}-player"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def entity_registry_visible_default(self):
|
||||||
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
state = self._data.get("player", {}).get("state")
|
state = self._data.get("player", {}).get("state")
|
||||||
@ -76,7 +83,6 @@ class BrowserModPlayer(BrowserModEntity2, MediaPlayerEntity):
|
|||||||
def is_volume_muted(self):
|
def is_volume_muted(self):
|
||||||
return self._data.get("player", {}).get("muted", False)
|
return self._data.get("player", {}).get("muted", False)
|
||||||
|
|
||||||
|
|
||||||
def set_volume_level(self, volume):
|
def set_volume_level(self, volume):
|
||||||
self.device.send("player-set-volume", volume_level=volume)
|
self.device.send("player-set-volume", volume_level=volume)
|
||||||
|
|
||||||
@ -86,7 +92,9 @@ class BrowserModPlayer(BrowserModEntity2, MediaPlayerEntity):
|
|||||||
async def async_play_media(self, media_type, media_id, **kwargs):
|
async def async_play_media(self, media_type, media_id, **kwargs):
|
||||||
if media_source.is_media_source_id(media_id):
|
if media_source.is_media_source_id(media_id):
|
||||||
media_type = MEDIA_TYPE_URL
|
media_type = MEDIA_TYPE_URL
|
||||||
play_item = await media_source.async_resolve_media(self.hass, media_id, self.entity_id)
|
play_item = await media_source.async_resolve_media(
|
||||||
|
self.hass, media_id, self.entity_id
|
||||||
|
)
|
||||||
media_id = play_item.url
|
media_id = play_item.url
|
||||||
if media_type in (MEDIA_TYPE_URL, MEDIA_TYPE_MUSIC):
|
if media_type in (MEDIA_TYPE_URL, MEDIA_TYPE_MUSIC):
|
||||||
media_id = async_process_play_media_url(self.hass, media_id)
|
media_id = async_process_play_media_url(self.hass, media_id)
|
||||||
|
@ -1,22 +1,29 @@
|
|||||||
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 .helpers import BrowserModEntity2
|
from .helpers import BrowserModEntity
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config_entry, async_add_entities, discoveryInfo = None):
|
async def async_setup_platform(
|
||||||
|
hass, config_entry, async_add_entities, discoveryInfo=None
|
||||||
|
):
|
||||||
hass.data[DOMAIN][DATA_ADDERS]["sensor"] = async_add_entities
|
hass.data[DOMAIN][DATA_ADDERS]["sensor"] = async_add_entities
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
await async_setup_platform(hass, {}, async_add_entities)
|
await async_setup_platform(hass, {}, async_add_entities)
|
||||||
|
|
||||||
|
|
||||||
class BrowserSensor(BrowserModEntity2, SensorEntity):
|
class BrowserSensor(BrowserModEntity, SensorEntity):
|
||||||
def __init__(self, coordinator, deviceID, parameter,
|
def __init__(
|
||||||
name,
|
self,
|
||||||
unit_of_measurement = None,
|
coordinator,
|
||||||
device_class = None,
|
deviceID,
|
||||||
):
|
parameter,
|
||||||
|
name,
|
||||||
|
unit_of_measurement=None,
|
||||||
|
device_class=None,
|
||||||
|
):
|
||||||
super().__init__(coordinator, deviceID, name)
|
super().__init__(coordinator, deviceID, name)
|
||||||
self.parameter = parameter
|
self.parameter = parameter
|
||||||
self._device_class = device_class
|
self._device_class = device_class
|
||||||
@ -28,9 +35,11 @@ class BrowserSensor(BrowserModEntity2, SensorEntity):
|
|||||||
data = data.get("browser", {})
|
data = data.get("browser", {})
|
||||||
data = data.get(self.parameter, None)
|
data = data.get(self.parameter, None)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self):
|
||||||
return self._device_class
|
return self._device_class
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_unit_of_measurement(self):
|
def native_unit_of_measurement(self):
|
||||||
return self._unit_of_measurement
|
return self._unit_of_measurement
|
||||||
@ -41,8 +50,10 @@ class BrowserSensor(BrowserModEntity2, SensorEntity):
|
|||||||
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"] = self._data.get("browser", {}).get("path", "").split("/")
|
retval["pathSegments"] = (
|
||||||
|
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")
|
||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
@ -26,22 +26,28 @@ class DeviceStoreData:
|
|||||||
def asdict(self):
|
def asdict(self):
|
||||||
return attr.asdict(self)
|
return attr.asdict(self)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
class ConfigStoreData:
|
class ConfigStoreData:
|
||||||
devices = attr.ib(type=dict[str: DeviceStoreData], factory=dict)
|
devices = attr.ib(type=dict[str:DeviceStoreData], factory=dict)
|
||||||
version = attr.ib(type=str, default="2.0")
|
version = attr.ib(type=str, default="2.0")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data):
|
def from_dict(cls, data={}):
|
||||||
devices = {k: DeviceStoreData.from_dict(v) for k,v in data["devices"].items()}
|
devices = {k: DeviceStoreData.from_dict(v) for k, v in data["devices"].items()}
|
||||||
return cls(**(data | {
|
return cls(
|
||||||
"devices": devices,
|
**(
|
||||||
}
|
data
|
||||||
))
|
| {
|
||||||
|
"devices": devices,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def asdict(self):
|
def asdict(self):
|
||||||
return attr.asdict(self)
|
return attr.asdict(self)
|
||||||
|
|
||||||
|
|
||||||
class BrowserModStore:
|
class BrowserModStore:
|
||||||
def __init__(self, hass):
|
def __init__(self, hass):
|
||||||
self.store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
self.store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
||||||
@ -55,10 +61,12 @@ class BrowserModStore:
|
|||||||
self.dirty = False
|
self.dirty = False
|
||||||
|
|
||||||
async def load(self):
|
async def load(self):
|
||||||
self.data = ConfigStoreData.from_dict(await self.store.async_load())
|
stored = await self.store.async_load()
|
||||||
|
if stored:
|
||||||
|
self.data = ConfigStoreData.from_dict(stored)
|
||||||
if self.data is None:
|
if self.data is None:
|
||||||
self.data = ConfigStoreData()
|
self.data = ConfigStoreData()
|
||||||
self.save()
|
await self.save()
|
||||||
self.dirty = False
|
self.dirty = False
|
||||||
|
|
||||||
async def updated(self):
|
async def updated(self):
|
||||||
|
@ -17,6 +17,9 @@ loadDevTools().then(() => {
|
|||||||
changeDeviceID(ev) {
|
changeDeviceID(ev) {
|
||||||
window.browser_mod.deviceID = ev.target.value;
|
window.browser_mod.deviceID = ev.target.value;
|
||||||
}
|
}
|
||||||
|
toggleCameraEnabled() {
|
||||||
|
window.browser_mod.cameraEnabled = !window.browser_mod.cameraEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
unregister_device(ev) {
|
unregister_device(ev) {
|
||||||
const deviceID = ev.currentTarget.deviceID;
|
const deviceID = ev.currentTarget.deviceID;
|
||||||
@ -68,7 +71,13 @@ loadDevTools().then(() => {
|
|||||||
></ha-icon>
|
></ha-icon>
|
||||||
`}
|
`}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="card-content">Browser-mod not connected.</div>
|
<div class="card-content">
|
||||||
|
<p>Settings that apply to this browser.</p>
|
||||||
|
<p>
|
||||||
|
It is strongly recommended to refresh your browser window
|
||||||
|
after any change to those settings.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<ha-settings-row>
|
<ha-settings-row>
|
||||||
<span slot="heading">Enable</span>
|
<span slot="heading">Enable</span>
|
||||||
@ -99,7 +108,10 @@ loadDevTools().then(() => {
|
|||||||
>Get camera input from this device (hardware
|
>Get camera input from this device (hardware
|
||||||
dependent)</span
|
dependent)</span
|
||||||
>
|
>
|
||||||
<ha-switch> </ha-switch>
|
<ha-switch
|
||||||
|
.checked=${window.browser_mod?.cameraEnabled}
|
||||||
|
@change=${this.toggleCameraEnabled}
|
||||||
|
></ha-switch>
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
@ -116,7 +128,10 @@ loadDevTools().then(() => {
|
|||||||
.datetime=${window.browser_mod.devices[d].last_seen}
|
.datetime=${window.browser_mod.devices[d].last_seen}
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
</span>
|
</span>
|
||||||
<ha-icon-button .deviceID=${d} @click=${this.unregister_device}>
|
<ha-icon-button
|
||||||
|
.deviceID=${d}
|
||||||
|
@click=${this.unregister_device}
|
||||||
|
>
|
||||||
<ha-icon .icon=${"mdi:delete"}></ha-icon>
|
<ha-icon .icon=${"mdi:delete"}></ha-icon>
|
||||||
</ha-icon-button>
|
</ha-icon-button>
|
||||||
<ha-icon-button>
|
<ha-icon-button>
|
||||||
|
@ -1,63 +1,72 @@
|
|||||||
export const BrowserModCameraMixin = (C) =>
|
export const CameraMixin = (SuperClass) => {
|
||||||
class extends C {
|
return class CameraMixinClass extends SuperClass {
|
||||||
setup_camera() {
|
private _video;
|
||||||
console.log("Starting camera");
|
private _canvas;
|
||||||
|
private _framerate;
|
||||||
|
|
||||||
if (this._video) return;
|
constructor() {
|
||||||
this._video = document.createElement("video");
|
super();
|
||||||
this._video.autoplay = true;
|
this._framerate = 2;
|
||||||
this._video.playsInline = true;
|
|
||||||
this._video.style.display = "none";
|
|
||||||
|
|
||||||
this._canvas = document.createElement("canvas");
|
|
||||||
this._canvas.style.display = "none";
|
|
||||||
|
|
||||||
document.body.appendChild(this._video);
|
|
||||||
document.body.appendChild(this._canvas);
|
|
||||||
|
|
||||||
if (!navigator.mediaDevices) return;
|
|
||||||
|
|
||||||
console.log("Starting devices");
|
|
||||||
navigator.mediaDevices
|
|
||||||
.getUserMedia({ video: true, audio: false })
|
|
||||||
.then((stream) => {
|
|
||||||
this._video.srcObject = stream;
|
|
||||||
this._video.play();
|
|
||||||
this.update_camera();
|
|
||||||
});
|
|
||||||
|
|
||||||
this._camera_framerate = 2;
|
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"click",
|
"pointerdown",
|
||||||
() => {
|
() => {
|
||||||
if (this._video.ended || this._video.paused) this._video.play();
|
this._setup_camera();
|
||||||
},
|
},
|
||||||
{
|
{ once: true }
|
||||||
once: true,
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
update_camera() {
|
async _setup_camera() {
|
||||||
this._canvas.width = this._video.videoWidth;
|
if (this._video) return;
|
||||||
this._canvas.height = this._video.videoHeight;
|
await this.connectionPromise;
|
||||||
|
if (!this.cameraEnabled) return;
|
||||||
|
const video = (this._video = document.createElement("video"));
|
||||||
|
video.autoplay = true;
|
||||||
|
video.playsInline = true;
|
||||||
|
video.style.display = "none";
|
||||||
|
|
||||||
|
const canvas = (this._canvas = document.createElement("canvas"));
|
||||||
|
canvas.style.display = "none";
|
||||||
|
|
||||||
|
document.body.appendChild(video);
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
|
||||||
|
if (!navigator.mediaDevices) return;
|
||||||
|
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
video: true,
|
||||||
|
audio: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
video.srcObject = stream;
|
||||||
|
video.play();
|
||||||
|
this.update_camera();
|
||||||
|
}
|
||||||
|
|
||||||
|
async update_camera() {
|
||||||
|
if (!this.cameraEnabled) {
|
||||||
|
const stream = this._video?.srcObject;
|
||||||
|
if (stream) {
|
||||||
|
stream.getTracks().forEach((t) => t.stop());
|
||||||
|
this._video.scrObject = undefined;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const video = this._video;
|
||||||
|
const width = video.videoWidth;
|
||||||
|
const height = video.videoHeight;
|
||||||
|
this._canvas.width = width;
|
||||||
|
this._canvas.height = height;
|
||||||
const context = this._canvas.getContext("2d");
|
const context = this._canvas.getContext("2d");
|
||||||
context.drawImage(
|
context.drawImage(video, 0, 0, width, height);
|
||||||
this._video,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
this._video.videoWidth,
|
|
||||||
this._video.videoHeight
|
|
||||||
);
|
|
||||||
|
|
||||||
this.sendUpdate({
|
this.sendUpdate({
|
||||||
camera: this._canvas.toDataURL("image/jpeg"),
|
camera: this._canvas.toDataURL("image/jpeg"),
|
||||||
});
|
});
|
||||||
setTimeout(
|
|
||||||
() => this.update_camera(),
|
const interval = Math.round(1000 / this._framerate);
|
||||||
Math.round(1000 / this._camera_framerate)
|
setTimeout(() => this.update_camera(), interval);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
@ -103,21 +103,32 @@ export const ConnectionMixin = (SuperClass) => {
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async _reregister(newData = {}) {
|
||||||
|
await this.connection.sendMessage({
|
||||||
|
type: "browser_mod/reregister",
|
||||||
|
deviceID: this.deviceID,
|
||||||
|
data: {
|
||||||
|
...this.devices[this.deviceID],
|
||||||
|
...newData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get meta() {
|
get meta() {
|
||||||
if (!this.registered) return null;
|
if (!this.registered) return null;
|
||||||
return this.devices[this.deviceID].meta;
|
return this.devices[this.deviceID].meta;
|
||||||
}
|
}
|
||||||
set meta(value) {
|
set meta(value) {
|
||||||
(async () => {
|
this._reregister({ meta: value });
|
||||||
await this.connection.sendMessage({
|
}
|
||||||
type: "browser_mod/reregister",
|
|
||||||
deviceID: this.deviceID,
|
get cameraEnabled() {
|
||||||
data: {
|
if (!this.registered) return null;
|
||||||
...this.devices[this.deviceID],
|
return this.devices[this.deviceID].camera;
|
||||||
meta: value,
|
}
|
||||||
}
|
set cameraEnabled(value) {
|
||||||
})
|
this._reregister({ camera: value });
|
||||||
})()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendUpdate(data) {
|
sendUpdate(data) {
|
||||||
|
@ -9,8 +9,8 @@ import "./browser-player";
|
|||||||
import { ConnectionMixin } from "./connection";
|
import { ConnectionMixin } from "./connection";
|
||||||
import { ScreenSaverMixin } from "./screensaver";
|
import { ScreenSaverMixin } from "./screensaver";
|
||||||
import { MediaPlayerMixin } from "./mediaPlayer";
|
import { MediaPlayerMixin } from "./mediaPlayer";
|
||||||
|
import { CameraMixin } from "./camera";
|
||||||
import { FullyKioskMixin } from "./fullyKiosk";
|
import { FullyKioskMixin } from "./fullyKiosk";
|
||||||
import { BrowserModCameraMixin } from "./camera";
|
|
||||||
import { BrowserModScreensaverMixin } from "./screensaver";
|
import { BrowserModScreensaverMixin } from "./screensaver";
|
||||||
import { BrowserModPopupsMixin } from "./popups";
|
import { BrowserModPopupsMixin } from "./popups";
|
||||||
import { BrowserModBrowserMixin } from "./browser";
|
import { BrowserModBrowserMixin } from "./browser";
|
||||||
@ -27,7 +27,9 @@ const ext = (baseClass, mixins) =>
|
|||||||
// FullyKioskMixin,
|
// FullyKioskMixin,
|
||||||
// BrowserModMediaPlayerMixin,
|
// BrowserModMediaPlayerMixin,
|
||||||
// ]) {
|
// ]) {
|
||||||
export class BrowserMod extends MediaPlayerMixin(ScreenSaverMixin(ConnectionMixin(EventTarget))) {
|
export class BrowserMod extends CameraMixin(
|
||||||
|
MediaPlayerMixin(ScreenSaverMixin(ConnectionMixin(EventTarget)))
|
||||||
|
) {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.entity_id = deviceID.replace("-", "_");
|
this.entity_id = deviceID.replace("-", "_");
|
||||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "browser_mod",
|
"name": "browser_mod",
|
||||||
"version": "1.5.3",
|
"version": "2.0.0b0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -25,4 +25,4 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"card-tools": "github:thomasloven/lovelace-card-tools"
|
"card-tools": "github:thomasloven/lovelace-card-tools"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user