Custom animated weather card for Lovelace

how did you fix this error?

using platform Buienradar :wink:

  - type: custom:card-modder
    style:
      background-image: url("/local/lovelace/images/weather-background-[[ sun.sun.state ]].png")
      background-size: 100% 400px
      --primary-text-color: var(--primary-text-color)
      --secondary-text-color: var(--secondary-text-color)
      --paper-item-icon-color: var(--primary-text-color) #small variation icons
    card:
      type: custom:weather-card
      title: Modded Woensdrecht
      entity_weather: weather.woensdrecht
      entity_sun: sun.sun
      entity_apparent_temperature: sensor.jagti_windchill
      entity_wind_force: sensor.br_wind_force

46

Is it background-image: “weather-background-above_horizon.png”

The actual filename?

yes it is:

26

why you ask? please note I do use another path than the default for the images compared to what many people use, or is in the card itself, Ive changed that.

I was struggling to get it to work, just wanted to make sure the naming convention was right!

Thank you!

here you can find an updated version for dark sky

cant get it to work:

please define entities…

card:
entities:
  - entity_sun: sun.sun
  - entity_daily_summary: sensor.dark_sky_daily_summary
  - entity_current_conditions: sensor.dark_sky_icon  
  - entity_humidity: sensor.dark_sky_humidity
  - entity_pressure: sensor.dark_sky_pressure
  - entity_temperature: sensor.dark_sky_temperature
  - entity_visibility: sensor.dark_sky_visibility
  - entity_wind_bearing: sensor.dark_sky_wind_bearing
  - entity_wind_speed: sensor.dark_sky_wind_speed
  - entity_forecast_high_temp_1: sensor.dark_sky_daytime_high_temperature_1
  - entity_forecast_high_temp_2: sensor.dark_sky_daytime_high_temperature_2
  - entity_forecast_high_temp_3: sensor.dark_sky_daytime_high_temperature_3
  - entity_forecast_high_temp_4: sensor.dark_sky_daytime_high_temperature_4
  - entity_forecast_high_temp_5: sensor.dark_sky_daytime_high_temperature_5
  - entity_forecast_low_temp_1: sensor.dark_sky_overnight_low_temperature
  - entity_forecast_low_temp_2: sensor.dark_sky_overnight_low_temperature_1
  - entity_forecast_low_temp_3: sensor.dark_sky_overnight_low_temperature_2
  - entity_forecast_low_temp_4: sensor.dark_sky_overnight_low_temperature_3
  - entity_forecast_low_temp_5: sensor.dark_sky_overnight_low_temperature_4
  - entity_forecast_icon_1: sensor.dark_sky_icon_1
  - entity_forecast_icon_2: sensor.dark_sky_icon_2
  - entity_forecast_icon_3: sensor.dark_sky_icon_3
  - entity_forecast_icon_4: sensor.dark_sky_icon_4
  - entity_forecast_icon_5: sensor.dark_sky_icon_5
type: custom:weather-card

you can find a very good readme on github

maybe this helps. it works for me

Thanks but i already have done this, whenever i trey to create to card 0.84) i am getting this error

2019-01-09 17:32:55 ERROR (MainThread) [frontend.js.latest.201812112] http://10.3.1.2:8123/frontend_latest/73dd7f5c04d8a295f213.chunk.js:14:1162 Uncaught NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

youre post show type js but githob shows type module

I migrated this card to Lit for better performance, it can be found here:

3 Likes

I also like to convert from the Dark sky platform to the Buienradar platform but I don’t have a clue how to map or convert from buienradar icons to the animated icons. Are you willing to share your code ?

why of course:

this is the Lovelace card:

cards:
  - type: custom:useful-markdown-card
    content: >
      **Weersverwachting:** <font color=var(--secondary-text-color)>[[ sensor.vandaag.state ]]</font> 
                [[ sensor.dark_sky_daily_summary.state ]]
  - type: custom:card-modder
    style:
      background-image: url("/local/lovelace/images/weather-background-[[ sun.sun.state ]].png")
      background-size: 100% 400px
      --primary-text-color: var(--primary-text-color)
      --secondary-text-color: var(--secondary-text-color)
      --paper-item-icon-color: var(--primary-text-color) #small variation icons
    card:
      type: custom:weather-card
      title: Modded Woensdrecht
      entity_weather: weather.woensdrecht
      entity_sun: sun.sun
      entity_apparent_temperature: sensor.jagti_windchill
      entity_wind_force: sensor.br_wind_force

