mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
Merge branch 'dev' into transmission-dev
Merging in the latest from the dev branch
This commit is contained in:
commit
df9e8f5214
@ -1,2 +1,2 @@
|
|||||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||||
VERSION = "1e004712440afc642a44ad927559587e"
|
VERSION = "f51c439b587ce03928e2db4cc08ef492"
|
||||||
|
File diff suppressed because one or more lines are too long
@ -2,26 +2,58 @@
|
|||||||
|
|
||||||
<link rel="import" href="../bower_components/google-apis/google-jsapi.html">
|
<link rel="import" href="../bower_components/google-apis/google-jsapi.html">
|
||||||
|
|
||||||
<polymer-element name="state-timeline" attributes="stateHistory">
|
<polymer-element name="state-timeline" attributes="stateHistory isLoadingData">
|
||||||
<template>
|
<template>
|
||||||
<style>
|
<style>
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#loadingbox {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingmessage {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hiddencharts {
|
||||||
|
visibility:hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.singlelinechart {
|
||||||
|
min-height:140px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<div style='width: 100%; height: auto;' class="{{ {hiddencharts: !isLoading} | tokenList}}" >
|
||||||
|
<div layout horizontal center fit id="splash">
|
||||||
|
<div layout vertical center flex>
|
||||||
|
<div id="loadingbox">
|
||||||
|
<paper-spinner active="true"></paper-spinner><br />
|
||||||
|
<div class="loadingmessage">{{spinnerMessage}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<google-jsapi on-api-load="{{googleApiLoaded}}"></google-jsapi>
|
<google-jsapi on-api-load="{{googleApiLoaded}}"></google-jsapi>
|
||||||
<div id="timeline" style='width: 100%; height: auto;'></div>
|
<div id="timeline" style='width: 100%; height: auto;' class="{{ {hiddencharts: isLoadingData, singlelinechart: isSingleDevice && hasLineChart } | tokenList}}"></div>
|
||||||
|
<div id="line_graphs" style='width: 100%; height: auto;' class="{{ {hiddencharts: isLoadingData} | tokenList}}"></div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
Polymer({
|
Polymer({
|
||||||
apiLoaded: false,
|
apiLoaded: false,
|
||||||
stateHistory: null,
|
stateHistory: null,
|
||||||
|
isLoading: true,
|
||||||
|
isLoadingData: false,
|
||||||
|
spinnerMessage: "Loading data...",
|
||||||
|
isSingleDevice: false,
|
||||||
|
hasLineChart: false,
|
||||||
|
|
||||||
googleApiLoaded: function() {
|
googleApiLoaded: function() {
|
||||||
google.load("visualization", "1", {
|
google.load("visualization", "1", {
|
||||||
packages: ["timeline"],
|
packages: ["timeline", "corechart"],
|
||||||
callback: function() {
|
callback: function() {
|
||||||
this.apiLoaded = true;
|
this.apiLoaded = true;
|
||||||
this.drawChart();
|
this.drawChart();
|
||||||
@ -33,10 +65,17 @@
|
|||||||
this.drawChart();
|
this.drawChart();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isLoadingDataChanged: function() {
|
||||||
|
if(this.isLoadingData) {
|
||||||
|
isLoading = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
drawChart: function() {
|
drawChart: function() {
|
||||||
if (!this.apiLoaded || !this.stateHistory) {
|
if (!this.apiLoaded || !this.stateHistory) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
var container = this.$.timeline;
|
var container = this.$.timeline;
|
||||||
var chart = new google.visualization.Timeline(container);
|
var chart = new google.visualization.Timeline(container);
|
||||||
@ -55,21 +94,39 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// people can pass in history of 1 entityId or a collection.
|
|
||||||
|
|
||||||
|
this.hasLineChart = false;
|
||||||
|
this.isSingleDevice = false;
|
||||||
|
|
||||||
|
// people can pass in history of 1 entityId or a collection.
|
||||||
var stateHistory;
|
var stateHistory;
|
||||||
if (_.isArray(this.stateHistory[0])) {
|
if (_.isArray(this.stateHistory[0])) {
|
||||||
stateHistory = this.stateHistory;
|
stateHistory = this.stateHistory;
|
||||||
} else {
|
} else {
|
||||||
stateHistory = [this.stateHistory];
|
stateHistory = [this.stateHistory];
|
||||||
|
this.isSingleDevice = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var lineChartDevices = {};
|
||||||
|
var numTimelines = 0;
|
||||||
// stateHistory is a list of lists of sorted state objects
|
// stateHistory is a list of lists of sorted state objects
|
||||||
stateHistory.forEach(function(stateInfo) {
|
stateHistory.forEach(function(stateInfo) {
|
||||||
if(stateInfo.length === 0) return;
|
if(stateInfo.length === 0) return;
|
||||||
|
|
||||||
var entityDisplay = stateInfo[0].entityDisplay;
|
var entityDisplay = stateInfo[0].entityDisplay;
|
||||||
var newLastChanged, prevState = null, prevLastChanged = null;
|
var newLastChanged, prevState = null, prevLastChanged = null;
|
||||||
|
//get the latest update to get the graph type from the component attributes
|
||||||
|
var attributes = stateInfo[stateInfo.length - 1].attributes;
|
||||||
|
|
||||||
|
//if the device has a unit of meaurment it will be added as a line graph further down
|
||||||
|
if(attributes['unit_of_measurement']) {
|
||||||
|
if(!lineChartDevices[attributes['unit_of_measurement']]){
|
||||||
|
lineChartDevices[attributes['unit_of_measurement']] = [];
|
||||||
|
}
|
||||||
|
lineChartDevices[attributes['unit_of_measurement']].push(stateInfo);
|
||||||
|
this.hasLineChart = true
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
stateInfo.forEach(function(state) {
|
stateInfo.forEach(function(state) {
|
||||||
if (prevState !== null && state.state !== prevState) {
|
if (prevState !== null && state.state !== prevState) {
|
||||||
@ -86,10 +143,11 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
addRow(entityDisplay, prevState, prevLastChanged, new Date());
|
addRow(entityDisplay, prevState, prevLastChanged, new Date());
|
||||||
|
numTimelines++;
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
chart.draw(dataTable, {
|
chart.draw(dataTable, {
|
||||||
height: 55 + stateHistory.length * 42,
|
height: 55 + numTimelines * 42,
|
||||||
|
|
||||||
// interactive properties require CSS, the JS api puts it on the document
|
// interactive properties require CSS, the JS api puts it on the document
|
||||||
// instead of inside our Shadow DOM.
|
// instead of inside our Shadow DOM.
|
||||||
@ -103,6 +161,162 @@
|
|||||||
format: 'H:mm'
|
format: 'H:mm'
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**************************************************
|
||||||
|
The following code gererates line line graphs for devices with continuous
|
||||||
|
values(which are devices that have a unit_of_measurment values defined).
|
||||||
|
On each graph the devices are grouped by their unit of measurement, eg. all
|
||||||
|
sensors measuring MB will be a separate line on single graph. The google
|
||||||
|
chart API takes data as a 2 dimensional array in the format:
|
||||||
|
|
||||||
|
DateTime, device1, device2, device3
|
||||||
|
2015-04-01, 1, 2, 0
|
||||||
|
2015-04-01, 0, 1, 0
|
||||||
|
2015-04-01, 2, 1, 1
|
||||||
|
|
||||||
|
NOTE: the first column is a javascript date objects.
|
||||||
|
|
||||||
|
The first thing we do is build up the data with rows for each time of a state
|
||||||
|
change and initialise the values to 0. THen we loop through each device and
|
||||||
|
fill in its data.
|
||||||
|
|
||||||
|
**************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
while (this.$.line_graphs.firstChild) {
|
||||||
|
this.$.line_graphs.removeChild(this.$.line_graphs.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var key in lineChartDevices) {
|
||||||
|
var deviceStates = lineChartDevices[key];
|
||||||
|
|
||||||
|
if(this.isSingleDevice) {
|
||||||
|
container = this.$.timeline
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
container = document.createElement("DIV");
|
||||||
|
this.$.line_graphs.appendChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var chart = new google.visualization.LineChart(container);
|
||||||
|
|
||||||
|
|
||||||
|
var dataTable = new google.visualization.DataTable();
|
||||||
|
dataTable.addColumn({ type: 'datetime', id: 'Time' });
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
legend: { position: 'top' },
|
||||||
|
titlePosition: 'none',
|
||||||
|
vAxes: {
|
||||||
|
// Adds units to the left hand side of the graph
|
||||||
|
0: {title: key}
|
||||||
|
},
|
||||||
|
hAxis: {
|
||||||
|
format: 'H:mm'
|
||||||
|
},
|
||||||
|
lineWidth: 1,
|
||||||
|
chartArea:{left:'60',width:"95%"},
|
||||||
|
explorer: {
|
||||||
|
actions: ['dragToZoom', 'rightClickToReset', 'dragToPan'],
|
||||||
|
keepInBounds: true,
|
||||||
|
axis: 'horizontal',
|
||||||
|
maxZoomIn: 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
if(this.isSingleDevice) {
|
||||||
|
options.legend.position = 'none';
|
||||||
|
options.vAxes[0].title = null;
|
||||||
|
options.chartArea.left = 40;
|
||||||
|
options.chartArea.height = '80%';
|
||||||
|
options.chartArea.top = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a unique list of times of state changes for all the device
|
||||||
|
// for a particular unit of measureent.
|
||||||
|
var times = _.pluck(_.flatten(deviceStates), "lastChangedAsDate");
|
||||||
|
times = _.uniq(times, function(e) {
|
||||||
|
return e.getTime();
|
||||||
|
});
|
||||||
|
|
||||||
|
times = _.sortBy(times, function(o) { return o; });
|
||||||
|
|
||||||
|
var data = [];
|
||||||
|
var empty = new Array(deviceStates.length);
|
||||||
|
for(var i = 0; i < empty.length; i++) {
|
||||||
|
empty[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeIndex = 1;
|
||||||
|
var endDate = new Date();
|
||||||
|
var prevDate = times[0];
|
||||||
|
|
||||||
|
for(var i = 0; i < times.length; i++) {
|
||||||
|
var currentDate = new Date(prevDate);
|
||||||
|
|
||||||
|
// because we only have state changes we add an extra point at the same time
|
||||||
|
// that holds the previous state which makes the line display correctly
|
||||||
|
var beforePoint = new Date(times[i]);
|
||||||
|
data.push([beforePoint].concat(empty));
|
||||||
|
|
||||||
|
data.push([times[i]].concat(empty));
|
||||||
|
prevDate = times[i];
|
||||||
|
timeIndex++;
|
||||||
|
}
|
||||||
|
data.push([endDate].concat(empty));
|
||||||
|
|
||||||
|
|
||||||
|
var deviceCount = 0;
|
||||||
|
deviceStates.forEach(function(device) {
|
||||||
|
var attributes = device[device.length - 1].attributes;
|
||||||
|
dataTable.addColumn('number', attributes['friendly_name']);
|
||||||
|
|
||||||
|
var currentState = 0;
|
||||||
|
var previousState = 0;
|
||||||
|
var lastIndex = 0;
|
||||||
|
var count = 0;
|
||||||
|
var prevTime = data[0][0];
|
||||||
|
device.forEach(function(state) {
|
||||||
|
|
||||||
|
currentState = state.state;
|
||||||
|
var start = state.lastChangedAsDate;
|
||||||
|
if(state.state == 'None') {
|
||||||
|
currentState = previousState;
|
||||||
|
}
|
||||||
|
for(var i = lastIndex; i < data.length; i++) {
|
||||||
|
data[i][1 + deviceCount] = parseFloat(previousState);
|
||||||
|
// this is where data gets filled in for each time for the particular device
|
||||||
|
// because for each time two entires were create we fill the first one with the
|
||||||
|
// previous value and the second one with the new value
|
||||||
|
if(prevTime.getTime() == data[i][0].getTime() && data[i][0].getTime() == start.getTime()) {
|
||||||
|
data[i][1 + deviceCount] = parseFloat(currentState);
|
||||||
|
lastIndex = i;
|
||||||
|
prevTime = data[i][0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prevTime = data[i][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
previousState = currentState;
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
//fill in the rest of the Array
|
||||||
|
for(var i = lastIndex; i < data.length; i++) {
|
||||||
|
data[i][1 + deviceCount] = parseFloat(previousState);
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceCount++;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
dataTable.addRows(data);
|
||||||
|
chart.draw(dataTable, options);
|
||||||
|
}
|
||||||
|
this.isLoading = (!this.isLoadingData) ? false : true;
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<state-card-content stateObj="{{stateObj}}" style='margin-bottom: 24px;'>
|
<state-card-content stateObj="{{stateObj}}" style='margin-bottom: 24px;'>
|
||||||
</state-card-content>
|
</state-card-content>
|
||||||
<state-timeline stateHistory="{{stateHistory}}"></state-timeline>
|
<state-timeline stateHistory="{{stateHistory}}" isLoadingData="{{isLoadingHistoryData}}"></state-timeline>
|
||||||
<more-info-content
|
<more-info-content
|
||||||
stateObj="{{stateObj}}"
|
stateObj="{{stateObj}}"
|
||||||
dialogOpen="{{dialogOpen}}"></more-info-content>
|
dialogOpen="{{dialogOpen}}"></more-info-content>
|
||||||
@ -30,6 +30,7 @@ Polymer(Polymer.mixin({
|
|||||||
stateHistory: null,
|
stateHistory: null,
|
||||||
hasHistoryComponent: false,
|
hasHistoryComponent: false,
|
||||||
dialogOpen: false,
|
dialogOpen: false,
|
||||||
|
isLoadingHistoryData: false,
|
||||||
|
|
||||||
observe: {
|
observe: {
|
||||||
'stateObj.attributes': 'reposition'
|
'stateObj.attributes': 'reposition'
|
||||||
@ -67,7 +68,7 @@ Polymer(Polymer.mixin({
|
|||||||
} else {
|
} else {
|
||||||
newHistory = null;
|
newHistory = null;
|
||||||
}
|
}
|
||||||
|
this.isLoadingHistoryData = false;
|
||||||
if (newHistory !== this.stateHistory) {
|
if (newHistory !== this.stateHistory) {
|
||||||
this.stateHistory = newHistory;
|
this.stateHistory = newHistory;
|
||||||
}
|
}
|
||||||
@ -87,6 +88,7 @@ Polymer(Polymer.mixin({
|
|||||||
this.stateHistoryStoreChanged();
|
this.stateHistoryStoreChanged();
|
||||||
|
|
||||||
if (this.hasHistoryComponent && stateHistoryStore.isStale(entityId)) {
|
if (this.hasHistoryComponent && stateHistoryStore.isStale(entityId)) {
|
||||||
|
this.isLoadingHistoryData = true;
|
||||||
stateHistoryActions.fetch(entityId);
|
stateHistoryActions.fetch(entityId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit e048bf6ece91983b9f03aafeb414ae5c535288a2
|
Subproject commit 282004e3e27134a3de1b9c0e6c264ce811f3e510
|
@ -26,7 +26,7 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div flex class="{{ {content: true, narrow: narrow, wide: !narrow} | tokenList }}">
|
<div flex class="{{ {content: true, narrow: narrow, wide: !narrow} | tokenList }}">
|
||||||
<state-timeline stateHistory="{{stateHistory}}"></state-timeline>
|
<state-timeline stateHistory="{{stateHistory}}" isLoadingData="{{isLoadingData}}"></state-timeline>
|
||||||
</div>
|
</div>
|
||||||
</partial-base>
|
</partial-base>
|
||||||
</template>
|
</template>
|
||||||
@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
Polymer(Polymer.mixin({
|
Polymer(Polymer.mixin({
|
||||||
stateHistory: null,
|
stateHistory: null,
|
||||||
|
isLoadingData: false,
|
||||||
|
|
||||||
attached: function() {
|
attached: function() {
|
||||||
this.listenToStores(true);
|
this.listenToStores(true);
|
||||||
@ -47,13 +48,18 @@
|
|||||||
|
|
||||||
stateHistoryStoreChanged: function(stateHistoryStore) {
|
stateHistoryStoreChanged: function(stateHistoryStore) {
|
||||||
if (stateHistoryStore.isStale()) {
|
if (stateHistoryStore.isStale()) {
|
||||||
|
this.isLoadingData = true;
|
||||||
stateHistoryActions.fetchAll();
|
stateHistoryActions.fetchAll();
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
this.isLoadingData = false;
|
||||||
|
}
|
||||||
|
|
||||||
this.stateHistory = stateHistoryStore.all;
|
this.stateHistory = stateHistoryStore.all;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleRefreshClick: function() {
|
handleRefreshClick: function() {
|
||||||
|
this.isLoadingData = true;
|
||||||
stateHistoryActions.fetchAll();
|
stateHistoryActions.fetchAll();
|
||||||
},
|
},
|
||||||
}, storeListenerMixIn));
|
}, storeListenerMixIn));
|
||||||
|
@ -50,8 +50,10 @@ def state_changes_during_period(start_time, end_time=None, entity_id=None):
|
|||||||
|
|
||||||
result = defaultdict(list)
|
result = defaultdict(list)
|
||||||
|
|
||||||
|
entity_ids = [entity_id] if entity_id is not None else None
|
||||||
|
|
||||||
# Get the states at the start time
|
# Get the states at the start time
|
||||||
for state in get_states(start_time):
|
for state in get_states(start_time, entity_ids):
|
||||||
state.last_changed = start_time
|
state.last_changed = start_time
|
||||||
result[state.entity_id].append(state)
|
result[state.entity_id].append(state)
|
||||||
|
|
||||||
@ -98,6 +100,7 @@ def get_state(point_in_time, entity_id, run=None):
|
|||||||
return states[0] if states else None
|
return states[0] if states else None
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
""" Setup history hooks. """
|
""" Setup history hooks. """
|
||||||
hass.http.register_path(
|
hass.http.register_path(
|
||||||
@ -113,6 +116,7 @@ def setup(hass, config):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
def _api_last_5_states(handler, path_match, data):
|
def _api_last_5_states(handler, path_match, data):
|
||||||
""" Return the last 5 states for an entity id as JSON. """
|
""" Return the last 5 states for an entity id as JSON. """
|
||||||
|
Loading…
x
Reference in New Issue
Block a user