Custom UI: Weather state card

@poebae

31 is clear-night. Need to figure out how to map night icons to this.

https://developer.yahoo.com/weather/documentation.html

@Jer78 super helpful, thanks for that!

Very nice icons.

Can’t wait to see how you used them.

Nice add. Could you please add check for 0° for the current temperature also. Thanks

Grazie! <3

That’s a good idea :slight_smile:

Made some more updates:

  • If the current temperature value is 0 then it will not be shown on the weather card, added some code to check that and save the temp as string so it will be shown
  • Forecast now supports 5 days
  • Added the fields pressure, visibility, precipation and precipationfc (forecast) which can be used in customize.yaml
input_boolean.weather:
  custom_ui_state_card: custom-weather-card
  config:
    weather: weather.buienradar
    pressure: sensor.br_pressure_de_bilt
    visibility: sensor.br_visibility_de_bilt
    precipation: sensor.buienradar_precipitation
    precipationfc: sensor.buienradar_precipitation_forecast_average

If your weather component doesn’t have this information (my nearest Buienradar station doesn’t have pressure and visibility available, so I get that from a template sensor), then you can add these.

weather_custom2

The HTML code
<dom-module id='custom-weather-card'>
  <template>
    <style>
      .clear {
        clear:both;
      }
      .card {
        margin:1em auto;
        padding-left: 1em;
        padding-right:1em;
        position: relative;
      }
      .iron-icon {
        height: 18px;
        color: #c8c8c8;
      }
      .temp {
        font-weight: 300;
        font-size: 4em;
        color:#5b5b5b;
        position: absolute;
        right: .5em;
      }
      .tempc {
        font-weight: 300;
        font-size: 1.5em;
        vertical-align: super;
        color:#5b5b5b;
        position: absolute;
        right: 0em;
        margin-top: -14px;
        margin-right: 7px;
      }
      .variations {
        font-weight:300;
        color:#8c8c8c;
        list-style:none;
        margin-left:-2em;
        margin-top: 3.5em;
      }
      .variations.right {
        float: right;
        margin-left: 0;
        margin-right: 1em;
      }
      .unit {
        font-size:.8em;
      }
      .forecast {
        width:100%;
        margin:0 auto;
        height:9em;
      }
      .day {
        display:block;
        width: 20%;
        float:left;
        text-align:center;
        color: #5b5b5b;
        border-right:.1em solid #d9d9d9;
        line-height: 2;
        box-sizing: border-box;
      }
      .dayname {
        text-transform: uppercase;
      }

      .forecast .day:first-child {
        margin-left: 0;
      }
      .forecast .day:nth-last-child(2) {
        border-right:none;
        margin-right: 0;
      }
      .highTemp {
        font-weight:bold;
      }
      .lowTemp {
        color: #8c8c8c;
      }
      .icon.bigger {
        width: 10em;
        height: 10em;
        margin-top: -4em;
        position: absolute;
        left: 0em;
      }
      .icon {
        width: 50px;
        height: 50px;
        display: inline-block;
        vertical-align: middle;
        background-size: contain;
        background-position: center center;
        background-repeat: no-repeat;
        text-indent: -9999px;
      }
       .weather {
        font-weight: 300;
        font-size: 1.5em;
        color:#5b5b5b;
        text-align:left;
        position: absolute;
        top: -0.5em;
        left: 6em;
        word-wrap: break-word;
        width: 30%;
      }

    </style>
    <div class="card">
      <span class="icon bigger" style="background: none, url(/local/weather_icons/animated/[[nowCond]].svg) no-repeat; background-size: contain;"></span>
      <span class="temp">[[roundedTemp]]</span><span class="tempc">&#176;C</span>
      <span class="weather">[[nowCondIT]]</span>
      <br>
      <span>
        <ul class="variations right">
          <template is="dom-if" if="[[weatherObj.attributes.humidity]]">
            <li><iron-icon icon="mdi:water-percent"></iron-icon> [[weatherObj.attributes.humidity]]<span class="unit"> %</span></li>
          </template>
          <template is="dom-if" if="[[weatherObj.attributes.pressure]]">
            <li><iron-icon icon="mdi:gauge"></iron-icon> [[weatherObj.attributes.pressure]]<span class="unit"> hPa</span></li>
          </template>
          <template is="dom-if" if="[[pressureObj.state]]"> <!-- if weather station doesn't have pressure info, get it from a separate sensor -->
            <li><iron-icon icon="mdi:gauge"></iron-icon> [[nowPressure]]<span class="unit"> hPa</span></li>
          </template>
          <template is="dom-if" if="[[precipationfcObj.state]]">
            <li><iron-icon icon="mdi:weather-rainy"></iron-icon> [[nowPrecipationfc]]<span class="unit" > mm/u (> uur)</span></li>
          </template>

        </ul>
        <ul class="variations">
          <template is="dom-if" if="[[weatherObj.attributes.wind_speed]]">
            <li><iron-icon icon="mdi:weather-windy"></iron-icon> [[windBearing]] [[weatherObj.attributes.wind_speed]]<span class="unit"> m/s</span></li>
          </template>
          <template is="dom-if" if="[[weatherObj.attributes.visibility]]">
            <li><iron-icon icon="mdi:weather-fog"></iron-icon> [[weatherObj.attributes.visibility]]<span class="unit"> m</span></li>
          </template>
          <template is="dom-if" if="[[visibilityObj.state]]"> <!-- if weather station doesn't have visibility info, get it from a separate sensor -->
            <li><iron-icon icon="mdi:weather-fog"></iron-icon> [[nowVisibility]]<span class="unit" > m</span></li>
          </template>
          <template is="dom-if" if="[[precipationObj.state]]">
            <li><iron-icon icon="mdi:weather-pouring"></iron-icon> [[nowPrecipation]]<span class="unit" > mm/u</span></li>
          </template>
        </ul>
      </span>

      <div class="forecast clear">
        <template is="dom-repeat" items="[[forecast]]">
        <div class="day"><span class="dayname">[[item.dayIT]]</span>
          <template is="dom-if" if="[[item.condIcon]]">
          <br> <i class="icon" style="background: none, url(/local/weather_icons/animated/[[item.condIcon]].svg) no-repeat; background-size: contain;"></i> 
          </template>
          <template is="dom-if" if="[[item.tempHigh]]">
          <br> <span class="highTemp">[[item.tempHigh]]&#176;C</span> 
          </template>
          <template is="dom-if" if="[[item.tempLow]]">
          <br> <span class="lowTemp">[[item.tempLow]]&#176;C</span>
          </template>
        </div>
      </template>
      </div>
    </div>
  </template>
