Move temperature display helper from components to helpers (#10555)

This commit is contained in:
Fabian Affolter 2017-11-14 10:36:18 +01:00 committed by Pascal Vizeli
parent b1afed9e52
commit d25f676711
9 changed files with 186 additions and 116 deletions

View File

@ -9,12 +9,12 @@ from datetime import timedelta
import logging import logging
import os import os
import functools as ft import functools as ft
from numbers import Number
import voluptuous as vol import voluptuous as vol
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
from homeassistant.helpers.temperature import display_temp as show_temp
from homeassistant.util.temperature import convert as convert_temperature from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
@ -22,7 +22,7 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN, ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN,
TEMP_CELSIUS) TEMP_CELSIUS, PRECISION_WHOLE, PRECISION_TENTHS)
DOMAIN = 'climate' DOMAIN = 'climate'
@ -71,11 +71,6 @@ ATTR_OPERATION_LIST = 'operation_list'
ATTR_SWING_MODE = 'swing_mode' ATTR_SWING_MODE = 'swing_mode'
ATTR_SWING_LIST = 'swing_list' ATTR_SWING_LIST = 'swing_list'
# The degree of precision for each platform
PRECISION_WHOLE = 1
PRECISION_HALVES = 0.5
PRECISION_TENTHS = 0.1
CONVERTIBLE_ATTRIBUTE = [ CONVERTIBLE_ATTRIBUTE = [
ATTR_TEMPERATURE, ATTR_TEMPERATURE,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_LOW,
@ -456,12 +451,18 @@ class ClimateDevice(Entity):
def state_attributes(self): def state_attributes(self):
"""Return the optional state attributes.""" """Return the optional state attributes."""
data = { data = {
ATTR_CURRENT_TEMPERATURE: ATTR_CURRENT_TEMPERATURE: show_temp(
self._convert_for_display(self.current_temperature), self.hass, self.current_temperature, self.temperature_unit,
ATTR_MIN_TEMP: self._convert_for_display(self.min_temp), self.precision),
ATTR_MAX_TEMP: self._convert_for_display(self.max_temp), ATTR_MIN_TEMP: show_temp(
ATTR_TEMPERATURE: self.hass, self.min_temp, self.temperature_unit,
self._convert_for_display(self.target_temperature), self.precision),
ATTR_MAX_TEMP: show_temp(
self.hass, self.max_temp, self.temperature_unit,
self.precision),
ATTR_TEMPERATURE: show_temp(
self.hass, self.target_temperature, self.temperature_unit,
self.precision),
} }
if self.target_temperature_step is not None: if self.target_temperature_step is not None:
@ -469,10 +470,12 @@ class ClimateDevice(Entity):
target_temp_high = self.target_temperature_high target_temp_high = self.target_temperature_high
if target_temp_high is not None: if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display( data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.target_temperature_high) self.hass, self.target_temperature_high, self.temperature_unit,
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display( self.precision)
self.target_temperature_low) data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit,
self.precision)
humidity = self.target_humidity humidity = self.target_humidity
if humidity is not None: if humidity is not None:
@ -733,24 +736,3 @@ class ClimateDevice(Entity):
def max_humidity(self): def max_humidity(self):
"""Return the maximum humidity.""" """Return the maximum humidity."""
return 99 return 99
def _convert_for_display(self, temp):
"""Convert temperature into preferred units for display purposes."""
if temp is None:
return temp
# if the temperature is not a number this can cause issues
# with polymer components, so bail early there.
if not isinstance(temp, Number):
raise TypeError("Temperature is not a number: %s" % temp)
if self.temperature_unit != self.unit_of_measurement:
temp = convert_temperature(
temp, self.temperature_unit, self.unit_of_measurement)
# Round in the units appropriate
if self.precision == PRECISION_HALVES:
return round(temp * 2) / 2.0
elif self.precision == PRECISION_TENTHS:
return round(temp, 1)
# PRECISION_WHOLE as a fall back
return round(temp)

View File

