Custom UI: Weather state card

@Yevgeniy
I changed the weather platform from Yweather to Open Weather Map, and the issue remains. Works on PC and still does not work on iPhone/iPad in either browser, and gives the same “service system_log/write called” error. I honestly don’t know what else to try.

this is what i use:

homeassistant:
  customize: 
    input_text.animated_weather:
      custom_ui_state_card: custom-weather-card
      config:
        weather: weather.home
#        pressure: sensor.br_pressure
#        visibility: sensor.br_visibility

input_text:
  animated_weather:
    name: Anything

group:
  animated_weather:
    name: Weerkaart
    entities:
      - input_text.animated_weather

weather:
  - platform: yweather
    name: Home

note the input_text opposed to the input_boolean. Also , I had to give the input a name, which isnt used (hence the Anything) without it it wouldn’t work.

Also, since you dont mention it: place the animated icons in the correct folder, and reference that in the custom card.

Thanks so much for your reply. I changed my setup to be like yours. When I tried using input_text yesterday, it didn’t work for me, but it did this time, because I gave it a name as you pointed out. (I do have the animated icons in the correct folder.) It DOES work on my PC, beautifully, whether I use input_text or input_boolean. The problem is that it still does NOT work on my iPhone/iPad, in Chrome or in Safari. I get the same errors about script/frontend.

you dont have this in your frontend.yaml:

javascript_version: auto

it’s the default setting, so probably not necessary , still, you might give it a try…

Actually, at one point I did have that, and tried all the other versions as well. None of them are any different. I have also tried using http://hassio.local:8123/states?es5 and http://hassio.local:8123/states?latest directly in the browser bar and that doesn’t help either. :frowning_face: But thanks for the idea!

At this point I may give up and see what happens when I move to 0.70 which changes the frontend. Usually I wait for the .1 version…

Weather card update:

  1. Click on the weather card to open weather more info window.
  2. Automatic detection of temperature units depending on the system settings (C or F).
  3. Time of sunrise and sunset in the weather card.
  4. Automatically translates days of the week depending on the selected system interface language.
  5. Improved layout for large and small card widths.
custom-weather-card.html
<dom-module id='custom-weather-card'>
  <template>
    <style>
      .clear {
        clear:both;
      }
      .card {
        margin: 0em auto;
        padding-left: 0em;
        padding-right: 0em;
        position: relative;
      }
      .iron-icon {
        height: 18px;
        color: var(--paper-item-icon-color);
      }
      .temp {
        font-weight: 300;
        font-size: 4.5em;
        color: var(--primary-text-color);
        position: absolute;
		top: 0.175em;
        right: 0.75em;
      }
      .temp_unit {
        font-weight: 300;
        font-size: 2.0em;
        vertical-align: super;
        color: var(--primary-text-color);
        position: absolute;
        right: 1em;
        margin-top: -0.1em;
        margin-right: -0.5em;
      }
      .variations {
        font-weight: 300;
        color: var(--primary-text-color);
        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: 8.9em;
		cursor: pointer;
      }
      .day {
        display: block;
        width: 20%;
        float: left;
        text-align: center;
        color: var(--primary-text-color);
        border-right: .1em solid #d9d9d9;
        line-height: 1.6;
        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: var(--secondary-text-color);
      }
      .icon.bigger {
        width: 10em;
        height: 10em;
        margin-top: -3.5em;
        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;
      }

    </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="temp_unit">[[getUnit('temperature')]]</span>
      <br>
      <span>
        <ul class="variations right">
          <template is="dom-if" if="[[weatherObj.attributes.humidity]]">
            <li><span class="iron-icon"><iron-icon icon="mdi:water-percent"></iron-icon></span> [[weatherObj.attributes.humidity]]<span class="unit"> %</span></li>
          </template>
          <template is="dom-if" if="[[weatherObj.attributes.pressure]]">
            <li><span class="iron-icon"><iron-icon icon="mdi:gauge"></iron-icon></span> [[weatherObj.attributes.pressure]]<span class="unit"> hPa</span></li>
          </template>
          <template is="dom-if" if="[[precipationfcObj.state]]">
            <li><span class="iron-icon"><iron-icon icon="mdi:weather-rainy"></iron-icon></span> [[nowPrecipationfc]]<span class="unit" > mm/u (> uur)</span></li>
          </template>
          <template is="dom-if" if="[[sunObj.state]]">
            <li><span class="iron-icon"><iron-icon icon="mdi:weather-sunset-down"></iron-icon></span> [[nowSunset]]</li>
          </template>

        </ul>
        <ul class="variations">
          <template is="dom-if" if="[[weatherObj.attributes.wind_speed]]">
            <li><span class="iron-icon"><iron-icon icon="mdi:weather-windy"></iron-icon></span> [[windBearing]] [[weatherObj.attributes.wind_speed]]<span class="unit"> km/h</span></li>
          </template>
          <template is="dom-if" if="[[weatherObj.attributes.visibility]]">
            <li><span class="iron-icon"><iron-icon icon="mdi:weather-fog"></iron-icon></span> [[weatherObj.attributes.visibility]]<span class="unit"> km</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><span class="iron-icon"><iron-icon icon="mdi:weather-fog"></iron-icon></span> [[nowVisibility]]<span class="unit" > m</span></li>
          </template>
          <template is="dom-if" if="[[precipationObj.state]]">
            <li><span class="iron-icon"><iron-icon icon="mdi:weather-pouring"></iron-icon></span> [[nowPrecipation]]<span class="unit" > mm/u</span></li>
          </template>
          <template is="dom-if" if="[[sunObj.state]]">
            <li><span class="iron-icon"><iron-icon icon="mdi:weather-sunset-up"></iron-icon></span> [[nowSunrise]]</li>
          </template>
        </ul>
      </span>

      <div class="forecast clear" on-click="weatherAttr">
        <template is="dom-repeat" items="[[forecast]]">
        <div class="day"><span class="dayname">[[item.day]]</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]][[getUnit('temperature')]]</span> 
          </template>
          <template is="dom-if" if="[[item.tempLow]]">
          <br> <span class="lowTemp">[[item.tempLow]][[getUnit('temperature')]]</span>
          </template>
        </div>
      </template>
      </div>
    </div>
  </template>
