Home Assistant系列美化篇——替换天气 UI

替换天气组件 weather 的默认 UI,生成美观大方的气象卡片。 Home Assistant 原生的天气平台不少,国内用户常用的有雅虎天气和 Darksky。其他论坛和社区也有分享自制的和风、彩云天气等。

依托国内天气服务商的组件优点自然是数据比较丰富,但前提是找服务商获取了 API。不过 API 的获取现在越来越难了,少不了隐私信息的提供,当然也可以通过抓包获取公共 API,但是持续有效性存疑。其实原生的天气平台本身信息并不少,数据出入不大,基本无需 API,即使需要流程也很简单。

原生天气组件生成的卡片 UI 实在是丑,雅虎的稍好一点懂得放大温度数字,但是同样没有完整的信息展示。而 Darksky 的 weather 组件信息丰富性远远比不上同款 sensor 组件。

官方英文论坛的 Bram Kragten 开发了一套天气组件使用的 UI(论坛讨论 见此 ),赏心悦目,国内爱好者也有利用此套 UI 做了魔改的天气组件。当然别人魔改的作品不在此文讨论内,这里讲一下在接入 Home Assistant 原生的天气平台 weather 下 (推荐雅虎 yweather),如何使用这套 UI。

首先,在 /www/custom_ui/ 文件夹下创建名为 custom-weather-card.html 的文件,没有相关文件夹请自行新建,文件内容为:(部分值已由我人工汉化过了),代码在微信内可能无法阅读,可复制链接 放入浏览器中进行浏览。

