First draft of climate (#91)

* First draft of climate

* Use thermostat state card info

* Make sure temperature is updated
This commit is contained in:
John Arild Berentsen 2016-08-19 09:28:35 +02:00 committed by Paulus Schoutsen
parent 2c61fb2b72
commit 533ac9fade
6 changed files with 407 additions and 1 deletions

View File

@ -182,6 +182,41 @@
};
}
states.forEach(processState);
} else if (domain === 'climate') {
// We differentiate between thermostats that have a target temperature
// range versus ones that have just a target temperature
hasTargetRange = states.reduce(
function (cum, cur) {
return cum || cur.attributes.target_temp_high !== cur.attributes.target_temp_low;
}, false);
dataTable.addColumn('number', name + ' current temperature');
if (hasTargetRange) {
dataTable.addColumn('number', name + ' target temperature high');
dataTable.addColumn('number', name + ' target temperature low');
noInterpolations = [false, true, true];
processState = function (state) {
var curTemp = saveParseFloat(state.attributes.current_temperature);
var targetHigh = saveParseFloat(state.attributes.target_temp_high);
var targetLow = saveParseFloat(state.attributes.target_temp_low);
pushData([state.lastUpdatedAsDate, curTemp, targetHigh, targetLow], noInterpolations);
};
} else {
dataTable.addColumn('number', name + ' target temperature');
noInterpolations = [false, true];
processState = function (state) {
var curTemp = saveParseFloat(state.attributes.current_temperature);
var target = saveParseFloat(state.attributes.temperature);
pushData([state.lastUpdatedAsDate, curTemp, target], noInterpolations);
};
}
states.forEach(processState);
} else {
dataTable.addColumn('number', name);

View File

@ -0,0 +1,300 @@
<link rel='import' href='../../bower_components/polymer/polymer.html'>
<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
<link rel='import' href='../../bower_components/paper-slider/paper-slider.html'>
<link rel='import' href='../../bower_components/paper-menu/paper-menu.html'>
<link rel='import' href='../../bower_components/paper-item/paper-item.html'>
<link rel='import' href='../../bower_components/paper-dropdown-menu/paper-dropdown-menu.html'>
<link rel='import' href='../../bower_components/paper-toggle-button/paper-toggle-button.html'>
<dom-module id='more-info-climate'>
<template>
<style is="custom-style" include="iron-flex"></style>
<style>
:host {
color: var(--primary-text-color);
}
.container-away_mode,
.container-aux_heat,
.container-temperature,
.container-humidity,
.container-operation_list,
.container-fan_list,
.container-swing_list {
display: none;
}
.has-away_mode .container-away_mode,
.has-aux_heat .container-aux_heat,
.has-temperature .container-temperature,
.has-humidity .container-humidity,
.has-operation_list .container-operation_list,
.has-fan_list .container-fan_list,
.has-swing_list .container-swing_list {
display: block;
}
.container-operation_list iron-icon,
.container-fan_list iron-icon,
.container-swing_list iron-icon {
margin: 22px 16px 0 0;
}
paper-dropdown-menu {
width: 100%;
}
.single-row {
padding: 8px 0;
}
</style>
<div class$='[[computeClassNames(stateObj)]]'>
<div class='container-temperature'>
<div class='single-row'>
<div>Target Temperature</div>
<paper-slider
min='[[stateObj.attributes.min_temp]]'
max='[[stateObj.attributes.max_temp]]'
step='0.5' pin
value='[[stateObj.attributes.temperature]]'
on-change='targetTemperatureSliderChanged'>
</paper-slider>
</div>
</div>
<div class='container-humidity'>
<div class='single-row'>
<div>Target Humidity</div>
<paper-slider
min='[[stateObj.attributes.min_humidity]]'
max='[[stateObj.attributes.max_humidity]]'
step='1' pin
value='[[stateObj.attributes.humidity]]'
on-change='targetHumiditySliderChanged'>
</paper-slider>
</div>
</div>
<div class='container-operation_list'>
<div class='controls'>
<paper-dropdown-menu label-float label='Operation'>
<paper-menu class="dropdown-content" selected="{{operationIndex}}">
<template is='dom-repeat'
items='[[stateObj.attributes.operation_list]]'>
<paper-item>[[item]]</paper-item>
</template>
</paper-menu>
</paper-dropdown-menu>
</div>
</div>
<div class='container-fan_list'>
<paper-dropdown-menu label-float label='Fan Mode'>
<paper-menu class="dropdown-content" selected="{{fanIndex}}">
<template is='dom-repeat'
items='[[stateObj.attributes.fan_list]]'>
<paper-item>[[item]]</paper-item>
</template>
</paper-menu>
</paper-dropdown-menu>
</div>
<div class='container-swing_list'>
<paper-dropdown-menu label-float label='Swing Mode'>
<paper-menu class="dropdown-content" selected="{{swingIndex}}">
<template is='dom-repeat'
items='[[stateObj.attributes.swing_list]]'>
<paper-item>[[item]]</paper-item>
</template>
</paper-menu>
</paper-dropdown-menu>
</div>
<div class='container-away_mode'>
<div class='center horizontal layout single-row'>
<div class='flex'>Away Mode</div>
<paper-toggle-button
checked='[[awayToggleChecked]]'
on-change='awayToggleChanged'>
</paper-toggle-button>
</div>
</div>
<div class='container-aux_heat'>
<div class='center horizontal layout single-row'>
<div class='flex'>Aux Heat</div>
<paper-toggle-button
checked='[[auxToggleChecked]]'
on-change='auxToggleChanged'>
</paper-toggle-button>
</div>
</div>
</div>
</template>
</dom-module>
<script>
Polymer({
is: 'more-info-climate',
properties: {
hass: {
type: Object,
},
stateObj: {
type: Object,
observer: 'stateObjChanged',
},
operationIndex: {
type: Number,
value: -1,
observer: 'handleOperationmodeChanged',
},
fanIndex: {
type: Number,
value: -1,
observer: 'handleFanmodeChanged',
},
swingIndex: {
type: Number,
value: -1,
observer: 'handleSwingmodeChanged',
},
awayToggleChecked: {
type: Boolean,
},
auxToggleChecked: {
type: Boolean,
},
},
stateObjChanged: function (newVal) {
this.targetTemperatureSliderValue = newVal.attributes.temperature;
this.awayToggleChecked = newVal.attributes.away_mode === 'on';
this.auxheatToggleChecked = newVal.attributes.aux_heat === 'on';
if (newVal.attributes.fan_list) {
this.fanIndex = newVal.attributes.fan_list.indexOf(
newVal.attributes.fan_mode);
} else {
this.fanIndex = -1;
}
if (newVal.attributes.operation_list) {
this.operationIndex = newVal.attributes.operation_list.indexOf(
newVal.attributes.operation_mode);
} else {
this.operationIndex = -1;
}
if (newVal.attributes.swing_list) {
this.swingIndex = newVal.attributes.swing_list.indexOf(
newVal.attributes.swing_mode);
} else {
this.swingIndex = -1;
}
this.async(function () {
this.fire('iron-resize');
}.bind(this), 500);
},
computeClassNames: function (stateObj) {
return 'more-info-climate' + window.hassUtil.attributeClassNames(
stateObj,
['away_mode', 'aux_heat', 'temperature', 'humidity', 'operation_list',
'fan_list', 'swing_list']
);
},
targetTemperatureSliderChanged: function (ev) {
var temperature = ev.target.value;
if (temperature === this.stateObj.attributes.temperature) return;
this.callServiceHelper('set_temperature', { temperature: temperature });
},
targetHumiditySliderChanged: function (ev) {
var humidity = ev.target.value;
if (humidity === this.stateObj.attributes.humidity) return;
this.callServiceHelper('set_humidity', { humidity: humidity });
},
awayToggleChanged: function (ev) {
var oldVal = this.stateObj.attributes.away_mode === 'on';
var newVal = ev.target.checked;
if (oldVal === newVal) return;
this.callServiceHelper('set_away_mode', { away_mode: newVal });
},
auxToggleChanged: function (ev) {
var oldVal = this.stateObj.attributes.aux_heat === 'on';
var newVal = ev.target.checked;
if (oldVal === newVal) return;
this.callServiceHelper('set_aux_heat', { aux_heat: newVal });
},
handleFanmodeChanged: function (fanIndex) {
var fanInput;
// Selected Option will transition to '' before transitioning to new value
if (fanIndex === '' || fanIndex === -1) return;
fanInput = this.stateObj.attributes.fan_list[fanIndex];
if (fanInput === this.stateObj.attributes.fan_mode) return;
this.callServiceHelper('set_fan_mode', { fan_mode: fanInput });
},
handleOperationmodeChanged: function (operationIndex) {
var operationInput;
// Selected Option will transition to '' before transitioning to new value
if (operationIndex === '' || operationIndex === -1) return;
operationInput = this.stateObj.attributes.operation_list[operationIndex];
if (operationInput === this.stateObj.attributes.operation_mode) return;
this.callServiceHelper('set_operation_mode', { operation_mode: operationInput });
},
handleSwingmodeChanged: function (swingIndex) {
var swingInput;
// Selected Option will transition to '' before transitioning to new value
if (swingIndex === '' || swingIndex === -1) return;
swingInput = this.stateObj.attributes.swing_list[swingIndex];
if (swingInput === this.stateObj.attributes.swing_mode) return;
this.callServiceHelper('set_swing_mode', { swing_mode: swingInput });
},
callServiceHelper: function (service, data) {
// We call stateChanged after a successful call to re-sync the inputs
// with the state. It will be out of sync if our service call did not
// result in the entity to be turned on. Since the state is not changing,
// the resync is not called automatic.
/* eslint-disable no-param-reassign */
data.entity_id = this.stateObj.entityId;
/* eslint-enable no-param-reassign */
this.hass.serviceActions.callService('climate', service, data)
.then(function () {
this.stateObjChanged(this.stateObj);
});
},
});
</script>

View File

@ -1,5 +1,6 @@
<link rel='import' href='../../bower_components/polymer/polymer.html'>
<link rel='import' href='more-info-climate.html'>
<link rel='import' href='more-info-default.html'>
<link rel='import' href='more-info-group.html'>
<link rel='import' href='more-info-sun.html'>

View File

@ -0,0 +1,65 @@
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="../components/entity/state-info.html">
<dom-module id="state-card-climate">
<template>
<style is="custom-style" include="iron-flex iron-flex-alignment"></style>
<style>
:host {
@apply(--paper-font-body1);
line-height: 1.5;
}
.state {
margin-left: 16px;
text-align: right;
}
.target {
color: var(--primary-text-color);
}
.current {
color: var(--secondary-text-color);
}
</style>
<div class='horizontal justified layout'>
<state-info state-obj="[[stateObj]]" in-dialog='[[inDialog]]'></state-info>
<div class='state'>
<div class='target'>[[computeTargetTemperature(stateObj)]]</div>
<div class='current'>
<span>Currently: </span>
<span>[[stateObj.attributes.current_temperature]]</span>
<span> </span>
<span>[[stateObj.attributes.unit_of_measurement]]</span>
</div>
</div>
</div>
</template>
</dom-module>
<script>
Polymer({
is: 'state-card-climate',
properties: {
inDialog: {
type: Boolean,
value: false,
},
stateObj: {
type: Object,
},
},
computeTargetTemperature: function (stateObj) {
return stateObj.attributes.temperature + ' ' + stateObj.attributes.unit_of_measurement;
},
});
</script>

View File

@ -1,5 +1,6 @@
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="state-card-climate.html">
<link rel="import" href="state-card-configurator.html">
<link rel="import" href="state-card-display.html">
<link rel="import" href="state-card-hvac.html">

View File

@ -11,6 +11,7 @@ window.hassUtil.DEFAULT_ICON = 'mdi:bookmark';
window.hassUtil.OFF_STATES = ['off', 'closed', 'unlocked'];
window.hassUtil.DOMAINS_WITH_CARD = [
'climate',
'configurator',
'hvac',
'input_select',
@ -24,7 +25,7 @@ window.hassUtil.DOMAINS_WITH_CARD = [
];
window.hassUtil.DOMAINS_WITH_MORE_INFO = [
'light', 'group', 'sun', 'configurator', 'thermostat', 'script',
'light', 'group', 'sun', 'climate', 'configurator', 'thermostat', 'script',
'media_player', 'camera', 'updater', 'alarm_control_panel', 'lock',
'hvac',
];
@ -200,6 +201,9 @@ window.hassUtil.domainIcon = function (domain, state) {
case 'camera':
return 'mdi:video';
case 'climate':
return 'mdi:nest-thermostat';
case 'configurator':
return 'mdi:settings';