</dom-module>

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

  var _WEATHER_TO_ICON_DAY = {
    'clear-night': 'day',
    '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_ICON_NIGHT = {
    'clear-night': 'night',
    'cloudy': 'cloudy',
    'fog': 'cloudy',
    'hail': 'rainy-7',
    'lightning': 'thunder',
    'lightning-rainy': 'thunder',
    'partlycloudy': 'cloudy-night-3',
    'pouring': 'rainy-6',
    'rainy': 'rainy-5',
    'snowy': 'snowy-6',
    'snowy-rainy': 'rainy-7',
    'sunny': 'night',
    'windy': 'cloudy',
    'windy-variant': 'cloudy-night-3',
    'exceptional': '!!',
  };

  var _DEGREE_TEXT = [
    'N', 'NNO', 'NO', 'ONO', 'O', 'OZO', 'ZO', 'ZZO',
    'Z', 'ZZW', 'ZW', 'WZW', 'W', 'WNW', 'NW', 'NNW', 'N'
  ];

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

    properties: {
      hass: {
        type: Object,
      },
      stateObj: {
        type: Object,
      },
      weatherObj: {
        type: Object,
        observer: 'checkRequirements',
        computed: 'computeWeatherObj(hass, stateObj)',
      },
      sunObj: {
        type: Object,
        observer: 'checkRequirements',
        computed: 'computeSunObj(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)',
      },
    },

	getUnit(unit) {
	  return this.hass.config.core.unit_system[unit] || '';
    },
	ready() {
	  this.addEventListener('weather-more-info', this._weatherAttrClickListener);
	},
	weatherAttr(ev) {
	  this.fire('weather-more-info');
	},
	_weatherAttrClickListener() {
	  this.fire('hass-more-info', { entityId: this.stateObj.attributes.config.weather });
	},
    computeWeatherObj: function (hass, stateObj) {
      return stateObj && stateObj.attributes && stateObj.attributes.config && stateObj.attributes.config.weather ? hass.states[stateObj.attributes.config.weather] : null;
    },
    computeSunObj: function (hass, stateObj) {
      return stateObj && stateObj.attributes && stateObj.attributes.config && stateObj.attributes.config.sun ? hass.states[stateObj.attributes.config.sun] : 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).toLocaleDateString(this.hass.selectedLanguage || this.hass.language, { weekday: 'short' });
        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_DAY[data[i].condition];
          forecast.push({day:day, 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;
      }
      if (this.nowSun == 'above_horizon') {
	    this.nowCond = _WEATHER_TO_ICON_DAY[this.weatherObj.state];
      }
      if (this.nowSun == 'below_horizon') {
	    this.nowCond = _WEATHER_TO_ICON_NIGHT[this.weatherObj.state];
      }
      if (this.pressureObj != null) {
        this.nowPressure = this.pressureObj.state;
      }
      if (this.sunObj != null) {
        this.nowSun = this.sunObj.state;
		this.nowSunrise = new Date(this.sunObj.attributes.next_rising).toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
		this.nowSunset = new Date(this.sunObj.attributes.next_setting).toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
      }
      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);
        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>
