Frontend streams events from HA
This commit is contained in:
parent
3f26fc3b06
commit
846e11d6b8
14 changed files with 228 additions and 91 deletions
|
@ -6,6 +6,8 @@ Provides a Rest API for Home Assistant.
|
|||
"""
|
||||
import re
|
||||
import logging
|
||||
import threading
|
||||
import json
|
||||
|
||||
import homeassistant as ha
|
||||
from homeassistant.helpers import TrackStates
|
||||
|
@ -34,6 +36,9 @@ def setup(hass, config):
|
|||
# /api - for validation purposes
|
||||
hass.http.register_path('GET', URL_API, _handle_get_api)
|
||||
|
||||
# /api/stream
|
||||
hass.http.register_path('GET', URL_API_STREAM, _handle_get_api_stream)
|
||||
|
||||
# /states
|
||||
hass.http.register_path('GET', URL_API_STATES, _handle_get_api_states)
|
||||
hass.http.register_path(
|
||||
|
@ -79,6 +84,40 @@ def _handle_get_api(handler, path_match, data):
|
|||
handler.write_json_message("API running.")
|
||||
|
||||
|
||||
def _handle_get_api_stream(handler, path_match, data):
|
||||
""" Provide a streaming interface for the event bus. """
|
||||
hass = handler.server.hass
|
||||
wfile = handler.wfile
|
||||
block = threading.Event()
|
||||
|
||||
def event_sourcer(event):
|
||||
""" Forwards events to the open request. """
|
||||
if block.is_set() or event.event_type == EVENT_TIME_CHANGED:
|
||||
return
|
||||
elif event.event_type == EVENT_HOMEASSISTANT_STOP:
|
||||
block.set()
|
||||
return
|
||||
|
||||
msg = "data: {}\n\n".format(
|
||||
json.dumps(event.as_dict(), cls=rem.JSONEncoder))
|
||||
|
||||
try:
|
||||
wfile.write(msg.encode("UTF-8"))
|
||||
wfile.flush()
|
||||
except IOError:
|
||||
block.set()
|
||||
|
||||
handler.send_response(HTTP_OK)
|
||||
handler.send_header('Content-type', 'text/event-stream')
|
||||
handler.end_headers()
|
||||
|
||||
hass.bus.listen(MATCH_ALL, event_sourcer)
|
||||
|
||||
block.wait()
|
||||
|
||||
hass.bus.remove_listener(MATCH_ALL, event_sourcer)
|
||||
|
||||
|
||||
def _handle_get_api_states(handler, path_match, data):
|
||||
""" Returns a dict containing all entity ids and their state. """
|
||||
handler.write_json(handler.server.hass.states.all())
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||
VERSION = "873a4efef163787ea768e761f47354a2"
|
||||
VERSION = "3c4dc2ed787b1b4c50b17f4b2bedf0c4"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,23 +1,12 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/paper-toggle-button/paper-toggle-button.html">
|
||||
<link rel="import" href="../bower_components/core-style/core-style.html">
|
||||
|
||||
<link rel="import" href="../components/state-info.html">
|
||||
|
||||
<polymer-element name="state-card-toggle" attributes="stateObj api">
|
||||
<template>
|
||||
<style>
|
||||
paper-toggle-button::shadow .toggle-ink {
|
||||
color: #039be5;
|
||||
}
|
||||
|
||||
paper-toggle-button::shadow [checked] .toggle-bar {
|
||||
background-color: #039be5;
|
||||
}
|
||||
|
||||
paper-toggle-button::shadow [checked] .toggle-button {
|
||||
background-color: #039be5;
|
||||
}
|
||||
</style>
|
||||
<core-style ref='ha-paper-toggle'></core-style>
|
||||
|
||||
<div horizontal justified layout>
|
||||
<state-info flex stateObj="{{stateObj}}"></state-info>
|
||||
|
|
|
@ -11,10 +11,6 @@
|
|||
}
|
||||
|
||||
@media all and (min-width: 1020px) {
|
||||
:host {
|
||||
/*padding-bottom: 8px;*/
|
||||
}
|
||||
|
||||
.state-card {
|
||||
width: calc(50% - 44px);
|
||||
margin: 8px 0 0 8px;
|
||||
|
@ -24,7 +20,6 @@
|
|||
@media all and (min-width: 1356px) {
|
||||
.state-card {
|
||||
width: calc(33% - 38px);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +35,7 @@
|
|||
border-radius: 2px;
|
||||
box-shadow: rgba(0, 0, 0, 0.098) 0px 2px 4px, rgba(0, 0, 0, 0.098) 0px 0px 3px;
|
||||
padding: 16px;
|
||||
margin: 0 auto;
|
||||
margin: 16px auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
|
||||
<link rel="import" href="../bower_components/core-style/core-style.html">
|
||||
|
||||
<link rel="import" href="../bower_components/core-icons/notification-icons.html">
|
||||
|
||||
<polymer-element name="stream-status">
|
||||
<template>
|
||||
<style>
|
||||
:host {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
paper-toggle-button {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
<core-style ref='ha-paper-toggle'></core-style>
|
||||
|
||||
<core-icon icon="warning" hidden?="{{!hasError}}"></core-icon>
|
||||
<paper-toggle-button id="toggle" on-change={{toggleChanged}} hidden?="{{hasError}}"></paper-toggle-button>
|
||||
</template>
|
||||
<script>
|
||||
var streamActions = window.hass.streamActions;
|
||||
var authStore = window.hass.authStore;
|
||||
var storeListenerMixIn = window.hass.storeListenerMixIn;
|
||||
|
||||
Polymer(Polymer.mixin({
|
||||
isStreaming: false,
|
||||
hasError: false,
|
||||
|
||||
icon: "swap-vert-circle",
|
||||
color: 'red',
|
||||
|
||||
ready: function() {
|
||||
this.listenToStores(true);
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
this.stopListeningToStores();
|
||||
},
|
||||
|
||||
streamStoreChanged: function(streamStore) {
|
||||
this.isStreaming = streamStore.isStreaming();
|
||||
this.hasError = streamStore.hasError();
|
||||
|
||||
this.$.toggle.checked = this.isStreaming;
|
||||
},
|
||||
|
||||
toggleChanged: function(ev) {
|
||||
if (this.isStreaming) {
|
||||
streamActions.stop();
|
||||
} else {
|
||||
streamActions.start(authStore.getAuthToken());
|
||||
}
|
||||
},
|
||||
}, storeListenerMixIn));
|
||||
</script>
|
||||
</polymer-element>
|
|
@ -27,6 +27,7 @@
|
|||
Polymer({
|
||||
stateObj: {},
|
||||
stateHistory: null,
|
||||
hasHistoryComponent: false,
|
||||
|
||||
observe: {
|
||||
'stateObj.attributes': 'reposition'
|
||||
|
@ -42,6 +43,10 @@ Polymer({
|
|||
window.hass.stateHistoryStore.removeChangeListener(this.stateHistoryStoreChanged);
|
||||
},
|
||||
|
||||
componentStoreChanged: function() {
|
||||
this.hasHistoryComponent = componentStore.isLoaded('history');
|
||||
},
|
||||
|
||||
stateHistoryStoreChanged: function() {
|
||||
if (this.stateObj.entityId) {
|
||||
this.stateHistory = window.hass.stateHistoryStore.get(this.stateObj.entityId);
|
||||
|
@ -53,7 +58,7 @@ Polymer({
|
|||
stateObjChanged: function() {
|
||||
if (this.stateObj.entityId &&
|
||||
window.hass.stateHistoryStore.isStale(this.stateObj.entityId)) {
|
||||
window.hass.stateHistoryActions.fetch(this.stateObj.entityId);
|
||||
// window.hass.stateHistoryActions.fetch(this.stateObj.entityId);
|
||||
}
|
||||
|
||||
this.stateHistoryStoreChanged();
|
||||
|
|
|
@ -51,8 +51,29 @@
|
|||
return moment(this.lastChangedAsDate).fromNow();
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var state,
|
||||
constants = window.hass.constants,
|
||||
dispatcher = window.hass.dispatcher,
|
||||
preferenceStore = window.hass.preferenceStore;
|
||||
|
||||
var uiActions = window.hass.uiActions = {
|
||||
ACTION_SHOW_TOAST: constants.ACTION_SHOW_TOAST,
|
||||
ACTION_SHOW_DIALOG_MORE_INFO: 'ACTION_SHOW_DIALOG_MORE_INFO',
|
||||
|
||||
showMoreInfoDialog: function(entityId) {
|
||||
dispatcher.dispatch({
|
||||
actionType: this.ACTION_SHOW_DIALOG_MORE_INFO,
|
||||
entityId: entityId,
|
||||
});
|
||||
},
|
||||
|
||||
validateAuth: function(authToken) {
|
||||
window.hass.authActions.validate(authToken, preferenceStore.useStreaming());
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<polymer-element name="home-assistant-api" attributes="auth">
|
||||
|
@ -63,30 +84,11 @@
|
|||
<script>
|
||||
Polymer({
|
||||
ready: function() {
|
||||
var state,
|
||||
actions = window.hass.actions,
|
||||
dispatcher = window.hass.dispatcher;
|
||||
|
||||
var uiActions = window.hass.uiActions = {
|
||||
ACTION_SHOW_TOAST: actions.ACTION_SHOW_TOAST,
|
||||
ACTION_SHOW_DIALOG_MORE_INFO: 'ACTION_SHOW_DIALOG_MORE_INFO',
|
||||
|
||||
showMoreInfoDialog: function(entityId) {
|
||||
dispatcher.dispatch({
|
||||
actionType: this.ACTION_SHOW_DIALOG_MORE_INFO,
|
||||
entityId: entityId,
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
var getState = function(entityId) {
|
||||
return window.hass.stateStore.get(entityId);
|
||||
};
|
||||
var getState = window.hass.stateStore.get;
|
||||
|
||||
dispatcher.register(function(payload) {
|
||||
switch (payload.actionType) {
|
||||
case actions.ACTION_SHOW_TOAST:
|
||||
case constants.ACTION_SHOW_TOAST:
|
||||
this.$.toast.text = payload.message;
|
||||
this.$.toast.show();
|
||||
break;
|
||||
|
@ -101,10 +103,9 @@
|
|||
|
||||
// if auth was given, tell the backend
|
||||
if(this.auth) {
|
||||
window.hass.authActions.validate(this.auth);
|
||||
window.hass.uiActions.validateAuth(this.auth);
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b32562325cf981691a18c88776a10b688cc7c6e6
|
||||
Subproject commit 7e398ff5239a2d5335a8c32bbe69f0e3c61fa07f
|
|
@ -14,6 +14,8 @@
|
|||
<link rel="import" href="../layouts/partial-dev-call-service.html">
|
||||
<link rel="import" href="../layouts/partial-dev-set-state.html">
|
||||
|
||||
<link rel="import" href="../components/stream-status.html">
|
||||
|
||||
<polymer-element name="home-assistant-main">
|
||||
<template>
|
||||
<core-style ref="ha-headers"></core-style>
|
||||
|
@ -48,12 +50,15 @@
|
|||
min-height: 53px;
|
||||
}
|
||||
|
||||
.seperator {
|
||||
.text {
|
||||
padding: 16px;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dev-tools {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
@ -85,12 +90,17 @@
|
|||
|
||||
<div flex></div>
|
||||
|
||||
<paper-item id="logout" on-click="{{handleLogOutClick}}">
|
||||
<paper-item on-click="{{handleLogOutClick}}">
|
||||
<core-icon icon="exit-to-app"></core-icon>
|
||||
Log Out
|
||||
</paper-item>
|
||||
|
||||
<div class='seperator'>Developer Tools</div>
|
||||
<div class='text' horizontal layout center>
|
||||
<div flex>Streaming updates</div>
|
||||
<stream-status></stream-status>
|
||||
</div>
|
||||
|
||||
<div class='text label'>Developer Tools</div>
|
||||
<div class='dev-tools' layout horizontal justified>
|
||||
<paper-icon-button
|
||||
icon="settings-remote" data-panel='call-service'
|
||||
|
@ -131,26 +141,37 @@
|
|||
|
||||
</template>
|
||||
<script>
|
||||
Polymer({
|
||||
var componentStore = window.hass.componentStore;
|
||||
var streamStore = window.hass.streamStore;
|
||||
var storeListenerMixIn = window.hass.storeListenerMixIn;
|
||||
|
||||
Polymer(Polymer.mixin({
|
||||
selected: "states",
|
||||
narrow: false,
|
||||
hasHistoryComponent: false,
|
||||
|
||||
isStreaming: false,
|
||||
hasStreamError: false,
|
||||
|
||||
ready: function() {
|
||||
this.togglePanel = this.togglePanel.bind(this);
|
||||
this.componentStoreChanged = this.componentStoreChanged.bind(this);
|
||||
|
||||
window.hass.componentStore.addChangeListener(this.componentStoreChanged);
|
||||
|
||||
this.componentStoreChanged();
|
||||
this.listenToStores(true);
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
window.hass.componentStore.removeChangeListener(this.componentStoreChanged);
|
||||
this.stopListeningToStores();
|
||||
},
|
||||
|
||||
componentStoreChanged: function() {
|
||||
this.hasHistoryComponent = window.hass.componentStore.isLoaded('history');
|
||||
this.hasHistoryComponent = componentStore.isLoaded('history');
|
||||
},
|
||||
|
||||
streamStoreChanged: function() {
|
||||
var state = streamStore.getState();
|
||||
|
||||
this.isStreaming = state === streamStore.STATE_CONNECTED;
|
||||
this.hasStreamError = state === streamStore.STATE_ERROR;
|
||||
},
|
||||
|
||||
menuSelect: function(ev, detail, sender) {
|
||||
|
@ -185,6 +206,6 @@ Polymer({
|
|||
window.hass.authActions.logOut();
|
||||
},
|
||||
|
||||
});
|
||||
}, storeListenerMixIn));
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
|
|
@ -58,7 +58,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
var storeListenerMixIn = window.hass.storeListenerMixIn;
|
||||
|
||||
Polymer(Polymer.mixin({
|
||||
MSG_VALIDATING: "Validating password…",
|
||||
MSG_LOADING_DATA: "Loading data…",
|
||||
|
||||
|
@ -70,13 +72,7 @@
|
|||
spinnerMessage: "",
|
||||
|
||||
ready: function() {
|
||||
this.authStore = window.hass.authStore;
|
||||
|
||||
this.authChangeListener = this.authChangeListener.bind(this);
|
||||
|
||||
this.authStore.addChangeListener(this.authChangeListener);
|
||||
|
||||
this.authChangeListener();
|
||||
this.listenToStores(true);
|
||||
},
|
||||
|
||||
attached: function() {
|
||||
|
@ -84,7 +80,7 @@
|
|||
},
|
||||
|
||||
detached: function() {
|
||||
this.authStore.removeChangeListener(this.authChangeListener);
|
||||
this.stopListeningToStores();
|
||||
},
|
||||
|
||||
authChangeListener: function() {
|
||||
|
@ -120,10 +116,10 @@
|
|||
validatePassword: function() {
|
||||
this.$.hideKeyboardOnFocus.focus();
|
||||
|
||||
window.hass.authActions.validate(this.authToken);
|
||||
window.hass.uiActions.validateAuth(this.authToken);
|
||||
},
|
||||
|
||||
});
|
||||
}, storeListenerMixIn));
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/core-style/core-style.html">
|
||||
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
|
||||
|
||||
<link rel="import" href="./partial-base.html">
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/core-style/core-style.html">
|
||||
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
|
||||
|
||||
<link rel="import" href="./partial-base.html">
|
||||
|
||||
|
@ -13,7 +15,7 @@
|
|||
|
||||
<span header-buttons>
|
||||
<paper-icon-button icon="refresh" class="{{isFetching && 'ha-spin'}}"
|
||||
on-click="{{handleRefreshClick}}"></paper-icon-button>
|
||||
on-click="{{handleRefreshClick}}" hidden?="{{isStreaming}}"></paper-icon-button>
|
||||
</span>
|
||||
|
||||
<state-cards states="{{states}}">
|
||||
|
@ -28,24 +30,20 @@
|
|||
</partial-base>
|
||||
</template>
|
||||
<script>
|
||||
Polymer({
|
||||
var storeListenerMixIn = window.hass.storeListenerMixIn;
|
||||
|
||||
Polymer(Polymer.mixin({
|
||||
headerTitle: "States",
|
||||
states: [],
|
||||
isFetching: false,
|
||||
isStreaming: false,
|
||||
|
||||
ready: function() {
|
||||
this.stateStoreChanged = this.stateStoreChanged.bind(this);
|
||||
window.hass.stateStore.addChangeListener(this.stateStoreChanged);
|
||||
|
||||
this.syncStoreChanged = this.syncStoreChanged.bind(this);
|
||||
window.hass.syncStore.addChangeListener(this.syncStoreChanged);
|
||||
|
||||
this.refreshStates();
|
||||
this.listenToStores(true);
|
||||
},
|
||||
|
||||
detached: function() {
|
||||
window.hass.stateStore.removeChangeListener(this.stateStoreChanged);
|
||||
window.hass.syncStore.removeChangeListener(this.syncStoreChanged);
|
||||
this.stopListeningToStores();
|
||||
},
|
||||
|
||||
stateStoreChanged: function() {
|
||||
|
@ -56,6 +54,10 @@
|
|||
this.isFetching = window.hass.syncStore.isFetching();
|
||||
},
|
||||
|
||||
streamStoreChanged: function(streamStore) {
|
||||
this.isStreaming = streamStore.isStreaming();
|
||||
},
|
||||
|
||||
filterChanged: function() {
|
||||
this.refreshStates();
|
||||
|
||||
|
@ -85,6 +87,6 @@
|
|||
handleRefreshClick: function() {
|
||||
window.hass.syncActions.sync();
|
||||
},
|
||||
});
|
||||
}, storeListenerMixIn));
|
||||
</script>
|
||||
</polymer>
|
|
@ -111,3 +111,17 @@
|
|||
word-break: break-all;
|
||||
}
|
||||
</core-style>
|
||||
|
||||
<core-style id='ha-paper-toggle'>
|
||||
paper-toggle-button::shadow .toggle-ink {
|
||||
color: #039be5;
|
||||
}
|
||||
|
||||
paper-toggle-button::shadow [checked] .toggle-bar {
|
||||
background-color: #039be5;
|
||||
}
|
||||
|
||||
paper-toggle-button::shadow [checked] .toggle-button {
|
||||
background-color: #039be5;
|
||||
}
|
||||
</core-style>
|
||||
|
|
Loading…
Add table
Reference in a new issue