Skip to content

Commit

Permalink
Convert to int when precision below 1 (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
Limych committed Jun 15, 2021
1 parent fc628e4 commit b3a04c4
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 24 deletions.
57 changes: 33 additions & 24 deletions custom_components/average/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@
UPDATE_MIN_TIME,
)

try:
try: # pragma: no cover
# HA version >=2021.6
from homeassistant.components.recorder import history
from homeassistant.components.recorder.models import LazyState
except ImportError:
except ImportError: # pragma: no cover
# HA version <=2021.5
from homeassistant.components import history
from homeassistant.components.history import LazyState
Expand Down Expand Up @@ -344,8 +344,8 @@ def _update_period(self): # pylint: disable=r0912
now = dt_util.now()

# Parse start
_LOGGER.debug("Process start template: %s", self._start_template)
if self._start_template is not None:
_LOGGER.debug("Process start template: %s", self._start_template)
try:
start_rendered = self._start_template.render()
except (TemplateError, TypeError) as ex:
Expand All @@ -365,8 +365,8 @@ def _update_period(self): # pylint: disable=r0912
return

# Parse end
_LOGGER.debug("Process end template: %s", self._end_template)
if self._end_template is not None:
_LOGGER.debug("Process end template: %s", self._end_template)
try:
end_rendered = self._end_template.render()
except (TemplateError, TypeError) as ex:
Expand All @@ -386,19 +386,22 @@ def _update_period(self): # pylint: disable=r0912
return

# Calculate start or end using the duration
_LOGGER.debug("Process duration: %s", self._duration)
if self._duration is not None:
_LOGGER.debug("Process duration: %s", self._duration)
if start is None:
if end is None:
end = now
start = end - self._duration
else:
end = start + self._duration

_LOGGER.debug("Start: %s, End: %s", start, end)
_LOGGER.debug("Calculation period: start=%s, end=%s", start, end)
if start is None or end is None:
return

if start > end:
start, end = end, start

if start > now:
# History hasn't been written yet for this period
return
Expand All @@ -410,6 +413,27 @@ def _update_period(self): # pylint: disable=r0912
self.start = start.replace(microsecond=0).isoformat()
self.end = end.replace(microsecond=0).isoformat()

def _init_mode(self, state: LazyState):
"""Initialize sensor mode."""
if self._temperature_mode is not None:
return

domain = split_entity_id(state.entity_id)[0]
self._device_class = state.attributes.get(ATTR_DEVICE_CLASS)
self._unit_of_measurement = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
self._temperature_mode = (
self._device_class == DEVICE_CLASS_TEMPERATURE
or domain in (WEATHER_DOMAIN, CLIMATE_DOMAIN, WATER_HEATER_DOMAIN)
or self._unit_of_measurement in TEMPERATURE_UNITS
)
if self._temperature_mode:
_LOGGER.debug("%s is a temperature entity.", state.entity_id)
self._device_class = DEVICE_CLASS_TEMPERATURE
self._unit_of_measurement = self.hass.config.units.temperature_unit
else:
_LOGGER.debug("%s is NOT a temperature entity.", state.entity_id)
self._icon = state.attributes.get(ATTR_ICON)

def _update_state(self): # pylint: disable=r0914,r0912,r0915
"""Update the sensor state."""
_LOGGER.debug('Updating sensor "%s"', self.name)
Expand Down Expand Up @@ -460,24 +484,7 @@ def _update_state(self): # pylint: disable=r0914,r0912,r0915
_LOGGER.error('Unable to find an entity "%s"', entity_id)
continue

if self._temperature_mode is None:
domain = split_entity_id(state.entity_id)[0]
self._device_class = state.attributes.get(ATTR_DEVICE_CLASS)
self._unit_of_measurement = state.attributes.get(
ATTR_UNIT_OF_MEASUREMENT
)
self._temperature_mode = (
self._device_class == DEVICE_CLASS_TEMPERATURE
or domain in (WEATHER_DOMAIN, CLIMATE_DOMAIN, WATER_HEATER_DOMAIN)
or self._unit_of_measurement in TEMPERATURE_UNITS
)
if self._temperature_mode:
_LOGGER.debug("%s is a temperature entity.", entity_id)
self._device_class = DEVICE_CLASS_TEMPERATURE
self._unit_of_measurement = self.hass.config.units.temperature_unit
else:
_LOGGER.debug("%s is NOT a temperature entity.", entity_id)
self._icon = state.attributes.get(ATTR_ICON)
self._init_mode(state)

