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 os
import functools as ft
from numbers import Number
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
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.helpers.entity_component import EntityComponent
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
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN,
TEMP_CELSIUS)
TEMP_CELSIUS, PRECISION_WHOLE, PRECISION_TENTHS)
DOMAIN = 'climate'
@ -71,11 +71,6 @@ ATTR_OPERATION_LIST = 'operation_list'
ATTR_SWING_MODE = 'swing_mode'
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 = [
ATTR_TEMPERATURE,
ATTR_TARGET_TEMP_LOW,
@ -456,12 +451,18 @@ class ClimateDevice(Entity):
def state_attributes(self):
"""Return the optional state attributes."""
data = {
ATTR_CURRENT_TEMPERATURE:
self._convert_for_display(self.current_temperature),
ATTR_MIN_TEMP: self._convert_for_display(self.min_temp),
ATTR_MAX_TEMP: self._convert_for_display(self.max_temp),
ATTR_TEMPERATURE:
self._convert_for_display(self.target_temperature),
ATTR_CURRENT_TEMPERATURE: show_temp(
self.hass, self.current_temperature, self.temperature_unit,
self.precision),
ATTR_MIN_TEMP: show_temp(
self.hass, self.min_temp, self.temperature_unit,
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:
@ -469,10 +470,12 @@ class ClimateDevice(Entity):
target_temp_high = self.target_temperature_high
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display(
self.target_temperature_high)
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display(
self.target_temperature_low)
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.hass, self.target_temperature_high, self.temperature_unit,
self.precision)
data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit,
self.precision)
humidity = self.target_humidity
if humidity is not None:
@ -733,24 +736,3 @@ class ClimateDevice(Entity):
def max_humidity(self):
"""Return the maximum humidity."""
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
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, PRECISION_HALVES,
STATE_AUTO, STATE_ON, STATE_OFF,
)
STATE_ON, STATE_OFF, STATE_AUTO, PLATFORM_SCHEMA, ClimateDevice)
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
REQUIREMENTS = ['python-eq3bt==0.1.6']
@ -58,15 +55,17 @@ class EQ3BTSmartThermostat(ClimateDevice):
def __init__(self, _mac, _name):
"""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
self.modes = {eq3.Mode.Open: STATE_ON,
eq3.Mode.Closed: STATE_OFF,
eq3.Mode.Auto: STATE_AUTO,
eq3.Mode.Manual: STATE_MANUAL,
eq3.Mode.Boost: STATE_BOOST,
eq3.Mode.Away: STATE_AWAY}
self.modes = {
eq3.Mode.Open: STATE_ON,
eq3.Mode.Closed: STATE_OFF,
eq3.Mode.Auto: STATE_AUTO,
eq3.Mode.Manual: STATE_MANUAL,
eq3.Mode.Boost: STATE_BOOST,
eq3.Mode.Away: STATE_AWAY,
}
self.reverse_modes = {v: k for k, v in self.modes.items()}
@ -153,11 +152,11 @@ class EQ3BTSmartThermostat(ClimateDevice):
def device_state_attributes(self):
"""Return the device specific state attributes."""
dev_specific = {
ATTR_STATE_AWAY_END: self._thermostat.away_end,
ATTR_STATE_LOCKED: self._thermostat.locked,
ATTR_STATE_LOW_BAT: self._thermostat.low_battery,
ATTR_STATE_VALVE: self._thermostat.valve_state,
ATTR_STATE_WINDOW_OPEN: self._thermostat.window_open,
ATTR_STATE_AWAY_END: self._thermostat.away_end,
}
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
https://home-assistant.io/components/climate.wink/
"""
import logging
import asyncio
import logging
from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE, STATE_FAN_ONLY,
ATTR_CURRENT_HUMIDITY, STATE_ECO, STATE_ELECTRIC,
STATE_PERFORMANCE, STATE_HIGH_DEMAND,
STATE_HEAT_PUMP, STATE_GAS)
STATE_ECO, STATE_GAS, STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ELECTRIC,
STATE_FAN_ONLY, STATE_HEAT_PUMP, ATTR_TEMPERATURE, STATE_HIGH_DEMAND,
STATE_PERFORMANCE, ATTR_TARGET_TEMP_LOW, ATTR_CURRENT_HUMIDITY,
ATTR_TARGET_TEMP_HIGH, ClimateDevice)
from homeassistant.components.wink import DOMAIN, WinkDevice
from homeassistant.const import (
TEMP_CELSIUS, STATE_ON,
STATE_OFF, STATE_UNKNOWN)
STATE_ON, STATE_OFF, TEMP_CELSIUS, STATE_UNKNOWN, PRECISION_TENTHS)
from homeassistant.helpers.temperature import display_temp as show_temp
_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']
SPEED_LOW = 'low'
SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high'
HA_STATE_TO_WINK = {STATE_AUTO: 'auto',
STATE_ECO: 'eco',
STATE_FAN_ONLY: 'fan_only',
STATE_HEAT: 'heat_only',
STATE_COOL: 'cool_only',
STATE_PERFORMANCE: 'performance',
STATE_HIGH_DEMAND: 'high_demand',
STATE_HEAT_PUMP: 'heat_pump',
STATE_ELECTRIC: 'electric_only',
STATE_GAS: 'gas',
STATE_OFF: 'off'}
WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()}
HA_STATE_TO_WINK = {
STATE_AUTO: 'auto',
STATE_COOL: 'cool_only',
STATE_ECO: 'eco',
STATE_ELECTRIC: 'electric_only',
STATE_FAN_ONLY: 'fan_only',
STATE_GAS: 'gas',
STATE_HEAT: 'heat_only',
STATE_HEAT_PUMP: 'heat_pump',
STATE_HIGH_DEMAND: 'high_demand',
STATE_OFF: 'off',
STATE_PERFORMANCE: 'performance',
}
ATTR_EXTERNAL_TEMPERATURE = "external_temperature"
ATTR_SMART_TEMPERATURE = "smart_temperature"
ATTR_ECO_TARGET = "eco_target"
ATTR_OCCUPIED = "occupied"
WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()}
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_low = self.target_temperature_low
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display(
self.target_temperature_high)
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.hass, self.target_temperature_high, self.temperature_unit,
PRECISION_TENTHS)
if target_temp_low is not None:
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display(
self.target_temperature_low)
data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit,
PRECISION_TENTHS)
if self.external_temperature:
data[ATTR_EXTERNAL_TEMPERATURE] = self._convert_for_display(
self.external_temperature)
data[ATTR_EXTERNAL_TEMPERATURE] = show_temp(
self.hass, self.external_temperature, self.temperature_unit,
PRECISION_TENTHS)
if 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_low = self.target_temperature_low
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display(
self.target_temperature_high)
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.hass, self.target_temperature_high, self.temperature_unit,
PRECISION_TENTHS)
if target_temp_low is not None:
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display(
self.target_temperature_low)
data["total_consumption"] = self.wink.total_consumption()
data["schedule_enabled"] = self.wink.schedule_enabled()
data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit,
PRECISION_TENTHS)
data[ATTR_TOTAL_CONSUMPTION] = self.wink.total_consumption()
data[ATTR_SCHEDULE_ENABLED] = self.wink.schedule_enabled()
return data
@ -471,8 +481,8 @@ class WinkWaterHeater(WinkDevice, ClimateDevice):
def device_state_attributes(self):
"""Return the optional state attributes."""
data = {}
data["vacation_mode"] = self.wink.vacation_mode_enabled()
data["rheem_type"] = self.wink.rheem_type()
data[ATTR_VACATION_MODE] = self.wink.vacation_mode_enabled()
data[ATTR_RHEEM_TYPE] = self.wink.rheem_type()
return data

