Add experimental camera support

This commit is contained in:
Thomas Lovén 2019-09-25 08:30:54 +02:00
parent a96667fb86
commit f13ac064ca
9 changed files with 336 additions and 64 deletions

View File

@ -3,7 +3,7 @@ browser\_mod
[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs)
A Home Assistant integration to turn your browser into a controllable entity - and also an audio player.
A Home Assistant integration to turn your browser into a controllable entity - and also an audio player and a security camera (WIP).
## Example uses
@ -61,12 +61,13 @@ This binds the *aliases* `arrakis` to `99980b13-dabc9563` and `dashboard` to `d2
Note: Aliases must be unique.
## Entities
Once `browser_mod` is installed, loading up your Home Assistant frontend on a new *device* will create three or four new devices.
Once `browser_mod` is installed, loading up your Home Assistant frontend on a new *device* will create three to five new devices.
- `sensor.<device>`
- `media_player.<device>`
- `light.<device>`
- If you're using Fully Kiosk Browser `binary_sensor.<device>`
- If you've enabled it: `camera.<device>`
- If you're using Fully Kiosk Browser: `binary_sensor.<device>`
`<device>` here will be the `deviceID` of the *device* but with the dash (`-`) replaced by an underscore (`_`). If you've defined an alias, it will be that instead.
@ -105,6 +106,24 @@ The `light` can be used to blackout the screen.
For Fully Kiosk Browser, the screen will actually turn off.
For other browsers, the interface will just be covered with black (the screen is still on, will have a visible glow in the dark, and you won't save any battery).
### camera (EXPERIMENTAL)
For security and UX reasons, the camera must be enabled manually on a device by device basis.
Enabling the camera is done by adding `camera: true` to the devices configuration in `configuration.yaml`:
```yaml
browser_mod:
devices:
99980b13-dabc9563:
name: arrakis
camera: true
d2fc860c-16379d23:
name: dashboard
```
After restarting Home Assistant (and [clearing cache](https://github.com/thomasloven/hass-config/wiki/Lovelace-Plugins#clearing-cache)), the next time you load your interface your browser will ask you if you want Home Assistant to be able to access your camera. Some browsers (e.g. mobile Safari) will ask every time you make a hard refresh.
Be aware that keeping the camera on may make your device run hot and drain your battery.
### binary\_sensor
The `binary_sensor` will only be available for Fully Kiosk Browser PRO *devices*.

View File

@ -3,7 +3,7 @@ import logging
from .mod_view import setup_view
from .connection import setup_connection
from .service import setup_service
from .const import DOMAIN, DATA_DEVICES, DATA_ALIASES, DATA_ADDERS, CONFIG_DEVICES
from .const import DOMAIN, DATA_DEVICES, DATA_ALIASES, DATA_ADDERS, CONFIG_DEVICES, DATA_CONFIG
_LOGGER = logging.getLogger(__name__)
@ -22,12 +22,14 @@ async def async_setup(hass, config):
DATA_DEVICES: {},
DATA_ALIASES: aliases,
DATA_ADDERS: {},
DATA_CONFIG: config[DOMAIN].get(CONFIG_DEVICES, {}),
}
await hass.helpers.discovery.async_load_platform("media_player", DOMAIN, {}, config)
await hass.helpers.discovery.async_load_platform("sensor", DOMAIN, {}, config)
await hass.helpers.discovery.async_load_platform("binary_sensor", DOMAIN, {}, config)
await hass.helpers.discovery.async_load_platform("light", DOMAIN, {}, config)
await hass.helpers.discovery.async_load_platform("camera", DOMAIN, {}, config)
await setup_connection(hass, config)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,36 @@
import logging
from datetime import datetime
import base64
from homeassistant.const import STATE_UNAVAILABLE, STATE_ON, STATE_OFF, STATE_IDLE
from homeassistant.components.camera import Camera
from .helpers import setup_platform, BrowserModEntity
PLATFORM = 'camera'
async def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
return setup_platform(hass, config, async_add_devices, PLATFORM, BrowserModCamera)
class BrowserModCamera(Camera, BrowserModEntity):
domain = PLATFORM
def __init__(self, hass, connection, deviceID, alias=None):
Camera.__init__(self)
BrowserModEntity.__init__(self, hass, connection, deviceID, alias)
self.last_seen = None
def updated(self):
self.last_seen = datetime.now()
self.schedule_update_ha_state()
def camera_image(self):
return base64.b64decode(self.data.split(',')[1])
@property
def device_state_attributes(self):
return {
"type": "browser_mod",
"deviceID": self.deviceID,
"last_seen": self.last_seen,
}

View File

@ -4,8 +4,8 @@ import voluptuous as vol
from homeassistant.components.websocket_api import websocket_command, result_message, event_message, async_register_command
from homeassistant.helpers.entity import Entity, async_generate_entity_id
from .const import DOMAIN, WS_CONNECT, WS_UPDATE
from .helpers import get_devices, create_entity
from .const import DOMAIN, WS_CONNECT, WS_UPDATE, WS_CAMERA
from .helpers import get_devices, create_entity, get_config
_LOGGER = logging.getLogger(__name__)
@ -38,7 +38,6 @@ async def setup_connection(hass, config):
async_register_command(hass, handle_connect)
async_register_command(hass, handle_update)
class BrowserModConnection:
def __init__(self, hass, deviceID):
self.hass = hass
@ -49,10 +48,11 @@ class BrowserModConnection:
self.screen = None
self.sensor = None
self.fully = None
self.camera = None
def connect(self, connection, cid):
self.connection.append((connection, cid))
self.send("update")
self.send("update", **get_config(self.hass, self.deviceID))
def disconnect():
self.connection.remove((connection, cid))
@ -100,3 +100,11 @@ class BrowserModConnection:
self)
self.fully.data = data.get('fully')
if data.get('camera'):
self.camera = self.camera or create_entity(
self.hass,
'camera',
self.deviceID,
self)
self.camera.data = data.get('camera')

View File

@ -7,9 +7,11 @@ DATA_EXTRA_MODULE_URL = 'frontend_extra_module_url'
DATA_DEVICES = "devices"
DATA_ALIASES = "aliases"
DATA_ADDERS = "adders"
DATA_CONFIG = "config"
CONFIG_DEVICES = "devices"
WS_ROOT = DOMAIN
WS_CONNECT = "{}/connect".format(WS_ROOT)
WS_UPDATE = "{}/update".format(WS_ROOT)
WS_CAMERA = "{}/camera".format(WS_ROOT)

View File

@ -2,7 +2,7 @@ import logging
from homeassistant.helpers.entity import Entity, async_generate_entity_id
from .const import DOMAIN, DATA_DEVICES, DATA_ALIASES, DATA_ADDERS, CONFIG_DEVICES
from .const import DOMAIN, DATA_DEVICES, DATA_ALIASES, DATA_ADDERS, CONFIG_DEVICES, DATA_CONFIG
_LOGGER = logging.getLogger(__name__)
@ -15,6 +15,10 @@ def get_alias(hass, deviceID):
return k
return None
def get_config(hass, deviceID):
config = hass.data[DOMAIN][DATA_CONFIG]
return config.get(deviceID, config.get(deviceID.replace('-','_'), {}))
def create_entity(hass, platform, deviceID, connection):
adder = hass.data[DOMAIN][DATA_ADDERS][platform]
entity = adder(hass, deviceID, connection, get_alias(hass, deviceID))

View File

@ -37,6 +37,8 @@ class BrowserModLight(Light, BrowserModEntity):
def device_state_attributes(self):
return {
"type": "browser_mod",
"deviceID": self.deviceID,
"last_seen": self.last_seen,
}
@property

View File

@ -27,6 +27,7 @@ class BrowserMod {
}
playOnce(ev) {
if(this._video) this._video.play();
if(window.browser_mod.playedOnce) return;
window.browser_mod.player.play();
window.browser_mod.playedOnce = true;
@ -297,12 +298,57 @@ class BrowserMod {
}
start_camera() {
if(this._video) return;
this._video = document.createElement("video");
this._video.autoplay = true;
this._video.playsInline = true;
this._video.style.cssText = `
visibility: hidden;
width: 0;
height: 0;
`;
this._canvas = document.createElement("canvas");
this._canvas.style.cssText = `
visibility: hidden;
width: 0;
height: 0;
`;
document.body.appendChild(this._canvas);
document.body.appendChild(this._video);
navigator.mediaDevices.getUserMedia({video: true, audio: false}).then((stream) => {
this._video.srcObject = stream;
this._video.play();
this.send_cam();
});
}
send_cam(data) {
const context = this._canvas.getContext('2d');
context.drawImage(this._video, 0, 0, this._canvas.width, this._canvas.height);
this.conn.sendMessage({
type: 'browser_mod/update',
deviceID: deviceID,
data: {
camera: this._canvas.toDataURL('image/png'),
},
});
setTimeout(this.send_cam.bind(this), 5000);
}
update(msg=null) {
if(!this.conn) return;
if(msg)
if(msg.entity_id)
if(msg) {
if(msg.entity_id) {
this.entity_id = msg.entity_id;
}
if(msg.camera) {
this.start_camera();
}
}
this.conn.sendMessage({
type: 'browser_mod/update',