weather.woensdrecht is my Buienradar weather component, and the apparent temperature sensor is my own template sensor:

  jagti_windchill:
    value_template: >
      {% set temp = states('sensor.br_temperature')|float %}
      {% set wind = states('sensor.br_wind_speed')|float %}
      {{(13.12 +0.6215*temp  + 0.3965*(temp - 28.676)*(wind**0.16)) | round(2) }}
    unit_of_measurement: '°C'
    device_class: temperature
    friendly_name: Jag/Ti Wchill

I did change several settings in the weather-card and .js file:

weather-card.css:

.clear {
    clear: both;
  }

  .card {
    margin: auto;
    padding-top: 2em;
    padding-bottom: 1em;
    padding-left: 1em;
    padding-right:1em;
    position: relative;
  }

  .header {
    font-weight: 400;
    font-size: 2em;
    color: var(--primary-text-color);
    text-align: left;
    position: absolute;
    top: 1em;
    left: 1em;
    text-transform: capitalize;
    word-wrap: break-word;
    width: 50%;
  }

  .weather {
    font-weight: 400;
    font-size: 2em;
    color: grey;
    position: absolute;
    margin-right: 0.5em;
    top: 1em;
    right: 0em;
    text-transform: capitalize;
#    word-wrap: break-word;
#    width: 40%;
  }

  .icon.bigger {
    width: 12em;
    height: 12em;
    left: 0em;
  }

  .ha-icon {
    height: 18px;
    margin-right: 5px;
    color: var(--paper-item-icon-color);
  }

  .temp {
    font-weight: 300;
    font-size: 4em;
    color: var(--primary-text-color);
    position: absolute;
    top: 1.5em;
    right: 1em;
  }

  .tempa {
    position: absolute;
    color: var(--primary-text-color);
    right: 1em;
    font-size: 1em;
    font-weight: 300;
    margin-right: 2em;
    margin-top: -5.5em;
  }

  .unitc {
    position: absolute;
    color: var(--secondary-text-color);
    font-weight: 300;
    font-size: 1em;
    right: .6em;
    vertical-align: super;
    margin-left: 0;
    margin-right: 1em;
    margin-top: -5.5em;
  }

  .tempc {
    position: absolute;
    color: var(--secondary-text-color);
    font-weight: 300;
    font-size: 1.5em;
    vertical-align: super;
    right: 1em;
    margin-top: -6em;
    margin-right: 7px;
  }

  .variations {
    display: inline-block;
    font-weight: 300;
    color: var(--primary-text-color);
    list-style: none;
    margin-left: -2em;
    margin-top: -2em;
  }

  .variations.right {
    position: absolute;
    right: 1em;
    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: var(--primary-text-color);
    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(1) {
    border-right: none;
    margin-right: 0;
  }

  .highTemp {
    font-weight: bold;
  }

  .lowTemp {
    color: var(--secondary-text-color);
  }

  .icon {
    width: 50px;
    height: 50px;
    margin-right: 5px;
    display: inline-block;
    vertical-align: middle;
    background-size: contain;
    background-position: center center;
    background-repeat: no-repeat;
    text-indent: -9999px;
  }

weather-card.js:

class WeatherCard extends HTMLElement {
  set hass(hass) {
    if (!this.content) {
      const card = document.createElement('ha-card');
      const link = document.createElement('link');
      link.type = 'text/css';
      link.rel = 'stylesheet';
      link.href = '/local/lovelace/resources/weather-card.css';
      card.appendChild(link);
      this.content = document.createElement('div');
      this.content.className = 'card';
      card.appendChild(this.content);
      this.appendChild(card);
    }

    const getUnit = function (measure) {
      const lengthUnit = hass.config.unit_system.length;
      switch (measure) {
        case 'air_pressure':
          return lengthUnit === 'km' ? 'hPa' : 'inHg';
        case 'length':
          return lengthUnit;
        case 'visibility':
          return lengthUnit === 'km' ? 'm' : 'in';
        case 'precipitation':
          return lengthUnit === 'km' ? 'mm' : 'in';
        default:
          return hass.config.unit_system[measure] || '';
      }
    };

    const transformDayNight = {
      "below_horizon": "night",
      "above_horizon": "day",
    }
    const sunLocation = transformDayNight[hass.states[this.config.entity_sun].state];
    const weatherIcons = {
      'clear-night': `${sunLocation}`,
      'cloudy': 'cloudy',
      'fog': 'cloudy',
      'hail': 'rainy-7',
      'lightning': 'thunder',
      'lightning-rainy': 'thunder',
      'partlycloudy': `cloudy-${sunLocation}-3`,
      'pouring': 'rainy-6',
      'rainy': 'rainy-5',
      'snowy': 'snowy-6',
      'snowy-rainy': 'rainy-7',
      'sunny': `${sunLocation}`,
      'windy': 'cloudy',
      'windy-variant': `cloudy-${sunLocation}-3`,
      'exceptional': '!!',
    }

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

    const entity = hass.states[this.config.entity_weather];
    const station = entity.attributes.friendly_name;
    const currentCondition = entity.state;
    const humidity = entity.attributes.humidity;
    const pressure = entity.attributes.pressure;
    const temperature = entity.attributes.temperature;
    const visibility = entity.attributes.visibility;
    const windBearing = windDirections[(parseInt((entity.attributes.wind_bearing + 11.25) / 22.5))];
    const windSpeed = entity.attributes.wind_speed;
    const windBft = hass.states[this.config.entity_wind_force].state;
    const forecast = entity.attributes.forecast.slice(0, 5);
    const apparent_temperature = hass.states[this.config.entity_apparent_temperature].state;

    var sunSetOrRiseA = new Date(hass.states[this.config.entity_sun].attributes.next_setting);
    var sunSetOrRiseIconA = "mdi:weather-sunset-down";
    var sunSetOrRiseB = new Date(hass.states[this.config.entity_sun].attributes.next_rising);
    var sunSetOrRiseIconB = "mdi:weather-sunset-up";
    // A == sunset   B == sunrise
    if ( hass.states[this.config.entity_sun].state == "above_horizon" ) {
        // sun has risen, but hasn't set
        // sunset is today (no date displayed). sunrise is tomorrow (display date)
        // next is sunset == A
        
        sunSetOrRiseA = sunSetOrRiseA.toLocaleTimeString();
        var ssrI = sunSetOrRiseA.lastIndexOf(":");
        sunSetOrRiseA = sunSetOrRiseA.substr(0,ssrI) + sunSetOrRiseA.substr(ssrI+4);
        sunSetOrRiseB = sunSetOrRiseB.toDateString().substr(0,3) + " " + sunSetOrRiseB.toLocaleTimeString();
        ssrI = sunSetOrRiseB.lastIndexOf(":");
        sunSetOrRiseB = sunSetOrRiseB.substr(0,ssrI) + sunSetOrRiseB.substr(ssrI+4);
    } else {
        // next is sunrise == B
        var ss = sunSetOrRiseA;
        if ( new Date().getDate() != sunSetOrRiseB.getDate() ) {
            // sun hasn't risen, and it's not same day
            // so display dates for both
            sunSetOrRiseA = sunSetOrRiseB.toDateString().substr(0,3) + " " + sunSetOrRiseB.toLocaleTimeString();
            sunSetOrRiseB = ss.toDateString().substr(0,3) + " " + ss.toLocaleTimeString();
        } else {
            // sun hasn't risen, but it's the same day
            // since rise and set are today, no dates displayed
            sunSetOrRiseA = sunSetOrRiseB.toLocaleTimeString();
            sunSetOrRiseB = ss.toLocaleTimeString();
        }
        var ssrI = sunSetOrRiseA.lastIndexOf(":");
        sunSetOrRiseA = sunSetOrRiseA.substr(0,ssrI) + sunSetOrRiseA.substr(ssrI+4);
        ssrI = sunSetOrRiseB.lastIndexOf(":");
        sunSetOrRiseB = sunSetOrRiseB.substr(0,ssrI) + sunSetOrRiseB.substr(ssrI+4);
        sunSetOrRiseIconA = "mdi:weather-sunset-up";
        sunSetOrRiseIconB = "mdi:weather-sunset-down";
    }

    this.content.innerHTML = `
      <span class="header" >${currentCondition}</span><span class="weather">${station}</span>
      <span class="icon bigger" style="background: none, url(/local/weather/animated/${weatherIcons[currentCondition]}.svg) no-repeat; background-size: contain;"> </span>

      <ul style="list-style-type:none">
        <li><span class="temp">${temperature}</span><span class="tempc"> ${getUnit('temperature')}</span></li>
        <li><span class="tempa">Feels like ${apparent_temperature}</span><span class="unitc"> ${getUnit('temperature')}</span></li>
      </ul>
      <span>
        <ul class="variations right">
            <li><span class="ha-icon"><ha-icon icon="mdi:water-percent"></ha-icon></span>${humidity}<span class="unit"> %</span></li>
            <li><span class="ha-icon"><ha-icon icon="mdi:gauge"></ha-icon></span>${pressure}<span class="unit"> ${getUnit('air_pressure')}</span></li>
            <li><span class="ha-icon"><ha-icon icon=${sunSetOrRiseIconB}></ha-icon></span>${sunSetOrRiseB}</li>
        </ul>
        <ul class="variations">
            <li><span class="ha-icon"><ha-icon icon="mdi:weather-windy"></ha-icon></span>Bft: ${windBft} - ${windBearing} ${windSpeed}<span class="unit"> ${getUnit('length')}/h</span></li>
            <li><span class="ha-icon"><ha-icon icon="mdi:weather-fog"></ha-icon></span>${visibility}<span class="unit"> ${getUnit('visibility')}</span></li>
            <li><span class="ha-icon"><ha-icon icon=${sunSetOrRiseIconA}></ha-icon></span>${sunSetOrRiseA}</li>
        </ul>
      </span>
      <div class="forecast clear">
          ${forecast.map(daily => `
              <div class="day">
                  <span class="dayname">${(new Date(daily.datetime)).toLocaleDateString((navigator.language) ? navigator.language : navigator.userLanguage, {weekday: 'short'}).split(' ')[0]}</span>
                  <br><i class="icon" style="background: none, url(/local/weather/animated/forecast/${weatherIcons[daily.condition]}.svg);"></i>
                  <br><span class="highTemp">${daily.temperature}${getUnit('temperature')}</span>
                  <br><span class="lowTemp">${daily.templow}${getUnit('temperature')}</span>
              </div>`).join('')}
      </div>`;
  }

  setConfig(config) {
    if (!config.entity_weather || !config.entity_sun) {
      throw new Error('Please define entities');
    }
    this.config = config;
  }

  getCardSize() {
    return 3;
  }
}

customElements.define('weather-card', WeatherCard);

HI Bram,

what exactly is that Lit? and why is this better for performance? I always notice heavy fanning when on the weather page, so very interested if a lighter weather card could solve that.

Lit:

A simple base class for creating fast, lightweight web components

We use it for Lovelace instead of Polymer.

The old card would be rebuilt on every state change in hass, so even if a light would be updated, the weather card is rebuilt.

Now it will update if the weather state is changed, like the built-in cards do it.

1 Like

Thanks for the code. As a “dummy” i’ll have to dive in to it to find out how it works but finally I will get there .

sounds good :wink:
will update swiftly and hope my ole Air will be quieter…

thanks for taking the effort explaining

quick look for the night/day icons doesn’t seem this new card has it built-in? In my current card I use this, maybe you would be willing to incorporate that too @Bram_Kragten ?

Always a silly sight when a clear night show a radiating sun…

const transformDayNight = {
  "below_horizon": "night",
  "above_horizon": "day",
}
const sunLocation = transformDayNight[hass.states[this.config.entity_sun].state];
const weatherIcons = {
  'clear-night': `${sunLocation}`,
  'cloudy': 'cloudy',
  'fog': 'cloudy',
  'hail': 'rainy-7',
  'lightning': 'thunder',
  'lightning-rainy': 'thunder',
  'partlycloudy': `cloudy-${sunLocation}-3`,
  'pouring': 'rainy-6',
  'rainy': 'rainy-5',
  'snowy': 'snowy-6',
  'snowy-rainy': 'rainy-7',
  'sunny': `${sunLocation}`,
  'windy': 'cloudy',
  'windy-variant': `cloudy-${sunLocation}-3`,
  'exceptional': '!!',
}

thanks for considering!

I does have it, just works different:

1 Like

Using it, works fine!
Only the description in the readme is wrong.
It says:

  1. Save, the amcharts 1.9k icon under

www\icons\weather_icons\animated

But looking in the file I see

/local/custom_ui/weather_icons/animated/

Just my 2 cents :slight_smile:

Thanks will fix!