@ -9,12 +9,9 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.climate import ( from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, PRECISION_HALVES, STATE_ON, STATE_OFF, STATE_AUTO, PLATFORM_SCHEMA, ClimateDevice)
STATE_AUTO, STATE_ON, STATE_OFF,
)
from homeassistant.const import ( from homeassistant.const import (
CONF_MAC, TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE) CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-eq3bt==0.1.6'] REQUIREMENTS = ['python-eq3bt==0.1.6']
@ -58,15 +55,17 @@ class EQ3BTSmartThermostat(ClimateDevice):
def __init__(self, _mac, _name): def __init__(self, _mac, _name):
"""Initialize the thermostat.""" """Initialize the thermostat."""
# we want to avoid name clash with this module.. # We want to avoid name clash with this module.
import eq3bt as eq3 import eq3bt as eq3
self.modes = {eq3.Mode.Open: STATE_ON, self.modes = {
eq3.Mode.Closed: STATE_OFF, eq3.Mode.Open: STATE_ON,
eq3.Mode.Auto: STATE_AUTO, eq3.Mode.Closed: STATE_OFF,
eq3.Mode.Manual: STATE_MANUAL, eq3.Mode.Auto: STATE_AUTO,
eq3.Mode.Boost: STATE_BOOST, eq3.Mode.Manual: STATE_MANUAL,
eq3.Mode.Away: STATE_AWAY} eq3.Mode.Boost: STATE_BOOST,
eq3.Mode.Away: STATE_AWAY,
}
self.reverse_modes = {v: k for k, v in self.modes.items()} self.reverse_modes = {v: k for k, v in self.modes.items()}
@ -153,11 +152,11 @@ class EQ3BTSmartThermostat(ClimateDevice):
def device_state_attributes(self): def device_state_attributes(self):
"""Return the device specific state attributes.""" """Return the device specific state attributes."""
dev_specific = { dev_specific = {
ATTR_STATE_AWAY_END: self._thermostat.away_end,
ATTR_STATE_LOCKED: self._thermostat.locked, ATTR_STATE_LOCKED: self._thermostat.locked,
ATTR_STATE_LOW_BAT: self._thermostat.low_battery, ATTR_STATE_LOW_BAT: self._thermostat.low_battery,
ATTR_STATE_VALVE: self._thermostat.valve_state, ATTR_STATE_VALVE: self._thermostat.valve_state,
ATTR_STATE_WINDOW_OPEN: self._thermostat.window_open, ATTR_STATE_WINDOW_OPEN: self._thermostat.window_open,
ATTR_STATE_AWAY_END: self._thermostat.away_end,
} }
return dev_specific return dev_specific

View File

@ -4,46 +4,51 @@ Support for Wink thermostats, Air Conditioners, and Water Heaters.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.wink/ https://home-assistant.io/components/climate.wink/
""" """
import logging
import asyncio import asyncio
import logging
from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.components.climate import ( from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice, STATE_ECO, STATE_GAS, STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ELECTRIC,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, STATE_FAN_ONLY, STATE_HEAT_PUMP, ATTR_TEMPERATURE, STATE_HIGH_DEMAND,
ATTR_TEMPERATURE, STATE_FAN_ONLY, STATE_PERFORMANCE, ATTR_TARGET_TEMP_LOW, ATTR_CURRENT_HUMIDITY,
ATTR_CURRENT_HUMIDITY, STATE_ECO, STATE_ELECTRIC, ATTR_TARGET_TEMP_HIGH, ClimateDevice)
STATE_PERFORMANCE, STATE_HIGH_DEMAND, from homeassistant.components.wink import DOMAIN, WinkDevice
STATE_HEAT_PUMP, STATE_GAS)
from homeassistant.const import ( from homeassistant.const import (
TEMP_CELSIUS, STATE_ON, STATE_ON, STATE_OFF, TEMP_CELSIUS, STATE_UNKNOWN, PRECISION_TENTHS)
STATE_OFF, STATE_UNKNOWN) from homeassistant.helpers.temperature import display_temp as show_temp
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTR_ECO_TARGET = 'eco_target'
ATTR_EXTERNAL_TEMPERATURE = 'external_temperature'
ATTR_OCCUPIED = 'occupied'
ATTR_RHEEM_TYPE = 'rheem_type'
ATTR_SCHEDULE_ENABLED = 'schedule_enabled'
ATTR_SMART_TEMPERATURE = 'smart_temperature'
ATTR_TOTAL_CONSUMPTION = 'total_consumption'
ATTR_VACATION_MODE = 'vacation_mode'
DEPENDENCIES = ['wink'] DEPENDENCIES = ['wink']
SPEED_LOW = 'low' SPEED_LOW = 'low'
SPEED_MEDIUM = 'medium' SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high' SPEED_HIGH = 'high'
HA_STATE_TO_WINK = {STATE_AUTO: 'auto', HA_STATE_TO_WINK = {
STATE_ECO: 'eco', STATE_AUTO: 'auto',
STATE_FAN_ONLY: 'fan_only', STATE_COOL: 'cool_only',
STATE_HEAT: 'heat_only', STATE_ECO: 'eco',
STATE_COOL: 'cool_only', STATE_ELECTRIC: 'electric_only',
STATE_PERFORMANCE: 'performance', STATE_FAN_ONLY: 'fan_only',
STATE_HIGH_DEMAND: 'high_demand', STATE_GAS: 'gas',
STATE_HEAT_PUMP: 'heat_pump', STATE_HEAT: 'heat_only',
STATE_ELECTRIC: 'electric_only', STATE_HEAT_PUMP: 'heat_pump',
STATE_GAS: 'gas', STATE_HIGH_DEMAND: 'high_demand',
STATE_OFF: 'off'} STATE_OFF: 'off',
WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()} STATE_PERFORMANCE: 'performance',
}
ATTR_EXTERNAL_TEMPERATURE = "external_temperature" WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()}
ATTR_SMART_TEMPERATURE = "smart_temperature"
ATTR_ECO_TARGET = "eco_target"
ATTR_OCCUPIED = "occupied"
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
@ -85,15 +90,18 @@ class WinkThermostat(WinkDevice, ClimateDevice):
target_temp_high = self.target_temperature_high target_temp_high = self.target_temperature_high
target_temp_low = self.target_temperature_low target_temp_low = self.target_temperature_low
if target_temp_high is not None: if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display( data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.target_temperature_high) self.hass, self.target_temperature_high, self.temperature_unit,
PRECISION_TENTHS)
if target_temp_low is not None: if target_temp_low is not None:
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display( data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.target_temperature_low) self.hass, self.target_temperature_low, self.temperature_unit,
PRECISION_TENTHS)
if self.external_temperature: if self.external_temperature:
data[ATTR_EXTERNAL_TEMPERATURE] = self._convert_for_display( data[ATTR_EXTERNAL_TEMPERATURE] = show_temp(
self.external_temperature) self.hass, self.external_temperature, self.temperature_unit,
PRECISION_TENTHS)
if self.smart_temperature: if self.smart_temperature:
data[ATTR_SMART_TEMPERATURE] = self.smart_temperature data[ATTR_SMART_TEMPERATURE] = self.smart_temperature
@ -358,13 +366,15 @@ class WinkAC(WinkDevice, ClimateDevice):
target_temp_high = self.target_temperature_high target_temp_high = self.target_temperature_high
target_temp_low = self.target_temperature_low target_temp_low = self.target_temperature_low
if target_temp_high is not None: if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display( data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.target_temperature_high) self.hass, self.target_temperature_high, self.temperature_unit,
PRECISION_TENTHS)
if target_temp_low is not None: if target_temp_low is not None:
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display( data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.target_temperature_low) self.hass, self.target_temperature_low, self.temperature_unit,
data["total_consumption"] = self.wink.total_consumption() PRECISION_TENTHS)
data["schedule_enabled"] = self.wink.schedule_enabled() data[ATTR_TOTAL_CONSUMPTION] = self.wink.total_consumption()
data[ATTR_SCHEDULE_ENABLED] = self.wink.schedule_enabled()
return data return data
@ -471,8 +481,8 @@ class WinkWaterHeater(WinkDevice, ClimateDevice):
def device_state_attributes(self): def device_state_attributes(self):
"""Return the optional state attributes.""" """Return the optional state attributes."""
data = {} data = {}
data["vacation_mode"] = self.wink.vacation_mode_enabled() data[ATTR_VACATION_MODE] = self.wink.vacation_mode_enabled()
data["rheem_type"] = self.wink.rheem_type() data[ATTR_RHEEM_TYPE] = self.wink.rheem_type()
return data return data