代码示例

  1. <dom-module id='custom-weather-card'>

  2.  <template>

  3.    <style>

  4.      .clear {

  5.        clear:both;

  6.      }

  7.      .card {

  8.        margin:1em auto;

  9.        padding-left: 1em;

  10.        padding-right:1em;

  11.        position: relative;

  12.      }

  13.      .iron-icon {

  14.        height: 18px;

  15.        color: #c8c8c8;

  16.      }

  17.      .temp {

  18.        font-weight: 300;

  19.        font-size: 4em;

  20.        color:#5b5b5b;

  21.        position: absolute;

  22.        right: .5em;

  23.      }

  24.      .tempc {

  25.        font-weight: 300;

  26.        font-size: 1.5em;

  27.        vertical-align: super;

  28.        color:#5b5b5b;

  29.        position: absolute;

  30.        right: 0em;

  31.      }

  32.      .variations {

  33.        font-weight:300;

  34.        color:#8c8c8c;

  35.        list-style:none;

  36.        margin-left:-2em;

  37.        margin-top: 2.5em;

  38.      }

  39.      .variations.right {

  40.        float: right;

  41.        margin-left: 0;

  42.        margin-right: 1em;

  43.      }

  44.      .unit {

  45.        font-size:.8em;

  46.      }

  47.      .forecast {

  48.        width:100%;

  49.        margin:0 auto;

  50.        height:9em;

  51.      }

  52.      .day {

  53.        display:block;

  54.        width: 25%;

  55.        float:left;

  56.        text-align:center;

  57.        color: #5b5b5b;

  58.        border-right:.1em solid #d9d9d9;

  59.        line-height: 2;

  60.        box-sizing: border-box;

  61.      }

  62.      .dayname {

  63.        text-transform: uppercase;

  64.      }

  65.      .forecast .day:first-child {

  66.        margin-left: 0;

  67.      }

  68.      .forecast .day:nth-last-child(2) {

  69.        border-right:none;

  70.        margin-right: 0;

  71.      }

  72.      .highTemp {

  73.        font-weight:bold;

  74.      }

  75.      .lowTemp {

  76.        color: #8c8c8c;

  77.      }

  78.      .icon.bigger {

  79.        width: 10em;

  80.        height: 10em;

  81.        margin-top: -4em;

  82.        position: absolute;

  83.        left: 0em;

  84.      }

  85.      .icon {

  86.          width: 50px;

  87.          height: 50px;

  88.          display: inline-block;

  89.          vertical-align: middle;

  90.          background-size: contain;

  91.          background-position: center center;

  92.          background-repeat: no-repeat;

  93.          text-indent: -9999px;

  94.      }

  95.       .weather {

  96.        font-weight: 300;

  97.        font-size: 1.5em;

  98.        color:#5b5b5b;

  99.        text-align:center;

  100.        position: absolute;

  101.        top: 0em;

  102.        left: 6em;

  103.      }

  104.    </style>

  105.    <div class="card">

  106.      <span class="icon bigger" style="background: none, url(/local/weather_icons/animated/[[nowCond]].svg) no-repeat; background-size: contain;"></span>

  107.      <span class="temp">[[roundedTemp]]</span><span class="tempc">&#176;C</span>

  108.      <span class="weather">[[nowCondIT]]</span>

  109.      <br>

  110.      <span>

  111.        <ul class="variations right">

  112.          <template is="dom-if" if="[[weatherObj.attributes.humidity]]">

  113.              <li><iron-icon icon="mdi:water-percent"></iron-icon> [[weatherObj.attributes.humidity]]<span class="unit">%</span></li>

  114.          </template>

  115.          <template is="dom-if" if="[[weatherObj.attributes.pressure]]">

  116.              <li><iron-icon icon="mdi:gauge"></iron-icon> [[weatherObj.attributes.pressure]]<span class="unit"> hPa</span></li>

  117.          </template>

  118.        </ul>

  119.        <ul class="variations">

  120.          <template is="dom-if" if="[[weatherObj.attributes.wind_speed]]">

  121.            <li><iron-icon icon="mdi:weather-windy"></iron-icon> [[windBearing]] [[weatherObj.attributes.wind_speed]]<span class="unit"> m/s</span></li>

  122.          </template>

  123.          <template is="dom-if" if="[[weatherObj.attributes.visibility]]">

  124.              <li><iron-icon icon="mdi:weather-fog"></iron-icon> [[weatherObj.attributes.visibility]]<span class="unit">m</span></li>

  125.          </template>

  126.        </ul>

  127.      </span>

  128.      <div class="forecast clear">

  129.        <template is="dom-repeat" items="[[forecast]]">

  130.        <div class="day"><span class="dayname">[[item.dayIT]]</span>

  131.          <template is="dom-if" if="[[item.condIcon]]">

  132.          <br> <i class="icon" style="background: none, url(/local/weather_icons/animated/[[item.condIcon]].svg) no-repeat; background-size: contain;"></i>

  133.          </template>

  134.          <template is="dom-if" if="[[item.tempHigh]]">

  135.          <br> <span class="highTemp">[[item.tempHigh]]&#176;C</span>

  136.          </template>

  137.          <template is="dom-if" if="[[item.tempLow]]">

  138.          <br> <span class="lowTemp">[[item.tempLow]]&#176;C</span>

  139.          </template>

  140.        </div>

  141.      </template>

  142.      </div>

  143.    </div>

  144.  </template>

  145. </dom-module>

  146. <script>

  147. (function () {

  148.  'use strict';

  149.  var _WEATHER_TO_ICON = {

  150.    cloudy: 'cloudy',

  151.    fog: 'cloudy',

  152.    hail: 'rainy-7',

  153.    lightning: 'thunder',

  154.    'lightning-rainy': 'thunder',

  155.    partlycloudy: 'cloudy-day-3',

  156.    pouring: 'rainy-6',

  157.    rainy: 'rainy-5',

  158.    snowy: 'snowy-6',

  159.    'snowy-rainy': 'rainy-7',

  160.    sunny: 'day',

  161.    windy: 'cloudy',

  162.    'windy-variant': 'cloudy-day-3',

  163.    exceptional: '!!',

  164.  };

  165.  var _WEATHER_TO_NAME = {

  166.    cloudy: '多云',

  167.    fog: '雾',

  168.    hail: '冰雹',

  169.    lightning: '雷电',

  170.    'lightning-rainy': '雷雨',

  171.    partlycloudy: '局部多云',

  172.    pouring: '大雨',

  173.    rainy: '雨',

  174.    snowy: '雪',

  175.    'snowy-rainy': '雨夹雪',

  176.    sunny: '晴',

  177.    windy: '多风',

  178.    'windy-variant': '阵风',

  179.    exceptional: '!',

  180.  };

  181.  var _DEGREE_TEXT = [

  182.    '北', '东北偏北', '东北', '东北偏东', '东', '东南偏东', '东南', '东南偏南',

  183.    '南', '西南偏南', '西南', '西南偏西', '西', '西北偏西', '西北', '西北偏北'

  184.  ];

  185.  var _DAY_TO_DAY = {

  186.    Mon: '周一',

  187.    Tue: '周二',

  188.    Wed: '周三',

  189.    Thu: '周四',

  190.    Fri: '周五',

  191.    Sat: '周六',

  192.    Sun: '周日',

  193.  };

  194.  Polymer({

  195.    is: 'custom-weather-card',

  196.    properties: {

  197.      hass: {

  198.        type: Object,

  199.      },

  200.      stateObj: {

  201.        type: Object,

  202.      },

  203.      weatherObj: {

  204.        type: Object,

  205.        observer: 'checkRequirements',

  206.        computed: 'computeWeatherObj(hass, stateObj)',

  207.      },

  208.    },

  209.    computeWeatherObj: function (hass, stateObj) {

  210.      return stateObj && stateObj.attributes && stateObj.attributes.config && stateObj.attributes.config.weather ? hass.states[stateObj.attributes.config.weather] : null;

  211.    },

  212.    getForecastArray: function () {

  213.      if (!this.weatherObj.attributes.forecast) {

  214.        return [];

  215.      }

  216.      var data = this.weatherObj.attributes.forecast;

  217.      var forecast = [];

  218.      var prevDay = '';

  219.      for (var i = 0; i < data.length; i++) {

  220.        var day = new Date(data[i].datetime).toString().split(' ')[0];

  221.        if (day != prevDay) {

  222.          if (data[i].max_temp) {

  223.            var tempHigh = Math.round(data[i].max_temp * 10) / 10;

  224.          } else {

  225.            var tempHigh = Math.round(data[i].temperature * 10) / 10;

  226.          }

  227.          if (tempHigh == 0) {

  228.            tempHigh = '0'; // otherwise the value 0 will not be shown on the weather card

  229.          }

  230.          var tempLow = Math.round(data[i].templow * 10) / 10;

  231.          if (tempLow == 0) {

  232.            tempLow = '0'; // otherwise the value 0 will not be shown on the weather card

  233.          }

  234.          var condIcon = _WEATHER_TO_ICON[data[i].condition];

  235.          var dayIT = _DAY_TO_DAY[day];

  236.          forecast.push({dayIT:dayIT, tempHigh:tempHigh, tempLow:tempLow, condIcon:condIcon});

  237.          prevDay = day;

  238.        } else {

  239.          if (data[i].max_temp) {

  240.            var tempHigh = Math.round(data[i].max_temp * 10) / 10;

  241.          } else {

  242.            var tempHigh = Math.round(data[i].temperature * 10) / 10;

  243.          }

  244.          var tempLow = Math.round(data[i].tempLow * 10) / 10;

  245.          if (tempLow > forecast[forecast.length-1].tempHigh) {

  246.            forecast[forecast.length-1].tempHigh = tempLow;

  247.          }

  248.          if (tempHigh > forecast[forecast.length-1].tempHigh) {

  249.            forecast[forecast.length-1].tempHigh = tempHigh;

  250.          }

  251.          if (!forecast[forecast.length-1].tempLow) {

  252.            forecast[forecast.length-1].tempLow = tempHigh;

  253.          }

  254.          if (tempHigh < forecast[forecast.length-1].tempLow) {

  255.            forecast[forecast.length-1].tempLow = tempHigh;

  256.          }

  257.          if (tempLow < forecast[forecast.length-1].tempLow) {

  258.            forecast[forecast.length-1].tempLow = tempLow;

  259.          }

  260.        }

  261.      }

  262.      return forecast;

  263.    },

  264.    checkRequirements: function () {

  265.      if (!this.weatherObj) {

  266.        return;

  267.      }

  268.      this.nowCond = _WEATHER_TO_ICON[this.weatherObj.state];

  269.      this.nowCondIT = _WEATHER_TO_NAME[this.weatherObj.state];

  270.      if (this.weatherObj.attributes.temperature) {

  271.      this.roundedTemp = Math.round( this.weatherObj.attributes.temperature * 10) / 10;

  272.      }

  273.      if (this.weatherObj.attributes.wind_bearing) {

  274.      this.windBearing = this.windBearingToText(this.weatherObj.attributes.wind_bearing);

  275.      }

  276.        this.forecast = this.getForecastArray().slice(0, 4);

  277.    },

  278.    windBearingToText: function (degree) {

  279.      // return _DEGREE_TEXT[((parseInt(degree) + 5.63) / 11.25) | 0];

  280.      return _DEGREE_TEXT[(parseInt((degree + 11.25) / 22.5))];

  281.    },

  282.  });

  283. }());

