diff --git a/public/static/images/weather/cloudy.png b/public/static/images/weather/cloudy.png
new file mode 100644
index 0000000000..257b958bfc
Binary files /dev/null and b/public/static/images/weather/cloudy.png differ
diff --git a/public/static/images/weather/lightning-rainy.png b/public/static/images/weather/lightning-rainy.png
new file mode 100644
index 0000000000..e28b7667bb
Binary files /dev/null and b/public/static/images/weather/lightning-rainy.png differ
diff --git a/public/static/images/weather/lightning.png b/public/static/images/weather/lightning.png
new file mode 100644
index 0000000000..c4966bd5c9
Binary files /dev/null and b/public/static/images/weather/lightning.png differ
diff --git a/public/static/images/weather/night.png b/public/static/images/weather/night.png
new file mode 100644
index 0000000000..d7e79d2ac9
Binary files /dev/null and b/public/static/images/weather/night.png differ
diff --git a/public/static/images/weather/partly-cloudy.png b/public/static/images/weather/partly-cloudy.png
new file mode 100644
index 0000000000..a39a400ba1
Binary files /dev/null and b/public/static/images/weather/partly-cloudy.png differ
diff --git a/public/static/images/weather/pouring.png b/public/static/images/weather/pouring.png
new file mode 100644
index 0000000000..06bef9bc4c
Binary files /dev/null and b/public/static/images/weather/pouring.png differ
diff --git a/public/static/images/weather/rainy.png b/public/static/images/weather/rainy.png
new file mode 100644
index 0000000000..e0c9a9a975
Binary files /dev/null and b/public/static/images/weather/rainy.png differ
diff --git a/public/static/images/weather/snowy.png b/public/static/images/weather/snowy.png
new file mode 100644
index 0000000000..059f8da70e
Binary files /dev/null and b/public/static/images/weather/snowy.png differ
diff --git a/public/static/images/weather/sunny.png b/public/static/images/weather/sunny.png
new file mode 100644
index 0000000000..6c67835dce
Binary files /dev/null and b/public/static/images/weather/sunny.png differ
diff --git a/public/static/images/weather/windy.png b/public/static/images/weather/windy.png
new file mode 100644
index 0000000000..9b4b20f227
Binary files /dev/null and b/public/static/images/weather/windy.png differ
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`
+