value = 0
elapsed = 0
Expand Down Expand Up @@ -540,6 +547,8 @@ def _update_state(self): # pylint: disable=r0914,r0912,r0915

if values:
self._state = round(sum(values) / len(values), self._precision)
if self._precision < 1:
self._state = int(self._state)
else:
self._state = None
_LOGGER.debug("Total average state: %s", self._state)
132 changes: 132 additions & 0 deletions tests/test_sensor.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
"""The test for the average sensor platform."""
# pylint: disable=redefined-outer-name
import json
import logging
from asyncio import sleep
from datetime import timedelta
from unittest.mock import MagicMock, patch

import homeassistant.util.dt as dt_util
import pytest
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR
from homeassistant.components.water_heater import DOMAIN as WATER_HEATER_DOMAIN
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ICON,
ATTR_UNIT_OF_MEASUREMENT,
CONF_ENTITIES,
CONF_NAME,
CONF_PLATFORM,
DEVICE_CLASS_TEMPERATURE,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
TEMP_FAHRENHEIT,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.template import Template
from homeassistant.setup import async_setup_component
from homeassistant.util.unit_system import TEMPERATURE_UNITS
from pytest import raises
from pytest_homeassistant_custom_component.common import assert_setup_component
from voluptuous import Invalid
Expand Down Expand Up @@ -355,6 +363,130 @@ async def test__get_state_value(default_sensor):
assert default_sensor.max_value == 34


async def test__init_mode(hass: HomeAssistant, default_sensor, caplog):
"""Test sensor mode initialization."""
caplog.set_level(logging.DEBUG)

assert default_sensor._temperature_mode is None
assert default_sensor._device_class is None
assert default_sensor._unit_of_measurement is None
assert default_sensor._icon is None

# Detect by device class
state = LazyState(
Objectview(
{
"entity_id": "sensor.test",
"state": None,
"attributes": json.dumps(
{
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
}
),
}
)
)

caplog.clear()
default_sensor._temperature_mode = None
default_sensor._device_class = None
default_sensor._unit_of_measurement = None

default_sensor._init_mode(state)

assert default_sensor._temperature_mode is True
assert default_sensor._device_class is DEVICE_CLASS_TEMPERATURE
assert default_sensor._unit_of_measurement is hass.config.units.temperature_unit
assert len(caplog.records) == 1

# Detect by measuring unit
for unit in TEMPERATURE_UNITS:
state = LazyState(
Objectview(
{
"entity_id": "sensor.test",
"state": None,
"attributes": json.dumps(
{
ATTR_UNIT_OF_MEASUREMENT: unit,
}
),
}
)
)

caplog.clear()
default_sensor._temperature_mode = None
default_sensor._device_class = None
default_sensor._unit_of_measurement = None

default_sensor._init_mode(state)

assert default_sensor._temperature_mode is True
assert default_sensor._device_class is DEVICE_CLASS_TEMPERATURE
assert default_sensor._unit_of_measurement == hass.config.units.temperature_unit
assert len(caplog.records) == 1

# Detect by domain
for domain in (WEATHER_DOMAIN, CLIMATE_DOMAIN, WATER_HEATER_DOMAIN):
state = LazyState(
Objectview(
{
"entity_id": f"{domain}.test",
"state": None,
"attributes": json.dumps({}),
}
)
)

caplog.clear()
default_sensor._temperature_mode = None
default_sensor._device_class = None
default_sensor._unit_of_measurement = None

default_sensor._init_mode(state)

assert default_sensor._temperature_mode is True
assert default_sensor._device_class is DEVICE_CLASS_TEMPERATURE
assert default_sensor._unit_of_measurement == hass.config.units.temperature_unit
assert len(caplog.records) == 1

# Can't detect
state = LazyState(
Objectview(
{
"entity_id": "sensor.test",
"state": None,
"attributes": json.dumps(
{
ATTR_ICON: "some_icon",
}
),
}
)
)

caplog.clear()
default_sensor._temperature_mode = None
default_sensor._device_class = None
default_sensor._unit_of_measurement = None

default_sensor._init_mode(state)

assert default_sensor._temperature_mode is False
assert default_sensor._device_class is None
assert default_sensor._unit_of_measurement is None
assert default_sensor._icon == "some_icon"
assert len(caplog.records) == 1

# Skip if mode already detected
caplog.clear()

default_sensor._init_mode(state)

assert len(caplog.records) == 0


async def test_update(default_sensor):
"""Test update throttler."""
with patch.object(default_sensor, "_update_state") as ups:
Expand Down

0 comments on commit b3a04c4

Please sign in to comment.