mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 01:07:10 +00:00
Add Google Timelines to UI
This commit is contained in:
parent
3439f4bb93
commit
fbae2ef725
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "212470c7842a8715f81fba76a3bd985f"
|
VERSION = "954620894f13782f17ae7443f0df4ffc"
|
||||||
|
File diff suppressed because one or more lines are too long
@ -21,6 +21,7 @@
|
|||||||
"core-input": "Polymer/core-input#~0.5.4",
|
"core-input": "Polymer/core-input#~0.5.4",
|
||||||
"core-icons": "polymer/core-icons#~0.5.4",
|
"core-icons": "polymer/core-icons#~0.5.4",
|
||||||
"core-image": "polymer/core-image#~0.5.4",
|
"core-image": "polymer/core-image#~0.5.4",
|
||||||
|
"core-style": "polymer/core-style#~0.5.4",
|
||||||
"paper-toast": "Polymer/paper-toast#~0.5.4",
|
"paper-toast": "Polymer/paper-toast#~0.5.4",
|
||||||
"paper-dialog": "Polymer/paper-dialog#~0.5.4",
|
"paper-dialog": "Polymer/paper-dialog#~0.5.4",
|
||||||
"paper-spinner": "Polymer/paper-spinner#~0.5.4",
|
"paper-spinner": "Polymer/paper-spinner#~0.5.4",
|
||||||
@ -32,9 +33,9 @@
|
|||||||
"paper-menu-button": "polymer/paper-menu-button#~0.5.4",
|
"paper-menu-button": "polymer/paper-menu-button#~0.5.4",
|
||||||
"paper-dropdown": "polymer/paper-dropdown#~0.5.4",
|
"paper-dropdown": "polymer/paper-dropdown#~0.5.4",
|
||||||
"paper-item": "polymer/paper-item#~0.5.4",
|
"paper-item": "polymer/paper-item#~0.5.4",
|
||||||
"moment": "~2.8.4",
|
|
||||||
"core-style": "polymer/core-style#~0.5.4",
|
|
||||||
"paper-slider": "polymer/paper-slider#~0.5.4",
|
"paper-slider": "polymer/paper-slider#~0.5.4",
|
||||||
"color-picker-element": "~0.0.2"
|
"moment": "~2.8.4",
|
||||||
|
"color-picker-element": "~0.0.2",
|
||||||
|
"google-apis": "GoogleWebComponents/google-apis#~0.4.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
stateObjChanged: function() {
|
stateObjChanged: function() {
|
||||||
this.recentStates = null;
|
this.recentStates = null;
|
||||||
|
|
||||||
this.api.call_api('GET', 'history/' + this.stateObj.entity_id + '/recent_states', {}, this.newStates.bind(this));
|
this.api.call_api('GET', 'history/entity/' + this.stateObj.entity_id + '/recent_states', {}, this.newStates.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
newStates: function(states) {
|
newStates: function(states) {
|
||||||
|
@ -0,0 +1,128 @@
|
|||||||
|
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||||
|
|
||||||
|
<link rel="import" href="../bower_components/google-apis/google-jsapi.html">
|
||||||
|
|
||||||
|
<polymer-element name="state-timeline" attributes="stateObj api">
|
||||||
|
<template>
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<google-jsapi on-api-load="{{googleApiLoaded}}"></google-jsapi>
|
||||||
|
<div id="timeline" style='width: 100%; height: auto;'></div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
Polymer({
|
||||||
|
apiLoaded: false,
|
||||||
|
stateData: null,
|
||||||
|
|
||||||
|
googleApiLoaded: function() {
|
||||||
|
google.load("visualization", "1", {
|
||||||
|
packages: ["timeline"],
|
||||||
|
callback: function() {
|
||||||
|
this.apiLoaded = true;
|
||||||
|
this.drawChart();
|
||||||
|
}.bind(this)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stateObjChanged: function(oldVal, newVal) {
|
||||||
|
// update data if we get a new stateObj
|
||||||
|
if (!oldVal || (newVal && oldVal.entity_id === newVal.entity_id)) {
|
||||||
|
this.drawChart();
|
||||||
|
} else {
|
||||||
|
this.fetchData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchData: function() {
|
||||||
|
if (!this.api) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stateData = null;
|
||||||
|
|
||||||
|
var url = 'history/period';
|
||||||
|
|
||||||
|
if (this.stateObj) {
|
||||||
|
url += '?filter_entity_id=' + this.stateObj.entity_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.api.call_api('GET', url, {}, function(stateData) {
|
||||||
|
this.stateData = stateData;
|
||||||
|
this.drawChart();
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
drawChart: function() {
|
||||||
|
if (!this.apiLoaded || this.stateData === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var container = this.$.timeline;
|
||||||
|
var chart = new google.visualization.Timeline(container);
|
||||||
|
var dataTable = new google.visualization.DataTable();
|
||||||
|
|
||||||
|
dataTable.addColumn({ type: 'string', id: 'Entity' });
|
||||||
|
dataTable.addColumn({ type: 'string', id: 'State' });
|
||||||
|
dataTable.addColumn({ type: 'date', id: 'Start' });
|
||||||
|
dataTable.addColumn({ type: 'date', id: 'End' });
|
||||||
|
|
||||||
|
var stateTimeToDate = function(time) {
|
||||||
|
if (!time) return new Date();
|
||||||
|
|
||||||
|
return ha.util.parseTime(time).toDate();
|
||||||
|
};
|
||||||
|
|
||||||
|
var addRow = function(baseState, state, tillState) {
|
||||||
|
tillState = tillState || {};
|
||||||
|
|
||||||
|
dataTable.addRow([
|
||||||
|
baseState.entityDisplay, state.state,
|
||||||
|
stateTimeToDate(state.last_changed),
|
||||||
|
stateTimeToDate(tillState.last_changed)]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// this.stateData is a list of lists of sorted state objects
|
||||||
|
this.stateData.forEach(function(stateInfo) {
|
||||||
|
var baseState = new this.api.State(stateInfo[0], this.api);
|
||||||
|
|
||||||
|
var prevRow = null;
|
||||||
|
|
||||||
|
stateInfo.forEach(function(state) {
|
||||||
|
if (prevRow !== null && state.state !== prevRow.state) {
|
||||||
|
addRow(baseState, prevRow, state);
|
||||||
|
prevRow = state;
|
||||||
|
} else if (prevRow === null) {
|
||||||
|
prevRow = state;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addRow(baseState, prevRow, null);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
chart.draw(dataTable, {
|
||||||
|
height: 55 + this.stateData.length * 42,
|
||||||
|
|
||||||
|
// interactive properties require CSS, the JS api puts it on the document
|
||||||
|
// instead of inside our Shadow DOM.
|
||||||
|
enableInteractivity: false,
|
||||||
|
|
||||||
|
timeline: {
|
||||||
|
showRowLabels: this.stateData.length > 1
|
||||||
|
},
|
||||||
|
|
||||||
|
hAxis: {
|
||||||
|
format: 'H:mm'
|
||||||
|
},
|
||||||
|
// colors: ['#CCC', '#03a9f4']
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</polymer-element>
|
@ -0,0 +1,37 @@
|
|||||||
|
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||||
|
<link rel="import" href="ha-action-dialog.html">
|
||||||
|
<link rel="import" href="../components/state-timeline.html">
|
||||||
|
|
||||||
|
<polymer-element name="history-dialog" attributes="api">
|
||||||
|
<template>
|
||||||
|
<ha-action-dialog id="dialog" heading="History">
|
||||||
|
<style>
|
||||||
|
#timeline {
|
||||||
|
width: 614px;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<state-timeline id='timeline' api="{{api}}"></state-timeline>
|
||||||
|
</ha-action-dialog>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
Polymer({
|
||||||
|
show: function() {
|
||||||
|
this.$.dialog.toggle();
|
||||||
|
|
||||||
|
this.job('repositionDialogAfterRender', function() {
|
||||||
|
|
||||||
|
this.$.timeline.fetchData();
|
||||||
|
|
||||||
|
this.job('repositionDialogAfterRender', function() {
|
||||||
|
this.$.dialog.resizeHandler();
|
||||||
|
}.bind(this), 1000);
|
||||||
|
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</polymer-element>
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<link rel="import" href="ha-action-dialog.html">
|
<link rel="import" href="ha-action-dialog.html">
|
||||||
<link rel="import" href="../cards/state-card-content.html">
|
<link rel="import" href="../cards/state-card-content.html">
|
||||||
|
<link rel="import" href="../components/state-timeline.html">
|
||||||
<link rel="import" href="../more-infos/more-info-content.html">
|
<link rel="import" href="../more-infos/more-info-content.html">
|
||||||
|
|
||||||
<polymer-element name="more-info-dialog" attributes="api">
|
<polymer-element name="more-info-dialog" attributes="api">
|
||||||
@ -17,6 +18,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<state-card-content stateObj="{{stateObj}}" api="{{api}}" class='title-card'>
|
<state-card-content stateObj="{{stateObj}}" api="{{api}}" class='title-card'>
|
||||||
</state-card-content>
|
</state-card-content>
|
||||||
|
<state-timeline stateObj="{{stateObj}}" api="{{api}}"></state-timeline>
|
||||||
<more-info-content stateObj="{{stateObj}}" api="{{api}}"></more-info-content>
|
<more-info-content stateObj="{{stateObj}}" api="{{api}}"></more-info-content>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
<link rel="import" href="dialogs/service-call-dialog.html">
|
<link rel="import" href="dialogs/service-call-dialog.html">
|
||||||
<link rel="import" href="dialogs/state-set-dialog.html">
|
<link rel="import" href="dialogs/state-set-dialog.html">
|
||||||
<link rel="import" href="dialogs/more-info-dialog.html">
|
<link rel="import" href="dialogs/more-info-dialog.html">
|
||||||
|
<link rel="import" href="dialogs/history-dialog.html">
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var ha = {};
|
var ha = {};
|
||||||
@ -37,6 +38,7 @@
|
|||||||
<service-call-dialog id="serviceDialog" api={{api}}></service-call-dialog>
|
<service-call-dialog id="serviceDialog" api={{api}}></service-call-dialog>
|
||||||
<state-set-dialog id="stateSetDialog" api={{api}}></state-set-dialog>
|
<state-set-dialog id="stateSetDialog" api={{api}}></state-set-dialog>
|
||||||
<more-info-dialog id="moreInfoDialog" api={{api}}></more-info-dialog>
|
<more-info-dialog id="moreInfoDialog" api={{api}}></more-info-dialog>
|
||||||
|
<history-dialog id="historyDialog" api={{api}}></history-dialog>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
var domainsWithCard = ['thermostat', 'configurator'];
|
var domainsWithCard = ['thermostat', 'configurator'];
|
||||||
@ -128,6 +130,10 @@
|
|||||||
events: [],
|
events: [],
|
||||||
stateUpdateTimeout: null,
|
stateUpdateTimeout: null,
|
||||||
|
|
||||||
|
// available classes
|
||||||
|
State: State,
|
||||||
|
|
||||||
|
// Polymer lifecycle methods
|
||||||
created: function() {
|
created: function() {
|
||||||
this.api = this;
|
this.api = this;
|
||||||
|
|
||||||
@ -451,6 +457,10 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
// show dialogs
|
// show dialogs
|
||||||
|
showHistoryDialog: function() {
|
||||||
|
this.$.historyDialog.show();
|
||||||
|
},
|
||||||
|
|
||||||
showmoreInfoDialog: function(entityId) {
|
showmoreInfoDialog: function(entityId) {
|
||||||
this.$.moreInfoDialog.show(this.getState(entityId));
|
this.$.moreInfoDialog.show(this.getState(entityId));
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<link rel="import" href="bower_components/core-header-panel/core-header-panel.html">
|
<link rel="import" href="bower_components/core-header-panel/core-header-panel.html">
|
||||||
<link rel="import" href="bower_components/core-toolbar/core-toolbar.html">
|
<link rel="import" href="bower_components/core-toolbar/core-toolbar.html">
|
||||||
|
<link rel="import" href="bower_components/core-icon/core-icon.html">
|
||||||
<link rel="import" href="bower_components/paper-tabs/paper-tabs.html">
|
<link rel="import" href="bower_components/paper-tabs/paper-tabs.html">
|
||||||
<link rel="import" href="bower_components/paper-tabs/paper-tab.html">
|
<link rel="import" href="bower_components/paper-tabs/paper-tab.html">
|
||||||
<link rel="import" href="bower_components/paper-icon-button/paper-icon-button.html">
|
<link rel="import" href="bower_components/paper-icon-button/paper-icon-button.html">
|
||||||
@ -74,21 +75,34 @@
|
|||||||
<div flex>Home Assistant</div>
|
<div flex>Home Assistant</div>
|
||||||
<paper-icon-button icon="refresh"
|
<paper-icon-button icon="refresh"
|
||||||
on-click="{{handleRefreshClick}}"></paper-icon-button>
|
on-click="{{handleRefreshClick}}"></paper-icon-button>
|
||||||
<paper-icon-button icon="settings-remote"
|
<paper-icon-button icon="assessment"
|
||||||
on-click="{{handleServiceClick}}"></paper-icon-button>
|
on-click="{{handleHistoryClick}}"></paper-icon-button>
|
||||||
|
|
||||||
<paper-menu-button>
|
<paper-menu-button>
|
||||||
<paper-icon-button icon="more-vert" noink></paper-icon-button>
|
<paper-icon-button icon="more-vert" noink></paper-icon-button>
|
||||||
<paper-dropdown halign="right" duration="200" class="dropdown">
|
<paper-dropdown halign="right" duration="200" class="dropdown">
|
||||||
<core-menu class="menu">
|
<core-menu class="menu">
|
||||||
<paper-item>
|
<paper-item>
|
||||||
<a on-click={{handleAddStateClick}}>Set State</a>
|
<a on-click={{handleServiceClick}}>
|
||||||
|
<!-- <core-icon icon="settings-remote"></core-icon> -->
|
||||||
|
Call Service
|
||||||
|
</a>
|
||||||
</paper-item>
|
</paper-item>
|
||||||
<paper-item>
|
<paper-item>
|
||||||
<a on-click={{handleEventClick}}>Trigger Event</a>
|
<a on-click={{handleAddStateClick}}>
|
||||||
|
Set State
|
||||||
|
</a>
|
||||||
</paper-item>
|
</paper-item>
|
||||||
<paper-item>
|
<paper-item>
|
||||||
<a on-click={{handleLogOutClick}}>Log Out</a>
|
<a on-click={{handleEventClick}}>
|
||||||
|
Trigger Event
|
||||||
|
</a>
|
||||||
|
</paper-item>
|
||||||
|
<paper-item>
|
||||||
|
<a on-click={{handleLogOutClick}}>
|
||||||
|
<!-- <core-icon icon="exit-to-app"></core-icon> -->
|
||||||
|
Log Out
|
||||||
|
</a>
|
||||||
</paper-item>
|
</paper-item>
|
||||||
</core-menu>
|
</core-menu>
|
||||||
</paper-dropdown>
|
</paper-dropdown>
|
||||||
@ -147,6 +161,10 @@
|
|||||||
this.api.fetchAll();
|
this.api.fetchAll();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleHistoryClick: function() {
|
||||||
|
this.api.showHistoryDialog();
|
||||||
|
},
|
||||||
|
|
||||||
handleEventClick: function() {
|
handleEventClick: function() {
|
||||||
this.api.showFireEventDialog();
|
this.api.showFireEventDialog();
|
||||||
},
|
},
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
<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/core-style/core-style.html">
|
||||||
|
|
||||||
<link rel="import" href="../components/recent-states.html">
|
|
||||||
|
|
||||||
<polymer-element name="more-info-default" attributes="stateObj api">
|
<polymer-element name="more-info-default" attributes="stateObj api">
|
||||||
<template>
|
<template>
|
||||||
<core-style ref='ha-key-value-table'></core-style>
|
<core-style ref='ha-key-value-table'></core-style>
|
||||||
@ -24,23 +22,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template if="{{hasHistoryComponent}}">
|
|
||||||
<h4>Recent states</h4>
|
|
||||||
<recent-states api="{{api}}" stateObj="{{stateObj}}"></recent-states>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
Polymer({
|
Polymer({
|
||||||
hasHistoryComponent: false,
|
|
||||||
|
|
||||||
getKeys: function(obj) {
|
getKeys: function(obj) {
|
||||||
return Object.keys(obj || {});
|
return Object.keys(obj || {});
|
||||||
},
|
|
||||||
|
|
||||||
apiChanged: function(oldVal, newVal) {
|
|
||||||
this.hasHistoryComponent = this.api && this.api.hasComponent('history');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -5,6 +5,8 @@ homeassistant.components.history
|
|||||||
Provide pre-made queries on top of the recorder component.
|
Provide pre-made queries on top of the recorder component.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from itertools import groupby
|
||||||
|
|
||||||
import homeassistant.components.recorder as recorder
|
import homeassistant.components.recorder as recorder
|
||||||
|
|
||||||
@ -14,23 +16,55 @@ DEPENDENCIES = ['recorder', 'http']
|
|||||||
|
|
||||||
def last_5_states(entity_id):
|
def last_5_states(entity_id):
|
||||||
""" Return the last 5 states for entity_id. """
|
""" Return the last 5 states for entity_id. """
|
||||||
|
entity_id = entity_id.lower()
|
||||||
|
|
||||||
query = """
|
query = """
|
||||||
SELECT * FROM states WHERE entity_id=? AND
|
SELECT * FROM states WHERE entity_id=? AND
|
||||||
last_changed=last_updated AND {}
|
last_changed=last_updated
|
||||||
ORDER BY last_changed DESC LIMIT 0, 5
|
ORDER BY last_changed DESC LIMIT 0, 5
|
||||||
""".format(recorder.limit_to_run())
|
"""
|
||||||
|
|
||||||
return recorder.query_states(query, (entity_id, ))
|
return recorder.query_states(query, (entity_id, ))
|
||||||
|
|
||||||
|
|
||||||
|
def state_changes_during_period(start_time, end_time=None, entity_id=None):
|
||||||
|
"""
|
||||||
|
Return states changes during period start_time - end_time.
|
||||||
|
Currently does _not_ include how the states where at exactly start_time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
where = "last_changed=last_updated AND last_changed > ? "
|
||||||
|
data = [start_time]
|
||||||
|
|
||||||
|
if end_time is not None:
|
||||||
|
where += "AND last_changed < ? "
|
||||||
|
data.append(end_time)
|
||||||
|
|
||||||
|
if entity_id is not None:
|
||||||
|
where += "AND entity_id = ? "
|
||||||
|
data.append(entity_id.lower())
|
||||||
|
|
||||||
|
query = ("SELECT * FROM states WHERE {} "
|
||||||
|
"ORDER BY entity_id, last_changed ASC").format(where)
|
||||||
|
|
||||||
|
states = recorder.query_states(query, data)
|
||||||
|
|
||||||
|
return [list(group) for _, group in
|
||||||
|
groupby(states, lambda state: state.entity_id)]
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Setup history hooks. """
|
""" Setup history hooks. """
|
||||||
hass.http.register_path(
|
hass.http.register_path(
|
||||||
'GET',
|
'GET',
|
||||||
re.compile(
|
re.compile(
|
||||||
r'/api/history/(?P<entity_id>[a-zA-Z\._0-9]+)/recent_states'),
|
r'/api/history/entity/(?P<entity_id>[a-zA-Z\._0-9]+)/'
|
||||||
|
r'recent_states'),
|
||||||
_api_last_5_states)
|
_api_last_5_states)
|
||||||
|
|
||||||
|
hass.http.register_path(
|
||||||
|
'GET', re.compile(r'/api/history/period'), _api_history_period)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -40,3 +74,14 @@ def _api_last_5_states(handler, path_match, data):
|
|||||||
entity_id = path_match.group('entity_id')
|
entity_id = path_match.group('entity_id')
|
||||||
|
|
||||||
handler.write_json(list(last_5_states(entity_id)))
|
handler.write_json(list(last_5_states(entity_id)))
|
||||||
|
|
||||||
|
|
||||||
|
def _api_history_period(handler, path_match, data):
|
||||||
|
""" Return history over a period of time. """
|
||||||
|
# 1 day for now..
|
||||||
|
start_time = datetime.now() - timedelta(seconds=86400)
|
||||||
|
|
||||||
|
entity_id = data.get('filter_entity_id')
|
||||||
|
|
||||||
|
handler.write_json(
|
||||||
|
state_changes_during_period(start_time, entity_id=entity_id))
|
||||||
|
@ -84,32 +84,19 @@ def limit_to_run(point_in_time=None):
|
|||||||
"""
|
"""
|
||||||
_verify_instance()
|
_verify_instance()
|
||||||
|
|
||||||
end_event = None
|
|
||||||
|
|
||||||
# Targetting current run
|
# Targetting current run
|
||||||
if point_in_time is None:
|
if point_in_time is None:
|
||||||
return "created >= {} ".format(
|
return "created >= {} ".format(
|
||||||
_adapt_datetime(_INSTANCE.recording_start))
|
_adapt_datetime(_INSTANCE.recording_start))
|
||||||
|
|
||||||
start_event = query(
|
raise NotImplementedError()
|
||||||
("SELECT * FROM events WHERE event_type = ? AND created < ? "
|
|
||||||
"ORDER BY created DESC LIMIT 0, 1"),
|
|
||||||
(EVENT_HOMEASSISTANT_START, point_in_time))[0]
|
|
||||||
|
|
||||||
end_query = query(
|
|
||||||
("SELECT * FROM events WHERE event_type = ? AND created > ? "
|
|
||||||
"ORDER BY created ASC LIMIT 0, 1"),
|
|
||||||
(EVENT_HOMEASSISTANT_START, point_in_time))
|
|
||||||
|
|
||||||
if end_query:
|
def recording_start():
|
||||||
end_event = end_query[0]
|
""" Return when the recorder started. """
|
||||||
|
_verify_instance()
|
||||||
|
|
||||||
where_part = "created >= {}".format(start_event['created'])
|
return _INSTANCE.recording_start
|
||||||
|
|
||||||
if end_event is None:
|
|
||||||
return where_part
|
|
||||||
else:
|
|
||||||
return "{} and created < {}".format(where_part, end_event['created'])
|
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
@ -183,13 +170,13 @@ class Recorder(threading.Thread):
|
|||||||
info = (entity_id, '', "{}", now, now, now)
|
info = (entity_id, '', "{}", now, now, now)
|
||||||
else:
|
else:
|
||||||
info = (
|
info = (
|
||||||
entity_id, state.state, json.dumps(state.attributes),
|
entity_id.lower(), state.state, json.dumps(state.attributes),
|
||||||
state.last_changed, state.last_updated, now)
|
state.last_changed, state.last_updated, now)
|
||||||
|
|
||||||
self.query(
|
self.query(
|
||||||
"insert into states ("
|
"INSERT INTO states ("
|
||||||
"entity_id, state, attributes, last_changed, last_updated,"
|
"entity_id, state, attributes, last_changed, last_updated,"
|
||||||
"created) values (?, ?, ?, ?, ?, ?)", info)
|
"created) VALUES (?, ?, ?, ?, ?, ?)", info)
|
||||||
|
|
||||||
def record_event(self, event):
|
def record_event(self, event):
|
||||||
""" Save an event to the database. """
|
""" Save an event to the database. """
|
||||||
@ -199,9 +186,9 @@ class Recorder(threading.Thread):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.query(
|
self.query(
|
||||||
"insert into events ("
|
"INSERT INTO events ("
|
||||||
"event_type, event_data, origin, created"
|
"event_type, event_data, origin, created"
|
||||||
") values (?, ?, ?, ?)", info)
|
") VALUES (?, ?, ?, ?)", info)
|
||||||
|
|
||||||
def query(self, sql_query, data=None, return_value=None):
|
def query(self, sql_query, data=None, return_value=None):
|
||||||
""" Query the database. """
|
""" Query the database. """
|
||||||
|
@ -261,6 +261,16 @@ class JSONEncoder(json.JSONEncoder):
|
|||||||
if isinstance(obj, (ha.State, ha.Event)):
|
if isinstance(obj, (ha.State, ha.Event)):
|
||||||
return obj.as_dict()
|
return obj.as_dict()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return json.JSONEncoder.default(self, obj)
|
||||||
|
except TypeError:
|
||||||
|
# If the JSON serializer couldn't serialize it
|
||||||
|
# it might be a generator, convert it to a list
|
||||||
|
try:
|
||||||
|
return [json.JSONEncoder.default(self, child_obj)
|
||||||
|
for child_obj in obj]
|
||||||
|
except TypeError:
|
||||||
|
# Ok, we're lost, cause the original error
|
||||||
return json.JSONEncoder.default(self, obj)
|
return json.JSONEncoder.default(self, obj)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user