For new install

If you already use custom weather card, simply replace the code in custom-weather-card.html

configuration.yaml

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

frontend:
  extra_html_url:
    - /local/custom_ui/custom-weather-card.html
  extra_html_url_es5:
    - /local/custom_ui/custom-weather-card.html

weather:
  - platform: yweather

input_text:
  weather:
    name: weather 

group:
  weather:
    name: Weather
    entities:
      - input_text.weather

Place custom-weather-card.html to \\HASSIO\config\www\custom_ui.

Place icons from here to: \\HASSIO\config\www\weather_icons\animated.

It also works with input_boolean instead input_text.

Screenshots

big
small

Tested on browser Chrome (Windows, Android, Ubuntu), and Firefox (Ubuntu)

12 Likes

Thank you very much.

Thanks @Yevgeniy for sharing, very nice look.

Does it work only with Yahoo weather?

In my city (Rome) Yahoo weather predictions are not very realistic most of the times.

I checked it only with Yahoo weather and OpenWeatherMap custom component. I think it should work also with Buienradar

Thanks, it seems better and more realistic to me, regarding weather predictions.

The only thing that doesn’t show up using Openweathermap is the Visibiliy (expressed in km, the one above the sunrise), but maybe it’s not available in OWM.

Yes, in OpenWeatherMap forecasts, visibility is not available

How can I make my own custom ui card, like this but obtaning data from another sensors instead de weather component?? I’m trying to make with weather underground and other sensors that I have, I’v tried and almost got it but In last update of HA 0.70 I have errors like “Uncaught TypeError: Cannot read property ‘EventsMixin’ of undefined”, Can someone help me ? thanks

I just installed 0.70 and can confirm that the card works as before

@Yevgeniy I like the new card, especially clicking on it for more info! (I have to go in and edit the units to change to miles and wind directions to use N/S/E/W, but that’s easy.) I updated to HA 0.70. I’m so frustrated because I’m still having the same issues with the custom weather card and my iOS devices. Still getting the
service system_log/write called problem - now the related error in the log looks like this:

ERROR (MainThread) [frontend.js.es5.201805264] http://hassio.local:8123/frontend_es5/app-1277c62fbd5787659346fe106c01cc31.js:2:105156 TypeError: Attempted to assign to readonly property.

If I comment out custom-weather-card.html in frontend.yaml, the issue goes away. All my other Custom UI stuff works great. It’s definitely something to do with the weather card, I just wish I knew what! :persevere:

Working fine here on IOS app. Try opening your HA interface on Safari and hard reload it then open IOS app again.

@SouzaaThales Thank you - but that didn’t work either. By hard reload, do you mean just hit the reload icon in the Safari browser bar? I don’t know what I’m doing wrong. I go into Settings and and clear History and Website Data, open Safari, get the error, reload, and get the error again. Safari, Chrome, iOS app all giving me the same error on my iDevices. But it works on my PC with no issues.

What version of iOS are you using? I’m still on 10.3.3. If everyone else is on iOS 11, maybe it has something to do with that?

I’m on 11.3.1. Have you tried on MacOS Safari?

Thanks for that information. I wonder if it could have to do with the version of Safari. I don’t have a Mac, just iPhone and iPad. Seems like mobile Chrome should work; I’m using Chrome on my Windows PC.

1 Like

Not that it helps, but I’m seeing similar with other Custom UI projects, even after upgrading them. I’ve gone back to 0.69 for now - will investigate tomorrow (wife didn’t want HASS offline this evening)

Thanks, Greg - it helps to know I’m not the only one! Although rolling back won’t help me - I had the same issue on 0.67.1. I was hoping moving to 0.70 might help. Good luck!