Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions homeassistant/components/sensor/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
CONF_UNIT_PREFIX = 'unit_prefix'
CONF_UNIT_TIME = 'unit_time'
CONF_UNIT_OF_MEASUREMENT = 'unit'
CONF_METHOD = 'method'

TRAPEZOIDAL_METHOD = 'trapezoidal'
LEFT_METHOD = 'left'
RIGHT_METHOD = 'right'
INTEGRATION_METHOD = [TRAPEZOIDAL_METHOD, LEFT_METHOD, RIGHT_METHOD]

# SI Metric prefixes
UNIT_PREFIXES = {None: 1,
Expand All @@ -49,7 +55,9 @@
vol.Optional(CONF_ROUND_DIGITS, default=DEFAULT_ROUND): vol.Coerce(int),
vol.Optional(CONF_UNIT_PREFIX, default=None): vol.In(UNIT_PREFIXES),
vol.Optional(CONF_UNIT_TIME, default='h'): vol.In(UNIT_TIME),
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_METHOD, default=TRAPEZOIDAL_METHOD):
vol.In(INTEGRATION_METHOD),
})


Expand All @@ -61,7 +69,8 @@ async def async_setup_platform(hass, config, async_add_entities,
config[CONF_ROUND_DIGITS],
config[CONF_UNIT_PREFIX],
config[CONF_UNIT_TIME],
config.get(CONF_UNIT_OF_MEASUREMENT))
config.get(CONF_UNIT_OF_MEASUREMENT),
config[CONF_METHOD])

async_add_entities([integral])

Expand All @@ -70,11 +79,12 @@ class IntegrationSensor(RestoreEntity):
"""Representation of an integration sensor."""

def __init__(self, source_entity, name, round_digits, unit_prefix,
unit_time, unit_of_measurement):
unit_time, unit_of_measurement, integration_method):
"""Initialize the integration sensor."""
self._sensor_source_id = source_entity
self._round_digits = round_digits
self._state = 0
self._method = integration_method

self._name = name if name is not None\
else '{} integral'.format(source_entity)
Expand Down Expand Up @@ -117,12 +127,19 @@ def calc_integration(entity, old_state, new_state):

try:
# integration as the Riemann integral of previous measures.
area = 0
elapsed_time = (new_state.last_updated
- old_state.last_updated).total_seconds()
area = (Decimal(new_state.state)
+ Decimal(old_state.state))*Decimal(elapsed_time)/2
integral = area / (self._unit_prefix * self._unit_time)

if self._method == TRAPEZOIDAL_METHOD:
area = (Decimal(new_state.state)
+ Decimal(old_state.state))*Decimal(elapsed_time)/2
elif self._method == LEFT_METHOD:
area = Decimal(old_state.state)*Decimal(elapsed_time)
elif self._method == RIGHT_METHOD:
area = Decimal(new_state.state)*Decimal(elapsed_time)

integral = area / (self._unit_prefix * self._unit_time)
assert isinstance(integral, Decimal)
except ValueError as err:
_LOGGER.warning("While calculating integration: %s", err)
Expand Down
104 changes: 104 additions & 0 deletions tests/components/sensor/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,110 @@ async def test_state(hass):
assert state.attributes.get('unit_of_measurement') == 'kWh'


async def test_trapezoidal(hass):
"""Test integration sensor state."""
config = {
'sensor': {
'platform': 'integration',
'name': 'integration',
'source': 'sensor.power',
'unit': 'kWh',
'round': 2,
}
}

assert await async_setup_component(hass, 'sensor', config)

entity_id = config['sensor']['source']
hass.states.async_set(entity_id, 0, {})
await hass.async_block_till_done()

# Testing a power sensor with non-monotonic intervals and values
for time, value in [(20, 10), (30, 30), (40, 5), (50, 0)]:
now = dt_util.utcnow() + timedelta(minutes=time)
with patch('homeassistant.util.dt.utcnow',
return_value=now):
hass.states.async_set(entity_id, value, {}, force_update=True)
await hass.async_block_till_done()

state = hass.states.get('sensor.integration')
assert state is not None

assert round(float(state.state), config['sensor']['round']) == 8.33

assert state.attributes.get('unit_of_measurement') == 'kWh'


async def test_left(hass):
"""Test integration sensor state with left reimann method."""
config = {
'sensor': {
'platform': 'integration',
'name': 'integration',
'method': 'left',
'source': 'sensor.power',
'unit': 'kWh',
'round': 2,
}
}

assert await async_setup_component(hass, 'sensor', config)

entity_id = config['sensor']['source']
hass.states.async_set(entity_id, 0, {})
await hass.async_block_till_done()

# Testing a power sensor with non-monotonic intervals and values
for time, value in [(20, 10), (30, 30), (40, 5), (50, 0)]:
now = dt_util.utcnow() + timedelta(minutes=time)
with patch('homeassistant.util.dt.utcnow',
return_value=now):
hass.states.async_set(entity_id, value, {}, force_update=True)
await hass.async_block_till_done()

state = hass.states.get('sensor.integration')
assert state is not None

assert round(float(state.state), config['sensor']['round']) == 7.5

assert state.attributes.get('unit_of_measurement') == 'kWh'


async def test_right(hass):
"""Test integration sensor state with left reimann method."""
config = {
'sensor': {
'platform': 'integration',
'name': 'integration',
'method': 'right',
'source': 'sensor.power',
'unit': 'kWh',
'round': 2,
}
}

assert await async_setup_component(hass, 'sensor', config)

entity_id = config['sensor']['source']
hass.states.async_set(entity_id, 0, {})
await hass.async_block_till_done()

# Testing a power sensor with non-monotonic intervals and values
for time, value in [(20, 10), (30, 30), (40, 5), (50, 0)]:
now = dt_util.utcnow() + timedelta(minutes=time)
with patch('homeassistant.util.dt.utcnow',
return_value=now):
hass.states.async_set(entity_id, value, {}, force_update=True)
await hass.async_block_till_done()

state = hass.states.get('sensor.integration')
assert state is not None

assert round(float(state.state), config['sensor']['round']) == 9.17

assert state.attributes.get('unit_of_measurement') == 'kWh'


async def test_prefix(hass):
"""Test integration sensor state using a power source."""
config = {
Expand Down