View File

@ -6,11 +6,10 @@ https://home-assistant.io/components/weather/
""" """
import asyncio import asyncio
import logging import logging
from numbers import Number
from homeassistant.const import TEMP_CELSIUS
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util.temperature import convert as convert_temperature from homeassistant.helpers.temperature import display_temp as show_temp
from homeassistant.const import PRECISION_WHOLE, PRECISION_TENTHS, TEMP_CELSIUS
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
@ -98,11 +97,19 @@ class WeatherEntity(Entity):
"""Return the forecast.""" """Return the forecast."""
return None return None
@property
def precision(self):
"""Return the forecast."""
return PRECISION_TENTHS if self.temperature_unit == TEMP_CELSIUS \
else PRECISION_WHOLE
@property @property
def state_attributes(self): def state_attributes(self):
"""Return the state attributes.""" """Return the state attributes."""
data = { data = {
ATTR_WEATHER_TEMPERATURE: self._temp_for_display(self.temperature), ATTR_WEATHER_TEMPERATURE: show_temp(
self.hass, self.temperature, self.temperature_unit,
self.precision),
ATTR_WEATHER_HUMIDITY: self.humidity, ATTR_WEATHER_HUMIDITY: self.humidity,
} }
@ -134,8 +141,9 @@ class WeatherEntity(Entity):
forecast = [] forecast = []
for forecast_entry in self.forecast: for forecast_entry in self.forecast:
forecast_entry = dict(forecast_entry) forecast_entry = dict(forecast_entry)
forecast_entry[ATTR_FORECAST_TEMP] = self._temp_for_display( forecast_entry[ATTR_FORECAST_TEMP] = show_temp(
forecast_entry[ATTR_FORECAST_TEMP]) self.hass, forecast_entry[ATTR_FORECAST_TEMP],
self.temperature_unit, self.precision)
forecast.append(forecast_entry) forecast.append(forecast_entry)
data[ATTR_FORECAST] = forecast data[ATTR_FORECAST] = forecast
@ -151,19 +159,3 @@ class WeatherEntity(Entity):
def condition(self): def condition(self):
"""Return the current condition.""" """Return the current condition."""
raise NotImplementedError() raise NotImplementedError()
def _temp_for_display(self, temp):
"""Convert temperature into preferred units for display purposes."""
unit = self.temperature_unit
hass_unit = self.hass.config.units.temperature_unit
if (temp is None or not isinstance(temp, Number) or
unit == hass_unit):
return temp
value = convert_temperature(temp, unit, hass_unit)
if hass_unit == TEMP_CELSIUS:
return round(value, 1)
# Users of fahrenheit generally expect integer units.
return round(value)

