diff --git a/README.md b/README.md index 2705350..2df2ecc 100644 --- a/README.md +++ b/README.md @@ -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.` - `media_player.` - `light.` -- If you're using Fully Kiosk Browser `binary_sensor.` +- If you've enabled it: `camera.` +- If you're using Fully Kiosk Browser: `binary_sensor.` `` 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*. diff --git a/custom_components/browser_mod/__init__.py b/custom_components/browser_mod/__init__.py index 17b2918..9b62ed0 100644 --- a/custom_components/browser_mod/__init__.py +++ b/custom_components/browser_mod/__init__.py @@ -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) diff --git a/custom_components/browser_mod/browser_mod.js b/custom_components/browser_mod/browser_mod.js index d61f661..7d9f2e0 100644 --- a/custom_components/browser_mod/browser_mod.js +++ b/custom_components/browser_mod/browser_mod.js @@ -1,56 +1,209 @@ -!function(e){var t={};function i(o){if(t[o])return t[o].exports;var n=t[o]={i:o,l:!1,exports:{}};return e[o].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.m=e,i.c=t,i.d=function(e,t,o){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(o,n,function(t){return e[t]}.bind(null,n));return o},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=0)}([function(e,t,i){"use strict";i.r(t);let o=function(){if(window.fully&&"function"==typeof fully.getDeviceId)return fully.getDeviceId();if(!localStorage["lovelace-player-device-id"]){const e=()=>Math.floor(1e5*(1+Math.random())).toString(16).substring(1);localStorage["lovelace-player-device-id"]=`${e()}${e()}-${e()}${e()}`}return localStorage["lovelace-player-device-id"]}();function n(){return document.querySelector("home-assistant").hass}function s(e){return document.querySelector("home-assistant").provideHass(e)}function r(){if(customElements.get("hui-view"))return!0;const e=document.createElement("partial-panel-resolver");e.hass=n(),e.route={path:"/lovelace/"};try{document.querySelector("home-assistant").appendChild(e).catch(e=>{})}catch(t){document.querySelector("home-assistant").removeChild(e)}return!!customElements.get("hui-view")}function a(e,t,i=null){if((e=new Event(e,{bubbles:!0,cancelable:!1,composed:!0})).detail=t||{},i)i.dispatchEvent(e);else{var o=document.querySelector("home-assistant");(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=(o=o&&o.shadowRoot)&&o.querySelector("home-assistant-main"))&&o.shadowRoot)&&o.querySelector("app-drawer-layout partial-panel-resolver"))&&o.shadowRoot||o)&&o.querySelector("ha-panel-lovelace"))&&o.shadowRoot)&&o.querySelector("hui-root"))&&o.shadowRoot)&&o.querySelector("ha-app-layout #view"))&&o.firstElementChild)&&o.dispatchEvent(e)}}const l="custom:";function c(e,t){const i=document.createElement("hui-error-card");return i.setConfig({type:"error",error:e,config:t}),i}function u(e,t){if(!t||"object"!=typeof t||!t.type)return c(`No ${e} type configured`,t);let i=t.type;if(i=i.startsWith(l)?i.substr(l.length):`hui-${i}-${e}`,customElements.get(i))return function(e,t){const i=document.createElement(e);try{i.setConfig(t)}catch(e){return c(e,t)}return i}(i,t);const o=c(`Custom element doesn't exist: ${i}.`,t);o.style.display="None";const n=setTimeout(()=>{o.style.display=""},2e3);return customElements.whenDefined(i).then(()=>{clearTimeout(n),a("ll-rebuild",{},o)}),o}function d(e,t=!1){a("hass-more-info",{entityId:e},document.querySelector("home-assistant"));const i=document.querySelector("home-assistant")._moreInfoEl;return i.large=t,i}const h=customElements.get("home-assistant-main")?Object.getPrototypeOf(customElements.get("home-assistant-main")):Object.getPrototypeOf(customElements.get("hui-view")),m=h.prototype.html,p=h.prototype.css;class y extends h{static get properties(){return{hass:{},config:{}}}setConfig(e){this._config=e,this.el?this.el.setConfig(e):this.el=this.create(e),this._hass&&(this.el.hass=this._hass),this.noHass&&s(this)}set config(e){this.setConfig(e)}set hass(e){this._hass=e,this.el&&(this.el.hass=e)}createRenderRoot(){return this}render(){return m`${this.el}`}}if(!customElements.get("card-maker")){class e extends y{create(e){return function(e){return u("card",e)}(e)}}customElements.define("card-maker",e)}if(!customElements.get("element-maker")){class e extends y{create(e){return function(e){return u("element",e)}(e)}}customElements.define("element-maker",e)}if(!customElements.get("entity-row-maker")){class e extends y{create(e){return function(e){const t=new Set(["call-service","divider","section","weblink"]);if(!e)return c("Invalid configuration given.",e);if("string"==typeof e&&(e={entity:e}),"object"!=typeof e||!e.entity&&!e.type)return c("Invalid configuration given.",e);const i=e.type||"default";if(t.has(i)||i.startsWith(l))return u("row",e);const o=e.entity.split(".",1)[0];return Object.assign(e,{type:{alert:"toggle",automation:"toggle",climate:"climate",cover:"cover",fan:"toggle",group:"group",input_boolean:"toggle",input_number:"input-number",input_select:"input-select",input_text:"input-text",light:"toggle",lock:"lock",media_player:"media-player",remote:"toggle",scene:"scene",script:"script",sensor:"sensor",timer:"timer",switch:"toggle",vacuum:"toggle",water_heater:"climate",input_datetime:"input-datetime"}[o]||"text"}),u("entity-row",e)}(e)}}customElements.define("entity-row-maker",e)}function v(e,t,i=!1,o=null,s=!1){d(Object.keys(n().states)[0]);const r=document.createElement("card-maker");r.noHass=!0,r.config=t;const a=document.createElement("div");a.innerHTML="\n \n ";const l=document.createElement("app-toolbar");l.innerHTML=`\n \n
\n ${e}\n
\n `;const c=document.createElement("div");c.classList.add("scrollable"),c.appendChild(r),s||a.appendChild(l),a.appendChild(c);const u=document.querySelector("home-assistant")._moreInfoEl;u.sizingTarget=c,u.large=i,u._page="none",u.shadowRoot.appendChild(a);let h={};if(o)for(var m in o)h[m]=u.style[m],u.style.setProperty(m,o[m]);return setTimeout(()=>{let e=setInterval(()=>{if(u.getAttribute("aria-hidden"))for(var t in clearInterval(e),a.parentNode.removeChild(a),h)h[t]?u.style.setProperty(t,h[t]):u.style.removeProperty(t)},100)},1e3),u}customElements.define("browser-player",class extends h{static get properties(){return{hass:{}}}setConfig(e){this._config=e}handleMute(e){window.browser_mod.mute({})}handleVolumeChange(e){const t=parseFloat(e.target.value);window.browser_mod.set_volume({volume_level:t})}handleMoreInfo(e){d(window.browser_mod.entity_id)}handlePlayPause(e){window.browser_mod.player.paused?window.browser_mod.play({}):window.browser_mod.pause({})}render(){const e=window.browser_mod.player;return m` - -
- - +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = "./js/main.js"); +/******/ }) +/************************************************************************/ +/******/ ({ - ${"stopped"===window.browser_mod.player_state?m`
`:m` - - `} - -
+/***/ "../../../card-tools/card-maker.js": +/*!*********************************!*\ + !*** /card-tools/card-maker.js ***! + \*********************************/ +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -
- ${o} -
+"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _lit_element_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./lit-element.js */ \"../../../card-tools/lit-element.js\");\n/* harmony import */ var _lovelace_element_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./lovelace-element.js */ \"../../../card-tools/lovelace-element.js\");\n/* harmony import */ var _hass_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./hass.js */ \"../../../card-tools/hass.js\");\n\n\n\n\nclass ThingMaker extends _lit_element_js__WEBPACK_IMPORTED_MODULE_0__[\"LitElement\"] {\n static get properties() {\n return {\n 'hass': {},\n 'config': {},\n };\n }\n setConfig(config) {\n this._config = config;\n if(!this.el)\n this.el = this.create(config);\n else\n this.el.setConfig(config);\n if(this._hass) this.el.hass = this._hass;\n if(this.noHass) Object(_hass_js__WEBPACK_IMPORTED_MODULE_2__[\"provideHass\"])(this);\n }\n set config(config) {\n this.setConfig(config);\n }\n set hass(hass) {\n this._hass = hass;\n if(this.el) this.el.hass = hass;\n }\n\n createRenderRoot() {\n return this;\n }\n render() {\n return _lit_element_js__WEBPACK_IMPORTED_MODULE_0__[\"html\"]`${this.el}`;\n }\n}\n\nif(!customElements.get(\"card-maker\")) {\n class CardMaker extends ThingMaker {\n create(config) {\n return Object(_lovelace_element_js__WEBPACK_IMPORTED_MODULE_1__[\"createCard\"])(config);\n }\n }\n customElements.define(\"card-maker\", CardMaker);\n}\n\nif(!customElements.get(\"element-maker\")) {\n class ElementMaker extends ThingMaker {\n create(config) {\n return Object(_lovelace_element_js__WEBPACK_IMPORTED_MODULE_1__[\"createElement\"])(config);\n }\n }\n customElements.define(\"element-maker\", ElementMaker);\n}\n\nif(!customElements.get(\"entity-row-maker\")) {\n class EntityRowMaker extends ThingMaker {\n create(config) {\n return Object(_lovelace_element_js__WEBPACK_IMPORTED_MODULE_1__[\"createEntityRow\"])(config);\n }\n }\n customElements.define(\"entity-row-maker\", EntityRowMaker);\n}\n\n\n//# sourceURL=webpack:////card-tools/card-maker.js?"); -
- `}static get styles(){return p` - paper-icon-button[highlight] { - color: var(--accent-color); - } - .card-content { - display: flex; - justify-content: center; - } - .placeholder { - width: 24px; - padding: 8px; - } - .device-id { - opacity: 0.7; - font-size: xx-small; - margin-top: -10px; - user-select: all; - -webkit-user-select: all; - -moz-user-select: all; - -ms-user-select: all; - } - `}});window.browser_mod=new class{set hass(e){if(!e)return;if(this._hass=e,this.hassPatched)return;const t=e.callService;e.callService=(e,i,n)=>{if(n&&n.deviceID){const e=n.deviceID.indexOf("this");-1!==e&&(n.deviceID[e]=o)}return t(e,i,n)},this.hassPatched=!0,document.querySelector("home-assistant").hassChanged(e,e)}playOnce(e){window.browser_mod.playedOnce||(window.browser_mod.player.play(),window.browser_mod.playedOnce=!0)}constructor(){window.setTimeout(r,500),window.hassConnection.then(e=>this.connect(e.conn)),this.player=new Audio,this.playedOnce=!1,this.autoclose_popup_active=!1;const e=this.update.bind(this);this.player.addEventListener("ended",e),this.player.addEventListener("play",e),this.player.addEventListener("pause",e),this.player.addEventListener("volumechange",e),document.addEventListener("visibilitychange",e),window.addEventListener("location-changed",e),window.addEventListener("click",this.playOnce),window.addEventListener("mousemove",this.no_blackout.bind(this)),window.addEventListener("mousedown",this.no_blackout.bind(this)),window.addEventListener("keydown",this.no_blackout.bind(this)),window.addEventListener("touchstart",this.no_blackout.bind(this)),s(this),window.fully&&(this._fullyMotion=!1,this._motionTimeout=void 0,fully.bind("screenOn","browser_mod.update();"),fully.bind("screenOff","browser_mod.update();"),fully.bind("pluggedAC","browser_mod.update();"),fully.bind("pluggedUSB","browser_mod.update();"),fully.bind("onBatteryLevelChanged","browser_mod.update();"),fully.bind("unplugged","browser_mod.update();"),fully.bind("networkReconnect","browser_mod.update();"),fully.bind("onMotion","browser_mod.fullyMotion();")),this._screenSaver=void 0,this._screenSaverTimer=void 0,this._screenSaverTime=0,this._blackout=document.createElement("div"),this._blackout.style.cssText="\n position: fixed;\n left: 0;\n top: 0;\n padding: 0;\n margin: 0;\n width: 100%;\n height: 100%;\n background: black;\n visibility: hidden;\n ",document.body.appendChild(this._blackout)}connect(e){this.conn=e,e.subscribeMessage(e=>this.callback(e),{type:"browser_mod/connect",deviceID:o})}callback(e){switch(e.command){case"update":this.update(e);break;case"debug":this.debug(e);break;case"play":this.play(e);break;case"pause":this.pause(e);break;case"stop":this.stop(e);break;case"set_volume":this.set_volume(e);break;case"mute":this.mute(e);break;case"popup":this.popup(e);break;case"close-popup":this.close_popup(e);break;case"navigate":this.navigate(e);break;case"more-info":this.more_info(e);break;case"set-theme":this.set_theme(e);break;case"lovelace-reload":this.lovelace_reload(e);break;case"blackout":this.blackout(e);break;case"no-blackout":this.no_blackout(e)}}get player_state(){return this.player.src?this.player.ended?"stopped":this.player.paused?"paused":"playing":"stopped"}debug(e){v("deviceID",{type:"markdown",content:`# ${o}`}),alert(o)}play(e){const t=e.media_content_id;t&&(this.player.src=t),this.player.play()}pause(e){this.player.pause()}stop(e){this.player.pause(),this.player.src=null}set_volume(e){void 0!==e.volume_level&&(this.player.volume=e.volume_level)}mute(e){void 0===e.mute&&(e.mute=!this.player.muted),this.player.muted=Boolean(e.mute)}popup(e){if(!e.title&&!e.auto_close)return;if(!e.card)return;const t=()=>{v(e.title,e.card,e.large,e.style,e.auto_close),e.auto_close&&(this.autoclose_popup_active=!0)};if(e.auto_close&&e.time){if(e.time=parseInt(e.time),-1==e.time)return clearTimeout(this._screenSaverTimer),void(this._screenSaverTime=0);this._screenSaverTime=1e3*e.time,this._screenSaver=t,this._screenSaverTimer=setTimeout(this._screenSaver,this._screenSaverTime)}else t()}close_popup(e){this.autoclose_popup_active=!1,function(){const e=document.querySelector("home-assistant")._moreInfoEl;e&&e.close()}(),this._screenSaverTime&&(this._screenSaverTimer=setTimeout(this._screenSaver,this._screenSaverTime))}navigate(e){e.navigation_path&&(history.pushState(null,"",e.navigation_path),a("location-changed",{},document.querySelector("home-assistant")))}more_info(e){e.entity_id&&d(e.entity_id,e.large)}set_theme(e){e.theme||(e.theme="default"),a("settheme",e.theme,document.querySelector("home-assistant"))}lovelace_reload(e){const t=i=(i=(i=(i=(i=(i=(i=(i=(i=(i=(i=(i=document.querySelector("home-assistant"))&&i.shadowRoot)&&i.querySelector("home-assistant-main"))&&i.shadowRoot)&&i.querySelector("app-drawer-layout partial-panel-resolver"))&&i.shadowRoot||i)&&i.querySelector("ha-panel-lovelace"))&&i.shadowRoot)&&i.querySelector("hui-root"))&&i.shadowRoot)&&i.querySelector("ha-app-layout #view"))&&i.firstElementChild;var i;t&&a("config-refresh",{},t)}blackout(e){const t=()=>{window.fully?fully.turnScreenOff():this._blackout.style.visibility="visible",this.update()};if(e.time){if(e.time=parseInt(e.time),-1==e.time)return clearTimeout(this._screenSaverTimer),void(this._screenSaverTime=0);this._screenSaverTime=1e3*e.time,this._screenSaver=t,this._screenSaverTimer=setTimeout(this._screenSaver,this._screenSaverTime)}else t()}no_blackout(e){if(clearTimeout(this._screenSaverTimer),this.autoclose_popup_active)return this.close_popup();window.fully?(fully.getScreenOn()||fully.turnScreenOn(),e.brightness&&fully.setScreenBrightness(e.brightness),this.update()):"hidden"!==this._blackout.style.visibility&&(this._blackout.style.visibility="hidden",this.update()),this._screenSaverTime&&(this._screenSaverTimer=setTimeout(this._screenSaver,this._screenSaverTime))}is_blackout(){return window.fully?!fully.getScreenOn():Boolean("visible"===this._blackout.style.visibility)}fullyMotion(){this._fullyMotion=!0,clearTimeout(this._motionTimeout),this._motionTimeout=setTimeout(()=>{this._fullyMotion=!1,this.update()},5e3),this.update()}update(e=null){this.conn&&(e&&e.entity_id&&(this.entity_id=e.entity_id),this.conn.sendMessage({type:"browser_mod/update",deviceID:o,data:{browser:{path:window.location.pathname,visibility:document.visibilityState,userAgent:navigator.userAgent,currentUser:this._hass&&this._hass.user&&this._hass.user.name,fullyKiosk:!!window.fully||void 0,width:window.innerWidth,height:window.innerHeight},player:{volume:this.player.volume,muted:this.player.muted,src:this.player.src,state:this.player_state},screen:{blackout:this.is_blackout(),brightness:window.fully?fully.getScreenBrightness():void 0},fully:window.fully?{battery:window.fully?fully.getBatteryLevel():void 0,charging:window.fully?fully.isPlugged():void 0,motion:window.fully?this._fullyMotion:void 0}:void 0}}))}}}]); \ No newline at end of file +/***/ }), + +/***/ "../../../card-tools/deviceId.js": +/*!*******************************!*\ + !*** /card-tools/deviceId.js ***! + \*******************************/ +/*! exports provided: deviceID */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"deviceID\", function() { return deviceID; });\nfunction _deviceID() {\n const ID_STORAGE_KEY = 'lovelace-player-device-id';\n if(window['fully'] && typeof fully.getDeviceId === \"function\")\n return fully.getDeviceId();\n if(!localStorage[ID_STORAGE_KEY])\n {\n const s4 = () => {\n return Math.floor((1+Math.random())*100000).toString(16).substring(1);\n }\n localStorage[ID_STORAGE_KEY] = `${s4()}${s4()}-${s4()}${s4()}`;\n }\n return localStorage[ID_STORAGE_KEY];\n};\n\nlet deviceID = _deviceID();\n\n\n//# sourceURL=webpack:////card-tools/deviceId.js?"); + +/***/ }), + +/***/ "../../../card-tools/event.js": +/*!****************************!*\ + !*** /card-tools/event.js ***! + \****************************/ +/*! exports provided: fireEvent */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"fireEvent\", function() { return fireEvent; });\nfunction fireEvent(ev, detail, entity=null) {\n ev = new Event(ev, {\n bubbles: true,\n cancelable: false,\n composed: true,\n });\n ev.detail = detail || {};\n if(entity) {\n entity.dispatchEvent(ev);\n } else {\n var root = document.querySelector(\"home-assistant\");\n root = root && root.shadowRoot;\n root = root && root.querySelector(\"home-assistant-main\");\n root = root && root.shadowRoot;\n root = root && root.querySelector(\"app-drawer-layout partial-panel-resolver\");\n root = root && root.shadowRoot || root;\n root = root && root.querySelector(\"ha-panel-lovelace\");\n root = root && root.shadowRoot;\n root = root && root.querySelector(\"hui-root\");\n root = root && root.shadowRoot;\n root = root && root.querySelector(\"ha-app-layout #view\");\n root = root && root.firstElementChild;\n if (root) root.dispatchEvent(ev);\n }\n}\n\n\n//# sourceURL=webpack:////card-tools/event.js?"); + +/***/ }), + +/***/ "../../../card-tools/hass.js": +/*!***************************!*\ + !*** /card-tools/hass.js ***! + \***************************/ +/*! exports provided: hass, provideHass, lovelace, lovelace_view, load_lovelace */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"hass\", function() { return hass; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"provideHass\", function() { return provideHass; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"lovelace\", function() { return lovelace; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"lovelace_view\", function() { return lovelace_view; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"load_lovelace\", function() { return load_lovelace; });\nfunction hass() {\n return document.querySelector('home-assistant').hass\n};\n\nfunction provideHass(element) {\n return document.querySelector(\"home-assistant\").provideHass(element);\n}\n\nfunction lovelace() {\n var root = document.querySelector(\"home-assistant\");\n root = root && root.shadowRoot;\n root = root && root.querySelector(\"home-assistant-main\");\n root = root && root.shadowRoot;\n root = root && root.querySelector(\"app-drawer-layout partial-panel-resolver\");\n root = root && root.shadowRoot || root;\n root = root && root.querySelector(\"ha-panel-lovelace\")\n root = root && root.shadowRoot;\n root = root && root.querySelector(\"hui-root\")\n if (root) {\n var ll = root.lovelace\n ll.current_view = root.___curView;\n return ll;\n }\n return null;\n}\n\nfunction lovelace_view() {\n var root = document.querySelector(\"home-assistant\");\n root = root && root.shadowRoot;\n root = root && root.querySelector(\"home-assistant-main\");\n root = root && root.shadowRoot;\n root = root && root.querySelector(\"app-drawer-layout partial-panel-resolver\");\n root = root && root.shadowRoot || root;\n root = root && root.querySelector(\"ha-panel-lovelace\");\n root = root && root.shadowRoot;\n root = root && root.querySelector(\"hui-root\");\n root = root && root.shadowRoot;\n root = root && root.querySelector(\"ha-app-layout #view\");\n root = root && root.firstElementChild;\n return root;\n}\n\nfunction load_lovelace() {\n if(customElements.get(\"hui-view\")) return true;\n\n const res = document.createElement(\"partial-panel-resolver\");\n res.hass = hass();\n res.route = {path: \"/lovelace/\"};\n // res._updateRoutes();\n try {\n document.querySelector(\"home-assistant\").appendChild(res).catch((error) => {});\n } catch (error) {\n document.querySelector(\"home-assistant\").removeChild(res);\n }\n if(customElements.get(\"hui-view\")) return true;\n return false;\n\n}\n\n\n//# sourceURL=webpack:////card-tools/hass.js?"); + +/***/ }), + +/***/ "../../../card-tools/lit-element.js": +/*!**********************************!*\ + !*** /card-tools/lit-element.js ***! + \**********************************/ +/*! exports provided: LitElement, html, css */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"LitElement\", function() { return LitElement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"html\", function() { return html; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"css\", function() { return css; });\nconst LitElement = customElements.get('home-assistant-main') ? Object.getPrototypeOf(customElements.get('home-assistant-main')) : Object.getPrototypeOf(customElements.get('hui-view'));\n\nconst html = LitElement.prototype.html;\n\nconst css = LitElement.prototype.css;\n\n\n//# sourceURL=webpack:////card-tools/lit-element.js?"); + +/***/ }), + +/***/ "../../../card-tools/lovelace-element.js": +/*!***************************************!*\ + !*** /card-tools/lovelace-element.js ***! + \***************************************/ +/*! exports provided: CUSTOM_TYPE_PREFIX, DOMAINS_HIDE_MORE_INFO, createCard, createElement, createEntityRow */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"CUSTOM_TYPE_PREFIX\", function() { return CUSTOM_TYPE_PREFIX; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"DOMAINS_HIDE_MORE_INFO\", function() { return DOMAINS_HIDE_MORE_INFO; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"createCard\", function() { return createCard; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"createElement\", function() { return createElement; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"createEntityRow\", function() { return createEntityRow; });\n/* harmony import */ var _event_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./event.js */ \"../../../card-tools/event.js\");\n\n\nconst CUSTOM_TYPE_PREFIX = \"custom:\";\n\nconst DOMAINS_HIDE_MORE_INFO = [\n \"input_number\",\n \"input_select\",\n \"input_text\",\n \"scene\",\n \"weblink\",\n];\n\nfunction errorElement(error, config) {\n const el = document.createElement(\"hui-error-card\");\n el.setConfig({\n type: \"error\",\n error,\n config,\n });\n return el;\n}\n\nfunction _createElement(tag, config) {\n const el = document.createElement(tag);\n try {\n el.setConfig(config);\n } catch (err) {\n return errorElement(err, config);\n }\n return el;\n}\n\nfunction createLovelaceElement(thing, config) {\n if(!config || typeof config !== \"object\" || !config.type)\n return errorElement(`No ${thing} type configured`, config);\n\n let tag = config.type;\n if(tag.startsWith(CUSTOM_TYPE_PREFIX))\n tag = tag.substr(CUSTOM_TYPE_PREFIX.length);\n else\n tag = `hui-${tag}-${thing}`;\n\n if(customElements.get(tag))\n return _createElement(tag, config);\n\n const el = errorElement(`Custom element doesn't exist: ${tag}.`, config);\n el.style.display = \"None\";\n\n const timer = setTimeout(() => {\n el.style.display = \"\";\n }, 2000);\n\n customElements.whenDefined(tag).then(() => {\n clearTimeout(timer);\n Object(_event_js__WEBPACK_IMPORTED_MODULE_0__[\"fireEvent\"])(\"ll-rebuild\", {}, el);\n });\n\n return el;\n}\n\nfunction createCard(config) {\n return createLovelaceElement('card', config);\n}\nfunction createElement(config) {\n return createLovelaceElement('element', config);\n}\nfunction createEntityRow(config) {\n const SPECIAL_TYPES = new Set([\n \"call-service\",\n \"divider\",\n \"section\",\n \"weblink\",\n ]);\n const DEFAULT_ROWS = {\n alert: \"toggle\",\n automation: \"toggle\",\n climate: \"climate\",\n cover: \"cover\",\n fan: \"toggle\",\n group: \"group\",\n input_boolean: \"toggle\",\n input_number: \"input-number\",\n input_select: \"input-select\",\n input_text: \"input-text\",\n light: \"toggle\",\n lock: \"lock\",\n media_player: \"media-player\",\n remote: \"toggle\",\n scene: \"scene\",\n script: \"script\",\n sensor: \"sensor\",\n timer: \"timer\",\n switch: \"toggle\",\n vacuum: \"toggle\",\n water_heater: \"climate\",\n input_datetime: \"input-datetime\",\n };\n\n if(!config)\n return errorElement(\"Invalid configuration given.\", config);\n if(typeof config === \"string\")\n config = {entity: config};\n if(typeof config !== \"object\" || (!config.entity && !config.type))\n return errorElement(\"Invalid configuration given.\", config);\n\n const type = config.type || \"default\";\n if(SPECIAL_TYPES.has(type) || type.startsWith(CUSTOM_TYPE_PREFIX))\n return createLovelaceElement('row', config);\n\n const domain = config.entity.split(\".\", 1)[0];\n Object.assign(config, {type: DEFAULT_ROWS[domain] || \"text\"});\n\n return createLovelaceElement('entity-row', config);\n}\n\n\n//# sourceURL=webpack:////card-tools/lovelace-element.js?"); + +/***/ }), + +/***/ "../../../card-tools/more-info.js": +/*!********************************!*\ + !*** /card-tools/more-info.js ***! + \********************************/ +/*! exports provided: moreInfo */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"moreInfo\", function() { return moreInfo; });\n/* harmony import */ var _event_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./event.js */ \"../../../card-tools/event.js\");\n\n\nfunction moreInfo(entity, large=false) {\n Object(_event_js__WEBPACK_IMPORTED_MODULE_0__[\"fireEvent\"])(\"hass-more-info\", {entityId: entity}, document.querySelector(\"home-assistant\"));\n const el = document.querySelector(\"home-assistant\")._moreInfoEl;\n el.large = large;\n return el;\n}\n\n\n//# sourceURL=webpack:////card-tools/more-info.js?"); + +/***/ }), + +/***/ "../../../card-tools/popup.js": +/*!****************************!*\ + !*** /card-tools/popup.js ***! + \****************************/ +/*! exports provided: closePopUp, popUp */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"closePopUp\", function() { return closePopUp; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"popUp\", function() { return popUp; });\n/* harmony import */ var _hass_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./hass.js */ \"../../../card-tools/hass.js\");\n/* harmony import */ var _event_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./event.js */ \"../../../card-tools/event.js\");\n/* harmony import */ var _lovelace_element_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./lovelace-element.js */ \"../../../card-tools/lovelace-element.js\");\n/* harmony import */ var _more_info_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./more-info.js */ \"../../../card-tools/more-info.js\");\n/* harmony import */ var _card_maker_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./card-maker.js */ \"../../../card-tools/card-maker.js\");\n\n\n\n\n\n\nfunction closePopUp() {\n const moreInfoEl = document.querySelector(\"home-assistant\")._moreInfoEl;\n if(moreInfoEl)\n moreInfoEl.close();\n}\n\nfunction popUp(title, card, large=false, style=null, fullscreen=false) {\n\n const dummy_entity = Object.keys(Object(_hass_js__WEBPACK_IMPORTED_MODULE_0__[\"hass\"])().states)[0];\n Object(_more_info_js__WEBPACK_IMPORTED_MODULE_3__[\"moreInfo\"])(dummy_entity);\n\n const content = document.createElement(\"card-maker\")\n content.noHass = true;\n content.config = card;\n\n const wrapper = document.createElement(\"div\");\n wrapper.innerHTML = `\n \n `;\n\n const header = document.createElement(\"app-toolbar\");\n header.innerHTML = `\n \n
\n ${title}\n
\n `;\n\n const scroll = document.createElement(\"div\");\n scroll.classList.add('scrollable');\n scroll.appendChild(content);\n\n if(!fullscreen) {\n wrapper.appendChild(header);\n }\n wrapper.appendChild(scroll);\n\n const moreInfoEl = document.querySelector(\"home-assistant\")._moreInfoEl;\n moreInfoEl.sizingTarget = scroll;\n moreInfoEl.large = large;\n moreInfoEl._page = \"none\";\n moreInfoEl.shadowRoot.appendChild(wrapper);\n\n let oldStyle = {};\n if(style) {\n for (var k in style) {\n oldStyle[k] = moreInfoEl.style[k];\n moreInfoEl.style.setProperty(k, style[k]);\n }\n }\n\n setTimeout(() => {\n let interval = setInterval(() => {\n if (moreInfoEl.getAttribute(\"aria-hidden\")) {\n clearInterval(interval);\n wrapper.parentNode.removeChild(wrapper);\n for (var k in oldStyle)\n if (oldStyle[k])\n moreInfoEl.style.setProperty(k, oldStyle[k]);\n else\n moreInfoEl.style.removeProperty(k);\n }\n }, 100);\n }, 1000);\n return moreInfoEl;\n}\n\n\n//# sourceURL=webpack:////card-tools/popup.js?"); + +/***/ }), + +/***/ "./js/browser-player.js": +/*!******************************!*\ + !*** ./js/browser-player.js ***! + \******************************/ +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _card_tools_lit_element__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! /card-tools/lit-element */ \"../../../card-tools/lit-element.js\");\n/* harmony import */ var _card_tools_deviceId__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! /card-tools/deviceId */ \"../../../card-tools/deviceId.js\");\n/* harmony import */ var _card_tools_more_info__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! /card-tools/more-info */ \"../../../card-tools/more-info.js\");\n\n\n\n\nclass BrowserPlayer extends _card_tools_lit_element__WEBPACK_IMPORTED_MODULE_0__[\"LitElement\"] {\n\n static get properties() {\n return {\n hass: {},\n };\n }\n\n setConfig(config) {\n this._config = config;\n }\n handleMute(ev) {\n window.browser_mod.mute({});\n }\n handleVolumeChange(ev) {\n const vol = parseFloat(ev.target.value);\n window.browser_mod.set_volume({volume_level: vol});\n }\n handleMoreInfo(ev) {\n Object(_card_tools_more_info__WEBPACK_IMPORTED_MODULE_2__[\"moreInfo\"])(window.browser_mod.entity_id);\n }\n handlePlayPause(ev) {\n if (window.browser_mod.player.paused)\n window.browser_mod.play({});\n else\n window.browser_mod.pause({});\n }\n\n render() {\n const player = window.browser_mod.player;\n return _card_tools_lit_element__WEBPACK_IMPORTED_MODULE_0__[\"html\"]`\n \n
\n \n \n\n ${window.browser_mod.player_state === \"stopped\"\n ? _card_tools_lit_element__WEBPACK_IMPORTED_MODULE_0__[\"html\"]`
`\n : _card_tools_lit_element__WEBPACK_IMPORTED_MODULE_0__[\"html\"]`\n \n `}\n \n
\n\n
\n ${_card_tools_deviceId__WEBPACK_IMPORTED_MODULE_1__[\"deviceID\"]}\n
\n\n
\n `;\n }\n\n static get styles() {\n return _card_tools_lit_element__WEBPACK_IMPORTED_MODULE_0__[\"css\"]`\n paper-icon-button[highlight] {\n color: var(--accent-color);\n }\n .card-content {\n display: flex;\n justify-content: center;\n }\n .placeholder {\n width: 24px;\n padding: 8px;\n }\n .device-id {\n opacity: 0.7;\n font-size: xx-small;\n margin-top: -10px;\n user-select: all;\n -webkit-user-select: all;\n -moz-user-select: all;\n -ms-user-select: all;\n }\n `\n }\n\n}\n\ncustomElements.define(\"browser-player\", BrowserPlayer);\n\n\n//# sourceURL=webpack:///./js/browser-player.js?"); + +/***/ }), + +/***/ "./js/main.js": +/*!********************!*\ + !*** ./js/main.js ***! + \********************/ +/*! no exports provided */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _card_tools_deviceId__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! /card-tools/deviceId */ \"../../../card-tools/deviceId.js\");\n/* harmony import */ var _card_tools_hass__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! /card-tools/hass */ \"../../../card-tools/hass.js\");\n/* harmony import */ var _card_tools_popup__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! /card-tools/popup */ \"../../../card-tools/popup.js\");\n/* harmony import */ var _card_tools_event__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! /card-tools/event */ \"../../../card-tools/event.js\");\n/* harmony import */ var _card_tools_more_info_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! /card-tools/more-info.js */ \"../../../card-tools/more-info.js\");\n/* harmony import */ var _browser_player__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./browser-player */ \"./js/browser-player.js\");\n\n\n\n\n\n\n\nclass BrowserMod {\n\n set hass(hass) {\n if(!hass) return;\n this._hass = hass;\n if(this.hassPatched) return;\n const callService = hass.callService;\n const newCallService = (domain, service, serviceData) => {\n if(serviceData && serviceData.deviceID) {\n const index = serviceData.deviceID.indexOf('this');\n if(index !== -1)\n serviceData.deviceID[index] = _card_tools_deviceId__WEBPACK_IMPORTED_MODULE_0__[\"deviceID\"];\n }\n return callService(domain, service, serviceData);\n };\n hass.callService = newCallService;\n\n this.hassPatched = true;\n document.querySelector(\"home-assistant\").hassChanged(hass, hass);\n }\n\n playOnce(ev) {\n if(this._video) this._video.play();\n if(window.browser_mod.playedOnce) return;\n window.browser_mod.player.play();\n window.browser_mod.playedOnce = true;\n }\n\n constructor() {\n window.setTimeout(_card_tools_hass__WEBPACK_IMPORTED_MODULE_1__[\"load_lovelace\"], 500);\n window.hassConnection.then((conn) => this.connect(conn.conn));\n this.player = new Audio();\n this.playedOnce = false;\n\n this.autoclose_popup_active = false;\n\n const updater = this.update.bind(this);\n this.player.addEventListener(\"ended\", updater);\n this.player.addEventListener(\"play\", updater);\n this.player.addEventListener(\"pause\", updater);\n this.player.addEventListener(\"volumechange\", updater);\n document.addEventListener(\"visibilitychange\", updater);\n window.addEventListener(\"location-changed\", updater);\n window.addEventListener(\"click\", this.playOnce);\n window.addEventListener(\"mousemove\", this.no_blackout.bind(this));\n window.addEventListener(\"mousedown\", this.no_blackout.bind(this));\n window.addEventListener(\"keydown\", this.no_blackout.bind(this));\n window.addEventListener(\"touchstart\", this.no_blackout.bind(this));\n Object(_card_tools_hass__WEBPACK_IMPORTED_MODULE_1__[\"provideHass\"])(this);\n\n if(window.fully)\n {\n this._fullyMotion = false;\n this._motionTimeout = undefined;\n fully.bind('screenOn', 'browser_mod.update();');\n fully.bind('screenOff', 'browser_mod.update();');\n fully.bind('pluggedAC', 'browser_mod.update();');\n fully.bind('pluggedUSB', 'browser_mod.update();');\n fully.bind('onBatteryLevelChanged', 'browser_mod.update();');\n fully.bind('unplugged', 'browser_mod.update();');\n fully.bind('networkReconnect', 'browser_mod.update();');\n\n fully.bind('onMotion', 'browser_mod.fullyMotion();');\n }\n\n this._screenSaver = undefined;\n this._screenSaverTimer = undefined;\n this._screenSaverTime = 0;\n this._blackout = document.createElement(\"div\");\n this._blackout.style.cssText = `\n position: fixed;\n left: 0;\n top: 0;\n padding: 0;\n margin: 0;\n width: 100%;\n height: 100%;\n background: black;\n visibility: hidden;\n `;\n document.body.appendChild(this._blackout);\n }\n\n connect(conn) {\n this.conn = conn\n conn.subscribeMessage((msg) => this.callback(msg), {\n type: 'browser_mod/connect',\n deviceID: _card_tools_deviceId__WEBPACK_IMPORTED_MODULE_0__[\"deviceID\"],\n });\n }\n\n callback(msg) {\n switch (msg.command) {\n case \"update\":\n this.update(msg);\n break;\n\n case \"debug\":\n this.debug(msg);\n break;\n\n case \"play\":\n this.play(msg);\n break;\n case \"pause\":\n this.pause(msg);\n break;\n case \"stop\":\n this.stop(msg);\n break;\n case \"set_volume\":\n this.set_volume(msg);\n break;\n case \"mute\":\n this.mute(msg);\n break;\n\n case \"popup\":\n this.popup(msg);\n break;\n case \"close-popup\":\n this.close_popup(msg);\n break;\n case \"navigate\":\n this.navigate(msg);\n break;\n case \"more-info\":\n this.more_info(msg);\n break;\n case \"set-theme\":\n this.set_theme(msg);\n break;\n\n case \"lovelace-reload\":\n this.lovelace_reload(msg);\n break;\n\n case \"blackout\":\n this.blackout(msg);\n break;\n case \"no-blackout\":\n this.no_blackout(msg);\n break;\n }\n }\n\n get player_state() {\n if (!this.player.src) return \"stopped\";\n if (this.player.ended) return \"stopped\";\n if (this.player.paused) return \"paused\";\n return \"playing\";\n }\n\n debug(msg) {\n Object(_card_tools_popup__WEBPACK_IMPORTED_MODULE_2__[\"popUp\"])(`deviceID`, {type: \"markdown\", content: `# ${_card_tools_deviceId__WEBPACK_IMPORTED_MODULE_0__[\"deviceID\"]}`})\n alert(_card_tools_deviceId__WEBPACK_IMPORTED_MODULE_0__[\"deviceID\"]);\n }\n\n play(msg) {\n const src = msg.media_content_id;\n if(src)\n this.player.src = src;\n this.player.play();\n }\n pause(msg) {\n this.player.pause();\n }\n stop(msg) {\n this.player.pause();\n this.player.src = null;\n }\n set_volume(msg) {\n if (msg.volume_level === undefined) return;\n this.player.volume = msg.volume_level;\n }\n mute(msg) {\n if (msg.mute === undefined)\n msg.mute = !this.player.muted;\n this.player.muted = Boolean(msg.mute)\n }\n\n popup(msg){\n if(!msg.title && !msg.auto_close) return;\n if(!msg.card) return;\n const fn = () => {\n Object(_card_tools_popup__WEBPACK_IMPORTED_MODULE_2__[\"popUp\"])(msg.title, msg.card, msg.large, msg.style, msg.auto_close);\n if(msg.auto_close)\n this.autoclose_popup_active = true;\n };\n if(msg.auto_close && msg.time) {\n msg.time = parseInt(msg.time)\n if(msg.time == -1) {\n clearTimeout(this._screenSaverTimer);\n this._screenSaverTime = 0;\n return;\n }\n this._screenSaverTime = msg.time * 1000;\n this._screenSaver = fn;\n this._screenSaverTimer = setTimeout(this._screenSaver, this._screenSaverTime)\n } else {\n fn();\n }\n }\n close_popup(msg){\n this.autoclose_popup_active = false;\n Object(_card_tools_popup__WEBPACK_IMPORTED_MODULE_2__[\"closePopUp\"])();\n if(this._screenSaverTime) {\n this._screenSaverTimer = setTimeout(this._screenSaver, this._screenSaverTime)\n }\n }\n navigate(msg){\n if(!msg.navigation_path) return;\n history.pushState(null, \"\", msg.navigation_path);\n Object(_card_tools_event__WEBPACK_IMPORTED_MODULE_3__[\"fireEvent\"])(\"location-changed\", {}, document.querySelector(\"home-assistant\"));\n }\n more_info(msg){\n if(!msg.entity_id) return;\n Object(_card_tools_more_info_js__WEBPACK_IMPORTED_MODULE_4__[\"moreInfo\"])(msg.entity_id, msg.large);\n }\n set_theme(msg){\n if(!msg.theme) msg.theme = \"default\";\n Object(_card_tools_event__WEBPACK_IMPORTED_MODULE_3__[\"fireEvent\"])(\"settheme\", msg.theme, document.querySelector(\"home-assistant\"));\n }\n\n lovelace_reload(msg) {\n const ll = Object(_card_tools_hass__WEBPACK_IMPORTED_MODULE_1__[\"lovelace_view\"])();\n if (ll)\n Object(_card_tools_event__WEBPACK_IMPORTED_MODULE_3__[\"fireEvent\"])(\"config-refresh\", {}, ll);\n }\n\n blackout(msg){\n const fn = () => {\n if (window.fully)\n {\n fully.turnScreenOff();\n } else {\n this._blackout.style.visibility = \"visible\";\n }\n this.update();\n };\n if(msg.time) {\n msg.time = parseInt(msg.time)\n if(msg.time == -1) {\n clearTimeout(this._screenSaverTimer);\n this._screenSaverTime = 0;\n return;\n }\n this._screenSaverTime = msg.time * 1000;\n this._screenSaver = fn;\n this._screenSaverTimer = setTimeout(this._screenSaver, this._screenSaverTime)\n } else {\n fn();\n }\n }\n no_blackout(msg){\n\n clearTimeout(this._screenSaverTimer);\n if(this.autoclose_popup_active)\n return this.close_popup();\n if (window.fully)\n {\n if (!fully.getScreenOn())\n fully.turnScreenOn();\n if (msg.brightness)\n fully.setScreenBrightness(msg.brightness);\n this.update();\n } else {\n if(this._blackout.style.visibility !== \"hidden\") {\n this._blackout.style.visibility = \"hidden\";\n this.update();\n }\n }\n if(this._screenSaverTime) {\n this._screenSaverTimer = setTimeout(this._screenSaver, this._screenSaverTime)\n }\n }\n is_blackout(){\n if (window.fully)\n return !fully.getScreenOn();\n return Boolean(this._blackout.style.visibility === \"visible\")\n }\n\n fullyMotion() {\n this._fullyMotion = true;\n clearTimeout(this._motionTimeout);\n this._motionTimeout = setTimeout(() => {\n this._fullyMotion = false;\n this.update();\n }, 5000);\n this.update();\n }\n\n\n start_camera() {\n if(this._video) return;\n this._video = document.createElement(\"video\");\n this._video.autoplay = true;\n this._video.playsInline = true;\n this._video.style.cssText = `\n visibility: hidden;\n width: 0;\n height: 0;\n `;\n this._canvas = document.createElement(\"canvas\");\n this._canvas.style.cssText = `\n visibility: hidden;\n width: 0;\n height: 0;\n `;\n document.body.appendChild(this._canvas);\n document.body.appendChild(this._video);\n navigator.mediaDevices.getUserMedia({video: true, audio: false}).then((stream) => {\n this._video.srcObject = stream;\n this._video.play();\n this.send_cam();\n });\n }\n\n send_cam(data) {\n const context = this._canvas.getContext('2d');\n context.drawImage(this._video, 0, 0, this._canvas.width, this._canvas.height);\n this.conn.sendMessage({\n type: 'browser_mod/update',\n deviceID: _card_tools_deviceId__WEBPACK_IMPORTED_MODULE_0__[\"deviceID\"],\n data: {\n camera: this._canvas.toDataURL('image/png'),\n },\n });\n setTimeout(this.send_cam.bind(this), 5000);\n }\n\n\n update(msg=null) {\n if(!this.conn) return;\n\n if(msg) {\n if(msg.entity_id) {\n this.entity_id = msg.entity_id;\n }\n if(msg.camera) {\n this.start_camera();\n }\n }\n\n\n this.conn.sendMessage({\n type: 'browser_mod/update',\n deviceID: _card_tools_deviceId__WEBPACK_IMPORTED_MODULE_0__[\"deviceID\"],\n data: {\n browser: {\n path: window.location.pathname,\n visibility: document.visibilityState,\n userAgent: navigator.userAgent,\n currentUser: this._hass && this._hass.user && this._hass.user.name,\n fullyKiosk: window.fully ? true : undefined,\n width: window.innerWidth,\n height: window.innerHeight,\n },\n player: {\n volume: this.player.volume,\n muted: this.player.muted,\n src: this.player.src,\n state: this.player_state,\n },\n screen: {\n blackout: this.is_blackout(),\n brightness: window.fully ? fully.getScreenBrightness() : undefined,\n },\n fully: window.fully ? {\n battery: window.fully ? fully.getBatteryLevel() : undefined,\n charging: window.fully ? fully.isPlugged(): undefined,\n motion: window.fully ? this._fullyMotion : undefined,\n } : undefined,\n },\n });\n\n }\n\n}\n\nwindow.browser_mod = new BrowserMod();\n\n\n//# sourceURL=webpack:///./js/main.js?"); + +/***/ }) + +/******/ }); \ No newline at end of file diff --git a/custom_components/browser_mod/camera.py b/custom_components/browser_mod/camera.py new file mode 100644 index 0000000..497fc89 --- /dev/null +++ b/custom_components/browser_mod/camera.py @@ -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, + } diff --git a/custom_components/browser_mod/connection.py b/custom_components/browser_mod/connection.py index 37e5c76..3368188 100644 --- a/custom_components/browser_mod/connection.py +++ b/custom_components/browser_mod/connection.py @@ -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') + diff --git a/custom_components/browser_mod/const.py b/custom_components/browser_mod/const.py index 329d94f..0d63a42 100644 --- a/custom_components/browser_mod/const.py +++ b/custom_components/browser_mod/const.py @@ -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) diff --git a/custom_components/browser_mod/helpers.py b/custom_components/browser_mod/helpers.py index cba018a..3bda4e6 100644 --- a/custom_components/browser_mod/helpers.py +++ b/custom_components/browser_mod/helpers.py @@ -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)) diff --git a/custom_components/browser_mod/light.py b/custom_components/browser_mod/light.py index 9fb182e..321339d 100644 --- a/custom_components/browser_mod/light.py +++ b/custom_components/browser_mod/light.py @@ -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 diff --git a/js/main.js b/js/main.js index 6cd6b0b..456ee82 100644 --- a/js/main.js +++ b/js/main.js @@ -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',