Add Compensation Integration (#41675)
* Add Compensation Integration Adds the Compensation Integration * Add Requirements add missing requirements to compensation integration * Fix for tests Fix files after tests * Fix isort ran isort * Handle ADR-0007 Change the configuration to deal with ADR-0007 * fix flake8 Fix flake8 * Added Error Trapping Catch errors. Raise Rank Warnings but continue. Fixed bad imports * fix flake8 & pylint * fix isort.... again * fix tests & comments fix tests and comments * fix flake8 * remove discovery message * Fixed Review changes * Fixed review requests. * Added test to test get more coverage. * Roll back numpy requirement Roll back numpy requirement to match other integrations. * Fix flake8 * Fix requested changes Removed some necessary comments. Changed a test case to be more readable. * Fix doc strings and continue * Fixed a few test case doc strings * Removed a continue/else * Remove periods from logger Removed periods from _LOGGER errors. * Fixes changed name to unqiue_id. implemented suggested changes. * Add name and fix unique_id * removed conf name and auto construct it
This commit is contained in:
parent
23fae255ff
commit
545fe7a7be
9 changed files with 537 additions and 0 deletions
|
@ -89,6 +89,7 @@ homeassistant/components/cloud/* @home-assistant/cloud
|
||||||
homeassistant/components/cloudflare/* @ludeeus @ctalkington
|
homeassistant/components/cloudflare/* @ludeeus @ctalkington
|
||||||
homeassistant/components/color_extractor/* @GenericStudent
|
homeassistant/components/color_extractor/* @GenericStudent
|
||||||
homeassistant/components/comfoconnect/* @michaelarnauts
|
homeassistant/components/comfoconnect/* @michaelarnauts
|
||||||
|
homeassistant/components/compensation/* @Petro31
|
||||||
homeassistant/components/config/* @home-assistant/core
|
homeassistant/components/config/* @home-assistant/core
|
||||||
homeassistant/components/configurator/* @home-assistant/core
|
homeassistant/components/configurator/* @home-assistant/core
|
||||||
homeassistant/components/control4/* @lawtancool
|
homeassistant/components/control4/* @lawtancool
|
||||||
|
|
120
homeassistant/components/compensation/__init__.py
Normal file
120
homeassistant/components/compensation/__init__.py
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
"""The Compensation integration."""
|
||||||
|
import logging
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_ATTRIBUTE,
|
||||||
|
CONF_SOURCE,
|
||||||
|
CONF_UNIQUE_ID,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.discovery import async_load_platform
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_COMPENSATION,
|
||||||
|
CONF_DATAPOINTS,
|
||||||
|
CONF_DEGREE,
|
||||||
|
CONF_POLYNOMIAL,
|
||||||
|
CONF_PRECISION,
|
||||||
|
DATA_COMPENSATION,
|
||||||
|
DEFAULT_DEGREE,
|
||||||
|
DEFAULT_PRECISION,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def datapoints_greater_than_degree(value: dict) -> dict:
|
||||||
|
"""Validate data point list is greater than polynomial degrees."""
|
||||||
|
if len(value[CONF_DATAPOINTS]) <= value[CONF_DEGREE]:
|
||||||
|
raise vol.Invalid(
|
||||||
|
f"{CONF_DATAPOINTS} must have at least {value[CONF_DEGREE]+1} {CONF_DATAPOINTS}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
COMPENSATION_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_SOURCE): cv.entity_id,
|
||||||
|
vol.Required(CONF_DATAPOINTS): [
|
||||||
|
vol.ExactSequence([vol.Coerce(float), vol.Coerce(float)])
|
||||||
|
],
|
||||||
|
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||||
|
vol.Optional(CONF_ATTRIBUTE): cv.string,
|
||||||
|
vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): cv.positive_int,
|
||||||
|
vol.Optional(CONF_DEGREE, default=DEFAULT_DEGREE): vol.All(
|
||||||
|
vol.Coerce(int),
|
||||||
|
vol.Range(min=1, max=7),
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
DOMAIN: vol.Schema(
|
||||||
|
{cv.slug: vol.All(COMPENSATION_SCHEMA, datapoints_greater_than_degree)}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
extra=vol.ALLOW_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, config):
|
||||||
|
"""Set up the Compensation sensor."""
|
||||||
|
hass.data[DATA_COMPENSATION] = {}
|
||||||
|
|
||||||
|
for compensation, conf in config.get(DOMAIN).items():
|
||||||
|
_LOGGER.debug("Setup %s.%s", DOMAIN, compensation)
|
||||||
|
|
||||||
|
degree = conf[CONF_DEGREE]
|
||||||
|
|
||||||
|
# get x values and y values from the x,y point pairs
|
||||||
|
x_values, y_values = zip(*conf[CONF_DATAPOINTS])
|
||||||
|
|
||||||
|
# try to get valid coefficients for a polynomial
|
||||||
|
coefficients = None
|
||||||
|
with np.errstate(all="raise"):
|
||||||
|
with warnings.catch_warnings(record=True) as all_warnings:
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
try:
|
||||||
|
coefficients = np.polyfit(x_values, y_values, degree)
|
||||||
|
except FloatingPointError as error:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Setup of %s encountered an error, %s",
|
||||||
|
compensation,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
for warning in all_warnings:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Setup of %s encountered a warning, %s",
|
||||||
|
compensation,
|
||||||
|
str(warning.message).lower(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if coefficients is not None:
|
||||||
|
data = {
|
||||||
|
k: v for k, v in conf.items() if k not in [CONF_DEGREE, CONF_DATAPOINTS]
|
||||||
|
}
|
||||||
|
data[CONF_POLYNOMIAL] = np.poly1d(coefficients)
|
||||||
|
|
||||||
|
hass.data[DATA_COMPENSATION][compensation] = data
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
async_load_platform(
|
||||||
|
hass,
|
||||||
|
SENSOR_DOMAIN,
|
||||||
|
DOMAIN,
|
||||||
|
{CONF_COMPENSATION: compensation},
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
16
homeassistant/components/compensation/const.py
Normal file
16
homeassistant/components/compensation/const.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""Compensation constants."""
|
||||||
|
DOMAIN = "compensation"
|
||||||
|
|
||||||
|
SENSOR = "compensation"
|
||||||
|
|
||||||
|
CONF_COMPENSATION = "compensation"
|
||||||
|
CONF_DATAPOINTS = "data_points"
|
||||||
|
CONF_DEGREE = "degree"
|
||||||
|
CONF_PRECISION = "precision"
|
||||||
|
CONF_POLYNOMIAL = "polynomial"
|
||||||
|
|
||||||
|
DATA_COMPENSATION = "compensation_data"
|
||||||
|
|
||||||
|
DEFAULT_DEGREE = 1
|
||||||
|
DEFAULT_NAME = "Compensation"
|
||||||
|
DEFAULT_PRECISION = 2
|
7
homeassistant/components/compensation/manifest.json
Normal file
7
homeassistant/components/compensation/manifest.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"domain": "compensation",
|
||||||
|
"name": "Compensation",
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/compensation",
|
||||||
|
"requirements": ["numpy==1.20.2"],
|
||||||
|
"codeowners": ["@Petro31"]
|
||||||
|
}
|
162
homeassistant/components/compensation/sensor.py
Normal file
162
homeassistant/components/compensation/sensor.py
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
"""Support for compensation sensor."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorEntity
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
CONF_ATTRIBUTE,
|
||||||
|
CONF_SOURCE,
|
||||||
|
CONF_UNIQUE_ID,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
)
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.event import async_track_state_change_event
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_COMPENSATION,
|
||||||
|
CONF_POLYNOMIAL,
|
||||||
|
CONF_PRECISION,
|
||||||
|
DATA_COMPENSATION,
|
||||||
|
DEFAULT_NAME,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ATTR_COEFFICIENTS = "coefficients"
|
||||||
|
ATTR_SOURCE = "source"
|
||||||
|
ATTR_SOURCE_ATTRIBUTE = "source_attribute"
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
|
"""Set up the Compensation sensor."""
|
||||||
|
if discovery_info is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
compensation = discovery_info[CONF_COMPENSATION]
|
||||||
|
conf = hass.data[DATA_COMPENSATION][compensation]
|
||||||
|
|
||||||
|
source = conf[CONF_SOURCE]
|
||||||
|
attribute = conf.get(CONF_ATTRIBUTE)
|
||||||
|
name = f"{DEFAULT_NAME} {source}"
|
||||||
|
if attribute is not None:
|
||||||
|
name = f"{name} {attribute}"
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
[
|
||||||
|
CompensationSensor(
|
||||||
|
conf.get(CONF_UNIQUE_ID),
|
||||||
|
name,
|
||||||
|
source,
|
||||||
|
attribute,
|
||||||
|
conf[CONF_PRECISION],
|
||||||
|
conf[CONF_POLYNOMIAL],
|
||||||
|
conf.get(CONF_UNIT_OF_MEASUREMENT),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CompensationSensor(SensorEntity):
|
||||||
|
"""Representation of a Compensation sensor."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
unique_id,
|
||||||
|
name,
|
||||||
|
source,
|
||||||
|
attribute,
|
||||||
|
precision,
|
||||||
|
polynomial,
|
||||||
|
unit_of_measurement,
|
||||||
|
):
|
||||||
|
"""Initialize the Compensation sensor."""
|
||||||
|
self._source_entity_id = source
|
||||||
|
self._precision = precision
|
||||||
|
self._source_attribute = attribute
|
||||||
|
self._unit_of_measurement = unit_of_measurement
|
||||||
|
self._poly = polynomial
|
||||||
|
self._coefficients = polynomial.coefficients.tolist()
|
||||||
|
self._state = None
|
||||||
|
self._unique_id = unique_id
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Handle added to Hass."""
|
||||||
|
self.async_on_remove(
|
||||||
|
async_track_state_change_event(
|
||||||
|
self.hass,
|
||||||
|
[self._source_entity_id],
|
||||||
|
self._async_compensation_sensor_state_listener,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return the unique id of this sensor."""
|
||||||
|
return self._unique_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the sensor."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""No polling needed."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self):
|
||||||
|
"""Return the state attributes of the sensor."""
|
||||||
|
ret = {
|
||||||
|
ATTR_SOURCE: self._source_entity_id,
|
||||||
|
ATTR_COEFFICIENTS: self._coefficients,
|
||||||
|
}
|
||||||
|
if self._source_attribute:
|
||||||
|
ret[ATTR_SOURCE_ATTRIBUTE] = self._source_attribute
|
||||||
|
return ret
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the unit the value is expressed in."""
|
||||||
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_compensation_sensor_state_listener(self, event):
|
||||||
|
"""Handle sensor state changes."""
|
||||||
|
new_state = event.data.get("new_state")
|
||||||
|
if new_state is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._unit_of_measurement is None and self._source_attribute is None:
|
||||||
|
self._unit_of_measurement = new_state.attributes.get(
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self._source_attribute:
|
||||||
|
value = float(new_state.attributes.get(self._source_attribute))
|
||||||
|
else:
|
||||||
|
value = (
|
||||||
|
None if new_state.state == STATE_UNKNOWN else float(new_state.state)
|
||||||
|
)
|
||||||
|
self._state = round(self._poly(value), self._precision)
|
||||||
|
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
self._state = None
|
||||||
|
if self._source_attribute:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"%s attribute %s is not numerical",
|
||||||
|
self._source_entity_id,
|
||||||
|
self._source_attribute,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_LOGGER.warning("%s state is not numerical", self._source_entity_id)
|
||||||
|
|
||||||
|
self.async_write_ha_state()
|
|
@ -1011,6 +1011,7 @@ nuheat==0.3.0
|
||||||
# homeassistant.components.numato
|
# homeassistant.components.numato
|
||||||
numato-gpio==0.10.0
|
numato-gpio==0.10.0
|
||||||
|
|
||||||
|
# homeassistant.components.compensation
|
||||||
# homeassistant.components.iqvia
|
# homeassistant.components.iqvia
|
||||||
# homeassistant.components.opencv
|
# homeassistant.components.opencv
|
||||||
# homeassistant.components.tensorflow
|
# homeassistant.components.tensorflow
|
||||||
|
|
|
@ -529,6 +529,7 @@ nuheat==0.3.0
|
||||||
# homeassistant.components.numato
|
# homeassistant.components.numato
|
||||||
numato-gpio==0.10.0
|
numato-gpio==0.10.0
|
||||||
|
|
||||||
|
# homeassistant.components.compensation
|
||||||
# homeassistant.components.iqvia
|
# homeassistant.components.iqvia
|
||||||
# homeassistant.components.opencv
|
# homeassistant.components.opencv
|
||||||
# homeassistant.components.tensorflow
|
# homeassistant.components.tensorflow
|
||||||
|
|
1
tests/components/compensation/__init__.py
Normal file
1
tests/components/compensation/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Tests for the compensation component."""
|
228
tests/components/compensation/test_sensor.py
Normal file
228
tests/components/compensation/test_sensor.py
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
"""The tests for the integration sensor platform."""
|
||||||
|
|
||||||
|
from homeassistant.components.compensation.const import CONF_PRECISION, DOMAIN
|
||||||
|
from homeassistant.components.compensation.sensor import ATTR_COEFFICIENTS
|
||||||
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
EVENT_HOMEASSISTANT_START,
|
||||||
|
EVENT_STATE_CHANGED,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
)
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
|
||||||
|
async def test_linear_state(hass):
|
||||||
|
"""Test compensation sensor state."""
|
||||||
|
config = {
|
||||||
|
"compensation": {
|
||||||
|
"test": {
|
||||||
|
"source": "sensor.uncompensated",
|
||||||
|
"data_points": [
|
||||||
|
[1.0, 2.0],
|
||||||
|
[2.0, 3.0],
|
||||||
|
],
|
||||||
|
"precision": 2,
|
||||||
|
"unit_of_measurement": "a",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expected_entity_id = "sensor.compensation_sensor_uncompensated"
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, config)
|
||||||
|
assert await async_setup_component(hass, SENSOR_DOMAIN, config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||||
|
entity_id = config[DOMAIN]["test"]["source"]
|
||||||
|
hass.states.async_set(entity_id, 4, {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(expected_entity_id)
|
||||||
|
assert state is not None
|
||||||
|
|
||||||
|
assert round(float(state.state), config[DOMAIN]["test"][CONF_PRECISION]) == 5.0
|
||||||
|
|
||||||
|
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "a"
|
||||||
|
|
||||||
|
coefs = [round(v, 1) for v in state.attributes.get(ATTR_COEFFICIENTS)]
|
||||||
|
assert coefs == [1.0, 1.0]
|
||||||
|
|
||||||
|
hass.states.async_set(entity_id, "foo", {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(expected_entity_id)
|
||||||
|
assert state is not None
|
||||||
|
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_linear_state_from_attribute(hass):
|
||||||
|
"""Test compensation sensor state that pulls from attribute."""
|
||||||
|
config = {
|
||||||
|
"compensation": {
|
||||||
|
"test": {
|
||||||
|
"source": "sensor.uncompensated",
|
||||||
|
"attribute": "value",
|
||||||
|
"data_points": [
|
||||||
|
[1.0, 2.0],
|
||||||
|
[2.0, 3.0],
|
||||||
|
],
|
||||||
|
"precision": 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expected_entity_id = "sensor.compensation_sensor_uncompensated_value"
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, config)
|
||||||
|
assert await async_setup_component(hass, SENSOR_DOMAIN, config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||||
|
|
||||||
|
entity_id = config[DOMAIN]["test"]["source"]
|
||||||
|
hass.states.async_set(entity_id, 3, {"value": 4})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(expected_entity_id)
|
||||||
|
assert state is not None
|
||||||
|
|
||||||
|
assert round(float(state.state), config[DOMAIN]["test"][CONF_PRECISION]) == 5.0
|
||||||
|
|
||||||
|
coefs = [round(v, 1) for v in state.attributes.get(ATTR_COEFFICIENTS)]
|
||||||
|
assert coefs == [1.0, 1.0]
|
||||||
|
|
||||||
|
hass.states.async_set(entity_id, 3, {"value": "bar"})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(expected_entity_id)
|
||||||
|
assert state is not None
|
||||||
|
|
||||||
|
assert state.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_quadratic_state(hass):
|
||||||
|
"""Test 3 degree polynominial compensation sensor."""
|
||||||
|
config = {
|
||||||
|
"compensation": {
|
||||||
|
"test": {
|
||||||
|
"source": "sensor.temperature",
|
||||||
|
"data_points": [
|
||||||
|
[50, 3.3],
|
||||||
|
[50, 2.8],
|
||||||
|
[50, 2.9],
|
||||||
|
[70, 2.3],
|
||||||
|
[70, 2.6],
|
||||||
|
[70, 2.1],
|
||||||
|
[80, 2.5],
|
||||||
|
[80, 2.9],
|
||||||
|
[80, 2.4],
|
||||||
|
[90, 3.0],
|
||||||
|
[90, 3.1],
|
||||||
|
[90, 2.8],
|
||||||
|
[100, 3.3],
|
||||||
|
[100, 3.5],
|
||||||
|
[100, 3.0],
|
||||||
|
],
|
||||||
|
"degree": 2,
|
||||||
|
"precision": 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert await async_setup_component(hass, DOMAIN, config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_id = config[DOMAIN]["test"]["source"]
|
||||||
|
hass.states.async_set(entity_id, 43.2, {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.compensation_sensor_temperature")
|
||||||
|
|
||||||
|
assert state is not None
|
||||||
|
|
||||||
|
assert round(float(state.state), config[DOMAIN]["test"][CONF_PRECISION]) == 3.327
|
||||||
|
|
||||||
|
|
||||||
|
async def test_numpy_errors(hass, caplog):
|
||||||
|
"""Tests bad polyfits."""
|
||||||
|
config = {
|
||||||
|
"compensation": {
|
||||||
|
"test": {
|
||||||
|
"source": "sensor.uncompensated",
|
||||||
|
"data_points": [
|
||||||
|
[1.0, 1.0],
|
||||||
|
[1.0, 1.0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"test2": {
|
||||||
|
"source": "sensor.uncompensated2",
|
||||||
|
"data_points": [
|
||||||
|
[0.0, 1.0],
|
||||||
|
[0.0, 1.0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await async_setup_component(hass, DOMAIN, config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert "polyfit may be poorly conditioned" in caplog.text
|
||||||
|
|
||||||
|
assert "invalid value encountered in true_divide" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_datapoints_greater_than_degree(hass, caplog):
|
||||||
|
"""Tests 3 bad data points."""
|
||||||
|
config = {
|
||||||
|
"compensation": {
|
||||||
|
"test": {
|
||||||
|
"source": "sensor.uncompensated",
|
||||||
|
"data_points": [
|
||||||
|
[1.0, 2.0],
|
||||||
|
[2.0, 3.0],
|
||||||
|
],
|
||||||
|
"degree": 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await async_setup_component(hass, DOMAIN, config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert "data_points must have at least 3 data_points" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_new_state_is_none(hass):
|
||||||
|
"""Tests catch for empty new states."""
|
||||||
|
config = {
|
||||||
|
"compensation": {
|
||||||
|
"test": {
|
||||||
|
"source": "sensor.uncompensated",
|
||||||
|
"data_points": [
|
||||||
|
[1.0, 2.0],
|
||||||
|
[2.0, 3.0],
|
||||||
|
],
|
||||||
|
"precision": 2,
|
||||||
|
"unit_of_measurement": "a",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expected_entity_id = "sensor.compensation_sensor_uncompensated"
|
||||||
|
|
||||||
|
await async_setup_component(hass, DOMAIN, config)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
await hass.async_start()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
last_changed = hass.states.get(expected_entity_id).last_changed
|
||||||
|
|
||||||
|
hass.bus.async_fire(
|
||||||
|
EVENT_STATE_CHANGED, event_data={"entity_id": "sensor.uncompensated"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert last_changed == hass.states.get(expected_entity_id).last_changed
|
Loading…
Add table
Add a link
Reference in a new issue