View File

@ -31,7 +31,7 @@ CONDITION_CLASSES = {
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Demo weather.""" """Set up the Demo weather."""
add_devices([ add_devices([
DemoWeather('South', 'Sunshine', 21, 92, 1099, 0.5, TEMP_CELSIUS, DemoWeather('South', 'Sunshine', 21.6414, 92, 1099, 0.5, TEMP_CELSIUS,
[22, 19, 15, 12, 14, 18, 21]), [22, 19, 15, 12, 14, 18, 21]),
DemoWeather('North', 'Shower rain', -12, 54, 987, 4.8, TEMP_FAHRENHEIT, DemoWeather('North', 'Shower rain', -12, 54, 987, 4.8, TEMP_FAHRENHEIT,
[-10, -13, -18, -23, -19, -14, -9]) [-10, -13, -18, -23, -19, -14, -9])

View File

@ -417,3 +417,8 @@ SPEED_MS = 'speed_ms' # type: str
ILLUMINANCE = 'illuminance' # type: str ILLUMINANCE = 'illuminance' # type: str
WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
# The degree of precision for platforms
PRECISION_WHOLE = 1
PRECISION_HALVES = 0.5
PRECISION_TENTHS = 0.1

View File

@ -0,0 +1,33 @@
"""Temperature helpers for Home Assistant."""
from numbers import Number
from homeassistant.core import HomeAssistant
from homeassistant.util.temperature import convert as convert_temperature
def display_temp(hass: HomeAssistant, temperature: float, unit: str,
precision: float) -> float:
"""Convert temperature into preferred units for display purposes."""
temperature_unit = unit
ha_unit = hass.config.units.temperature_unit
if temperature is None:
return temperature
# If the temperature is not a number this can cause issues
# with Polymer components, so bail early there.
if not isinstance(temperature, Number):
raise TypeError(
"Temperature is not a number: {}".format(temperature))
if temperature_unit != ha_unit:
temperature = convert_temperature(
temperature, temperature_unit, ha_unit)
# Round in the units appropriate
if precision == 0.5:
return round(temperature * 2) / 2.0
elif precision == 0.1:
return round(temperature, 1)
# Integer as a fall back (PRECISION_WHOLE)
return round(temperature)

View File

@ -37,7 +37,7 @@ class TestWeather(unittest.TestCase):
assert state.state == 'sunny' assert state.state == 'sunny'
data = state.attributes data = state.attributes
assert data.get(ATTR_WEATHER_TEMPERATURE) == 21 assert data.get(ATTR_WEATHER_TEMPERATURE) == 21.6
assert data.get(ATTR_WEATHER_HUMIDITY) == 92 assert data.get(ATTR_WEATHER_HUMIDITY) == 92
assert data.get(ATTR_WEATHER_PRESSURE) == 1099 assert data.get(ATTR_WEATHER_PRESSURE) == 1099
assert data.get(ATTR_WEATHER_WIND_SPEED) == 0.5 assert data.get(ATTR_WEATHER_WIND_SPEED) == 0.5
@ -57,4 +57,4 @@ class TestWeather(unittest.TestCase):
assert state.state == 'rainy' assert state.state == 'rainy'
data = state.attributes data = state.attributes
assert data.get(ATTR_WEATHER_TEMPERATURE) == -24.4 assert data.get(ATTR_WEATHER_TEMPERATURE) == -24

View File

@ -0,0 +1,49 @@
"""Tests Home Assistant temperature helpers."""
import unittest
from tests.common import get_test_home_assistant
from homeassistant.const import (
TEMP_CELSIUS, PRECISION_WHOLE, TEMP_FAHRENHEIT, PRECISION_HALVES,
PRECISION_TENTHS)
from homeassistant.helpers.temperature import display_temp
from homeassistant.util.unit_system import METRIC_SYSTEM
TEMP = 24.636626
class TestHelpersTemperature(unittest.TestCase):
"""Setup the temperature tests."""
def setUp(self):
"""Setup the tests."""
self.hass = get_test_home_assistant()
self.hass.config.unit_system = METRIC_SYSTEM
def tearDown(self):
"""Stop down stuff we started."""
self.hass.stop()
def test_temperature_not_a_number(self):
"""Test that temperature is a number."""
temp = "Temperature"
with self.assertRaises(Exception) as context:
display_temp(self.hass, temp, TEMP_CELSIUS, PRECISION_HALVES)
self.assertTrue("Temperature is not a number: {}".format(temp)
in str(context.exception))
def test_celsius_halves(self):
"""Test temperature to celsius rounding to halves."""
self.assertEqual(24.5, display_temp(
self.hass, TEMP, TEMP_CELSIUS, PRECISION_HALVES))
def test_celsius_tenths(self):
"""Test temperature to celsius rounding to tenths."""
self.assertEqual(24.6, display_temp(
self.hass, TEMP, TEMP_CELSIUS, PRECISION_TENTHS))
def test_fahrenheit_wholes(self):
"""Test temperature to fahrenheit rounding to wholes."""
self.assertEqual(-4, display_temp(
self.hass, TEMP, TEMP_FAHRENHEIT, PRECISION_WHOLE))