Upgrades HACS

This commit is contained in:
dfcarvajal
2020-12-27 17:22:59 +01:00
parent 08ef839730
commit 5aa53ae7ae
38 changed files with 2652 additions and 557 deletions

View File

@@ -0,0 +1,740 @@
const LitElement =
window.LitElement || Object.getPrototypeOf(customElements.get('home-assistant') || customElements.get('hui-view'));
const { html, css } = LitElement.prototype;
const locale = {
da: {
minPrice: 'Minimumspris i dag:',
maxPrice: 'Maksimal pris i dag:',
minPriceNextDay: 'Minimumspris i morgen:',
maxPriceNextDay: 'Maksimal pris i morgen:',
infoNoNextDay: 'Morgendagens data er endnu ikke tilgængelige',
from: 'fra',
to: 'til'
},
de: {
minPrice: 'Mindestpreis heute:',
maxPrice: 'Maximaler preis heute:',
minPriceNextDay: 'Mindestpreis morgen:',
maxPriceNextDay: 'Maximaler preis morgen:',
infoNoNextDay: 'Die Daten von morgen sind noch nicht verfügbar',
from: 'von',
to: 'bis'
},
en: {
minPrice: 'Lowest price today:',
maxPrice: 'Highest price today:',
minPriceNextDay: 'Lowest price tomorrow:',
maxPriceNextDay: 'Highest price tomorrow:',
infoNoNextDay: "Tomorrow's data is not yet available",
from: 'from',
to: 'to'
},
es: {
minPrice: 'Precio mínimo hoy:',
maxPrice: 'Precio máximo hoy:',
minPriceNextDay: 'Precio mínimo mañana:',
maxPriceNextDay: 'Precio máximo mañana:',
infoNoNextDay: 'Los datos de mañana no están disponibles aún',
from: 'de',
to: 'a'
},
fr: {
minPrice: "Prix minimum aujourd'hui:",
maxPrice: "Prix maximum aujourd'hui:",
minPriceNextDay: 'Prix minimum demain:',
maxPriceNextDay: 'Prix maximum demain:',
infoNoNextDay: 'Les données de demain ne sont pas encore disponibles',
from: 'de',
to: 'à'
},
nl: {
minPrice: 'Minimumspris i dag:',
maxPrice: 'Maksimal pris i dag:',
minPriceNextDay: 'Minimum prijs morgen:',
maxPriceNextDay: 'Maximale prijs morgen:',
infoNoNextDay: 'De gegevens van morgen zijn nog niet beschikbaar',
from: 'fra',
to: 'til'
},
ru: {
minPrice: 'Минимальная цена сегодня:',
maxPrice: 'Максимальная цена сегодня:',
minPriceNextDay: 'Минимальная цена завтра:',
maxPriceNextDay: 'Максимальная цена завтра:',
infoNoNextDay: 'Данные завтра еще не доступны',
from: 'С',
to: 'до'
},
sv: {
minPrice: 'Lägsta pris idag:',
maxPrice: 'Maxpris idag:',
minPriceNextDay: 'Lägsta pris imorgon:',
maxPriceNextDay: 'Maxpris i morgon:',
infoNoNextDay: 'Morgondagens data är ännu inte tillgängliga',
from: '',
to: 'till'
}
};
const tariffPeriodIconColors = {
error: '--error-color',
normal: '--warning-color',
peak: '--error-color',
valley: '--success-color',
'super-valley': '--info-color'
};
const tariffPeriodIcons = {
error:
'M 28.342306,10.429944 27.798557,32.995546 H 24.243272 L 23.657695,10.429944 Z M 28.133172,41.570057 H 23.86683 v -4.412736 h 4.266342 z',
normal:
'M 31.032172,16.612305 20.999855,32.113255 15.66609,25.065424 H 0.97821381 a 25.017275,25.017275 0 0 0 -0.0332829,0.949884 25.017275,25.017275 0 0 0 0.0468985,0.940092 H 14.800215 l 6.199595,8.453119 10.03232,-15.502917 5.335714,7.049798 h 14.578421 a 25.017275,25.017275 0 0 0 0.03328,-0.940092 25.017275,25.017275 0 0 0 -0.0469,-0.949884 H 37.233737 Z',
peak:
'M 2.5238392,34.768609 A 25.003164,25.003164 0 0 1 1.9104804,32.879664 h 8.6436716 l 15.49805,-22.870055 15.121052,22.870055 h 8.891749 a 25.003164,25.003164 0 0 1 -0.628986,1.888945 H 40.038344 L 26.052202,13.679995 12.06606,34.768609 Z',
valley:
'm 2.5238392,17.238401 a 25.003164,25.003164 0 0 0 -0.6133588,1.888945 h 8.6436716 l 15.49805,22.870055 15.121052,-22.870055 h 8.891749 A 25.003164,25.003164 0 0 0 49.436017,17.238401 H 40.038344 L 26.052202,38.327015 12.06606,17.238401 Z',
'super-valley':
'm 30.867213,27.342466 c 0,0.670334 -0.543413,1.213747 -1.213747,1.213746 -0.670333,-10e-7 -1.213744,-0.543413 -1.213744,-1.213746 0,-0.670333 0.543411,-1.213745 1.213744,-1.213746 0.670334,-1e-6 1.213747,0.543412 1.213747,1.213746 z m -7.282476,0 c 0,0.670333 -0.543412,1.213746 -1.213745,1.213746 -0.670334,0 -1.213746,-0.543412 -1.213746,-1.213746 0,-0.670334 0.543412,-1.213746 1.213746,-1.213746 0.670333,0 1.213745,0.543413 1.213745,1.213746 z m 8.026907,-6.869803 c -0.161832,-0.477407 -0.614966,-0.817256 -1.149013,-0.817256 h -8.900804 c -0.534048,0 -0.979088,0.339849 -1.149012,0.817256 l -1.683061,4.846893 v 6.473312 c 0,0.445039 0.364123,0.809164 0.809163,0.809164 h 0.809164 c 0.445041,0 0.809165,-0.364125 0.809165,-0.809164 v -0.809165 h 9.709967 v 0.809165 c 0,0.445039 0.364125,0.809164 0.809164,0.809164 h 0.809165 c 0.445039,0 0.809163,-0.364125 0.809163,-0.809164 v -6.473312 z m -9.800018,0.767664 h 8.393115 l 0.841531,2.49431 H 20.970096 Z m 9.89816,8.158458 H 20.314672 v -3.837522 l 0.0971,-0.275116 h 11.209006 l 0.089,0.275116 z M 25.208235,17.875001 v -1.607989 h -3.215979 l 4.823966,-2.411981 v 1.607988 H 30.0322 Z M 2.5904451,17.061236 C 2.3615878,17.681074 2.1574473,18.309759 1.9785073,18.945805 H 10.602006 V 37.331696 H 41.150085 V 18.945805 h 8.871408 c -0.184075,-0.636272 -0.393416,-1.26496 -0.62753,-1.884569 H 38.720725 V 35.001194 H 12.908652 V 17.061236 Z'
};
const fireEvent = (node, type, detail, options) => {
options = options || {};
detail = detail === null || detail === undefined ? {} : detail;
const event = new Event(type, {
bubbles: options.bubbles === undefined ? true : options.bubbles,
cancelable: Boolean(options.cancelable),
composed: options.composed === undefined ? true : options.composed
});
event.detail = detail;
node.dispatchEvent(event);
return event;
};
function hasConfigOrEntityChanged(element, changedProps) {
if (changedProps.has('_config')) {
return true;
}
const oldHass = changedProps.get('hass');
if (oldHass) {
return oldHass.states[element._config.entity] !== element.hass.states[element._config.entity];
}
return true;
}
class PVPCHourlyPricingCard extends LitElement {
static get properties() {
return {
_config: { type: Object },
hass: { type: Object }
};
}
static async getConfigElement() {
await import('./pvpc-hourly-pricing-card-editor.js');
return document.createElement('pvpc-hourly-pricing-card-editor');
}
static getStubConfig(hass, entities, entitiesFallback) {
const entity = Object.keys(hass.states).find((eid) =>
Object.keys(hass.states[eid].attributes).some((aid) => aid == 'min_price_at')
);
return { entity: entity };
}
setConfig(config) {
if (!config.entity) {
throw new Error('Please define a "Spain electricity hourly pricing (PVPC)" entity');
}
this._config = config;
this.setPVPCHourlyPricingObj();
}
setPVPCHourlyPricingObj() {
if (!this.hass) return;
this.pvpcHourlyPricingObj = this._config.entity in this.hass.states ? this.hass.states[this._config.entity] : null;
if (!this.pvpcHourlyPricingObj) return;
this.despiction = this.getDespiction(this.pvpcHourlyPricingObj.attributes);
}
shouldUpdate(changedProps) {
return hasConfigOrEntityChanged(this, changedProps);
}
updated(param) {
this.setPVPCHourlyPricingObj();
let chart = this.shadowRoot.getElementById('Chart');
if (chart) {
chart.data = this.ChartData;
chart.hass = this.hass;
}
}
render() {
if (!this._config || !this.hass) {
return html``;
}
this.setPVPCHourlyPricingObj();
this.numberElements = 0;
this.lang = this.hass.selectedLanguage || this.hass.language;
if (!this.pvpcHourlyPricingObj) {
return html`
<style>
.not-found {
flex: 1;
background-color: yellow;
padding: 8px;
}
</style>
<ha-card>
<div class="not-found">Entity not available: ${this._config.entity}</div>
</ha-card>
`;
}
return html`
<ha-card header="${this._config.name ? this._config.name : ''}">
${this._config.current !== false ? this.renderCurrent() : ''}
${this._config.details !== false ? this.renderDetails() : ''}
${this._config.graph !== false ? this.renderGraph() : ''}
${this._config.info !== false ? this.renderInfo() : ''}
</ha-card>
`;
}
renderCurrent() {
this.numberElements++;
const tariffPeriod = this.getTariffPeriod(this.pvpcHourlyPricingObj.attributes.tariff);
const style = getComputedStyle(document.body);
const iconColor = style.getPropertyValue(tariffPeriodIconColors[tariffPeriod]);
return html`
<div class="current tappable ${this.numberElements > 1 ? 'spacer' : ''}" @click="${this._handleClick}">
<svg class="period-icon" viewBox="0 0 52 52">
<circle fill="${iconColor}" r="25" cy="26" cx="26" />
<path fill="#f9f9f9" d="${tariffPeriodIcons[tariffPeriod]}" />
</svg>
<span class="currentPrice">${this.getFixedFloat(this.pvpcHourlyPricingObj.state)}</span>
<span class="currentPriceUnit"> ${this.pvpcHourlyPricingObj.attributes.unit_of_measurement}</span>
</div>
`;
}
renderDetails() {
if (!this.despiction) {
return html``;
}
const minPrice = this.getFixedFloat(this.despiction.minPrice);
const minPriceFrom = this.getTimeString(new Date().setHours(this.despiction.minIndex, 0));
const minPriceTo = this.getTimeString(new Date().setHours(this.despiction.minIndex + 1, 0));
const maxPrice = this.getFixedFloat(this.despiction.maxPrice);
const maxPriceFrom = this.getTimeString(new Date().setHours(this.despiction.maxIndex, 0));
const maxPriceTo = this.getTimeString(new Date().setHours(this.despiction.maxIndex + 1, 0));
const minPriceNextDay = this.getFixedFloat(this.despiction.minPriceNextDay);
const minPriceFromNextDay = this.getTimeString(new Date().setHours(this.despiction.minIndexNextDay, 0));
const minPriceToNextDay = this.getTimeString(new Date().setHours(this.despiction.minIndexNextDay + 1, 0));
const maxPriceNextDay = this.getFixedFloat(this.despiction.maxPriceNextDay);
const maxPriceFromNextDay = this.getTimeString(new Date().setHours(this.despiction.maxIndexNextDay, 0));
const maxPriceToNextDay = this.getTimeString(new Date().setHours(this.despiction.maxIndexNextDay + 1, 0));
this.numberElements++;
return html`
<ul class="details tappable ${this.numberElements > 1 ? 'spacer' : ''}" @click="${this._handleClick}">
<li>
<ha-icon icon="mdi:thumb-up-outline"></ha-icon>
${this.ll('minPrice')} ${minPrice}${this.pvpcHourlyPricingObj.attributes.unit_of_measurement}
${this.ll('from')} ${minPriceFrom} ${this.ll('to')} ${minPriceTo}
</li>
<li>
<ha-icon icon="mdi:thumb-down-outline"></ha-icon>
${this.ll('maxPrice')} ${maxPrice}${this.pvpcHourlyPricingObj.attributes.unit_of_measurement}
${this.ll('from')} ${maxPriceFrom} ${this.ll('to')} ${maxPriceTo}
</li>
${this.despiction.minPriceNextDay
? html` <li>
<ha-icon icon="mdi:thumb-up-outline"></ha-icon>
${this.ll('minPriceNextDay')}
${minPriceNextDay}${this.pvpcHourlyPricingObj.attributes.unit_of_measurement} ${this.ll('from')}
${minPriceFromNextDay} ${this.ll('to')} ${minPriceToNextDay}
</li>
<li>
<ha-icon icon="mdi:thumb-down-outline"></ha-icon>
${this.ll('maxPriceNextDay')}
${maxPriceNextDay}${this.pvpcHourlyPricingObj.attributes.unit_of_measurement} ${this.ll('from')}
${maxPriceFromNextDay} ${this.ll('to')} ${maxPriceToNextDay}
</li>`
: ''}
</ul>
`;
}
renderGraph() {
if (!this.despiction) {
return html``;
}
this.numberElements++;
this.drawChart();
return html`
<div class="clear ${this.numberElements > 1 ? 'spacer' : ''}">
<ha-chart-base id="Chart"></ha-chart-base>
</div>
`;
}
renderInfo() {
if (!this.despiction) {
return html``;
}
this.numberElements++;
if (!this.despiction.minPriceNextDay) {
return html`
<div class="info clear ${this.numberElements > 1 ? 'spacer' : ''}">${this.ll('infoNoNextDay')}</div>
`;
} else {
return html``;
}
}
drawChart() {
if (!this.despiction) return;
const that = this;
const style = getComputedStyle(document.body);
const legendTextColor = style.getPropertyValue('--primary-text-color');
const axisTextColor = style.getPropertyValue('--secondary-text-color');
const dividerColor = style.getPropertyValue('--divider-color');
const selectionColor = style.getPropertyValue('--paper-grey-500');
const today = new Date();
const minIndex = this.despiction.minIndex;
const maxIndex = this.despiction.maxIndex;
const minIndexNextDay = this.despiction.minIndexNextDay;
const maxIndexNextDay = this.despiction.maxIndexNextDay;
const hasNextDayData = this.despiction.pricesNextDay[0] !== undefined;
const minIcon = '▼';
const maxIcon = '▲';
const chartOptions = {
type: 'line',
data: {
labels: this.despiction.dateTime,
datasets: [
{
label: that.getDateString(today),
type: 'line',
data: this.despiction.prices,
borderWidth: 2.0,
pointRadius: 0.0,
pointHitRadius: 0.0,
fill: false,
steppedLine: true
}
]
},
options: {
animation: {
duration: 300,
easing: 'linear',
onComplete: function () {
const chartInstance = this.chart;
const ctx = chartInstance.ctx;
const fontSize = 12;
const fontStyle = 'normal';
const fontFamily = 'Roboto';
ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const meta = chartInstance.controller.getDatasetMeta(0);
const minBarStart = meta.data[minIndex];
const minBarEnd = meta.data[minIndex + 1];
const pointToPointCenterXOffset = (minBarEnd._model.x - minBarStart._model.x) / 2;
const maxBar = meta.data[maxIndex];
const iconYOffset = 8;
ctx.fillStyle = meta.dataset._model.borderColor;
ctx.fillText(minIcon, minBarStart._model.x + pointToPointCenterXOffset, minBarStart._model.y - iconYOffset);
ctx.fillText(maxIcon, maxBar._model.x + pointToPointCenterXOffset, maxBar._model.y - iconYOffset);
ctx.save();
const selectedIndex =
chartInstance.tooltip._active &&
chartInstance.tooltip._active.length > 0 &&
chartInstance.tooltip._active[0]._index < 24
? chartInstance.tooltip._active[0]._index
: today.getHours();
const yaxis = meta.controller.chart.scales['y-axis-0'];
const xBarStart = meta.data[selectedIndex]._model.x;
const xBarEnd = meta.data[selectedIndex + 1]._model.x;
const yBarStart = yaxis.top;
const yBarEnd = yaxis.bottom;
ctx.globalAlpha = 0.6;
ctx.beginPath();
ctx.moveTo(xBarStart, yBarStart);
ctx.lineTo(xBarStart, yBarEnd);
ctx.strokeStyle = selectionColor;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(xBarEnd, yBarStart);
ctx.lineTo(xBarEnd, yBarEnd);
ctx.strokeStyle = selectionColor;
ctx.stroke();
ctx.globalAlpha = 0.3;
ctx.fillStyle = selectionColor;
ctx.fillRect(xBarStart, yBarStart, xBarEnd - xBarStart, yBarEnd - yBarStart);
ctx.restore();
if (hasNextDayData) {
const meta_next_day = chartInstance.controller.getDatasetMeta(1);
const minNextDayBar = meta_next_day.data[minIndexNextDay];
const maxNextDayBar = meta_next_day.data[maxIndexNextDay];
ctx.fillStyle = meta_next_day.dataset._model.borderColor;
ctx.fillText(
minIcon,
minNextDayBar._model.x + pointToPointCenterXOffset,
minNextDayBar._model.y - iconYOffset
);
ctx.fillText(
maxIcon,
maxNextDayBar._model.x + pointToPointCenterXOffset,
maxNextDayBar._model.y - iconYOffset
);
}
}
},
legend: {
display: true,
labels: {
fontColor: legendTextColor,
fontSize: 14,
usePointStyle: true,
boxWidth: 6
}
},
scales: {
xAxes: [
{
type: 'time',
maxBarThickness: 15,
display: false,
ticks: {
display: false
},
gridLines: {
display: false
}
},
{
position: 'bottom',
gridLines: {
display: true,
drawTicks: false,
drawBorder: false,
color: dividerColor
},
ticks: {
display: true,
padding: 10,
source: 'labels',
autoSkip: true,
fontColor: axisTextColor,
maxRotation: 0,
callback: function (value, index, values) {
return that.getHourString.call(that, value);
}
}
}
],
yAxes: [
{
position: 'left',
gridLines: {
display: true,
drawBorder: false,
drawTicks: false,
color: dividerColor,
borderDash: [4, 6]
},
ticks: {
display: true,
padding: 10,
fontColor: axisTextColor
}
}
]
},
tooltips: {
mode: 'index',
intersect: false,
callbacks: {
title: function (items, data) {
const index = items[0].index != 24 ? items[0].index : (items[0].index = 23);
const date = new Date(data.labels[index]);
const initDate = that.getTimeString(date);
const endDate = that.getTimeString(date.setHours(date.getHours() + 1));
return initDate + ' - ' + endDate;
},
label: function (tooltipItems, data) {
let icon;
const index = tooltipItems.index != 24 ? tooltipItems.index : (tooltipItems.index = 23);
if (tooltipItems.datasetIndex === 0) {
if (index == minIndex) {
icon = minIcon;
} else if (index == maxIndex) {
icon = maxIcon;
}
} else if (tooltipItems.datasetIndex === 1) {
if (index == minIndexNextDay) {
icon = minIcon;
} else if (index == maxIndexNextDay) {
icon = maxIcon;
}
}
const labelTitle = data.datasets[tooltipItems.datasetIndex].label || '';
const label =
labelTitle +
': ' +
parseFloat(tooltipItems.value).toFixed(5) +
' ' +
that.pvpcHourlyPricingObj.attributes.unit_of_measurement +
' ';
return icon ? label + icon : label;
}
}
}
}
};
if (hasNextDayData) {
chartOptions.data.datasets.push({
label: that.getDateString(today.setDate(today.getDate() + 1)),
type: 'line',
data: this.despiction.pricesNextDay,
borderWidth: 2.0,
pointRadius: 0.0,
pointHitRadius: 0.0,
fill: false,
steppedLine: true
});
}
this.ChartData = chartOptions;
}
getDespiction(attributes) {
const priceRegex = /price_\d\dh/;
const priceNextDayRegex = /price_(next|last)_day_\d\dh/;
const priceArray = Object.keys(attributes)
.filter((key) => priceRegex.test(key))
.map((key) => attributes[key]);
const priceNextDayArray = Object.keys(attributes)
.filter((key) => priceNextDayRegex.test(key))
.map((key) => attributes[key]);
let data = [];
let dateTime = [];
let prices = [];
let pricesNextDay = [];
for (let index = 0; index < 24; index++) {
dateTime.push(new Date().setHours(index, 0));
prices.push(priceArray[index]);
pricesNextDay.push(priceNextDayArray[index]);
}
dateTime.push(new Date().setHours(24, 0));
prices.push(priceArray[23]);
pricesNextDay.push(priceNextDayArray[23]);
data.dateTime = dateTime;
data.prices = prices;
data.pricesNextDay = pricesNextDay;
data.minPrice = Math.min.apply(null, prices);
data.maxPrice = Math.max.apply(null, prices);
data.minIndex = prices.indexOf(data.minPrice);
data.maxIndex = prices.indexOf(data.maxPrice);
data.minPriceNextDay = Math.min.apply(null, pricesNextDay);
data.maxPriceNextDay = Math.max.apply(null, pricesNextDay);
data.minIndexNextDay = pricesNextDay.indexOf(data.minPriceNextDay);
data.maxIndexNextDay = pricesNextDay.indexOf(data.maxPriceNextDay);
return data;
}
getTariffPeriod(tariff) {
let period;
switch (tariff) {
case 'normal':
period = 'normal';
break;
case 'discrimination':
const utcHours = new Date().getUTCHours();
if (utcHours >= 21 || utcHours < 11) {
period = 'valley';
} else {
period = 'peak';
}
break;
case 'electric_car':
const hours = new Date().getHours();
if (hours >= 1 && hours < 7) {
period = 'super-valley';
} else if (hours >= 13 && hours < 23) {
period = 'peak';
} else {
period = 'valley';
}
break;
default:
period = 'error';
}
return period;
}
getDateString(datetime) {
return new Date(datetime).toLocaleDateString(this.lang, {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
}
getHourString(datetime) {
return new Date(datetime).toLocaleTimeString(this.lang, { hour: '2-digit', hour12: false });
}
getTimeString(datetime) {
return new Date(datetime).toLocaleTimeString(this.lang, { hour: '2-digit', minute: '2-digit', hour12: false });
}
getFixedFloat(number) {
return parseFloat(number).toFixed(5);
}
_handleClick() {
fireEvent(this, 'hass-more-info', { entityId: this._config.entity });
}
getCardSize() {
return this.numberElements || 3;
}
static get styles() {
return css`
ha-card {
margin: auto;
padding-top: 1.3em;
padding-bottom: 1.3em;
padding-left: 1em;
padding-right: 1em;
position: relative;
}
ha-icon {
color: var(--paper-item-icon-color);
}
.spacer {
padding-top: 1em;
}
.clear {
clear: both;
}
.tappable {
cursor: pointer;
}
.current {
height: 5.5em;
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
}
.period-icon {
padding-left: 16px;
padding-right: 16px;
width: 5.5em;
height: 5.5em;
}
.currentPrice {
font-weight: 300;
font-size: 4em;
color: var(--primary-text-color);
margin-top: 0.5em;
margin-right: 8px;
}
.currentPriceUnit {
font-weight: 300;
font-size: 1.5em;
vertical-align: super;
color: var(--primary-text-color);
right: 0em;
top: 0em;
position: absolute;
margin-right: 8px;
}
.details {
font-weight: 300;
color: var(--primary-text-color);
list-style: none;
padding-right: 1em;
padding-left: 1em;
}
.details li {
display: flex;
align-items: center;
justify-content: flex-start;
}
.details ha-icon {
height: 22px;
margin-right: 4px;
}
.info {
color: var(--primary-text-color);
text-align: center;
padding-right: 1em;
padding-left: 1em;
}
`;
}
ll(str) {
if (locale[this.lang] === undefined) return locale.en[str];
return locale[this.lang][str];
}
}
customElements.define('pvpc-hourly-pricing-card', PVPCHourlyPricingCard);