Skip to content

Commit

Permalink
Remove unit_of_measurement from climate entities (#16012)
Browse files Browse the repository at this point in the history
* Remove unit_of_measurement from climate base class.

* Updated google_assistant component and tests to use core temp units.

* Fixes

* Convert Alexa component to use core temp units for climate entities.

* Fix tests.

* Converted prometheus component.

* Remove unit_of_measurement from homekit thermostat tests.

* Small fix.
  • Loading branch information
jeradM authored and balloob committed Aug 22, 2018
1 parent a31501d commit ae63980
Show file tree
Hide file tree
Showing 14 changed files with 184 additions and 224 deletions.
65 changes: 36 additions & 29 deletions homeassistant/components/alexa/smart_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.util.decorator import Registry
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, CONF_NAME,
SERVICE_LOCK, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE,
ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, SERVICE_LOCK,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_VOLUME_SET, TEMP_FAHRENHEIT, TEMP_CELSIUS,
CONF_UNIT_OF_MEASUREMENT, STATE_LOCKED, STATE_UNLOCKED, STATE_ON)
STATE_LOCKED, STATE_UNLOCKED, STATE_ON)

from .const import CONF_FILTER, CONF_ENTITY_CONFIG

Expand Down Expand Up @@ -160,7 +161,8 @@ class _AlexaEntity:
The API handlers should manipulate entities only through this interface.
"""

def __init__(self, config, entity):
def __init__(self, hass, config, entity):
self.hass = hass
self.config = config
self.entity = entity
self.entity_conf = config.entity_config.get(entity.entity_id, {})
Expand Down Expand Up @@ -384,6 +386,10 @@ def name(self):


class _AlexaTemperatureSensor(_AlexaInterface):
def __init__(self, hass, entity):
_AlexaInterface.__init__(self, entity)
self.hass = hass

def name(self):
return 'Alexa.TemperatureSensor'

Expand All @@ -397,9 +403,10 @@ def get_property(self, name):
if name != 'temperature':
raise _UnsupportedProperty(name)

unit = self.entity.attributes[CONF_UNIT_OF_MEASUREMENT]
unit = self.entity.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
temp = self.entity.state
if self.entity.domain == climate.DOMAIN:
unit = self.hass.config.units.temperature_unit
temp = self.entity.attributes.get(
climate.ATTR_CURRENT_TEMPERATURE)
return {
Expand All @@ -409,6 +416,10 @@ def get_property(self, name):


class _AlexaThermostatController(_AlexaInterface):
def __init__(self, hass, entity):
_AlexaInterface.__init__(self, entity)
self.hass = hass

def name(self):
return 'Alexa.ThermostatController'

Expand Down Expand Up @@ -439,8 +450,7 @@ def get_property(self, name):
raise _UnsupportedProperty(name)
return mode

unit = self.entity.attributes[CONF_UNIT_OF_MEASUREMENT]
temp = None
unit = self.hass.config.units.temperature_unit
if name == 'targetSetpoint':
temp = self.entity.attributes.get(climate.ATTR_TEMPERATURE)
elif name == 'lowerSetpoint':
Expand Down Expand Up @@ -491,8 +501,8 @@ def default_display_categories(self):
return [_DisplayCategory.THERMOSTAT]

def interfaces(self):
yield _AlexaThermostatController(self.entity)
yield _AlexaTemperatureSensor(self.entity)
yield _AlexaThermostatController(self.hass, self.entity)
yield _AlexaTemperatureSensor(self.hass, self.entity)


@ENTITY_ADAPTERS.register(cover.DOMAIN)
Expand Down Expand Up @@ -609,11 +619,11 @@ def default_display_categories(self):

def interfaces(self):
attrs = self.entity.attributes
if attrs.get(CONF_UNIT_OF_MEASUREMENT) in (
if attrs.get(ATTR_UNIT_OF_MEASUREMENT) in (
TEMP_FAHRENHEIT,
TEMP_CELSIUS,
):
yield _AlexaTemperatureSensor(self.entity)
yield _AlexaTemperatureSensor(self.hass, self.entity)


class _Cause:
Expand Down Expand Up @@ -823,7 +833,7 @@ async def async_api_discovery(hass, config, request, context):

if entity.domain not in ENTITY_ADAPTERS:
continue
alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
alexa_entity = ENTITY_ADAPTERS[entity.domain](hass, config, entity)

endpoint = {
'displayCategories': alexa_entity.display_categories(),
Expand Down Expand Up @@ -1364,11 +1374,12 @@ async def async_api_previous(hass, config, request, context, entity):
return api_message(request)


def api_error_temp_range(request, temp, min_temp, max_temp, unit):
def api_error_temp_range(hass, request, temp, min_temp, max_temp):
"""Create temperature value out of range API error response.
Async friendly.
"""
unit = hass.config.units.temperature_unit
temp_range = {
'minimumValue': {
'value': min_temp,
Expand All @@ -1389,8 +1400,9 @@ def api_error_temp_range(request, temp, min_temp, max_temp, unit):
)


def temperature_from_object(temp_obj, to_unit, interval=False):
def temperature_from_object(hass, temp_obj, interval=False):
"""Get temperature from Temperature object in requested unit."""
to_unit = hass.config.units.temperature_unit
from_unit = TEMP_CELSIUS
temp = float(temp_obj['value'])

Expand All @@ -1408,7 +1420,6 @@ def temperature_from_object(temp_obj, to_unit, interval=False):
@extract_entity
async def async_api_set_target_temp(hass, config, request, context, entity):
"""Process a set target temperature request."""
unit = entity.attributes[CONF_UNIT_OF_MEASUREMENT]
min_temp = entity.attributes.get(climate.ATTR_MIN_TEMP)
max_temp = entity.attributes.get(climate.ATTR_MAX_TEMP)

Expand All @@ -1418,25 +1429,22 @@ async def async_api_set_target_temp(hass, config, request, context, entity):

payload = request[API_PAYLOAD]
if 'targetSetpoint' in payload:
temp = temperature_from_object(
payload['targetSetpoint'], unit)
temp = temperature_from_object(hass, payload['targetSetpoint'])
if temp < min_temp or temp > max_temp:
return api_error_temp_range(
request, temp, min_temp, max_temp, unit)
hass, request, temp, min_temp, max_temp)
data[ATTR_TEMPERATURE] = temp
if 'lowerSetpoint' in payload:
temp_low = temperature_from_object(
payload['lowerSetpoint'], unit)
temp_low = temperature_from_object(hass, payload['lowerSetpoint'])
if temp_low < min_temp or temp_low > max_temp:
return api_error_temp_range(
request, temp_low, min_temp, max_temp, unit)
hass, request, temp_low, min_temp, max_temp)
data[climate.ATTR_TARGET_TEMP_LOW] = temp_low
if 'upperSetpoint' in payload:
temp_high = temperature_from_object(
payload['upperSetpoint'], unit)
temp_high = temperature_from_object(hass, payload['upperSetpoint'])
if temp_high < min_temp or temp_high > max_temp:
return api_error_temp_range(
request, temp_high, min_temp, max_temp, unit)
hass, request, temp_high, min_temp, max_temp)
data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high

await hass.services.async_call(
Expand All @@ -1450,17 +1458,16 @@ async def async_api_set_target_temp(hass, config, request, context, entity):
@extract_entity
async def async_api_adjust_target_temp(hass, config, request, context, entity):
"""Process an adjust target temperature request."""
unit = entity.attributes[CONF_UNIT_OF_MEASUREMENT]
min_temp = entity.attributes.get(climate.ATTR_MIN_TEMP)
max_temp = entity.attributes.get(climate.ATTR_MAX_TEMP)

temp_delta = temperature_from_object(
request[API_PAYLOAD]['targetSetpointDelta'], unit, interval=True)
hass, request[API_PAYLOAD]['targetSetpointDelta'], interval=True)
target_temp = float(entity.attributes.get(ATTR_TEMPERATURE)) + temp_delta

if target_temp < min_temp or target_temp > max_temp:
return api_error_temp_range(
request, target_temp, min_temp, max_temp, unit)
hass, request, target_temp, min_temp, max_temp)

data = {
ATTR_ENTITY_ID: entity.entity_id,
Expand Down Expand Up @@ -1512,7 +1519,7 @@ async def async_api_set_thermostat_mode(hass, config, request, context,
@extract_entity
async def async_api_reportstate(hass, config, request, context, entity):
"""Process a ReportState request."""
alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
alexa_entity = ENTITY_ADAPTERS[entity.domain](hass, config, entity)
properties = []
for interface in alexa_entity.interfaces():
properties.extend(interface.serialize_properties())
Expand Down
7 changes: 1 addition & 6 deletions homeassistant/components/climate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ def state(self):
@property
def precision(self):
"""Return the precision of the system."""
if self.unit_of_measurement == TEMP_CELSIUS:
if self.hass.config.units.temperature_unit == TEMP_CELSIUS:
return PRECISION_TENTHS
return PRECISION_WHOLE

Expand Down Expand Up @@ -394,11 +394,6 @@ def state_attributes(self):

return data

@property
def unit_of_measurement(self):
"""Return the unit of measurement to display."""
return self.hass.config.units.temperature_unit

@property
def temperature_unit(self):
"""Return the unit of measurement used by the platform."""
Expand Down
10 changes: 3 additions & 7 deletions homeassistant/components/climate/generic_thermostat.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
ATTR_OPERATION_MODE, ATTR_AWAY_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
STATE_UNKNOWN)
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, ATTR_ENTITY_ID,
SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_UNKNOWN)
from homeassistant.helpers import condition
from homeassistant.helpers.event import (
async_track_state_change, async_track_time_interval)
Expand Down Expand Up @@ -297,11 +296,8 @@ def _async_switch_changed(self, entity_id, old_state, new_state):
@callback
def _async_update_temp(self, state):
"""Update thermostat with latest state from sensor."""
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)

try:
self._cur_temp = self.hass.config.units.temperature(
float(state.state), unit)
self._cur_temp = float(state.state)
except ValueError as ex:
_LOGGER.error("Unable to update from sensor: %s", ex)

Expand Down
4 changes: 1 addition & 3 deletions homeassistant/components/climate/knx.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,6 @@ def __init__(self, hass, device):
self.hass = hass
self.async_register_callbacks()

self._unit_of_measurement = TEMP_CELSIUS

@property
def supported_features(self):
"""Return the list of supported features."""
Expand Down Expand Up @@ -157,7 +155,7 @@ def should_poll(self):
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
return TEMP_CELSIUS

@property
def current_temperature(self):
Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/climate/maxcube.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ class MaxCubeClimate(ClimateDevice):
def __init__(self, handler, name, rf_address):
"""Initialize MAX! Cube ClimateDevice."""
self._name = name
self._unit_of_measurement = TEMP_CELSIUS
self._operation_list = [STATE_AUTO, STATE_MANUAL, STATE_BOOST,
STATE_VACATION]
self._rf_address = rf_address
Expand Down Expand Up @@ -81,7 +80,7 @@ def max_temp(self):
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
return TEMP_CELSIUS

@property
def current_temperature(self):
Expand Down
10 changes: 6 additions & 4 deletions homeassistant/components/climate/sensibo.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
for dev in (
yield from client.async_get_devices(_INITIAL_FETCH_FIELDS)):
if config[CONF_ID] == ALL or dev['id'] in config[CONF_ID]:
devices.append(SensiboClimate(client, dev))
devices.append(SensiboClimate(
client, dev, hass.config.units.temperature_unit))
except (aiohttp.client_exceptions.ClientConnectorError,
asyncio.TimeoutError):
_LOGGER.exception('Failed to connect to Sensibo servers.')
Expand Down Expand Up @@ -106,7 +107,7 @@ def async_assume_state(service):
class SensiboClimate(ClimateDevice):
"""Representation of a Sensibo device."""

def __init__(self, client, data):
def __init__(self, client, data, units):
"""Build SensiboClimate.
client: aiohttp session.
Expand All @@ -115,6 +116,7 @@ def __init__(self, client, data):
self._client = client
self._id = data['id']
self._external_state = None
self._units = units
self._do_update(data)

@property
Expand All @@ -139,7 +141,7 @@ def _do_update(self, data):
self._temperatures_list = self._current_capabilities[
'temperatures'].get(temperature_unit_key, {}).get('values', [])
else:
self._temperature_unit = self.unit_of_measurement
self._temperature_unit = self._units
self._temperatures_list = []
self._supported_features = 0
for key in self._ac_states:
Expand Down Expand Up @@ -175,7 +177,7 @@ def target_temperature(self):
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
if self.temperature_unit == self.unit_of_measurement:
if self.temperature_unit == self.hass.config.units.temperature_unit:
# We are working in same units as the a/c unit. Use whole degrees
# like the API supports.
return 1
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/google_assistant/smart_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def traits(self):
domain = state.domain
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)

return [Trait(state) for Trait in trait.TRAITS
return [Trait(self.hass, state) for Trait in trait.TRAITS
if Trait.supported(domain, features)]

@callback
Expand Down Expand Up @@ -159,7 +159,7 @@ async def execute(self, command, params):
executed = False
for trt in self.traits():
if trt.can_execute(command, params):
await trt.execute(self.hass, command, params)
await trt.execute(command, params)
executed = True
break

Expand Down
Loading

0 comments on commit ae63980

Please sign in to comment.