Browser state sensors and Visit device for FKB

This commit is contained in:
Thomas Lovén 2022-07-14 23:06:04 +00:00
parent 1109980d61
commit a46d2b3cb0
8 changed files with 126 additions and 79 deletions

View File

@ -937,6 +937,8 @@ const MediaPlayerMixin = (SuperClass) => {
const CameraMixin = (SuperClass) => {
return class CameraMixinClass extends SuperClass {
// TODO: Enable WebRTC?
// https://levelup.gitconnected.com/establishing-the-webrtc-connection-videochat-with-javascript-step-3-48d4ae0e9ea4
constructor() {
super();
this._framerate = 2;
@ -1041,6 +1043,46 @@ const RequireInteractMixin = (SuperClass) => {
};
};
const BrowserStateMixin = (SuperClass) => {
return class BrowserStateMixinClass extends SuperClass {
constructor() {
super();
document.addEventListener("visibilitychange", () => this._browser_state_update());
window.addEventListener("location-changed", () => this._browser_state_update());
this.connectionPromise.then(() => this._browser_state_update());
}
_browser_state_update() {
const update = async () => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
const battery = (_b = (_a = navigator).getBattery) === null || _b === void 0 ? void 0 : _b.call(_a);
this.sendUpdate({
browser: {
path: window.location.pathname,
visibility: document.visibilityState,
userAgent: navigator.userAgent,
currentUser: (_d = (_c = this.hass) === null || _c === void 0 ? void 0 : _c.user) === null || _d === void 0 ? void 0 : _d.name,
fullyKiosk: this.isFully || false,
width: window.innerWidth,
height: window.innerHeight,
battery_level: (_f = (_e = window.fully) === null || _e === void 0 ? void 0 : _e.getBatteryLevel()) !== null && _f !== void 0 ? _f : (battery === null || battery === void 0 ? void 0 : battery.level) * 100,
charging: (_h = (_g = window.fully) === null || _g === void 0 ? void 0 : _g.isPlugged()) !== null && _h !== void 0 ? _h : battery === null || battery === void 0 ? void 0 : battery.charging,
darkMode: (_k = (_j = this.hass) === null || _j === void 0 ? void 0 : _j.themes) === null || _k === void 0 ? void 0 : _k.darkMode,
userData: (_l = this.hass) === null || _l === void 0 ? void 0 : _l.user,
ip_address: (_m = window.fully) === null || _m === void 0 ? void 0 : _m.getIp4Address(),
},
});
};
update();
}
do_navigate(path) {
if (!path)
return;
history.pushState(null, "", path);
fireEvent("location-changed", {}, ha_element());
}
};
};
var name = "browser_mod";
var version = "2.0.0b0";
var description = "";
@ -1088,7 +1130,7 @@ var pjson = {
// FullyKioskMixin,
// BrowserModMediaPlayerMixin,
// ]) {
class BrowserMod extends CameraMixin(MediaPlayerMixin(ScreenSaverMixin(RequireInteractMixin(ConnectionMixin(EventTarget))))) {
class BrowserMod extends BrowserStateMixin(CameraMixin(MediaPlayerMixin(ScreenSaverMixin(RequireInteractMixin(ConnectionMixin(EventTarget)))))) {
constructor() {
super();
this.entity_id = deviceID.replace("-", "_");

View File

@ -35,6 +35,5 @@ class BrowserModCamera(BrowserModEntity, Camera):
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])

View File

@ -128,8 +128,7 @@ async def async_setup_connection(hass):
if store.get_device(deviceID).enabled:
dev = getDevice(hass, deviceID)
dev.data.update(msg.get("data", {}))
dev.coordinator.async_set_updated_data(dev.data)
dev.update(hass, msg.get("data", {}))
async_register_command(hass, handle_connect)
async_register_command(hass, handle_register)

View File

@ -21,77 +21,74 @@ class BrowserModDevice:
""" """
self.deviceID = deviceID
self.coordinator = Coordinator(hass, deviceID)
self.entities = []
self.camera_entity = None
self.entities = {}
self.data = {}
self.setup_sensors(hass)
self.settings = {}
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)
self.update_entities(hass)
def setup_sensors(self, hass):
def update(self, hass, newData):
self.data.update(newData)
self.update_entities(hass)
self.coordinator.async_set_updated_data(self.data)
def update_settings(self, hass, settings):
self.settings = settings
self.update_entities(hass)
def update_entities(self, hass):
"""Create all entities associated with the device."""
coordinator = self.coordinator
deviceID = self.deviceID
sensors = [
("battery_level", "Browser battery", "%", "battery"),
("path", "Browser path"),
("userAgent", "Browser userAgent"),
("visibility", "Browser visibility"),
("currentUser", "Browser user"),
("height", "Browser height", "px"),
("width", "Browser width", "px"),
]
adder = hass.data[DOMAIN][DATA_ADDERS]["sensor"]
new = [BrowserSensor(coordinator, deviceID, *s) for s in sensors]
adder(new)
self.entities += new
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)
adder([new])
self.entities[name] = new
binary_sensors = [
("charging", "Browser charging"),
("darkMode", "Browser dark mode"),
("fullyKiosk", "Browser FullyKiosk"),
]
adder = hass.data[DOMAIN][DATA_ADDERS]["binary_sensor"]
new = [BrowserBinarySensor(coordinator, deviceID, *s) for s in binary_sensors]
adder(new)
self.entities += new
_assert_browser_sensor("sensor", "path", "Browser path")
_assert_browser_sensor("sensor", "visibility", "Browser visibility")
_assert_browser_sensor("sensor", "userAgent", "Browser userAgent")
_assert_browser_sensor("sensor", "currentUser", "Browser user")
_assert_browser_sensor("sensor", "width", "Browser width", "px")
_assert_browser_sensor("sensor", "height", "Browser height", "px")
if self.data.get("browser", {}).get("battery_level", None) is not None:
_assert_browser_sensor(
"sensor", "battery_level", "Browser battery", "%", "battery"
)
_assert_browser_sensor("binary_sensor", "darkMode", "Browser dark mode")
_assert_browser_sensor("binary_sensor", "fullyKiosk", "Browser FullyKiosk")
if self.data.get("browser", {}).get("charging", None) is not None:
_assert_browser_sensor("binary_sensor", "charging", "Browser charging")
if "screen" not in self.entities:
adder = hass.data[DOMAIN][DATA_ADDERS]["light"]
new = [BrowserModLight(coordinator, deviceID, self)]
adder(new)
self.entities += new
new = BrowserModLight(coordinator, deviceID, 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)]
adder(new)
self.entities += new
new = BrowserModPlayer(coordinator, deviceID, self)
adder([new])
self.entities["player"] = new
def add_camera(self, hass):
if self.camera_entity is not None:
return
coordinator = self.coordinator
deviceID = self.deviceID
if "camera" not in self.entities and self.settings.get("camera"):
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
new = BrowserModCamera(coordinator, deviceID)
adder([new])
self.entities["camera"] = new
if "camera" in self.entities and not self.settings.get("camera"):
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
er.async_remove(self.entities["camera"].entity_id)
del self.entities["camera"]
def send(self, command, **kwargs):
"""Send a command to this device."""
@ -115,11 +112,10 @@ class BrowserModDevice:
dr = device_registry.async_get(hass)
er = entity_registry.async_get(hass)
for e in self.entities:
for e in self.entities.items():
er.async_remove(e.entity_id)
self.entities = []
self.camera_entity = None
self.entities = {}
device = dr.async_get_device({(DOMAIN, self.deviceID)})
dr.async_remove_device(device.id)

View File

@ -22,10 +22,14 @@ class BrowserModEntity(CoordinatorEntity):
@property
def device_info(self):
config_url = {}
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,
"manufacturer": "Browser Mod",
**config_url,
}
@property