</dom-module>

<script>
(function () {
  'use strict';

  var _WEATHER_TO_ICON = {
    cloudy: 'cloudy',
    fog: 'cloudy',
    hail: 'rainy-7',
    lightning: 'thunder',
    'lightning-rainy': 'thunder',
    partlycloudy: 'cloudy-day-3',
    pouring: 'rainy-6',
    rainy: 'rainy-5',
    snowy: 'snowy-6',
    'snowy-rainy': 'rainy-7',
    sunny: 'day',
    windy: 'cloudy',
    'windy-variant': 'cloudy-day-3',
    exceptional: '!!',
  };
  
  var _WEATHER_TO_NAME = {
    cloudy: 'Bewolkt',
    fog: 'Mist',
    hail: 'Hagel',
    lightning: 'Onweer',
    'lightning-rainy': 'Onweer',
    partlycloudy: 'Gedeeltelijk bewolkt',
    pouring: 'Stortregen',
    rainy: 'Regen',
    snowy: 'Sneeuw',
    'snowy-rainy': 'Natte sneeuw',
    sunny: 'Zonnig',
    windy: 'Winderig',
    'windy-variant': 'Variabele wind',
    exceptional: '!',
  };


  var _DEGREE_TEXT = [
    'N', 'NNO', 'NO', 'ONO', 'O', 'OZO', 'ZO', 'ZZO',
    'Z', 'ZZW', 'ZW', 'WZW', 'W', 'WNW', 'NW', 'NNW', 'N'
  ];
  
  var _DAY_TO_DAY = {
    Mon: 'MA',
    Tue: 'DI',
    Wed: 'WO',
    Thu: 'DO',
    Fri: 'VR',
    Sat: 'ZA',
    Sun: 'ZO',
  };

  Polymer({
    is: 'custom-weather-card',

    properties: {
      hass: {
        type: Object,
      },
      stateObj: {
        type: Object,
      },
      weatherObj: {
        type: Object,
        observer: 'checkRequirements',
        computed: 'computeWeatherObj(hass, stateObj)',
      },
      pressureObj: {
        type: Object,
        observer: 'checkRequirements',
        computed: 'computePressureObj(hass, stateObj)',
      },
      visibilityObj: {
        type: Object,
        observer: 'checkRequirements',
        computed: 'computeVisibilityObj(hass, stateObj)',
      },
      precipationObj: {
        type: Object,
        observer: 'checkRequirements',
        computed: 'computePrecipationObj(hass, stateObj)',
      },
      precipationfcObj: {
        type: Object,
        observer: 'checkRequirements',
        computed: 'computePrecipationfcObj(hass, stateObj)',
      },
    },

    computeWeatherObj: function (hass, stateObj) {
      return stateObj && stateObj.attributes && stateObj.attributes.config && stateObj.attributes.config.weather ? hass.states[stateObj.attributes.config.weather] : null;
    },
    computePressureObj: function (hass, stateObj) {
      return stateObj && stateObj.attributes && stateObj.attributes.config && stateObj.attributes.config.pressure ? hass.states[stateObj.attributes.config.pressure] : null;
    },
    computeVisibilityObj: function (hass, stateObj) {
      return stateObj && stateObj.attributes && stateObj.attributes.config && stateObj.attributes.config.visibility ? hass.states[stateObj.attributes.config.visibility] : null;
    },
    computePrecipationObj: function (hass, stateObj) {
      return stateObj && stateObj.attributes && stateObj.attributes.config && stateObj.attributes.config.precipation ? hass.states[stateObj.attributes.config.precipation] : null;
    },
    computePrecipationfcObj: function (hass, stateObj) {
      return stateObj && stateObj.attributes && stateObj.attributes.config && stateObj.attributes.config.precipationfc ? hass.states[stateObj.attributes.config.precipationfc] : null;
    },

    getForecastArray: function () {
      if (!this.weatherObj.attributes.forecast) {
        return [];
      }
      
      var data = this.weatherObj.attributes.forecast;
      var forecast = [];
      var prevDay = '';
      
      for (var i = 0; i < data.length; i++) {
        var day = new Date(data[i].datetime).toString().split(' ')[0];
        if (day != prevDay) {
          if (data[i].max_temp) {
            var tempHigh = Math.round(data[i].max_temp * 10) / 10;
          } else {
            var tempHigh = Math.round(data[i].temperature * 10) / 10;
          }
          if (tempHigh == 0) {
            tempHigh = '0'; // otherwise the value 0 will not be shown on the weather card
          }
          var tempLow = Math.round(data[i].templow * 10) / 10;
          if (tempLow == 0) {
            tempLow = '0'; // otherwise the value 0 will not be shown on the weather card
          }
          var condIcon = _WEATHER_TO_ICON[data[i].condition];
          var dayIT = _DAY_TO_DAY[day];
          forecast.push({dayIT:dayIT, tempHigh:tempHigh, tempLow:tempLow, condIcon:condIcon});
          prevDay = day;
        } else {
          if (data[i].max_temp) {
            var tempHigh = Math.round(data[i].max_temp * 10) / 10;
          } else {
            var tempHigh = Math.round(data[i].temperature * 10) / 10;
          }
          var tempLow = Math.round(data[i].tempLow * 10) / 10;
          if (tempLow > forecast[forecast.length-1].tempHigh) {
            forecast[forecast.length-1].tempHigh = tempLow;
          }
          if (tempHigh > forecast[forecast.length-1].tempHigh) {
            forecast[forecast.length-1].tempHigh = tempHigh;
          }
          if (!forecast[forecast.length-1].tempLow) {
            forecast[forecast.length-1].tempLow = tempHigh;
          }
          if (tempHigh < forecast[forecast.length-1].tempLow) {
            forecast[forecast.length-1].tempLow = tempHigh;
          }
          if (tempLow < forecast[forecast.length-1].tempLow) {
            forecast[forecast.length-1].tempLow = tempLow;
          }
        }
      }

      return forecast;
    },

    checkRequirements: function () {
      if (!this.weatherObj) {
        return;
      }
  
      this.nowCond = _WEATHER_TO_ICON[this.weatherObj.state];
      this.nowCondIT = _WEATHER_TO_NAME[this.weatherObj.state];
      if (this.pressureObj != null) {
        this.nowPressure = this.pressureObj.state;
      }
      if (this.visibilityObj != null) {
        this.nowVisibility = this.visibilityObj.state;
      }
      if (this.precipationObj != null) {
        this.nowPrecipation = this.precipationObj.state;
      }
      if (this.precipationfcObj != null) {
        this.nowPrecipationfc = this.precipationfcObj.state;
      }
      if (this.weatherObj.attributes.temperature) {
        this.roundedTemp = Math.round(this.weatherObj.attributes.temperature * 10) / 10;
        if (this.roundedTemp == 0) {
          this.roundedTemp = '0'; // otherwise the value 0 will not be shown on the weather card
        }
      }
      if (this.weatherObj.attributes.wind_bearing) {
        this.windBearing = this.windBearingToText(this.weatherObj.attributes.wind_bearing);
      }
      this.forecast = this.getForecastArray().slice(0, 5);
    },

    windBearingToText: function (degree) {
      // return _DEGREE_TEXT[((parseInt(degree) + 5.63) / 11.25) | 0];
      return _DEGREE_TEXT[(parseInt((degree + 11.25) / 22.5))];
    },
  });
}());
</script>
3 Likes

