From 6f556f69d6dd4d653b757a6d11f924d4fbe47e96 Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Tue, 31 Mar 2020 16:07:05 -0400 Subject: [PATCH] Weather Entity Row: Row Addition (#4959) * Create file * Weather Row * Comments * Find high and low for hourly * Add images * testing * Weather images in row * lint * Updated design * css changes - dont show high unless it is the high * Clean * Sean Suggestion * Update hui-weather-entity-row.ts * Reviews * move tab index * reviews * Update src/panels/lovelace/entity-rows/hui-weather-entity-row.ts Co-Authored-By: Bram Kragten * pretty Co-authored-by: Bram Kragten --- public/static/images/weather/cloudy.png | Bin 0 -> 715 bytes .../static/images/weather/lightning-rainy.png | Bin 0 -> 910 bytes public/static/images/weather/lightning.png | Bin 0 -> 822 bytes public/static/images/weather/night.png | Bin 0 -> 465 bytes .../static/images/weather/partly-cloudy.png | Bin 0 -> 1064 bytes public/static/images/weather/pouring.png | Bin 0 -> 862 bytes public/static/images/weather/rainy.png | Bin 0 -> 818 bytes public/static/images/weather/snowy.png | Bin 0 -> 1278 bytes public/static/images/weather/sunny.png | Bin 0 -> 678 bytes public/static/images/weather/windy.png | Bin 0 -> 817 bytes src/data/weather.ts | 77 +++++ .../create-element/create-row-element.ts | 2 + .../entity-rows/hui-weather-entity-row.ts | 279 ++++++++++++++++++ src/translations/en.json | 7 +- src/types.ts | 16 + 15 files changed, 379 insertions(+), 2 deletions(-) create mode 100644 public/static/images/weather/cloudy.png create mode 100644 public/static/images/weather/lightning-rainy.png create mode 100644 public/static/images/weather/lightning.png create mode 100644 public/static/images/weather/night.png create mode 100644 public/static/images/weather/partly-cloudy.png create mode 100644 public/static/images/weather/pouring.png create mode 100644 public/static/images/weather/rainy.png create mode 100644 public/static/images/weather/snowy.png create mode 100644 public/static/images/weather/sunny.png create mode 100644 public/static/images/weather/windy.png create mode 100644 src/data/weather.ts create mode 100644 src/panels/lovelace/entity-rows/hui-weather-entity-row.ts diff --git a/public/static/images/weather/cloudy.png b/public/static/images/weather/cloudy.png new file mode 100644 index 0000000000000000000000000000000000000000..257b958bfc994b2aeed9c2acea667de3664c3a7d GIT binary patch literal 715 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!>H>U1T!HjUFnIOq)$iZGA#4cw z|Nnnv#-BfbfZV@-{{l(4)cg1EfByXW?c29szka=W^9INODg(+w07N~|SfCo9$d@l) zfVKdYef;?G-Me={#S*#3Z9o^umjw9*GaL~3_~Amsg9Z8p3dRPX6TWURb(r9D|JKXn zn^yRXbKmhE?N&wlQEaL1xm)3^Wm+9kaqGELyuG4&0pVZSErz9RWi@_XQ= z?9aD<{ozsNH{9)I|2XkkrF6l{&&*HU6KvcjUs+!{#i*EFPsR1%<0-5`=G_04Bf|bf zt!lIS6#0iGn0edE3O#Kpn|+UG&YQi2ZOyY;|5(;CJ+XWH;Aj2KEIu2@g})!axXf4O zqV3E-;pu@ZH73ViGWPJvu3K5W;w{4?o$baaLRh@ajxjULuwwAvpmI#L&cSlaGUxfL zCKqzQV^uqFbP==Umz5_prho9yPI;l1eVsMiRN_n06y>$sUR+>g;!C-}y+Np9D|<RY(<4=JyD%d;cm_KO3zo7u_@ z7&?~o|IpdvJ7=|`h%?LF#7pe~x|)jGCJt(Re(KflP1YUPZTNNcmq11QzL&-g0rCPI zcRaS-=@#tx;@Ea?8n;?$Q-S;~yNdPtGyDYJHJ5x<{yf(qsa9Zep^k>(Pn%Yw1RnSE zQbviJtILctmMmRgGk^d7`|-EGvp1(7?S7Q_J22zR_r}xbA0N2f8qT`2SpBH{CLiu- UrFjDBz%;?&>FVdQ&MBb@0R7&1TmS$7 literal 0 HcmV?d00001 diff --git a/public/static/images/weather/lightning-rainy.png b/public/static/images/weather/lightning-rainy.png new file mode 100644 index 0000000000000000000000000000000000000000..e28b7667bbf324b8fce2eaf73a364a50eb24dc21 GIT binary patch literal 910 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!<_GwMxB_W|&HrD$di4?pfLsUy z3IG`pVYtZe-@k$EH*em2`uPV4AZj485XBG@W*Eo>AOh-w00{fruRlM2{CNNV{f7@9 z{{IJA@%Qgvpn{)2e*(Gh-o5+&{ri_MUx2c<_ZuC7PShz0@(X5odO+aA0&Tt<4r?w< zXtY%LxLg0<*9Q~AJWe(g7bFC1NR@qlXVtu%U(c?tP&#n@W+(##Q<0~OV@Sl|z0*4L z78?k(wVw`{ImqGnp-aBDOkStjQGva0I_FWT{V?|{atxg`nJ?>%AS46!qetZ-s^vvT_-Q{@!$O$zC3e% zQzqZ}yTVg8Rs<``Uv;@N&tc*s$K#Ds6)fv-AKZG~HP&ED|IEm2b`Iu$FDE=twcR1U zpmTa=ceIH88mD=@nGTEZr>M5i373cx*zt1l{#%U)wKKR=dK}whET+t267A-aq_v?@7S&aFA+X0fF8*H(7zDQ@hM z2afGI@H1FS*6Z)HcLy(n?r{r)E=;h9d2vy zYk0G9(Z27G?{PiI*veDEsPyM!)B4lGb+1ML1q-+cn8~_iuvj^Hn|PjUJR#ihS#Q_N zwGpNAB?(<7r#C3=Wm9H~D)4%Ec4_r??w-aqO}xvt)$nimbi0-z_(Su%Eqy-euiRq3 x)a5UKviRSB@rQ@nIoCAl-eFEW{eS8oW~bzaAL^_v%Ydnt!PC{xWt~$(69Al!RSfBpLP`}c1k2~i9r|Ni|8VFMMsef#$F=g&ZH^|}B3 z*FeSs6$2G~|Nb2)3zPyWIOmoIbeVQZkY6yvQ-K2yK3r(Hks!ao;8lUC_HI4C4aETy zJRA&U-`(9kZ$fH(m?ih;$0sLbKgdl7`j1J))5S3);_%j~)#Z;A1dh5-l308*FLtZl zw*K7hcf~ypeg9viT_B{;pz`?TdFB6iPtQ%{_+U99Yj&{bwt9yX)3viEPG?ybc!}}n z5|I?Y+Hb;-y!W2x+Gu}Hak1gM1fFseuZ=Rn4vvr42(Z5}QfiuKS0kQPHPvzx6Z1D7 z-xi<1u!(s#PfBz?YfZ3M^pDwKwLCad&>-ZBTZ}|7JEz}wo~NGW)J(2Yg@%wn?_Hh(G&T<)FL6%EQY+7YvRma2z` z-_*vKb?2O=t94&5da5(&V41+b>r0A%9QAE`Ve?_(zP5S4%*`d0J|FlSez3{rx8$Ao z%;N99sq$1*PZGOW`GDDyvA=)&^X13gUp~6bwfO9&PaJ}F`#k-YU*50ZwLf0ed&a3W z!{9y4lcvv1tL4{-OSiRWJovrU@CIYk+>81FrhAgVE^BMnOWMn*W4-wJ#(gj9?<~0Q zX`8X(`42Ah4~y71&L?iOIKZ3pZ^h%|#r?aSD*iEN6_mMi2q~=wCNl<4S3j3^P6)`im;7VZG5*rT&+Xdc`{1YshXL1|zzqjAGQt|059wUuYRxLP;!xV| z>h-K@35TU~YPHURiD{y5!Wegj2CQLP6Xv*)YeN+4)F_pGfs*kZ&rdB0S$3{uwe32N zBO))u++q}^D@CR{>|wS%*}zy@CeOmGGBG*iRm|qH9?k7tai!AjQ|vr`KlpIT?e2!{ zZ>{HcG5FPg{^wZa^LO&wg*z|rQWcPywzly>ft|M8W54~s`#;wiN|>zw;q`2)+c|&F zCQTKd_1?!=ey)?xRF_}xva{FSzHqS&+l~k7`ne_InYvGT?2f+qx5;wZ>yv*OUMvf; U{nS%;9T>zopr0BO?MRsaA1 literal 0 HcmV?d00001 diff --git a/public/static/images/weather/partly-cloudy.png b/public/static/images/weather/partly-cloudy.png new file mode 100644 index 0000000000000000000000000000000000000000..a39a400ba1c6aa2898b237cac148797b9efc4330 GIT binary patch literal 1064 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+yeRz^EMH6XFWw!okazFX3z;1IUGd zzc+2*+@C*xLIi<~7cX7_#lK&%2C{$r_;L5{U7*PKD^~aRnE=)NxnTn&fqH@H-5HDD zH>`n-U%!6+yarPL;K2hRd2XZOmoHz|uV4T8u<4U0Pk`#rpFa;&{O#K}piw}@Z=W6p zia&gC=i?=hua~UepS8Sy|2|OJ@9Q>~w;BNz|NsAg*REY}-n;=Cn-whyr;{QmtLXb#YMKq;VIK$G9Tef$3X`*%6yqJb_kQBAqYJr`#zeK~E(?|s{yZ#Zw6wdD5S zcvp5dmbAnNYtB#JBUiuv{(pYuuys45YG%IcJ^uLQ?%ph)nO_PnT}v)oc6d|Yx#DZXGdt6$L+Rn}~juz3N;s~HZAm46m- zyfQ6n63V`lm(Y~AE}>AYXlG1l<$Tc$=70%Dg=9}&ciTHpX9K%efndyr>wl*>OmwuY zJFLF*e#Vie?FvWFw9fg&^IZS8N5k6scXJhbB#(bkI-%^CRlTop)1hY)*OCt^&UBBE zIo$k0Vz+1gWe=V|eCIDt)@Tx)mzcskC$x_1ebX!v1E;R=3Sq9G=jj4%H3x+?PWU7U zu}MGLS^P|D_R4s}`)0m(8VY3k6@N?++F{@)CG&%mr>AsXd06|4NVUWc)e{GVR=oJF z+nrdjNYiJPLzkPyR)sZue6JobP1?Qjz~+cHmt+Q^z>dC)+jQ#KPl+A6zMDsV!_799 zPj#yoE)ch2a^=z6$+WOb?=sJ`ooqo0@(b-F?De*T8>biUS= z|I>H-N3xyvUtXQmdT@I`d$~gPgxeGT{*_kDW=}el_;l9){`!~i7c;SayHpQM%M6~b KelF{r5}E*aoih6X literal 0 HcmV?d00001 diff --git a/public/static/images/weather/pouring.png b/public/static/images/weather/pouring.png new file mode 100644 index 0000000000000000000000000000000000000000..06bef9bc4cd935a387b20846b7be55940338b435 GIT binary patch literal 862 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 literal 0 HcmV?d00001 diff --git a/public/static/images/weather/windy.png b/public/static/images/weather/windy.png new file mode 100644 index 0000000000000000000000000000000000000000..9b4b20f2271ef4c96125225352641b56ac53eb70 GIT binary patch literal 817 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!x&nMcT!HkF za0W6PA_yUW{rUyx{saR!7sB}c`!|pQl!23f|Nez=|Ns9F6a;dCh5*?gKYslB_3OKL z?>>M2tg!avcA#t2N`m}?8J_YrSQaOInBXAyVBv)W0S4#Rc-%OBJZyKW_PmQ5Ru_Cb znSHC#<)K9SUj_yy15X#nkch)ir^e2nWgya$tRUcUv?;ozv~+iF|HIe+|L=9W!J()q zFz?;feK9Z2@))-}JH#J7SRBQ)$4mAS2TS$&Tg>^6hxXronJjT5Pw5=SqGm`e7NZ`L*!Tp6%qT_;1euC1x`w9k|w$`$Iliik}#uvr6?Yx8Ptz);C ztRwuMXnx{&bHg+AgLK5>YUaID_Gw5ar!sD?@c(O2_gCOdbVnrLyZDXn{LGC%MN38Y zK1!%@b$%gO<;ZEw^68&wzv0yy79I8%#!tGHWG0yNDO_)mobymJ!1!ChvHhVq-AYT`u<6CF=7YKa`3)HtP~H##F+^5-dzDV7~# zQ@=$&WVkY^u1fyPa;a}qC0~REWp8lSHH>EtJiLM5$C#0ib(wRlK9>z zX?Okhq^WyaGv&3O>PR}=+j(pY|5lyv$tr?>=FN0gJT2Sf_Mrdu?=8%eKfQk^k$Irr zK5zT{In$@f%U*wdy4T9qv0ZxSYa$HEQ;8o0e<&1h4*e?B-43WxoBNqugu69^3qjeC*hF>GFbf@#wq%rUXnf z6+Y>4>GqsS+@9e+i(V?7x)7JYJE>(!UdKQ6{|upzd`_LYnRgJF+88`t{an^LB{Ts5 DA@s4x literal 0 HcmV?d00001 diff --git a/src/data/weather.ts b/src/data/weather.ts new file mode 100644 index 0000000000..34fc4da252 --- /dev/null +++ b/src/data/weather.ts @@ -0,0 +1,77 @@ +import { HomeAssistant } from "../types"; + +export const weatherIcons = { + "clear-night": "/static/images/weather/night.png", + cloudy: "/static/images/weather/cloudy.png", + exceptional: "hass:alert-circle-outline", + fog: "hass:weather-fog", + hail: "hass:weather-hail", + lightning: "/static/images/weather/lightning.png", + "lightning-rainy": "/static/images/weather/lightning-rainy.png", + partlycloudy: "/static/images/weather/partly-cloudy.png", + pouring: "/static/images/weather/pouring.png", + rainy: "/static/images/weather/rainy.png", + snowy: "/static/images/weather/snowy.png", + "snowy-rainy": "hass:weather-snowy-rainy", + sunny: "/static/images/weather/sunny.png", + windy: "/static/images/weather/windy.png", + "windy-variant": "hass:weather-windy-variant", +}; + +export const cardinalDirections = [ + "N", + "NNE", + "NE", + "ENE", + "E", + "ESE", + "SE", + "SSE", + "S", + "SSW", + "SW", + "WSW", + "W", + "WNW", + "NW", + "NNW", + "N", +]; + +const getWindBearingText = (degree: string): string => { + const degreenum = parseInt(degree, 10); + if (isFinite(degreenum)) { + // tslint:disable-next-line: no-bitwise + return cardinalDirections[(((degreenum + 11.25) / 22.5) | 0) % 16]; + } + return degree; +}; + +export const getWindBearing = (bearing: string): string => { + if (bearing != null) { + return getWindBearingText(bearing); + } + return ""; +}; + +export const getWeatherUnit = ( + hass: HomeAssistant, + measure: string +): string => { + const lengthUnit = hass.config.unit_system.length || ""; + switch (measure) { + case "pressure": + return lengthUnit === "km" ? "hPa" : "inHg"; + case "wind_speed": + return `${lengthUnit}/h`; + case "length": + return lengthUnit; + case "precipitation": + return lengthUnit === "km" ? "mm" : "in"; + case "humidity": + case "precipitation_probability": + return "%"; + default: + return hass.config.unit_system[measure] || ""; + } +}; diff --git a/src/panels/lovelace/create-element/create-row-element.ts b/src/panels/lovelace/create-element/create-row-element.ts index d23464f7fb..0bcf355154 100644 --- a/src/panels/lovelace/create-element/create-row-element.ts +++ b/src/panels/lovelace/create-element/create-row-element.ts @@ -31,6 +31,7 @@ const LAZY_LOAD_TYPES = { "lock-entity": () => import("../entity-rows/hui-lock-entity-row"), "timer-entity": () => import("../entity-rows/hui-timer-entity-row"), conditional: () => import("../special-rows/hui-conditional-row"), + "weather-entity": () => import("../entity-rows/hui-weather-entity-row"), divider: () => import("../special-rows/hui-divider-row"), section: () => import("../special-rows/hui-section-row"), weblink: () => import("../special-rows/hui-weblink-row"), @@ -63,6 +64,7 @@ const DOMAIN_TO_ELEMENT_TYPE = { // water heater should get it's own row. water_heater: "climate", input_datetime: "input-datetime", + weather: "weather", }; export const createRowElement = (config: EntityConfig) => diff --git a/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts b/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts new file mode 100644 index 0000000000..24262065a6 --- /dev/null +++ b/src/panels/lovelace/entity-rows/hui-weather-entity-row.ts @@ -0,0 +1,279 @@ +import { + html, + LitElement, + TemplateResult, + css, + CSSResult, + property, + customElement, + PropertyValues, +} from "lit-element"; +import { ifDefined } from "lit-html/directives/if-defined"; +import { classMap } from "lit-html/directives/class-map"; + +import "../../../components/entity/state-badge"; +import "../components/hui-warning"; + +import { LovelaceRow } from "./types"; +import { HomeAssistant, WeatherEntity } from "../../../types"; +import { EntitiesCardEntityConfig } from "../cards/types"; +import { hasConfigOrEntityChanged } from "../common/has-changed"; +import { UNAVAILABLE } from "../../../data/entity"; +import { weatherIcons, getWeatherUnit } from "../../../data/weather"; +import { DOMAINS_HIDE_MORE_INFO } from "../../../common/const"; +import { computeDomain } from "../../../common/entity/compute_domain"; +import { actionHandler } from "../common/directives/action-handler-directive"; +import { hasAction } from "../common/has-action"; +import { computeStateName } from "../../../common/entity/compute_state_name"; +import { ActionHandlerEvent } from "../../../data/lovelace"; +import { handleAction } from "../common/handle-action"; + +@customElement("hui-weather-entity-row") +class HuiWeatherEntityRow extends LitElement implements LovelaceRow { + @property() public hass?: HomeAssistant; + @property() private _config?: EntitiesCardEntityConfig; + + public setConfig(config: EntitiesCardEntityConfig): void { + if (!config?.entity) { + throw new Error("Invalid Configuration: 'entity' required"); + } + + this._config = config; + } + + protected shouldUpdate(changedProps: PropertyValues): boolean { + return hasConfigOrEntityChanged(this, changedProps); + } + + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } + + const stateObj = this.hass.states[this._config.entity] as WeatherEntity; + + if (!stateObj || stateObj.state === UNAVAILABLE) { + return html` + ${this.hass.localize( + "ui.panel.lovelace.warning.entity_not_found", + "entity", + this._config.entity + )} + `; + } + + const pointer = + (this._config.tap_action && this._config.tap_action.action !== "none") || + (this._config.entity && + !DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this._config.entity))); + + const secondaryAttribute = this._getSecondaryAttribute(stateObj); + + return html` +
+ +
+
+
+
+ ${this._config.name || computeStateName(stateObj)} +
+
+ ${this.hass.localize(`state.weather.${stateObj.state}`) || + stateObj.state} +
+
+
+ ${stateObj.attributes.temperature}${getWeatherUnit(this.hass, "temperature")} +
+
+ ${secondaryAttribute + ? html` +
+ + ${secondaryAttribute} + +
+ ` + : ""} +
+
+ `; + } + + private _getSecondaryAttribute(stateObj: WeatherEntity): string | undefined { + const extrema = this._getExtrema(stateObj); + + if (extrema) { + return extrema; + } + + let value: number; + let attribute: string; + + if ( + stateObj.attributes.forecast?.length && + stateObj.attributes.forecast[0].precipitation !== undefined && + stateObj.attributes.forecast[0].precipitation !== null + ) { + value = stateObj.attributes.forecast[0].precipitation!; + attribute = "precipitation"; + } else if ("humidity" in stateObj.attributes) { + value = stateObj.attributes.humidity!; + attribute = "humidity"; + } else { + return undefined; + } + + return ` + ${this.hass!.localize( + `ui.card.weather.attributes.${attribute}` + )} ${value}${getWeatherUnit(this.hass!, attribute)} + `; + } + + private _getExtrema(stateObj: WeatherEntity): string | undefined { + if (!stateObj.attributes.forecast?.length) { + return undefined; + } + + let tempLow: number | undefined; + let tempHigh: number | undefined; + const today = new Date().getDate(); + + for (const forecast of stateObj.attributes.forecast!) { + if (new Date(forecast.datetime).getDate() !== today) { + break; + } + if (!tempHigh || forecast.temperature > tempHigh) { + tempHigh = forecast.temperature; + } + if (!tempLow || (forecast.templow && forecast.templow < tempLow)) { + tempLow = forecast.templow; + } + if (!forecast.templow && (!tempLow || forecast.temperature < tempLow)) { + tempLow = forecast.temperature; + } + } + + if (!tempLow && !tempHigh) { + return undefined; + } + + const unit = getWeatherUnit(this.hass!, "temperature"); + + return ` + ${ + tempHigh + ? ` + ${this.hass!.localize(`ui.card.weather.high`)} ${tempHigh}${unit} + ` + : "" + } + ${tempLow && tempHigh ? " / " : ""} + ${ + tempLow + ? ` + ${this.hass!.localize(`ui.card.weather.low`)} ${tempLow}${unit} + ` + : "" + } + `; + } + + private _handleAction(ev: ActionHandlerEvent) { + handleAction(this, this.hass!, this._config!, ev.detail.action!); + } + + static get styles(): CSSResult { + return css` + .pointer { + cursor: pointer; + } + + .main { + display: flex; + align-items: center; + justify-content: flex-end; + width: 100%; + } + + .name { + font-weight: 500; + } + + .state { + color: var(--secondary-text-color); + } + + .temperature { + font-size: 28px; + margin-right: 16px; + } + + .temperature span { + font-size: 18px; + position: absolute; + } + + .container { + flex: 1 0; + display: flex; + flex-flow: column; + } + + .info { + display: flex; + flex-flow: column; + justify-content: center; + } + + .info, + .attributes { + flex: 1 0; + margin-left: 16px; + overflow: hidden; + } + + .info > *, + .attributes > * { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .attributes { + padding-top: 1px; + color: var(--secondary-text-color); + } + + .attributes > *:not(:first-child) { + padding-left: 4px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-weather-entity-row": HuiWeatherEntityRow; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index c2585e1a2c..56ab3b5b9c 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -493,7 +493,8 @@ "humidity": "Humidity", "temperature": "Temperature", "visibility": "Visibility", - "wind_speed": "Wind speed" + "wind_speed": "Wind speed", + "precipitation": "Precipitation" }, "cardinal_direction": { "e": "E", @@ -513,7 +514,9 @@ "wnw": "WNW", "wsw": "WSW" }, - "forecast": "Forecast" + "forecast": "Forecast", + "high": "High", + "low": "Low" } }, "common": { diff --git a/src/types.ts b/src/types.ts index 348577ef0a..066c644fea 100644 --- a/src/types.ts +++ b/src/types.ts @@ -243,3 +243,19 @@ export interface LocalizeMixin { hass?: HomeAssistant; localize: LocalizeFunc; } + +interface ForecastAttribute { + temperature: number; + datetime: string; + templow?: number; + precipitation?: number; + humidity?: number; +} + +export type WeatherEntity = HassEntityBase & { + attributes: HassEntityAttributeBase & { + temperature: number; + humidity?: number; + forecast?: ForecastAttribute[]; + }; +};