View File

@ -1,20 +1,21 @@
import { fireEvent } from "card-tools/src/event";
import { ha_element } from "card-tools/src/hass";
export const BrowserModBrowserMixin = (C) =>
class extends C {
export const BrowserStateMixin = (SuperClass) => {
return class BrowserStateMixinClass extends SuperClass {
constructor() {
super();
document.addEventListener("visibilitychange", () => this.sensor_update());
window.addEventListener("location-changed", () => this.sensor_update());
this.addEventListener("browser-mod-connected", () =>
this.sensor_update()
document.addEventListener("visibilitychange", () =>
this._browser_state_update()
);
// window.setInterval(() => this.sensor_update(), 10000);
window.addEventListener("location-changed", () =>
this._browser_state_update()
);
this.connectionPromise.then(() => this._browser_state_update());
}
sensor_update() {
_browser_state_update() {
const update = async () => {
const battery = (<any>navigator).getBattery?.();
this.sendUpdate({
@ -23,7 +24,7 @@ export const BrowserModBrowserMixin = (C) =>
visibility: document.visibilityState,
userAgent: navigator.userAgent,
currentUser: this.hass?.user?.name,
fullyKiosk: this.isFully,
fullyKiosk: this.isFully || false,
width: window.innerWidth,
height: window.innerHeight,
battery_level:
@ -31,7 +32,7 @@ export const BrowserModBrowserMixin = (C) =>
charging: window.fully?.isPlugged() ?? battery?.charging,
darkMode: this.hass?.themes?.darkMode,
userData: this.hass?.user,
// config: this.config,
ip_address: window.fully?.getIp4Address(),
},
});
};
@ -44,3 +45,4 @@ export const BrowserModBrowserMixin = (C) =>
fireEvent("location-changed", {}, ha_element());
}
};
};

View File

@ -4,6 +4,9 @@ export const CameraMixin = (SuperClass) => {
private _canvas;
private _framerate;
// TODO: Enable WebRTC?
// https://levelup.gitconnected.com/establishing-the-webrtc-connection-videochat-with-javascript-step-3-48d4ae0e9ea4
constructor() {
super();
this._framerate = 2;

View File

@ -14,7 +14,7 @@ import { RequireInteractMixin } from "./require-interact";
import { FullyKioskMixin } from "./fullyKiosk";
import { BrowserModScreensaverMixin } from "./screensaver";
import { BrowserModPopupsMixin } from "./popups";
import { BrowserModBrowserMixin } from "./browser";
import { BrowserStateMixin } from "./browser";
import pjson from "../../package.json";
const ext = (baseClass, mixins) =>
@ -28,10 +28,12 @@ const ext = (baseClass, mixins) =>
// FullyKioskMixin,
// BrowserModMediaPlayerMixin,
// ]) {
export class BrowserMod extends CameraMixin(
export class BrowserMod extends BrowserStateMixin(
CameraMixin(
MediaPlayerMixin(
ScreenSaverMixin(RequireInteractMixin(ConnectionMixin(EventTarget)))
)
)
) {
constructor() {
super();