View File

@ -6,11 +6,10 @@ https://home-assistant.io/components/weather/
"""
import asyncio
import logging
from numbers import Number
from homeassistant.const import TEMP_CELSIUS
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.entity import Entity
@ -98,11 +97,19 @@ class WeatherEntity(Entity):
"""Return the forecast."""
return None
@property
def precision(self):
"""Return the forecast."""
return PRECISION_TENTHS if self.temperature_unit == TEMP_CELSIUS \
else PRECISION_WHOLE
@property
def state_attributes(self):
"""Return the state attributes."""
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,
}
@ -134,8 +141,9 @@ class WeatherEntity(Entity):
forecast = []
for forecast_entry in self.forecast:
forecast_entry = dict(forecast_entry)
forecast_entry[ATTR_FORECAST_TEMP] = self._temp_for_display(
forecast_entry[ATTR_FORECAST_TEMP])
forecast_entry[ATTR_FORECAST_TEMP] = show_temp(
self.hass, forecast_entry[ATTR_FORECAST_TEMP],
self.temperature_unit, self.precision)
forecast.append(forecast_entry)
data[ATTR_FORECAST] = forecast
@ -151,19 +159,3 @@ class WeatherEntity(Entity):
def condition(self):
"""Return the current condition."""
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):
"""Set up the Demo weather."""
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]),
DemoWeather('North', 'Shower rain', -12, 54, 987, 4.8, TEMP_FAHRENHEIT,
[-10, -13, -18, -23, -19, -14, -9])

View File

@ -417,3 +417,8 @@ SPEED_MS = 'speed_ms' # type: str
ILLUMINANCE = 'illuminance' # type: str
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'
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_PRESSURE) == 1099
assert data.get(ATTR_WEATHER_WIND_SPEED) == 0.5
@ -57,4 +57,4 @@ class TestWeather(unittest.TestCase):
assert state.state == 'rainy'
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))