Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 64 additions & 4 deletions homeassistant/components/template/weather.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Template platform that aggregates meteorological data."""
import logging

import voluptuous as vol

from homeassistant.components.weather import (
Expand All @@ -20,10 +22,18 @@
ENTITY_ID_FORMAT,
WeatherEntity,
)
from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID
from homeassistant.const import (
CONF_NAME,
CONF_UNIQUE_ID,
LENGTH_KILOMETERS,
PRESSURE_HPA,
PRESSURE_INHG,
)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.util.pressure import convert as convert_pressure
from homeassistant.util.unit_system import METRIC_SYSTEM

from .template_entity import TemplateEntity

Expand Down Expand Up @@ -74,6 +84,8 @@
}
)

_LOGGER = logging.getLogger(__name__)


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Template weather."""
Expand Down Expand Up @@ -183,32 +195,80 @@ def temperature_unit(self):
@property
def humidity(self):
"""Return the humidity."""
# unit from template: %
return self._humidity

@property
def wind_speed(self):
"""Return the wind speed."""
return self._wind_speed
# unit from template: km/h

if self._wind_speed is None or self._wind_speed == "":
return None

try:
return self.hass.config.units.length(
float(self._wind_speed), LENGTH_KILOMETERS
)
except ValueError:
_LOGGER.warning(
"Weather template '%s': wind_speed_template must expand to a number, but results in '%s'",
self.entity_id,
self._wind_speed,
)
return None

@property
def wind_bearing(self):
"""Return the wind bearing."""
# unit from template: °
return self._wind_bearing

@property
def ozone(self):
"""Return the ozone level."""
# unit from template: ppm (parts per million)
return self._ozone

@property
def visibility(self):
"""Return the visibility."""
return self._visibility
# unit from template: km

if self._visibility is None or self._visibility == "":
return None

try:
return self.hass.config.units.length(
float(self._visibility), LENGTH_KILOMETERS
)
except ValueError:
_LOGGER.warning(
"Weather template '%s': visibility_template must expand to a number, but results in '%s'",
self.entity_id,
self._visibility,
)
return None

@property
def pressure(self):
"""Return the air pressure."""
return self._pressure
# unit from template: hPa

if self._pressure is None or self._pressure == "":
return None

try:
if self.hass.config.units == METRIC_SYSTEM:
return float(self._pressure)
return convert_pressure(float(self._pressure), PRESSURE_HPA, PRESSURE_INHG)
except ValueError:
_LOGGER.warning(
"Weather template '%s': pressure_template must expand to a number, but results in '%s'",
self.entity_id,
self._pressure,
)
return None