@gerard33 - thank you for the tweaks you’v been making to this (and a big thanks to @Bram_Kragten for starting this off).

I’m having problems implementing your versions for some reason Gerard - I end up with this using your last two HTML code dumps (my only change is the WEATHER_TO_NAME array to give me English translations):
image

If I use @eddi89’s HTML with the exact same config it shows the temp value and table below. I’ve ruled out cache issues. I’m using the yweather component which is pulling data into the relevant states (apart from your recently added ones).

I did a bit of a compare and can’t see any obvious causes. Any ideas?

Once updated to the latest version cannot make it work. Tried switching from yweather to buienradar, no luck before and after the change.

Found the answer to my own problem.

It seems any of these 4 lines breaks it if there’s no data for them? Of course they were near the bottom :joy:

      this.nowPressure = this.pressureObj.state;
      this.nowVisibility = this.visibilityObj.state;
      this.nowPrecipation = this.precipationObj.state;
      this.nowPrecipationfc = this.precipationfcObj.state;

I will check that. Haven’t tested it without this data so could indeed be the cause of the issue.

Edit
@mynameisdaniel you were right, that part of the code didn’t work when there was no data.
I have now added a check for that so it will also work with empty data.
The code is updated in my post above. Can you try if it works now?

@ardeus, can you also try the new code? And if it still doesn’t work share some more details like screenprints and the Chrome console log?

