From 26526ca57ab847cefcbd0fbf8f61e4108ac101b1 Mon Sep 17 00:00:00 2001 From: "Teagan M. Glenn" Date: Sun, 31 Jul 2016 14:24:49 -0600 Subject: [PATCH 1/2] Add unit system support Add unit symbol constants Initial unit system object Import more constants Pydoc for unit system file Import constants for configuration validation Unit system validation method Typing for constants Inches are valid lengths too Typings Change base class to dict - needed for remote api call serialization Validation Use dictionary keys Defined unit systems Update location util to use metric instead of us fahrenheit Update constant imports Import defined unit systems Update configuration to use unit system Update schema to use unit system Update constants Add imports to core for unit system and distance Type for config Default unit system Convert distance from HASS instance Update temperature conversion to use unit system Update temperature conversion Set unit system based on configuration Set info unit system Return unit system dictionary with config dictionary Auto discover unit system Update location test for use metric Update forecast unit system Update mold indicator unit system Update thermostat unit system Update thermostat demo test Unit tests around unit system Update test common hass configuration Update configuration unit tests There should always be a unit system! Update core unit tests Constants typing Linting issues Remove unused import Update fitbit sensor to use application unit system Update google travel time to use application unit system Update configuration example Update dht sensor Update DHT temperature conversion to use the utility function Update swagger config Update my sensors metric flag Update hvac component temperature conversion HVAC conversion for temperature Pull unit from sensor type map Pull unit from sensor type map Update the temper sensor unit Update yWeather sensor unit Update hvac demo unit test Set unit test config unit system to metric Use hass unit system length for default in proximity Use the name of the system instead of temperature Use constants from const Unused import Forecasted temperature Fix calculation in case furthest distance is greater than 1000000 units Remove unneeded constants Set default length to km or miles Use constants Linting doesn't like importing just for typing Fix reference Test is expecting meters - set config to meters Use constant Use constant PyDoc for unit test Should be not in Rename to units Change unit system to be an object - not a dictionary Return tuple in conversion Move convert to temperature util Temperature conversion is now in unit system Update imports Rename to units Units is now an object Use temperature util conversion Unit system is now an object Validate and convert unit system config Return the scalar value in template distance Test is expecting meters Update unit tests around unit system Distance util returns tuple Fix location info test Set units Update unit tests Convert distance DOH Pull out the scalar from the vector Linting I really hate python linting Linting again BLARG Unit test documentation Unit test around is metric flag Break ternary statement into if/else blocks Don't use dictionary - use members is metric flag Rename constants Use is metric flag Move constants to CONST file Move to const file Raise error if unit is not expected Typing No need to return unit since only performing conversion if it can work Use constants Line wrapping Raise error if invalid value Remove subscripts from conversion as they are no longer returned as tuples No longer tuples No longer tuples Check for numeric type Fix string format to use correct variable Typing Assert errors raised Remove subscript Only convert temperature if we know the unit If no unit of measurement set - default to HASS config Convert only if we know the unit Remove subscription Fix not in clause Linting fixes Wants a boolean Clearer if-block Check if the key is in the config first Missed a couple expecting tuples Backwards compatibility No like-y ternary! Error handling around state setting Pretty unit system configuration validation More tuple crap Use is metric flag Error handling around min/max temp Explode if no unit Pull unit from config Celsius has a decimal Unused import Check if it's a temperature before we try to convert it to a temperature Linting says too many statements - combine lat/long in a fairly reasonable manner Backwards compatibility unit test Better doc --- config/configuration.yaml.example | 4 +- docs/swagger.yaml | 3 +- homeassistant/components/hvac/__init__.py | 21 +-- homeassistant/components/mysensors.py | 4 +- homeassistant/components/proximity.py | 11 +- homeassistant/components/sensor/dht.py | 10 +- homeassistant/components/sensor/fitbit.py | 4 +- homeassistant/components/sensor/forecast.py | 4 +- .../components/sensor/google_travel_time.py | 8 +- .../components/sensor/mold_indicator.py | 2 +- .../components/sensor/openweathermap.py | 9 +- homeassistant/components/sensor/temper.py | 2 +- homeassistant/components/sensor/vera.py | 4 +- homeassistant/components/sensor/yweather.py | 2 +- .../components/thermostat/__init__.py | 35 +++-- .../components/thermostat/eq3btsmart.py | 2 +- .../components/thermostat/heat_control.py | 2 +- .../components/thermostat/homematic.py | 2 +- homeassistant/config.py | 54 ++++--- homeassistant/const.py | 35 +++++ homeassistant/core.py | 35 ++--- homeassistant/helpers/config_validation.py | 6 +- homeassistant/helpers/entity.py | 16 ++- homeassistant/helpers/temperature.py | 13 -- homeassistant/helpers/template.py | 3 +- homeassistant/helpers/unit_system.py | 126 +++++++++++++++++ homeassistant/util/distance.py | 70 +++++----- homeassistant/util/location.py | 9 +- homeassistant/util/temperature.py | 23 +++ tests/common.py | 5 +- tests/components/hvac/test_demo.py | 6 +- tests/components/thermostat/test_demo.py | 10 +- .../thermostat/test_heat_control.py | 3 +- tests/helpers/test_template.py | 10 ++ tests/helpers/test_unit_system.py | 132 ++++++++++++++++++ tests/test_config.py | 40 ++++-- tests/test_core.py | 50 +------ tests/util/test_distance.py | 81 +++++++---- tests/util/test_location.py | 4 +- 39 files changed, 597 insertions(+), 263 deletions(-) delete mode 100644 homeassistant/helpers/temperature.py create mode 100644 homeassistant/helpers/unit_system.py create mode 100644 tests/helpers/test_unit_system.py diff --git a/config/configuration.yaml.example b/config/configuration.yaml.example index abf4bd5b035..73c03f94144 100644 --- a/config/configuration.yaml.example +++ b/config/configuration.yaml.example @@ -10,8 +10,8 @@ homeassistant: # Impacts weather/sunrise data elevation: 665 - # C for Celsius, F for Fahrenheit - temperature_unit: C + # 'metric' for Metric System, 'imperial' for imperial system + unit_system: metric # Pick yours from here: # http://en.wikipedia.org/wiki/List_of_tz_database_time_zones diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ac5a5b50599..b0d765be361 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -419,8 +419,9 @@ definitions: description: Longitude of Home Assistant server location_name: type: string - temperature_unit: + unit_system: type: string + description: The system for measurement units time_zone: type: string version: diff --git a/homeassistant/components/hvac/__init__.py b/homeassistant/components/hvac/__init__.py index 02a387bc70b..abd40a3ac93 100644 --- a/homeassistant/components/hvac/__init__.py +++ b/homeassistant/components/hvac/__init__.py @@ -6,13 +6,14 @@ https://home-assistant.io/components/hvac/ """ import logging import os +from numbers import Number from homeassistant.helpers.entity_component import EntityComponent from homeassistant.config import load_yaml_config_file import homeassistant.util as util +from homeassistant.util.temperature import convert as convert_temperature from homeassistant.helpers.entity import Entity -from homeassistant.helpers.temperature import convert from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN, @@ -204,8 +205,8 @@ def setup(hass, config): return for hvac in target_hvacs: - hvac.set_temperature(convert( - temperature, hass.config.temperature_unit, + hvac.set_temperature(convert_temperature( + temperature, hass.config.units.temperature_unit, hvac.unit_of_measurement)) if hvac.should_poll: @@ -462,12 +463,12 @@ class HvacDevice(Entity): @property def min_temp(self): """Return the minimum temperature.""" - return convert(19, TEMP_CELSIUS, self.unit_of_measurement) + return convert_temperature(19, TEMP_CELSIUS, self.unit_of_measurement) @property def max_temp(self): """Return the maximum temperature.""" - return convert(30, TEMP_CELSIUS, self.unit_of_measurement) + return convert_temperature(30, TEMP_CELSIUS, self.unit_of_measurement) @property def min_humidity(self): @@ -481,13 +482,13 @@ class HvacDevice(Entity): def _convert_for_display(self, temp): """Convert temperature into preferred units for display purposes.""" - if temp is None: - return None + if temp is None or not isinstance(temp, Number): + return temp - value = convert(temp, self.unit_of_measurement, - self.hass.config.temperature_unit) + value = convert_temperature(temp, self.unit_of_measurement, + self.hass.config.units.temperature_unit) - if self.hass.config.temperature_unit is TEMP_CELSIUS: + if self.hass.config.units.temperature_unit is TEMP_CELSIUS: decimal_count = 1 else: # Users of fahrenheit generally expect integer units. diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 767c2b966a6..b950ec39dd8 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -10,7 +10,7 @@ import socket from homeassistant.const import (ATTR_BATTERY_LEVEL, CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - STATE_OFF, STATE_ON, TEMP_CELSIUS) + STATE_OFF, STATE_ON) from homeassistant.helpers import validate_config, discovery CONF_GATEWAYS = 'gateways' @@ -53,7 +53,7 @@ def setup(hass, config): # pylint: disable=too-many-locals import mysensors.mysensors as mysensors version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION)) - is_metric = (hass.config.temperature_unit == TEMP_CELSIUS) + is_metric = hass.config.units.is_metric persistence = config[DOMAIN].get(CONF_PERSISTENCE, True) def setup_gateway(device, persistence_file, baud_rate, tcp_port): diff --git a/homeassistant/components/proximity.py b/homeassistant/components/proximity.py index ced3c9a947e..ba0a192398f 100644 --- a/homeassistant/components/proximity.py +++ b/homeassistant/components/proximity.py @@ -27,9 +27,6 @@ DEFAULT_TOLERANCE = 1 # Default zone DEFAULT_PROXIMITY_ZONE = 'home' -# Default unit of measure -DEFAULT_UNIT_OF_MEASUREMENT = 'km' - # Default distance to zone DEFAULT_DIST_TO_ZONE = NOT_SET @@ -71,7 +68,7 @@ def setup_proximity_component(hass, config): # Get the unit of measurement from configuration.yaml. unit_of_measure = config.get(ATTR_UNIT_OF_MEASUREMENT, - DEFAULT_UNIT_OF_MEASUREMENT) + hass.config.units.length_unit) zone_id = 'zone.{}'.format(proximity_zone) state = hass.states.get(zone_id) @@ -216,11 +213,11 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes # Loop through each of the distances collected and work out the # closest. - closest_device = '' - dist_to_zone = 1000000 + closest_device = None # type: str + dist_to_zone = None # type: float for device in distances_to_zone: - if distances_to_zone[device] < dist_to_zone: + if not dist_to_zone or distances_to_zone[device] < dist_to_zone: closest_device = device dist_to_zone = distances_to_zone[device] diff --git a/homeassistant/components/sensor/dht.py b/homeassistant/components/sensor/dht.py index a17f952aaf7..ec33b1e4042 100644 --- a/homeassistant/components/sensor/dht.py +++ b/homeassistant/components/sensor/dht.py @@ -10,6 +10,7 @@ from datetime import timedelta from homeassistant.const import TEMP_FAHRENHEIT from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle +from homeassistant.util.temperature import celsius_to_fahrenheit # Update this requirement to upstream as soon as it supports Python 3. REQUIREMENTS = ['http://github.com/mala-zaba/Adafruit_Python_DHT/archive/' @@ -32,8 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=import-error import Adafruit_DHT - SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit - unit = hass.config.temperature_unit + SENSOR_TYPES['temperature'][1] = hass.config.units.temperature_unit available_sensors = { "DHT11": Adafruit_DHT.DHT11, "DHT22": Adafruit_DHT.DHT22, @@ -58,7 +58,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if variable not in SENSOR_TYPES: _LOGGER.error('Sensor type: "%s" does not exist', variable) else: - dev.append(DHTSensor(data, variable, unit, name)) + dev.append( + DHTSensor(data, variable, SENSOR_TYPES[variable][1], name)) except KeyError: pass @@ -103,7 +104,8 @@ class DHTSensor(Entity): if self.type == 'temperature': self._state = round(data['temperature'], 1) if self.temp_unit == TEMP_FAHRENHEIT: - self._state = round(data['temperature'] * 1.8 + 32, 1) + self._state = round(celsius_to_fahrenheit(data['temperature']), + 1) elif self.type == 'humidity': self._state = round(data['humidity'], 1) diff --git a/homeassistant/components/sensor/fitbit.py b/homeassistant/components/sensor/fitbit.py index 8bbedf4dd2f..01c067a19f2 100644 --- a/homeassistant/components/sensor/fitbit.py +++ b/homeassistant/components/sensor/fitbit.py @@ -10,7 +10,6 @@ import logging import datetime import time -from homeassistant.const import TEMP_CELSIUS from homeassistant.util import Throttle from homeassistant.helpers.entity import Entity from homeassistant.loader import get_component @@ -238,8 +237,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for resource in config.get("monitored_resources", FITBIT_DEFAULT_RESOURCE_LIST): dev.append(FitbitSensor(authd_client, config_path, resource, - hass.config.temperature_unit == - TEMP_CELSIUS)) + hass.config.units.is_metric)) add_devices(dev) else: diff --git a/homeassistant/components/sensor/forecast.py b/homeassistant/components/sensor/forecast.py index cca1b7d52d7..1a569d3d4c3 100644 --- a/homeassistant/components/sensor/forecast.py +++ b/homeassistant/components/sensor/forecast.py @@ -10,7 +10,7 @@ from requests.exceptions import ConnectionError as ConnectError, \ HTTPError, Timeout from homeassistant.components.sensor import DOMAIN -from homeassistant.const import CONF_API_KEY, TEMP_CELSIUS +from homeassistant.const import CONF_API_KEY from homeassistant.helpers import validate_config from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -62,7 +62,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if 'units' in config: units = config['units'] - elif hass.config.temperature_unit == TEMP_CELSIUS: + elif hass.config.units.is_metric: units = 'si' else: units = 'us' diff --git a/homeassistant/components/sensor/google_travel_time.py b/homeassistant/components/sensor/google_travel_time.py index be42ddf8382..378e9c9c124 100644 --- a/homeassistant/components/sensor/google_travel_time.py +++ b/homeassistant/components/sensor/google_travel_time.py @@ -11,8 +11,7 @@ import voluptuous as vol from homeassistant.helpers.entity import Entity from homeassistant.const import ( - CONF_API_KEY, TEMP_CELSIUS, TEMP_FAHRENHEIT, - EVENT_HOMEASSISTANT_START, ATTR_LATITUDE, ATTR_LONGITUDE) + CONF_API_KEY, EVENT_HOMEASSISTANT_START, ATTR_LATITUDE, ATTR_LONGITUDE) from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv @@ -92,10 +91,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): options = config.get(CONF_OPTIONS) if options.get('units') is None: - if hass.config.temperature_unit is TEMP_CELSIUS: - options['units'] = 'metric' - elif hass.config.temperature_unit is TEMP_FAHRENHEIT: - options['units'] = 'imperial' + options['units'] = hass.config.units.name travel_mode = config.get(CONF_TRAVEL_MODE) mode = options.get(CONF_MODE) diff --git a/homeassistant/components/sensor/mold_indicator.py b/homeassistant/components/sensor/mold_indicator.py index 89db341b14d..4e59cd2cd62 100644 --- a/homeassistant/components/sensor/mold_indicator.py +++ b/homeassistant/components/sensor/mold_indicator.py @@ -65,7 +65,7 @@ class MoldIndicator(Entity): self._indoor_humidity_sensor = indoor_humidity_sensor self._outdoor_temp_sensor = outdoor_temp_sensor self._calib_factor = calib_factor - self._is_metric = (hass.config.temperature_unit == TEMP_CELSIUS) + self._is_metric = hass.config.units.is_metric self._dewpoint = None self._indoor_temp = None diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py index 060f3bd57e8..efaa8d450b4 100644 --- a/homeassistant/components/sensor/openweathermap.py +++ b/homeassistant/components/sensor/openweathermap.py @@ -48,8 +48,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): from pyowm import OWM - SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit - unit = hass.config.temperature_unit + SENSOR_TYPES['temperature'][1] = hass.config.units.temperature_unit forecast = config.get('forecast') owm = OWM(config.get(CONF_API_KEY, None)) @@ -67,13 +66,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if variable not in SENSOR_TYPES: _LOGGER.error('Sensor type: "%s" does not exist', variable) else: - dev.append(OpenWeatherMapSensor(data, variable, unit)) + dev.append(OpenWeatherMapSensor(data, variable, + SENSOR_TYPES[variable][1])) except KeyError: pass if forecast: SENSOR_TYPES['forecast'] = ['Forecast', None] - dev.append(OpenWeatherMapSensor(data, 'forecast', unit)) + dev.append(OpenWeatherMapSensor(data, 'forecast', + SENSOR_TYPES['temperature'][1])) add_devices(dev) diff --git a/homeassistant/components/sensor/temper.py b/homeassistant/components/sensor/temper.py index 5beb49d64e0..fe5ebb17982 100644 --- a/homeassistant/components/sensor/temper.py +++ b/homeassistant/components/sensor/temper.py @@ -21,7 +21,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): """Setup the Temper sensors.""" from temperusb.temper import TemperHandler - temp_unit = hass.config.temperature_unit + temp_unit = hass.config.units.temperature_unit name = config.get(CONF_NAME, DEVICE_DEFAULT_NAME) temper_devices = TemperHandler().get_devices() add_devices_callback([TemperSensor(dev, temp_unit, name + '_' + str(idx)) diff --git a/homeassistant/components/sensor/vera.py b/homeassistant/components/sensor/vera.py index 537b7847b7e..d7b642fb2fc 100644 --- a/homeassistant/components/sensor/vera.py +++ b/homeassistant/components/sensor/vera.py @@ -61,11 +61,11 @@ class VeraSensor(VeraDevice, Entity): self._temperature_units = TEMP_CELSIUS if self.hass: - temp = self.hass.config.temperature( + temp = self.hass.config.units.temperature( current_temp, self._temperature_units) - current_temp, self._temperature_units = temp + current_temp = temp self.current_value = current_temp elif self.vera_device.category == "Light Sensor": diff --git a/homeassistant/components/sensor/yweather.py b/homeassistant/components/sensor/yweather.py index ffeb09dc92d..9011ae24b29 100644 --- a/homeassistant/components/sensor/yweather.py +++ b/homeassistant/components/sensor/yweather.py @@ -45,7 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Yahoo! weather sensor.""" from yahooweather import get_woeid, UNIT_C, UNIT_F - unit = hass.config.temperature_unit + unit = hass.config.units.temperature_unit woeid = config.get("woeid", None) forecast = config.get("forecast", 0) diff --git a/homeassistant/components/thermostat/__init__.py b/homeassistant/components/thermostat/__init__.py index 535a98567d8..b8a3b113671 100644 --- a/homeassistant/components/thermostat/__init__.py +++ b/homeassistant/components/thermostat/__init__.py @@ -6,6 +6,7 @@ https://home-assistant.io/components/thermostat/ """ import logging import os +from numbers import Number import voluptuous as vol @@ -13,9 +14,9 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.config import load_yaml_config_file from homeassistant.helpers.entity import Entity -from homeassistant.helpers.temperature import convert from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa import homeassistant.helpers.config_validation as cv +from homeassistant.util.temperature import convert from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS) @@ -146,9 +147,13 @@ def setup(hass, config): temperature = service.data[ATTR_TEMPERATURE] for thermostat in target_thermostats: - thermostat.set_temperature(convert( - temperature, hass.config.temperature_unit, - thermostat.unit_of_measurement)) + if thermostat.unit_of_measurement is not None: + converted_temperature = convert( + temperature, hass.config.units.temperature_unit, + thermostat.unit_of_measurement) + else: + converted_temperature = temperature + thermostat.set_temperature(converted_temperature) thermostat.update_ha_state(True) @@ -301,22 +306,30 @@ class ThermostatDevice(Entity): @property def min_temp(self): """Return the minimum temperature.""" - return convert(7, TEMP_CELSIUS, self.unit_of_measurement) + try: + unit = self.unit_of_measurement + return convert(7, TEMP_CELSIUS, unit) + except ValueError: + return STATE_UNKNOWN @property def max_temp(self): """Return the maximum temperature.""" - return convert(35, TEMP_CELSIUS, self.unit_of_measurement) + try: + unit = self.unit_of_measurement + return convert(35, TEMP_CELSIUS, unit) + except ValueError: + return STATE_UNKNOWN def _convert_for_display(self, temp): """Convert temperature into preferred units for display purposes.""" - if temp is None: - return None + if temp is None or not isinstance(temp, Number): + return temp - value = convert(temp, self.unit_of_measurement, - self.hass.config.temperature_unit) + value = self.hass.config.units.temperature(temp, + self.unit_of_measurement) - if self.hass.config.temperature_unit is TEMP_CELSIUS: + if self.hass.config.units.is_metric: decimal_count = 1 else: # Users of fahrenheit generally expect integer units. diff --git a/homeassistant/components/thermostat/eq3btsmart.py b/homeassistant/components/thermostat/eq3btsmart.py index 04ca9802af3..a2aec1b8f60 100644 --- a/homeassistant/components/thermostat/eq3btsmart.py +++ b/homeassistant/components/thermostat/eq3btsmart.py @@ -8,7 +8,7 @@ import logging from homeassistant.components.thermostat import ThermostatDevice from homeassistant.const import TEMP_CELSIUS -from homeassistant.helpers.temperature import convert +from homeassistant.util.temperature import convert REQUIREMENTS = ['bluepy_devices==0.2.0'] diff --git a/homeassistant/components/thermostat/heat_control.py b/homeassistant/components/thermostat/heat_control.py index 831f1dd41b9..0f2dbce6040 100644 --- a/homeassistant/components/thermostat/heat_control.py +++ b/homeassistant/components/thermostat/heat_control.py @@ -75,7 +75,7 @@ class HeatControl(ThermostatDevice): self._min_temp = min_temp self._max_temp = max_temp self._target_temp = target_temp - self._unit = None + self._unit = hass.config.units.temperature_unit track_state_change(hass, sensor_entity_id, self._sensor_changed) diff --git a/homeassistant/components/thermostat/homematic.py b/homeassistant/components/thermostat/homematic.py index 345b8785b42..73901ab61df 100644 --- a/homeassistant/components/thermostat/homematic.py +++ b/homeassistant/components/thermostat/homematic.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/thermostat.homematic/ import logging import homeassistant.components.homematic as homematic from homeassistant.components.thermostat import ThermostatDevice -from homeassistant.helpers.temperature import convert +from homeassistant.util.temperature import convert from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN DEPENDENCIES = ['homematic'] diff --git a/homeassistant/config.py b/homeassistant/config.py index 1061bd4f5ec..53c43be7c17 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -7,12 +7,14 @@ from types import MappingProxyType import voluptuous as vol from homeassistant.const import ( - CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_TEMPERATURE_UNIT, - CONF_TIME_ZONE, CONF_CUSTOMIZE, CONF_ELEVATION, TEMP_FAHRENHEIT, - TEMP_CELSIUS, __version__) + CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_UNIT_SYSTEM, + CONF_TIME_ZONE, CONF_CUSTOMIZE, CONF_ELEVATION, CONF_UNIT_SYSTEM_METRIC, + CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, TEMP_CELSIUS, + __version__) from homeassistant.exceptions import HomeAssistantError from homeassistant.util.yaml import load_yaml import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.unit_system import (IMPERIAL_SYSTEM, METRIC_SYSTEM) from homeassistant.helpers.entity import valid_entity_id, set_customize from homeassistant.util import dt as date_util, location as loc_util @@ -30,7 +32,9 @@ DEFAULT_CORE_CONFIG = ( ' the sun rises and sets'), (CONF_LONGITUDE, 0, 'longitude', None), (CONF_ELEVATION, 0, None, 'Impacts weather/sunrise data'), - (CONF_TEMPERATURE_UNIT, 'C', None, 'C for Celsius, F for Fahrenheit'), + (CONF_UNIT_SYSTEM, CONF_UNIT_SYSTEM_METRIC, None, + '{} for Metric, {} for Imperial'.format(CONF_UNIT_SYSTEM_METRIC, + CONF_UNIT_SYSTEM_IMPERIAL)), (CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki' 'pedia.org/wiki/List_of_tz_database_time_zones'), ) @@ -88,7 +92,8 @@ CORE_CONFIG_SCHEMA = vol.Schema({ CONF_LATITUDE: cv.latitude, CONF_LONGITUDE: cv.longitude, CONF_ELEVATION: vol.Coerce(int), - CONF_TEMPERATURE_UNIT: cv.temperature_unit, + vol.Optional(CONF_TEMPERATURE_UNIT): cv.temperature_unit, + CONF_UNIT_SYSTEM: cv.unit_system, CONF_TIME_ZONE: cv.time_zone, vol.Required(CONF_CUSTOMIZE, default=MappingProxyType({})): _valid_customize, @@ -131,8 +136,10 @@ def create_default_config(config_dir, detect_location=True): location_info = detect_location and loc_util.detect_location_info() if location_info: - if location_info.use_fahrenheit: - info[CONF_TEMPERATURE_UNIT] = 'F' + if location_info.use_metric: + info[CONF_UNIT_SYSTEM] = CONF_UNIT_SYSTEM_METRIC + else: + info[CONF_UNIT_SYSTEM] = CONF_UNIT_SYSTEM_IMPERIAL for attr, default, prop, _ in DEFAULT_CORE_CONFIG: if prop is None: @@ -244,18 +251,30 @@ def process_ha_core_config(hass, config): set_customize(config.get(CONF_CUSTOMIZE) or {}) - if CONF_TEMPERATURE_UNIT in config: - hac.temperature_unit = config[CONF_TEMPERATURE_UNIT] + if CONF_UNIT_SYSTEM in config: + if config[CONF_UNIT_SYSTEM] == CONF_UNIT_SYSTEM_IMPERIAL: + hac.units = IMPERIAL_SYSTEM + else: + hac.units = METRIC_SYSTEM + elif CONF_TEMPERATURE_UNIT in config: + unit = config[CONF_TEMPERATURE_UNIT] + if unit == TEMP_CELSIUS: + hac.units = METRIC_SYSTEM + else: + hac.units = IMPERIAL_SYSTEM + _LOGGER.warning("Found deprecated temperature unit in core config, " + "expected unit system. Replace 'temperature: %s' with " + "'unit_system: %s'", unit, hac.units.name) # Shortcut if no auto-detection necessary - if None not in (hac.latitude, hac.longitude, hac.temperature_unit, + if None not in (hac.latitude, hac.longitude, hac.units, hac.time_zone, hac.elevation): return discovered = [] # If we miss some of the needed values, auto detect them - if None in (hac.latitude, hac.longitude, hac.temperature_unit, + if None in (hac.latitude, hac.longitude, hac.units, hac.time_zone): info = loc_util.detect_location_info() @@ -264,18 +283,13 @@ def process_ha_core_config(hass, config): return if hac.latitude is None and hac.longitude is None: - hac.latitude = info.latitude - hac.longitude = info.longitude + hac.latitude, hac.longitude = (info.latitude, info.longitude) discovered.append(('latitude', hac.latitude)) discovered.append(('longitude', hac.longitude)) - if hac.temperature_unit is None: - if info.use_fahrenheit: - hac.temperature_unit = TEMP_FAHRENHEIT - discovered.append(('temperature_unit', 'F')) - else: - hac.temperature_unit = TEMP_CELSIUS - discovered.append(('temperature_unit', 'C')) + if hac.units is None: + hac.units = METRIC_SYSTEM if info.use_metric else IMPERIAL_SYSTEM + discovered.append((CONF_UNIT_SYSTEM, hac.units.name)) if hac.location_name is None: hac.location_name = info.city diff --git a/homeassistant/const.py b/homeassistant/const.py index 16fd9866058..6dac4acbaf0 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -47,6 +47,7 @@ CONF_PORT = 'port' CONF_SCAN_INTERVAL = 'scan_interval' CONF_STATE = 'state' CONF_TEMPERATURE_UNIT = 'temperature_unit' +CONF_UNIT_SYSTEM = 'unit_system' CONF_TIME_ZONE = 'time_zone' CONF_USERNAME = 'username' CONF_VALUE_TEMPLATE = 'value_template' @@ -112,11 +113,38 @@ ATTR_ICON = "icon" # The unit of measurement if applicable ATTR_UNIT_OF_MEASUREMENT = "unit_of_measurement" +CONF_UNIT_SYSTEM_METRIC = 'metric' # type: str +CONF_UNIT_SYSTEM_IMPERIAL = 'imperial' # type: str + # Temperature attribute ATTR_TEMPERATURE = "temperature" TEMP_CELSIUS = "°C" TEMP_FAHRENHEIT = "°F" +# Length units +LENGTH_CENTIMETERS = "cm" # type: str +LENGTH_METERS = "m" # type: str +LENGTH_KILOMETERS = "km" # type: str + +LENGTH_INCHES = "in" # type: str +LENGTH_FEET = "ft" # type: str +LENGTH_YARD = "yd" # type: str +LENGTH_MILES = "mi" # type: str + +# Volume units +VOLUME_LITERS = "L" # type: str +VOLUME_MILLILITERS = "mL" # type: str + +VOLUME_GALLONS = "gal" # type: str +VOLUME_FLUID_OUNCE = "fl. oz." # type: str + +# Mass units +MASS_GRAMS = "g" # type: str +MASS_KILOGRAMS = "kg" # type: str + +MASS_OUNCES = "oz" # type: str +MASS_POUNDS = "lb" # type: str + # Contains the information that is discovered ATTR_DISCOVERED = "discovered" @@ -243,3 +271,10 @@ CONTENT_TYPE_TEXT_PLAIN = 'text/plain' # The exit code to send to request a restart RESTART_EXIT_CODE = 100 + +UNIT_NOT_RECOGNIZED_TEMPLATE = '{} is not a recognized {} unit.' # type: str + +LENGTH = 'length' # type: str +MASS = 'mass' # type: str +VOLUME = 'volume' # type: str +TEMPERATURE = 'temperature' # type: str diff --git a/homeassistant/core.py b/homeassistant/core.py index 7ddf5a6c10f..8ce381b072f 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -17,7 +17,6 @@ from types import MappingProxyType from typing import Any, Callable import voluptuous as vol -import homeassistant.helpers.temperature as temp_helper import homeassistant.util as util import homeassistant.util.dt as dt_util import homeassistant.util.location as location @@ -28,11 +27,13 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_SERVICE_EXECUTED, EVENT_SERVICE_REGISTERED, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL, RESTART_EXIT_CODE, - SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, TEMP_CELSIUS, - TEMP_FAHRENHEIT, __version__) + SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, __version__) from homeassistant.exceptions import ( HomeAssistantError, InvalidEntityFormatError) from homeassistant.helpers.entity import split_entity_id, valid_entity_id +from homeassistant.helpers.unit_system import ( + METRIC_SYSTEM, +) DOMAIN = "homeassistant" @@ -95,7 +96,7 @@ class HomeAssistant(object): self.bus = EventBus(pool) self.services = ServiceRegistry(self.bus, self.add_job) self.states = StateMachine(self.bus) - self.config = Config() + self.config = Config() # type: Config self.state = CoreState.not_running @property @@ -715,9 +716,9 @@ class Config(object): self.latitude = None self.longitude = None self.elevation = None - self.temperature_unit = None self.location_name = None self.time_zone = None + self.units = METRIC_SYSTEM # If True, pip install is skipped for requirements on startup self.skip_pip = False @@ -731,29 +732,15 @@ class Config(object): # Directory that holds the configuration self.config_dir = get_default_config_dir() - def distance(self, lat, lon): - """Calculate distance from Home Assistant in meters.""" - return location.distance(self.latitude, self.longitude, lat, lon) + def distance(self: object, lat: float, lon: float) -> float: + """Calculate distance from Home Assistant.""" + return self.units.length( + location.distance(self.latitude, self.longitude, lat, lon), 'm') def path(self, *path): """Generate path to the file within the config dir.""" return os.path.join(self.config_dir, *path) - def temperature(self, value, unit): - """Convert temperature to user preferred unit if set.""" - if not (unit in (TEMP_CELSIUS, TEMP_FAHRENHEIT) and - self.temperature_unit and unit != self.temperature_unit): - return value, unit - - try: - temp = float(value) - except ValueError: # Could not convert value to float - return value, unit - - return ( - round(temp_helper.convert(temp, unit, self.temperature_unit), 1), - self.temperature_unit) - def as_dict(self): """Create a dict representation of this dict.""" time_zone = self.time_zone or dt_util.UTC @@ -761,7 +748,7 @@ class Config(object): return { 'latitude': self.latitude, 'longitude': self.longitude, - 'temperature_unit': self.temperature_unit, + 'unit_system': self.units.as_dict(), 'location_name': self.location_name, 'time_zone': time_zone.zone, 'components': self.components, diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 1dc0cc26b5c..314d886db70 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -9,7 +9,7 @@ from homeassistant.const import ( CONF_PLATFORM, CONF_SCAN_INTERVAL, TEMP_CELSIUS, TEMP_FAHRENHEIT, CONF_ALIAS, CONF_ENTITY_ID, CONF_VALUE_TEMPLATE, WEEKDAYS, CONF_CONDITION, CONF_BELOW, CONF_ABOVE, SUN_EVENT_SUNSET, - SUN_EVENT_SUNRISE) + SUN_EVENT_SUNRISE, CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC) from homeassistant.helpers.entity import valid_entity_id import homeassistant.util.dt as dt_util from homeassistant.util import slugify @@ -226,6 +226,10 @@ def temperature_unit(value): raise vol.Invalid('invalid temperature unit (expected C or F)') +unit_system = vol.All(vol.Lower, vol.Any(CONF_UNIT_SYSTEM_METRIC, + CONF_UNIT_SYSTEM_IMPERIAL)) + + def template(value): """Validate a jinja2 template.""" if value is None: diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 9968ad3df4a..61f1c53f2a2 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -199,13 +199,15 @@ class Entity(object): attr.pop(ATTR_HIDDEN) # Convert temperature if we detect one - if attr.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_CELSIUS, - TEMP_FAHRENHEIT): - - state, attr[ATTR_UNIT_OF_MEASUREMENT] = \ - self.hass.config.temperature( - state, attr[ATTR_UNIT_OF_MEASUREMENT]) - state = str(state) + try: + unit_of_measure = attr.get(ATTR_UNIT_OF_MEASUREMENT) + if unit_of_measure in (TEMP_CELSIUS, TEMP_FAHRENHEIT): + state = \ + str(self.hass.config.units.temperature(float(state), + unit_of_measure)) + except ValueError: + # Could not convert state to float + pass return self.hass.states.set( self.entity_id, state, attr, self.force_update) diff --git a/homeassistant/helpers/temperature.py b/homeassistant/helpers/temperature.py deleted file mode 100644 index 6155c08d8f0..00000000000 --- a/homeassistant/helpers/temperature.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Methods to help handle temperature in Home Assistant.""" -import homeassistant.util.temperature as temp_util -from homeassistant.const import TEMP_CELSIUS - - -def convert(temperature, unit, to_unit): - """Convert temperature to correct unit.""" - if unit == to_unit or unit is None or to_unit is None: - return temperature - elif unit == TEMP_CELSIUS: - return temp_util.celsius_to_fahrenheit(temperature) - - return temp_util.fahrenheit_to_celsius(temperature) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 8ccfd1e0bf5..fab081cc5c5 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -219,7 +219,8 @@ class LocationMethods(object): if len(locations) == 1: return self._hass.config.distance(*locations[0]) - return loc_util.distance(*locations[0] + locations[1]) + return self._hass.config.units.length( + loc_util.distance(*locations[0] + locations[1]), 'm') def _resolve_state(self, entity_id_or_state): """Return state or entity_id if given.""" diff --git a/homeassistant/helpers/unit_system.py b/homeassistant/helpers/unit_system.py new file mode 100644 index 00000000000..e5d1481d979 --- /dev/null +++ b/homeassistant/helpers/unit_system.py @@ -0,0 +1,126 @@ +"""Unit system helper class and methods.""" + +import logging +from numbers import Number +from homeassistant.const import ( + TEMP_CELSIUS, TEMP_FAHRENHEIT, LENGTH_CENTIMETERS, LENGTH_METERS, + LENGTH_KILOMETERS, LENGTH_INCHES, LENGTH_FEET, LENGTH_YARD, LENGTH_MILES, + VOLUME_LITERS, VOLUME_MILLILITERS, VOLUME_GALLONS, VOLUME_FLUID_OUNCE, + MASS_GRAMS, MASS_KILOGRAMS, MASS_OUNCES, MASS_POUNDS, + CONF_UNIT_SYSTEM_METRIC, + CONF_UNIT_SYSTEM_IMPERIAL, LENGTH, MASS, VOLUME, TEMPERATURE, + UNIT_NOT_RECOGNIZED_TEMPLATE) +from homeassistant.util import temperature as temperature_util +from homeassistant.util import distance as distance_util + +_LOGGER = logging.getLogger(__name__) + +LENGTH_UNITS = [ + LENGTH_MILES, + LENGTH_YARD, + LENGTH_FEET, + LENGTH_INCHES, + LENGTH_KILOMETERS, + LENGTH_METERS, + LENGTH_CENTIMETERS, +] + +MASS_UNITS = [ + MASS_POUNDS, + MASS_OUNCES, + MASS_KILOGRAMS, + MASS_GRAMS, +] + +VOLUME_UNITS = [ + VOLUME_GALLONS, + VOLUME_FLUID_OUNCE, + VOLUME_LITERS, + VOLUME_MILLILITERS, +] + +TEMPERATURE_UNITS = [ + TEMP_FAHRENHEIT, + TEMP_CELSIUS, +] + + +def is_valid_unit(unit: str, unit_type: str) -> bool: + """Check if the unit is valid for it's type.""" + if unit_type == LENGTH: + units = LENGTH_UNITS + elif unit_type == TEMPERATURE: + units = TEMPERATURE_UNITS + elif unit_type == MASS: + units = MASS_UNITS + elif unit_type == VOLUME: + units = VOLUME_UNITS + else: + return False + + return unit in units + + +class UnitSystem(object): + """A container for units of measure.""" + + # pylint: disable=too-many-arguments + def __init__(self: object, name: str, temperature: str, length: str, + volume: str, mass: str) -> None: + """Initialize the unit system object.""" + errors = \ + ', '.join(UNIT_NOT_RECOGNIZED_TEMPLATE.format(unit, unit_type) + for unit, unit_type in [ + (temperature, TEMPERATURE), + (length, LENGTH), + (volume, VOLUME), + (mass, MASS), ] + if not is_valid_unit(unit, unit_type)) # type: str + + if errors: + raise ValueError(errors) + + self.name = name + self.temperature_unit = temperature + self.length_unit = length + self.mass_unit = mass + self.volume_unit = volume + + @property + def is_metric(self: object) -> bool: + """Determine if this is the metric unit system.""" + return self.name == CONF_UNIT_SYSTEM_METRIC + + def temperature(self: object, temperature: float, from_unit: str) -> ( + float, str): + """Convert the given temperature to this unit system.""" + if not isinstance(temperature, Number): + raise TypeError( + '{} is not a numeric value.'.format(str(temperature))) + + return temperature_util.convert(temperature, + from_unit, self.temperature_unit) + + def length(self: object, length: float, from_unit: str) -> float: + """Convert the given length to this unit system.""" + if not isinstance(length, Number): + raise TypeError('{} is not a numeric value.'.format(str(length))) + + return distance_util.convert(length, from_unit, + self.length_unit) # type: float + + def as_dict(self) -> dict: + """Convert the unit system to a dictionary.""" + return { + LENGTH: self.length_unit, + MASS: self.mass_unit, + TEMPERATURE: self.temperature_unit, + VOLUME: self.volume_unit + } + + +METRIC_SYSTEM = UnitSystem(CONF_UNIT_SYSTEM_METRIC, TEMP_CELSIUS, + LENGTH_KILOMETERS, VOLUME_LITERS, MASS_GRAMS) + +IMPERIAL_SYSTEM = UnitSystem(CONF_UNIT_SYSTEM_IMPERIAL, TEMP_FAHRENHEIT, + LENGTH_MILES, VOLUME_GALLONS, MASS_POUNDS) diff --git a/homeassistant/util/distance.py b/homeassistant/util/distance.py index 69478313df1..ef4e6c02b1a 100644 --- a/homeassistant/util/distance.py +++ b/homeassistant/util/distance.py @@ -3,82 +3,86 @@ import logging from numbers import Number +from homeassistant.const import ( + LENGTH_KILOMETERS, + LENGTH_MILES, + LENGTH_FEET, + LENGTH_METERS, + UNIT_NOT_RECOGNIZED_TEMPLATE, + LENGTH, +) + _LOGGER = logging.getLogger(__name__) -KILOMETERS_SYMBOL = 'km' -METERS_SYMBOL = 'm' -FEET_SYMBOL = 'ft' -MILES_SYMBOL = 'mi' - VALID_UNITS = [ - KILOMETERS_SYMBOL, - METERS_SYMBOL, - FEET_SYMBOL, - MILES_SYMBOL, + LENGTH_KILOMETERS, + LENGTH_MILES, + LENGTH_FEET, + LENGTH_METERS, ] -def convert(value, unit_1, unit_2): +def convert(value: float, unit_1: str, unit_2: str) -> float: """Convert one unit of measurement to another.""" - if not isinstance(value, Number): - raise TypeError(str(value) + ' is not of numeric type') - - if unit_1 == unit_2: - return value - if unit_1 not in VALID_UNITS: - _LOGGER.error('Unknown unit of measure: ' + str(unit_1)) - raise ValueError('Unknown unit of measure: ' + str(unit_1)) - elif unit_2 not in VALID_UNITS: - _LOGGER.error('Unknown unit of measure: ' + str(unit_2)) - raise ValueError('Unknown unit of measure: ' + str(unit_2)) + raise ValueError( + UNIT_NOT_RECOGNIZED_TEMPLATE.format(unit_1, LENGTH)) + if unit_2 not in VALID_UNITS: + raise ValueError( + UNIT_NOT_RECOGNIZED_TEMPLATE.format(unit_2, LENGTH)) + + if not isinstance(value, Number): + raise TypeError('{} is not of numeric type'.format(value)) + + if unit_1 == unit_2 or unit_1 not in VALID_UNITS: + return value meters = value - if unit_1 == MILES_SYMBOL: + if unit_1 == LENGTH_MILES: meters = __miles_to_meters(value) - elif unit_1 == FEET_SYMBOL: + elif unit_1 == LENGTH_FEET: meters = __feet_to_meters(value) - elif unit_1 == KILOMETERS_SYMBOL: + elif unit_1 == LENGTH_KILOMETERS: meters = __kilometers_to_meters(value) result = meters - if unit_2 == MILES_SYMBOL: + if unit_2 == LENGTH_MILES: result = __meters_to_miles(meters) - elif unit_2 == FEET_SYMBOL: + elif unit_2 == LENGTH_FEET: result = __meters_to_feet(meters) - elif unit_2 == KILOMETERS_SYMBOL: + elif unit_2 == LENGTH_KILOMETERS: result = __meters_to_kilometers(meters) return result -def __miles_to_meters(miles): +def __miles_to_meters(miles: float) -> float: """Convert miles to meters.""" return miles * 1609.344 -def __feet_to_meters(feet): +def __feet_to_meters(feet: float) -> float: """Convert feet to meters.""" return feet * 0.3048 -def __kilometers_to_meters(kilometers): +def __kilometers_to_meters(kilometers: float) -> float: """Convert kilometers to meters.""" return kilometers * 1000 -def __meters_to_miles(meters): +def __meters_to_miles(meters: float) -> float: """Convert meters to miles.""" return meters * 0.000621371 -def __meters_to_feet(meters): +def __meters_to_feet(meters: float) -> float: """Convert meters to feet.""" return meters * 3.28084 -def __meters_to_kilometers(meters): +def __meters_to_kilometers(meters: float) -> float: """Convert meters to kilometers.""" return meters * 0.001 diff --git a/homeassistant/util/location.py b/homeassistant/util/location.py index 1cc8ffe0b9f..1fb1c22c2cd 100644 --- a/homeassistant/util/location.py +++ b/homeassistant/util/location.py @@ -31,7 +31,7 @@ LocationInfo = collections.namedtuple( "LocationInfo", ['ip', 'country_code', 'country_name', 'region_code', 'region_name', 'city', 'zip_code', 'time_zone', 'latitude', 'longitude', - 'use_fahrenheit']) + 'use_metric']) def detect_location_info(): @@ -44,11 +44,8 @@ def detect_location_info(): if data is None: return None - # From Wikipedia: Fahrenheit is used in the Bahamas, Belize, - # the Cayman Islands, Palau, and the United States and associated - # territories of American Samoa and the U.S. Virgin Islands - data['use_fahrenheit'] = data['country_code'] in ( - 'BS', 'BZ', 'KY', 'PW', 'US', 'AS', 'VI') + data['use_metric'] = data['country_code'] not in ( + 'US', 'MM', 'LR') return LocationInfo(**data) diff --git a/homeassistant/util/temperature.py b/homeassistant/util/temperature.py index a3799ad12a2..d6e245de04f 100644 --- a/homeassistant/util/temperature.py +++ b/homeassistant/util/temperature.py @@ -1,4 +1,10 @@ """Temperature util functions.""" +from homeassistant.const import ( + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + UNIT_NOT_RECOGNIZED_TEMPLATE, + TEMPERATURE +) def fahrenheit_to_celsius(fahrenheit: float) -> float: @@ -9,3 +15,20 @@ def fahrenheit_to_celsius(fahrenheit: float) -> float: def celsius_to_fahrenheit(celsius: float) -> float: """Convert a Celsius temperature to Fahrenheit.""" return celsius * 1.8 + 32.0 + + +def convert(temperature: float, from_unit: str, to_unit: str) -> float: + """Convert a temperature from one unit to another.""" + if from_unit not in (TEMP_CELSIUS, TEMP_FAHRENHEIT): + raise ValueError(UNIT_NOT_RECOGNIZED_TEMPLATE.format(from_unit, + TEMPERATURE)) + if to_unit not in (TEMP_CELSIUS, TEMP_FAHRENHEIT): + raise ValueError(UNIT_NOT_RECOGNIZED_TEMPLATE.format(to_unit, + TEMPERATURE)) + + if from_unit == to_unit: + return temperature + elif from_unit == TEMP_CELSIUS: + return celsius_to_fahrenheit(temperature) + else: + return round(fahrenheit_to_celsius(temperature), 1) diff --git a/tests/common.py b/tests/common.py index 4fd9da96ae3..e3ca00c8cbb 100644 --- a/tests/common.py +++ b/tests/common.py @@ -6,11 +6,12 @@ from unittest import mock from homeassistant import core as ha, loader from homeassistant.bootstrap import _setup_component from homeassistant.helpers.entity import ToggleEntity +from homeassistant.helpers.unit_system import METRIC_SYSTEM import homeassistant.util.dt as date_util from homeassistant.const import ( STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED, EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, - ATTR_DISCOVERED, SERVER_PORT, TEMP_CELSIUS) + ATTR_DISCOVERED, SERVER_PORT) from homeassistant.components import sun, mqtt _TEST_INSTANCE_PORT = SERVER_PORT @@ -37,7 +38,7 @@ def get_test_home_assistant(num_threads=None): hass.config.longitude = -117.22743 hass.config.elevation = 0 hass.config.time_zone = date_util.get_time_zone('US/Pacific') - hass.config.temperature_unit = TEMP_CELSIUS + hass.config.units = METRIC_SYSTEM if 'custom_components.test' not in loader.AVAILABLE_COMPONENTS: loader.prepare(hass) diff --git a/tests/components/hvac/test_demo.py b/tests/components/hvac/test_demo.py index bdb155e43b4..fb1abd90c65 100644 --- a/tests/components/hvac/test_demo.py +++ b/tests/components/hvac/test_demo.py @@ -1,8 +1,8 @@ """The tests for the demo hvac.""" import unittest -from homeassistant.const import ( - TEMP_CELSIUS, +from homeassistant.helpers.unit_system import ( + METRIC_SYSTEM, ) from homeassistant.components import hvac @@ -18,7 +18,7 @@ class TestDemoHvac(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.temperature_unit = TEMP_CELSIUS + self.hass.config.units = METRIC_SYSTEM self.assertTrue(hvac.setup(self.hass, {'hvac': { 'platform': 'demo', }})) diff --git a/tests/components/thermostat/test_demo.py b/tests/components/thermostat/test_demo.py index c4c6a52a20f..ba85c393f63 100644 --- a/tests/components/thermostat/test_demo.py +++ b/tests/components/thermostat/test_demo.py @@ -1,8 +1,8 @@ """The tests for the demo thermostat.""" import unittest -from homeassistant.const import ( - TEMP_CELSIUS, +from homeassistant.helpers.unit_system import ( + METRIC_SYSTEM, ) from homeassistant.components import thermostat @@ -18,7 +18,7 @@ class TestDemoThermostat(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.temperature_unit = TEMP_CELSIUS + self.hass.config.units = METRIC_SYSTEM self.assertTrue(thermostat.setup(self.hass, {'thermostat': { 'platform': 'demo', }})) @@ -43,10 +43,10 @@ class TestDemoThermostat(unittest.TestCase): def test_set_target_temp_bad_attr(self): """Test setting the target temperature without required attribute.""" - self.assertEqual('21', self.hass.states.get(ENTITY_NEST).state) + self.assertEqual('21.0', self.hass.states.get(ENTITY_NEST).state) thermostat.set_temperature(self.hass, None, ENTITY_NEST) self.hass.pool.block_till_done() - self.assertEqual('21', self.hass.states.get(ENTITY_NEST).state) + self.assertEqual('21.0', self.hass.states.get(ENTITY_NEST).state) def test_set_target_temp(self): """Test the setting of the target temperature.""" diff --git a/tests/components/thermostat/test_heat_control.py b/tests/components/thermostat/test_heat_control.py index 8ab571ce56b..f7f786adbbb 100644 --- a/tests/components/thermostat/test_heat_control.py +++ b/tests/components/thermostat/test_heat_control.py @@ -10,6 +10,7 @@ from homeassistant.const import ( STATE_OFF, TEMP_CELSIUS, ) +from homeassistant.helpers.unit_system import METRIC_SYSTEM from homeassistant.components import thermostat from tests.common import get_test_home_assistant @@ -75,7 +76,7 @@ class TestThermostatHeatControl(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.hass.config.temperature_unit = TEMP_CELSIUS + self.hass.config.units = METRIC_SYSTEM thermostat.setup(self.hass, {'thermostat': { 'platform': 'heat_control', 'name': 'test', diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 1529c879aab..4d62d328825 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -6,6 +6,13 @@ from unittest.mock import patch from homeassistant.components import group from homeassistant.exceptions import TemplateError from homeassistant.helpers import template +from homeassistant.helpers.unit_system import UnitSystem +from homeassistant.const import ( + LENGTH_METERS, + TEMP_CELSIUS, + MASS_GRAMS, + VOLUME_LITERS, +) import homeassistant.util.dt as dt_util from tests.common import get_test_home_assistant @@ -17,6 +24,9 @@ class TestUtilTemplate(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup the tests.""" self.hass = get_test_home_assistant() + self.hass.config.units = UnitSystem('custom', TEMP_CELSIUS, + LENGTH_METERS, VOLUME_LITERS, + MASS_GRAMS) def tearDown(self): # pylint: disable=invalid-name """Stop down stuff we started.""" diff --git a/tests/helpers/test_unit_system.py b/tests/helpers/test_unit_system.py new file mode 100644 index 00000000000..a31b3bfc53a --- /dev/null +++ b/tests/helpers/test_unit_system.py @@ -0,0 +1,132 @@ +"""Test the unit system helper.""" +import unittest + +from homeassistant.helpers.unit_system import ( + UnitSystem, + METRIC_SYSTEM, + IMPERIAL_SYSTEM, +) +from homeassistant.const import ( + LENGTH_METERS, + LENGTH_KILOMETERS, + MASS_GRAMS, + VOLUME_LITERS, + TEMP_CELSIUS, + LENGTH, + MASS, + TEMPERATURE, + VOLUME +) + +SYSTEM_NAME = 'TEST' +INVALID_UNIT = 'INVALID' + + +class TestUnitSystem(unittest.TestCase): + """Test the unit system helper.""" + + def test_invalid_units(self): + """Test errors are raised when invalid units are passed in.""" + with self.assertRaises(ValueError): + UnitSystem(SYSTEM_NAME, INVALID_UNIT, LENGTH_METERS, VOLUME_LITERS, + MASS_GRAMS) + + with self.assertRaises(ValueError): + UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, INVALID_UNIT, VOLUME_LITERS, + MASS_GRAMS) + + with self.assertRaises(ValueError): + UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, INVALID_UNIT, + MASS_GRAMS) + + with self.assertRaises(ValueError): + UnitSystem(SYSTEM_NAME, TEMP_CELSIUS, LENGTH_METERS, VOLUME_LITERS, + INVALID_UNIT) + + def test_invalid_value(self): + """Test no conversion happens if value is non-numeric.""" + with self.assertRaises(TypeError): + METRIC_SYSTEM.length('25a', LENGTH_KILOMETERS) + with self.assertRaises(TypeError): + METRIC_SYSTEM.temperature('50K', TEMP_CELSIUS) + + def test_as_dict(self): + """Test that the as_dict() method returns the expected dictionary.""" + expected = { + LENGTH: LENGTH_KILOMETERS, + TEMPERATURE: TEMP_CELSIUS, + VOLUME: VOLUME_LITERS, + MASS: MASS_GRAMS + } + + self.assertEqual(expected, METRIC_SYSTEM.as_dict()) + + def test_temperature_same_unit(self): + """Test no conversion happens if to unit is same as from unit.""" + self.assertEqual( + 5, + METRIC_SYSTEM.temperature(5, + METRIC_SYSTEM.temperature_unit)) + + def test_temperature_unknown_unit(self): + """Test no conversion happens if unknown unit.""" + with self.assertRaises(ValueError): + METRIC_SYSTEM.temperature(5, 'K') + + def test_temperature_to_metric(self): + """Test temperature conversion to metric system.""" + self.assertEqual( + 25, + METRIC_SYSTEM.temperature(25, METRIC_SYSTEM.temperature_unit)) + self.assertEqual( + 26.7, + METRIC_SYSTEM.temperature(80, IMPERIAL_SYSTEM.temperature_unit)) + + def test_temperature_to_imperial(self): + """Test temperature conversion to imperial system.""" + self.assertEqual( + 77, + IMPERIAL_SYSTEM.temperature(77, IMPERIAL_SYSTEM.temperature_unit)) + self.assertEqual( + 77, + IMPERIAL_SYSTEM.temperature(25, METRIC_SYSTEM.temperature_unit)) + + def test_length_unknown_unit(self): + """Test length conversion with unknown from unit.""" + with self.assertRaises(ValueError): + METRIC_SYSTEM.length(5, 'fr') + + def test_length_to_metric(self): + """Test length conversion to metric system.""" + self.assertEqual( + 100, + METRIC_SYSTEM.length(100, METRIC_SYSTEM.length_unit) + ) + self.assertEqual( + 8.04672, + METRIC_SYSTEM.length(5, IMPERIAL_SYSTEM.length_unit) + ) + + def test_length_to_imperial(self): + """Test length conversion to imperial system.""" + self.assertEqual( + 100, + IMPERIAL_SYSTEM.length(100, + IMPERIAL_SYSTEM.length_unit) + ) + self.assertEqual( + 3.106855, + IMPERIAL_SYSTEM.length(5, METRIC_SYSTEM.length_unit) + ) + + def test_properties(self): + """Test the unit properties are returned as expected.""" + self.assertEqual(LENGTH_KILOMETERS, METRIC_SYSTEM.length_unit) + self.assertEqual(TEMP_CELSIUS, METRIC_SYSTEM.temperature_unit) + self.assertEqual(MASS_GRAMS, METRIC_SYSTEM.mass_unit) + self.assertEqual(VOLUME_LITERS, METRIC_SYSTEM.volume_unit) + + def test_is_metric(self): + """Test the is metric flag.""" + self.assertTrue(METRIC_SYSTEM.is_metric) + self.assertFalse(IMPERIAL_SYSTEM.is_metric) diff --git a/tests/test_config.py b/tests/test_config.py index 0a6321c2240..4a4f1ef9b6f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -11,9 +11,9 @@ from voluptuous import MultipleInvalid from homeassistant.core import DOMAIN, HomeAssistantError, Config import homeassistant.config as config_util from homeassistant.const import ( - CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, + CONF_LATITUDE, CONF_LONGITUDE, CONF_UNIT_SYSTEM, CONF_NAME, CONF_TIME_ZONE, CONF_ELEVATION, CONF_CUSTOMIZE, __version__, - TEMP_FAHRENHEIT) + CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT) from homeassistant.util import location as location_util, dt as dt_util from homeassistant.helpers.entity import Entity @@ -145,7 +145,7 @@ class TestConfig(unittest.TestCase): CONF_LATITUDE: 32.8594, CONF_LONGITUDE: -117.2073, CONF_ELEVATION: 101, - CONF_TEMPERATURE_UNIT: 'F', + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, CONF_NAME: 'Home', CONF_TIME_ZONE: 'America/Los_Angeles' } @@ -167,7 +167,7 @@ class TestConfig(unittest.TestCase): def test_core_config_schema(self): for value in ( - {'temperature_unit': 'K'}, + {CONF_UNIT_SYSTEM: 'K'}, {'time_zone': 'non-exist'}, {'latitude': '91'}, {'longitude': -181}, @@ -182,7 +182,7 @@ class TestConfig(unittest.TestCase): 'name': 'Test name', 'latitude': '-23.45', 'longitude': '123.45', - 'temperature_unit': 'c', + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, 'customize': { 'sensor.temperature': { 'hidden': True, @@ -264,7 +264,7 @@ class TestConfig(unittest.TestCase): 'longitude': 50, 'elevation': 25, 'name': 'Huis', - 'temperature_unit': 'F', + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_IMPERIAL, 'time_zone': 'America/New_York', }) @@ -272,7 +272,28 @@ class TestConfig(unittest.TestCase): assert config.longitude == 50 assert config.elevation == 25 assert config.location_name == 'Huis' - assert config.temperature_unit == TEMP_FAHRENHEIT + assert config.units.name == CONF_UNIT_SYSTEM_IMPERIAL + assert config.time_zone.zone == 'America/New_York' + + def test_loading_configuration_temperature_unit(self): + """Test backward compatibility when loading core config.""" + config = Config() + hass = mock.Mock(config=config) + + config_util.process_ha_core_config(hass, { + 'latitude': 60, + 'longitude': 50, + 'elevation': 25, + 'name': 'Huis', + CONF_TEMPERATURE_UNIT: 'C', + 'time_zone': 'America/New_York', + }) + + assert config.latitude == 60 + assert config.longitude == 50 + assert config.elevation == 25 + assert config.location_name == 'Huis' + assert config.units.name == CONF_UNIT_SYSTEM_METRIC assert config.time_zone.zone == 'America/New_York' @mock.patch('homeassistant.util.location.detect_location_info', @@ -292,7 +313,8 @@ class TestConfig(unittest.TestCase): assert config.longitude == -117.2073 assert config.elevation == 101 assert config.location_name == 'San Diego' - assert config.temperature_unit == TEMP_FAHRENHEIT + assert config.units.name == CONF_UNIT_SYSTEM_METRIC + assert config.units.is_metric assert config.time_zone.zone == 'America/Los_Angeles' @mock.patch('homeassistant.util.location.detect_location_info', @@ -311,5 +333,5 @@ class TestConfig(unittest.TestCase): assert config.longitude == blankConfig.longitude assert config.elevation == blankConfig.elevation assert config.location_name == blankConfig.location_name - assert config.temperature_unit == blankConfig.temperature_unit + assert config.units == blankConfig.units assert config.time_zone == blankConfig.time_zone diff --git a/tests/test_core.py b/tests/test_core.py index e9513a2adb8..56a44964e79 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -15,10 +15,10 @@ import homeassistant.core as ha from homeassistant.exceptions import ( HomeAssistantError, InvalidEntityFormatError) import homeassistant.util.dt as dt_util +from homeassistant.helpers.unit_system import (METRIC_SYSTEM) from homeassistant.const import ( __version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - EVENT_STATE_CHANGED, ATTR_FRIENDLY_NAME, TEMP_CELSIUS, - TEMP_FAHRENHEIT) + EVENT_STATE_CHANGED, ATTR_FRIENDLY_NAME, CONF_UNIT_SYSTEM) from tests.common import get_test_home_assistant @@ -465,56 +465,12 @@ class TestConfig(unittest.TestCase): os.path.join(data_dir, ".homeassistant", "dir", "test.conf"), self.config.path("dir", "test.conf")) - def test_temperature_not_convert_if_no_preference(self): - """No unit conversion to happen if no preference.""" - self.assertEqual( - (25, TEMP_CELSIUS), - self.config.temperature(25, TEMP_CELSIUS)) - self.assertEqual( - (80, TEMP_FAHRENHEIT), - self.config.temperature(80, TEMP_FAHRENHEIT)) - - def test_temperature_not_convert_if_invalid_value(self): - """No unit conversion to happen if no preference.""" - self.config.temperature_unit = TEMP_FAHRENHEIT - self.assertEqual( - ('25a', TEMP_CELSIUS), - self.config.temperature('25a', TEMP_CELSIUS)) - - def test_temperature_not_convert_if_invalid_unit(self): - """No unit conversion to happen if no preference.""" - self.assertEqual( - (25, 'Invalid unit'), - self.config.temperature(25, 'Invalid unit')) - - def test_temperature_to_convert_to_celsius(self): - """Test temperature conversion to celsius.""" - self.config.temperature_unit = TEMP_CELSIUS - - self.assertEqual( - (25, TEMP_CELSIUS), - self.config.temperature(25, TEMP_CELSIUS)) - self.assertEqual( - (26.7, TEMP_CELSIUS), - self.config.temperature(80, TEMP_FAHRENHEIT)) - - def test_temperature_to_convert_to_fahrenheit(self): - """Test temperature conversion to fahrenheit.""" - self.config.temperature_unit = TEMP_FAHRENHEIT - - self.assertEqual( - (77, TEMP_FAHRENHEIT), - self.config.temperature(25, TEMP_CELSIUS)) - self.assertEqual( - (80, TEMP_FAHRENHEIT), - self.config.temperature(80, TEMP_FAHRENHEIT)) - def test_as_dict(self): """Test as dict.""" expected = { 'latitude': None, 'longitude': None, - 'temperature_unit': None, + CONF_UNIT_SYSTEM: METRIC_SYSTEM.as_dict(), 'location_name': None, 'time_zone': 'UTC', 'components': [], diff --git a/tests/util/test_distance.py b/tests/util/test_distance.py index 498416bc8eb..7f04f6f0569 100644 --- a/tests/util/test_distance.py +++ b/tests/util/test_distance.py @@ -2,14 +2,11 @@ import unittest import homeassistant.util.distance as distance_util - -KILOMETERS = distance_util.KILOMETERS_SYMBOL -METERS = distance_util.METERS_SYMBOL -FEET = distance_util.FEET_SYMBOL -MILES = distance_util.MILES_SYMBOL +from homeassistant.const import (LENGTH_KILOMETERS, LENGTH_METERS, LENGTH_FEET, + LENGTH_MILES) INVALID_SYMBOL = 'bob' -VALID_SYMBOL = KILOMETERS +VALID_SYMBOL = LENGTH_KILOMETERS class TestDistanceUtil(unittest.TestCase): @@ -17,52 +14,78 @@ class TestDistanceUtil(unittest.TestCase): def test_convert_same_unit(self): """Test conversion from any unit to same unit.""" - self.assertEqual(5, distance_util.convert(5, KILOMETERS, KILOMETERS)) - self.assertEqual(2, distance_util.convert(2, METERS, METERS)) - self.assertEqual(10, distance_util.convert(10, MILES, MILES)) - self.assertEqual(9, distance_util.convert(9, FEET, FEET)) + self.assertEqual(5, + distance_util.convert(5, LENGTH_KILOMETERS, + LENGTH_KILOMETERS)) + self.assertEqual(2, + distance_util.convert(2, LENGTH_METERS, + LENGTH_METERS)) + self.assertEqual(10, + distance_util.convert(10, LENGTH_MILES, LENGTH_MILES)) + self.assertEqual(9, + distance_util.convert(9, LENGTH_FEET, LENGTH_FEET)) def test_convert_invalid_unit(self): """Test exception is thrown for invalid units.""" with self.assertRaises(ValueError): - distance_util.convert(5, INVALID_SYMBOL, VALID_SYMBOL) + distance_util.convert(5, INVALID_SYMBOL, + VALID_SYMBOL) with self.assertRaises(ValueError): - distance_util.convert(5, VALID_SYMBOL, INVALID_SYMBOL) + distance_util.convert(5, VALID_SYMBOL, + INVALID_SYMBOL) def test_convert_nonnumeric_value(self): """Test exception is thrown for nonnumeric type.""" with self.assertRaises(TypeError): - distance_util.convert('a', KILOMETERS, METERS) + distance_util.convert('a', LENGTH_KILOMETERS, LENGTH_METERS) def test_convert_from_miles(self): """Test conversion from miles to other units.""" miles = 5 - self.assertEqual(distance_util.convert(miles, MILES, KILOMETERS), - 8.04672) - self.assertEqual(distance_util.convert(miles, MILES, METERS), 8046.72) - self.assertEqual(distance_util.convert(miles, MILES, FEET), - 26400.0008448) + self.assertEqual( + distance_util.convert(miles, LENGTH_MILES, LENGTH_KILOMETERS), + 8.04672) + self.assertEqual( + distance_util.convert(miles, LENGTH_MILES, LENGTH_METERS), + 8046.72) + self.assertEqual( + distance_util.convert(miles, LENGTH_MILES, LENGTH_FEET), + 26400.0008448) def test_convert_from_feet(self): """Test conversion from feet to other units.""" feet = 5000 - self.assertEqual(distance_util.convert(feet, FEET, KILOMETERS), 1.524) - self.assertEqual(distance_util.convert(feet, FEET, METERS), 1524) - self.assertEqual(distance_util.convert(feet, FEET, MILES), - 0.9469694040000001) + self.assertEqual( + distance_util.convert(feet, LENGTH_FEET, LENGTH_KILOMETERS), + 1.524) + self.assertEqual( + distance_util.convert(feet, LENGTH_FEET, LENGTH_METERS), + 1524) + self.assertEqual( + distance_util.convert(feet, LENGTH_FEET, LENGTH_MILES), + 0.9469694040000001) def test_convert_from_kilometers(self): """Test conversion from kilometers to other units.""" km = 5 - self.assertEqual(distance_util.convert(km, KILOMETERS, FEET), 16404.2) - self.assertEqual(distance_util.convert(km, KILOMETERS, METERS), 5000) - self.assertEqual(distance_util.convert(km, KILOMETERS, MILES), - 3.106855) + self.assertEqual( + distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_FEET), + 16404.2) + self.assertEqual( + distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_METERS), + 5000) + self.assertEqual( + distance_util.convert(km, LENGTH_KILOMETERS, LENGTH_MILES), + 3.106855) def test_convert_from_meters(self): """Test conversion from meters to other units.""" m = 5000 - self.assertEqual(distance_util.convert(m, METERS, FEET), 16404.2) - self.assertEqual(distance_util.convert(m, METERS, KILOMETERS), 5) - self.assertEqual(distance_util.convert(m, METERS, MILES), 3.106855) + self.assertEqual(distance_util.convert(m, LENGTH_METERS, LENGTH_FEET), + 16404.2) + self.assertEqual( + distance_util.convert(m, LENGTH_METERS, LENGTH_KILOMETERS), + 5) + self.assertEqual(distance_util.convert(m, LENGTH_METERS, LENGTH_MILES), + 3.106855) diff --git a/tests/util/test_location.py b/tests/util/test_location.py index 1dfb71a87bf..6d099ebcfac 100644 --- a/tests/util/test_location.py +++ b/tests/util/test_location.py @@ -79,7 +79,7 @@ class TestLocationUtil(TestCase): assert info.time_zone == 'America/Los_Angeles' assert info.latitude == 32.8594 assert info.longitude == -117.2073 - assert info.use_fahrenheit + assert not info.use_metric @requests_mock.Mocker() @patch('homeassistant.util.location._get_freegeoip', return_value=None) @@ -101,7 +101,7 @@ class TestLocationUtil(TestCase): assert info.time_zone == 'America/Los_Angeles' assert info.latitude == 32.8594 assert info.longitude == -117.2073 - assert info.use_fahrenheit + assert not info.use_metric @patch('homeassistant.util.location.elevation', return_value=0) @patch('homeassistant.util.location._get_freegeoip', return_value=None) From 231656916c293e444bc6c062c8cc96e75b1f4f97 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 4 Aug 2016 22:37:30 -0700 Subject: [PATCH 2/2] Address last comments --- .../www_static/home-assistant-polymer | 2 +- homeassistant/components/sensor/vera.py | 11 ++------ .../components/thermostat/__init__.py | 23 +++++----------- .../components/thermostat/heat_control.py | 27 +++++-------------- homeassistant/helpers/entity.py | 6 ++--- homeassistant/helpers/unit_system.py | 3 +-- .../thermostat/test_heat_control.py | 18 ++++++++++--- 7 files changed, 33 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index cbafbda5fdd..a343d3d098e 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit cbafbda5fdd2216ab4b6ebd962cbd1164fa8abec +Subproject commit a343d3d098e78976fc66f8ed66ce0ae5d7bc3861 diff --git a/homeassistant/components/sensor/vera.py b/homeassistant/components/sensor/vera.py index d7b642fb2fc..927c1863cce 100644 --- a/homeassistant/components/sensor/vera.py +++ b/homeassistant/components/sensor/vera.py @@ -51,7 +51,8 @@ class VeraSensor(VeraDevice, Entity): def update(self): """Update the state.""" if self.vera_device.category == "Temperature Sensor": - current_temp = self.vera_device.temperature + self.current_value = self.vera_device.temperature + vera_temp_units = ( self.vera_device.vera_controller.temperature_units) @@ -60,14 +61,6 @@ class VeraSensor(VeraDevice, Entity): else: self._temperature_units = TEMP_CELSIUS - if self.hass: - temp = self.hass.config.units.temperature( - current_temp, - self._temperature_units) - - current_temp = temp - - self.current_value = current_temp elif self.vera_device.category == "Light Sensor": self.current_value = self.vera_device.light elif self.vera_device.category == "Humidity Sensor": diff --git a/homeassistant/components/thermostat/__init__.py b/homeassistant/components/thermostat/__init__.py index b8a3b113671..09a18b91402 100644 --- a/homeassistant/components/thermostat/__init__.py +++ b/homeassistant/components/thermostat/__init__.py @@ -147,14 +147,11 @@ def setup(hass, config): temperature = service.data[ATTR_TEMPERATURE] for thermostat in target_thermostats: - if thermostat.unit_of_measurement is not None: - converted_temperature = convert( - temperature, hass.config.units.temperature_unit, - thermostat.unit_of_measurement) - else: - converted_temperature = temperature - thermostat.set_temperature(converted_temperature) + converted_temperature = convert( + temperature, hass.config.units.temperature_unit, + thermostat.unit_of_measurement) + thermostat.set_temperature(converted_temperature) thermostat.update_ha_state(True) hass.services.register( @@ -306,20 +303,12 @@ class ThermostatDevice(Entity): @property def min_temp(self): """Return the minimum temperature.""" - try: - unit = self.unit_of_measurement - return convert(7, TEMP_CELSIUS, unit) - except ValueError: - return STATE_UNKNOWN + return convert(7, TEMP_CELSIUS, self.unit_of_measurement) @property def max_temp(self): """Return the maximum temperature.""" - try: - unit = self.unit_of_measurement - return convert(35, TEMP_CELSIUS, unit) - except ValueError: - return STATE_UNKNOWN + return convert(35, TEMP_CELSIUS, self.unit_of_measurement) def _convert_for_display(self, temp): """Convert temperature into preferred units for display purposes.""" diff --git a/homeassistant/components/thermostat/heat_control.py b/homeassistant/components/thermostat/heat_control.py index 0f2dbce6040..9f70c90ced1 100644 --- a/homeassistant/components/thermostat/heat_control.py +++ b/homeassistant/components/thermostat/heat_control.py @@ -8,12 +8,10 @@ import logging import voluptuous as vol import homeassistant.helpers.config_validation as cv -import homeassistant.util as util from homeassistant.components import switch from homeassistant.components.thermostat import ( STATE_HEAT, STATE_COOL, STATE_IDLE, ThermostatDevice) -from homeassistant.const import ( - ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT) +from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT from homeassistant.helpers.event import track_state_change DEPENDENCIES = ['switch', 'sensor'] @@ -157,24 +155,11 @@ class HeatControl(ThermostatDevice): """Update thermostat with latest state from sensor.""" unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) - if unit not in (TEMP_CELSIUS, TEMP_FAHRENHEIT): - self._cur_temp = None - self._unit = None - _LOGGER.error('Sensor has unsupported unit: %s (allowed: %s, %s)', - unit, TEMP_CELSIUS, TEMP_FAHRENHEIT) - return - - temp = util.convert(state.state, float) - - if temp is None: - self._cur_temp = None - self._unit = None - _LOGGER.error('Unable to parse sensor temperature: %s', - state.state) - return - - self._cur_temp = temp - self._unit = unit + try: + self._cur_temp = self.hass.config.units.temperature( + float(state.state), unit) + except ValueError as ex: + _LOGGER.error('Unable to update from sensor: %s', ex) def _control_heating(self): """Check if we need to turn heating on or off.""" diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 61f1c53f2a2..4dac7f9f6d0 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -202,9 +202,9 @@ class Entity(object): try: unit_of_measure = attr.get(ATTR_UNIT_OF_MEASUREMENT) if unit_of_measure in (TEMP_CELSIUS, TEMP_FAHRENHEIT): - state = \ - str(self.hass.config.units.temperature(float(state), - unit_of_measure)) + units = self.hass.config.units + state = str(units.temperature(float(state), unit_of_measure)) + attr[ATTR_UNIT_OF_MEASUREMENT] = units.temperature_unit except ValueError: # Could not convert state to float pass diff --git a/homeassistant/helpers/unit_system.py b/homeassistant/helpers/unit_system.py index e5d1481d979..a83a2b9a2ba 100644 --- a/homeassistant/helpers/unit_system.py +++ b/homeassistant/helpers/unit_system.py @@ -91,8 +91,7 @@ class UnitSystem(object): """Determine if this is the metric unit system.""" return self.name == CONF_UNIT_SYSTEM_METRIC - def temperature(self: object, temperature: float, from_unit: str) -> ( - float, str): + def temperature(self: object, temperature: float, from_unit: str) -> float: """Convert the given temperature to this unit system.""" if not isinstance(temperature, Number): raise TypeError( diff --git a/tests/components/thermostat/test_heat_control.py b/tests/components/thermostat/test_heat_control.py index f7f786adbbb..7c14d2b1490 100644 --- a/tests/components/thermostat/test_heat_control.py +++ b/tests/components/thermostat/test_heat_control.py @@ -124,19 +124,29 @@ class TestThermostatHeatControl(unittest.TestCase): def test_sensor_bad_unit(self): """Test sensor that have bad unit.""" + state = self.hass.states.get(ENTITY) + temp = state.attributes.get('current_temperature') + unit = state.attributes.get('unit_of_measurement') + self._setup_sensor(22.0, unit='bad_unit') self.hass.pool.block_till_done() + state = self.hass.states.get(ENTITY) - self.assertEqual(None, state.attributes.get('unit_of_measurement')) - self.assertEqual(None, state.attributes.get('current_temperature')) + self.assertEqual(unit, state.attributes.get('unit_of_measurement')) + self.assertEqual(temp, state.attributes.get('current_temperature')) def test_sensor_bad_value(self): """Test sensor that have None as state.""" + state = self.hass.states.get(ENTITY) + temp = state.attributes.get('current_temperature') + unit = state.attributes.get('unit_of_measurement') + self._setup_sensor(None) self.hass.pool.block_till_done() + state = self.hass.states.get(ENTITY) - self.assertEqual(None, state.attributes.get('unit_of_measurement')) - self.assertEqual(None, state.attributes.get('current_temperature')) + self.assertEqual(unit, state.attributes.get('unit_of_measurement')) + self.assertEqual(temp, state.attributes.get('current_temperature')) def test_set_target_temp_heater_on(self): """Test if target temperature turn heater on."""