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) => { const CameraMixin = (SuperClass) => {
return class CameraMixinClass extends SuperClass { return class CameraMixinClass extends SuperClass {
// TODO: Enable WebRTC?
// https://levelup.gitconnected.com/establishing-the-webrtc-connection-videochat-with-javascript-step-3-48d4ae0e9ea4
constructor() { constructor() {
super(); super();
this._framerate = 2; 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 name = "browser_mod";
var version = "2.0.0b0"; var version = "2.0.0b0";
var description = ""; var description = "";
@ -1088,7 +1130,7 @@ var pjson = {
// FullyKioskMixin, // FullyKioskMixin,
// BrowserModMediaPlayerMixin, // BrowserModMediaPlayerMixin,
// ]) { // ]) {
class BrowserMod extends CameraMixin(MediaPlayerMixin(ScreenSaverMixin(RequireInteractMixin(ConnectionMixin(EventTarget))))) { class BrowserMod extends BrowserStateMixin(CameraMixin(MediaPlayerMixin(ScreenSaverMixin(RequireInteractMixin(ConnectionMixin(EventTarget)))))) {
constructor() { constructor() {
super(); super();
this.entity_id = deviceID.replace("-", "_"); this.entity_id = deviceID.replace("-", "_");

View File

@ -35,6 +35,5 @@ class BrowserModCamera(BrowserModEntity, Camera):
def camera_image(self, width=None, height=None): def camera_image(self, width=None, height=None):
if "camera" not in self._data: if "camera" not in self._data:
LOGGER.error(self._data)
return None return None
return base64.b64decode(self._data["camera"].split(",")[-1]) 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: if store.get_device(deviceID).enabled:
dev = getDevice(hass, deviceID) dev = getDevice(hass, deviceID)
dev.data.update(msg.get("data", {})) dev.update(hass, msg.get("data", {}))
dev.coordinator.async_set_updated_data(dev.data)
async_register_command(hass, handle_connect) async_register_command(hass, handle_connect)
async_register_command(hass, handle_register) async_register_command(hass, handle_register)

View File

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

View File

@ -22,10 +22,14 @@ class BrowserModEntity(CoordinatorEntity):
@property @property
def device_info(self): def device_info(self):
config_url = {}
if ip := self._data.get("browser", {}).get("ip_address"):
config_url = {"configuration_url": f"http://{ip}:2323"}
return { return {
"identifiers": {(DOMAIN, self.deviceID)}, "identifiers": {(DOMAIN, self.deviceID)},
"name": self.deviceID, "name": self.deviceID,
"manufacturer": "Browser Mod", "manufacturer": "Browser Mod",
**config_url,
} }
@property @property

View File

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

View File

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

View File

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