@gerard33 Tried your version and works much better than the original one I was using. Thanks

1 Like

Works perfectly now. Thanks!

It’s a lovely addition to my tablet interfaces (I still need to tidy up a few things to make colours/sizes more consistent.

Is there any way to override specific values pulled from the weather component to use defined sensor values instead? For example, I’m using Yahoo Weather which is horribly inaccurate for my location (It’s currently 20C, not 16C here…). Dark Sky is better, but only seems to only to today and tomorrow forecasting.

I’d like to use Weather Underground data instead but because it’s not packaged as a Weather component making that happen in this is a bit beyond me. Even if it’s just for today’s forecast and the current conditions, it would be a huge improvement.

3 Likes

hi,

still struggling a bit here… sorry. would you please care to post your full code (maybe in a package)?. Hopefully that might alleviate the issues i seem to run into (now had complaints form the validator about the input_boolean/text).
if yes, thanks! :wink:

tis is what i have now:

input_text:
  weather:

group:
  weather:
    name: Weer custom
    entities:
      - input_text.weather

homeassistant:
  customize: 
    input_text.weather:
      custom_ui_state_card: custom-weather-card
      config:
        weather: weather.yweather

weather:
  
- platform: openweathermap
  api_key: !secret openweather_key

- platform: yweather
  name: Home

- platform: buienradar
  name: !secret buienradar_station

Is there any way to use the default mdi icons for the weather instead of animated icons? I am also getting the blurry icons and like the consistent look of the mdi icons

I’m not getting blurry icons, but the cloud animation is shaky.

Any idea why this might be happening?
shakycloud

I get this too, but only on my Android tablets, I think it’s the webview browser rendering causing issues or struggling. Works fine on other computers/browsers and my iPhone.

Weird, it happens in Chrome (Windows) but not on my Android phone!

I find I only get the blurry icons when the weather forecast is using the same icon. And if I scroll so the forecast icons aren’t in the picture, the main weather icon becomes clear.

Hi All,

I’m totally noob about custom ui.

Can someone please share both conf of html and yaml configuration?

I’m struggling to make it work without success.

2 Likes

@gerard33, I have updated the code in my setup with the latest edits you have implemented and it works like a charm.
This card definitely needs to be implemented as a standard layout of weather component for Home Assistant.
I have managed to make it work as a secondary UI card and config is hassle free. To change the source of the weather data you simply need to configure weather component.

homeassistant:
frontend:
  extra_html_url:
    - /local/custom_ui/state-card-custom-ui.html
    - /local/custom_ui/custom-weather-card.html
    - /local/custom_ui/state-card-iframe.html
  extra_html_url_es5:
    - /local/custom_ui/state-card-custom-ui-es5.html
    - /local/custom_ui/custom-weather-card.html
    - /local/custom_ui/state-card-iframe.html
  customize_glob:
    "*.*":
      custom_ui_state_card: state-card-custom-ui  
    group.devices:
      state_card_mode: badges
    input_boolean.weather:
      state_card_custom_ui_secondary: custom-weather-card
      friendly_name: ''
      config:
        weather: weather.card
weather:
  - platform: yweather
    name: card
input_boolean:
  - weather:
1 Like