Upgrades HACS
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
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;
|
||||
};
|
||||
|
||||
const locale = {
|
||||
da: {
|
||||
optionName: 'Navn (valgfrit)',
|
||||
optionEntity: 'Enhed (påkrævet)',
|
||||
optionShowCurrent: 'Vis nuværende status',
|
||||
optionShowDetails: 'Vis detaljer',
|
||||
optionShowGraph: 'Vis graf',
|
||||
optionShowInfo: 'Vis information'
|
||||
},
|
||||
de: {
|
||||
optionName: 'Name (optional)',
|
||||
optionEntity: 'Entity (Erforderlich)',
|
||||
optionShowCurrent: 'Aktuellen Status anzeigen',
|
||||
optionShowDetails: 'Details anzeigen',
|
||||
optionShowGraph: 'Grafik anzeigen',
|
||||
optionShowInfo: 'Informationen anzeigen'
|
||||
},
|
||||
en: {
|
||||
optionName: 'Name (Optional)',
|
||||
optionEntity: 'Entity (Required)',
|
||||
optionShowCurrent: 'Show Current State',
|
||||
optionShowDetails: 'Show Details',
|
||||
optionShowGraph: 'Show Graph',
|
||||
optionShowInfo: 'Show Info'
|
||||
},
|
||||
es: {
|
||||
optionName: 'Nombre (Opcional)',
|
||||
optionEntity: 'Entidad (Necesario)',
|
||||
optionShowCurrent: 'Mostrar Estado Actual',
|
||||
optionShowDetails: 'Mostrar Detalles',
|
||||
optionShowGraph: 'Mostrar Gráfico',
|
||||
optionShowInfo: 'Mostrar Información'
|
||||
},
|
||||
fr: {
|
||||
optionName: 'Nom (Facultatif)',
|
||||
optionEntity: 'Entity (Required)',
|
||||
optionShowCurrent: "Afficher l'état actuel",
|
||||
optionShowDetails: 'Afficher les détails',
|
||||
optionShowGraph: 'Afficher le graphique',
|
||||
optionShowInfo: 'Afficher les informations'
|
||||
},
|
||||
nl: {
|
||||
optionName: 'Naam (optioneel)',
|
||||
optionEntity: 'Entiteit (vereist)',
|
||||
optionShowCurrent: 'Toon huidige status',
|
||||
optionShowDetails: 'Details weergeven',
|
||||
optionShowGraph: 'Show Graph',
|
||||
optionShowInfo: 'Informatie weergeven'
|
||||
},
|
||||
ru: {
|
||||
optionName: 'Имя (необязательно)',
|
||||
optionEntity: 'Entity (обязательно)',
|
||||
optionShowCurrent: 'Показать текущий статус',
|
||||
optionShowDetails: 'Показать детали',
|
||||
optionShowGraph: 'Показать график',
|
||||
optionShowInfo: 'Показать информацию'
|
||||
},
|
||||
sv: {
|
||||
optionName: 'Namn (valfritt)',
|
||||
optionEntity: 'Enhet (obligatoriskt)',
|
||||
optionShowCurrent: 'Visa aktuell status',
|
||||
optionShowDetails: 'Visa detaljer',
|
||||
optionShowGraph: 'Visa graf',
|
||||
optionShowInfo: 'Visa information'
|
||||
}
|
||||
};
|
||||
|
||||
const LitElement =
|
||||
window.LitElement || Object.getPrototypeOf(customElements.get('home-assistant') || customElements.get('hui-view'));
|
||||
const { html, css } = LitElement.prototype;
|
||||
|
||||
export class PVPCHourlyPricingCardEditor extends LitElement {
|
||||
setConfig(config) {
|
||||
this._config = { ...config };
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return { hass: {}, _config: {} };
|
||||
}
|
||||
|
||||
get _entity() {
|
||||
return this._config.entity || '';
|
||||
}
|
||||
|
||||
get _name() {
|
||||
return this._config.name || '';
|
||||
}
|
||||
|
||||
get _current() {
|
||||
return this._config.current !== false;
|
||||
}
|
||||
|
||||
get _details() {
|
||||
return this._config.details !== false;
|
||||
}
|
||||
|
||||
get _graph() {
|
||||
return this._config.graph !== false;
|
||||
}
|
||||
|
||||
get _info() {
|
||||
return this._config.info !== false;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
this.lang = this.hass.selectedLanguage || this.hass.language;
|
||||
|
||||
const entities = Object.keys(this.hass.states).filter((eid) =>
|
||||
Object.keys(this.hass.states[eid].attributes).some((aid) => aid == 'min_price_at')
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="card-config">
|
||||
<div class="side-by-side">
|
||||
<paper-input
|
||||
label="${this.ll('optionName')}"
|
||||
.value="${this._name}"
|
||||
.configValue="${'name'}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
>
|
||||
</paper-input>
|
||||
</div>
|
||||
<div class="side-by-side">
|
||||
<paper-dropdown-menu
|
||||
label="${this.ll('optionEntity')}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
.configValue="${'entity'}"
|
||||
>
|
||||
<paper-listbox slot="dropdown-content" .selected="${entities.indexOf(this._entity)}">
|
||||
${entities.map((entity) => {
|
||||
return html` <paper-item>${entity}</paper-item> `;
|
||||
})}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
<div class="side-by-side">
|
||||
<div>
|
||||
<ha-switch
|
||||
.checked=${this._current}
|
||||
.configValue="${'current'}"
|
||||
@change="${this._valueChanged}"
|
||||
></ha-switch>
|
||||
<label class="mdc-label">${this.ll('optionShowCurrent')}</label>
|
||||
</div>
|
||||
<div>
|
||||
<ha-switch
|
||||
.checked=${this._details}
|
||||
.configValue="${'details'}"
|
||||
@change="${this._valueChanged}"
|
||||
></ha-switch>
|
||||
<label class="mdc-label">${this.ll('optionShowDetails')}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="side-by-side">
|
||||
<div>
|
||||
<ha-switch .checked=${this._graph} .configValue="${'graph'}" @change="${this._valueChanged}"></ha-switch>
|
||||
<label class="mdc-label">${this.ll('optionShowGraph')}</label>
|
||||
</div>
|
||||
<div>
|
||||
<ha-switch .checked=${this._info} .configValue="${'info'}" @change="${this._valueChanged}"></ha-switch>
|
||||
<label class="mdc-label">${this.ll('optionShowInfo')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_valueChanged(ev) {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = ev.target;
|
||||
if (this[`_${target.configValue}`] === target.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.configValue) {
|
||||
if (target.value === '') {
|
||||
delete this._config[target.configValue];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue]: target.checked !== undefined ? target.checked : target.value
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fireEvent(this, 'config-changed', { config: this._config });
|
||||
}
|
||||
|
||||
ll(str) {
|
||||
if (locale[this.lang] === undefined) return locale.en[str];
|
||||
return locale[this.lang][str];
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-switch {
|
||||
padding-top: 16px;
|
||||
}
|
||||
.mdc-label {
|
||||
margin-left: 12px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
.side-by-side {
|
||||
display: flex;
|
||||
}
|
||||
.side-by-side > * {
|
||||
flex: 1;
|
||||
padding-right: 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('pvpc-hourly-pricing-card-editor', PVPCHourlyPricingCardEditor);
|
||||
|
||||
window.customCards = window.customCards || [];
|
||||
window.customCards.push({
|
||||
type: 'pvpc-hourly-pricing-card',
|
||||
name: 'PVPC Hourly Pricing',
|
||||
preview: true,
|
||||
description: 'The PVPC Hourly Pricing card allows you to display propertly the PVPC Hourly Pricing entity.'
|
||||
});
|
||||
Binary file not shown.
@@ -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);
|
||||
Binary file not shown.
Reference in New Issue
Block a user