接着,下载全套天气图标(公众号回复“天气图标” 获取文件),解压后的 svg 文件放入 wwwweather_iconsanimated 文件夹内。

最后,在配置文件 configuration.yaml 中添加相关设置(文档分离用户请自行判断):

  1. #接入雅虎天气

  2. weather:    

  3.  - platform: yweather

  4.    woeid:

  5. #引入引导组件

  6. input_boolean:  

  7.  weather:


  8. #前端接入自定义UI

  9. frontend:

  10.  extra_html_url:

  11.    - /local/custom_ui/custom-weather-card.html

  12.  extra_html_url_es5:

  13.    - /local/custom_ui/custom-weather-card.html

  14. #调用 UI

  15. customize:

  16.  input_boolean.weather:

  17.    custom_ui_state_card: custom-weather-card

  18.    config:

  19.      weather: weather.yweather

  20.  weather.yweather:    #隐藏原 UI 组件

  21.    hidden: true


  22. #合成群组

  23. group:

  24.  weather:

  25.    view: no

  26.    name: 天气

  27.    entities:

  28.      - input_boolean.weather

上述操作完成后,重启 Home Assistant,你的天气卡片就焕然一新了~~

此篇文章由墨澜妹纸投稿,感谢妹纸!


关注一下,

👇👇👇

Home Assistant系列美化篇——替换天气 UI





发表评论