@property
def forecast(self):
Expand Down
102 changes: 86 additions & 16 deletions tests/components/template/test_weather.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""The tests for the Template Weather platform."""
from pytest import approx

from homeassistant.components.weather import (
ATTR_WEATHER_ATTRIBUTION,
ATTR_WEATHER_HUMIDITY,
Expand All @@ -11,10 +13,20 @@
DOMAIN,
)
from homeassistant.setup import async_setup_component
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem


async def test_template_state_text_metric(hass):
"""Test the state text of a template with metric unit system."""
await _test_template_state_text(hass, METRIC_SYSTEM)


async def test_template_state_text(hass):
"""Test the state text of a template."""
async def test_template_state_text_imperial(hass):
"""Test the state text of a template with imperial unit system."""
await _test_template_state_text(hass, IMPERIAL_SYSTEM)


async def _test_template_state_text(hass, unit_system: UnitSystem):
await async_setup_component(
hass,
DOMAIN,
Expand Down Expand Up @@ -43,21 +55,26 @@ async def test_template_state_text(hass):
await hass.async_start()
await hass.async_block_till_done()

hass.config.units = unit_system
await hass.async_block_till_done()

hass.states.async_set("sensor.attribution", "The custom attribution")
await hass.async_block_till_done()
hass.states.async_set("sensor.temperature", 22.3)
# all temperature sensors are automatically converted into hass.config.units.temperature
# therefore, value will be interpreted as-is
hass.states.async_set("sensor.temperature", 22.3) # in °C or °F
await hass.async_block_till_done()
hass.states.async_set("sensor.humidity", 60)
hass.states.async_set("sensor.humidity", 60) # in %
await hass.async_block_till_done()
hass.states.async_set("sensor.pressure", 1000)
hass.states.async_set("sensor.pressure", 1002.3) # in hPa
await hass.async_block_till_done()
hass.states.async_set("sensor.windspeed", 20)
hass.states.async_set("sensor.windspeed", 10) # in km/h
await hass.async_block_till_done()
hass.states.async_set("sensor.windbearing", 180)
hass.states.async_set("sensor.windbearing", 180) # in °
await hass.async_block_till_done()
hass.states.async_set("sensor.ozone", 25)
hass.states.async_set("sensor.ozone", 25) # in ppm
await hass.async_block_till_done()
hass.states.async_set("sensor.visibility", 4.6)
hass.states.async_set("sensor.visibility", 4.6) # in km
await hass.async_block_till_done()

state = hass.states.get("weather.test")
Expand All @@ -67,10 +84,63 @@ async def test_template_state_text(hass):

data = state.attributes
assert data.get(ATTR_WEATHER_ATTRIBUTION) == "The custom attribution"
assert data.get(ATTR_WEATHER_TEMPERATURE) == 22.3
assert data.get(ATTR_WEATHER_HUMIDITY) == 60
assert data.get(ATTR_WEATHER_PRESSURE) == 1000
assert data.get(ATTR_WEATHER_WIND_SPEED) == 20
assert data.get(ATTR_WEATHER_WIND_BEARING) == 180
assert data.get(ATTR_WEATHER_OZONE) == 25
assert data.get(ATTR_WEATHER_VISIBILITY) == 4.6
assert data.get(ATTR_WEATHER_HUMIDITY) == 60 # in %
assert data.get(ATTR_WEATHER_WIND_BEARING) == 180 # in °
assert data.get(ATTR_WEATHER_OZONE) == 25 # in ppm

if unit_system is METRIC_SYSTEM:
assert data.get(ATTR_WEATHER_TEMPERATURE) == approx(22.3) # in °C
assert data.get(ATTR_WEATHER_PRESSURE) == approx(1002.3) # in hpa
assert data.get(ATTR_WEATHER_WIND_SPEED) == approx(10) # in km/h
assert data.get(ATTR_WEATHER_VISIBILITY) == approx(4.6) # in km
else:
assert data.get(ATTR_WEATHER_TEMPERATURE) == approx(22) # in °F (rounded)
assert data.get(ATTR_WEATHER_PRESSURE) == approx(29.597899) # in inhg
assert data.get(ATTR_WEATHER_WIND_SPEED) == approx(6.21371) # in mph
assert data.get(ATTR_WEATHER_VISIBILITY) == approx(2.858306) # in mi


async def test_template_state_text_empty_sensor_values(hass):
"""Test the state text of a template if sensors report empty values."""
await async_setup_component(
hass,
DOMAIN,
{
"weather": [
{"weather": {"platform": "demo"}},
{
"platform": "template",
"name": "test",
"attribution_template": "{{ states('sensor.attribution') }}",
"condition_template": "sunny",
"forecast_template": "{{ states.weather.demo.attributes.forecast }}",
"temperature_template": "{{ states('sensor.temperature') | float }}",
"humidity_template": "{{ states('sensor.humidity') | int }}",
"pressure_template": "{{ states('sensor.pressure') }}",
"wind_speed_template": "{{ states('sensor.windspeed') }}",
"wind_bearing_template": "{{ states('sensor.windbearing') }}",
"ozone_template": "{{ states('sensor.ozone') }}",
"visibility_template": "{{ states('sensor.visibility') }}",
},
]
},
)
await hass.async_block_till_done()

await hass.async_start()
await hass.async_block_till_done()

hass.states.async_set("sensor.pressure", "")
await hass.async_block_till_done()
hass.states.async_set("sensor.windspeed", "")
await hass.async_block_till_done()
hass.states.async_set("sensor.visibility", "")
await hass.async_block_till_done()

state = hass.states.get("weather.test")
assert state is not None

data = state.attributes
assert data.get(ATTR_WEATHER_PRESSURE) is None
assert data.get(ATTR_WEATHER_WIND_SPEED) is None
assert data.get(ATTR_WEATHER_VISIBILITY) is None