Dsmr hourly gas usage. (#4609)

* Hourly rate of Gas consumption. Use proper unknown state.

* Import unknown state constant.

* doh

* Cleanup device add.

* Fix lint.

* Add test for derivative calculation.

* Remove conflict.

* Document and move calculation into update call.
pull/4708/head
Johan Bloemberg 2016-12-04 05:45:42 +01:00 committed by Paulus Schoutsen
parent a099430834
commit 776e53a7f0
2 changed files with 117 additions and 20 deletions

View File

@ -26,15 +26,15 @@ stores/caches the latest telegram and notifies the Entities that the telegram
has been updated.
"""
import asyncio
import logging
from datetime import timedelta
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_PORT, EVENT_HOMEASSISTANT_STOP
from homeassistant.const import (
CONF_PORT, EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
import voluptuous as vol
_LOGGER = logging.getLogger(__name__)
@ -65,30 +65,37 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
# Suppress logging
logging.getLogger('dsmr_parser').setLevel(logging.ERROR)
from dsmr_parser import obis_references as obis
from dsmr_parser import obis_references as obis_ref
from dsmr_parser.protocol import create_dsmr_reader
dsmr_version = config[CONF_DSMR_VERSION]
# Define list of name,obis mappings to generate entities
obis_mapping = [
['Power Consumption', obis.CURRENT_ELECTRICITY_USAGE],
['Power Production', obis.CURRENT_ELECTRICITY_DELIVERY],
['Power Tariff', obis.ELECTRICITY_ACTIVE_TARIFF],
['Power Consumption (low)', obis.ELECTRICITY_USED_TARIFF_1],
['Power Consumption (normal)', obis.ELECTRICITY_USED_TARIFF_2],
['Power Production (low)', obis.ELECTRICITY_DELIVERED_TARIFF_1],
['Power Production (normal)', obis.ELECTRICITY_DELIVERED_TARIFF_2],
['Power Consumption', obis_ref.CURRENT_ELECTRICITY_USAGE],
['Power Production', obis_ref.CURRENT_ELECTRICITY_DELIVERY],
['Power Tariff', obis_ref.ELECTRICITY_ACTIVE_TARIFF],
['Power Consumption (low)', obis_ref.ELECTRICITY_USED_TARIFF_1],
['Power Consumption (normal)', obis_ref.ELECTRICITY_USED_TARIFF_2],
['Power Production (low)', obis_ref.ELECTRICITY_DELIVERED_TARIFF_1],
['Power Production (normal)', obis_ref.ELECTRICITY_DELIVERED_TARIFF_2],
]
# Protocol version specific obis
if dsmr_version == '4':
obis_mapping.append(['Gas Consumption', obis.HOURLY_GAS_METER_READING])
else:
obis_mapping.append(['Gas Consumption', obis.GAS_METER_READING])
# Generate device entities
devices = [DSMREntity(name, obis) for name, obis in obis_mapping]
# Protocol version specific obis
if dsmr_version == '4':
gas_obis = obis_ref.HOURLY_GAS_METER_READING
else:
gas_obis = obis_ref.GAS_METER_READING
# add gas meter reading and derivative for usage
devices += [
DSMREntity('Gas Consumption', gas_obis),
DerivativeDSMREntity('Hourly Gas Consumption', gas_obis),
]
yield from async_add_devices(devices)
def update_entities_telegram(telegram):
@ -151,7 +158,10 @@ class DSMREntity(Entity):
if self._obis == obis.ELECTRICITY_ACTIVE_TARIFF:
return self.translate_tariff(value)
else:
return value
if value:
return value
else:
return STATE_UNKNOWN
@property
def unit_of_measurement(self):
@ -168,4 +178,55 @@ class DSMREntity(Entity):
elif value == '0001':
return 'low'
else:
return None
return STATE_UNKNOWN
class DerivativeDSMREntity(DSMREntity):
"""Calculated derivative for values where the DSMR doesn't offer one.
Gas readings are only reported per hour and don't offer a rate only
the current meter reading. This entity converts subsequents readings
into a hourly rate.
"""
_previous_reading = None
_previous_timestamp = None
_state = STATE_UNKNOWN
@property
def state(self):
"""Return the calculated current hourly rate."""
return self._state
@asyncio.coroutine
def async_update(self):
"""Recalculate hourly rate if timestamp has changed.
DSMR updates gas meter reading every hour. Along with the
new value a timestamp is provided for the reading. Test
if the last known timestamp differs from the current one
then calculate a new rate for the previous hour.
"""
# check if the timestamp for the object differs from the previous one
timestamp = self.get_dsmr_object_attr('datetime')
if timestamp and timestamp != self._previous_timestamp:
current_reading = self.get_dsmr_object_attr('value')
if self._previous_reading is None:
# can't calculate rate without previous datapoint
# just store current point
pass
else:
# recalculate the rate
diff = current_reading - self._previous_reading
self._state = diff
self._previous_reading = current_reading
self._previous_timestamp = timestamp
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, per hour, if any."""
unit = self.get_dsmr_object_attr('unit')
if unit:
return unit + '/h'

View File

@ -9,6 +9,8 @@ from decimal import Decimal
from unittest.mock import Mock
from homeassistant.bootstrap import async_setup_component
from homeassistant.components.sensor.dsmr import DerivativeDSMREntity
from homeassistant.const import STATE_UNKNOWN
from tests.common import assert_setup_component
@ -62,3 +64,37 @@ def test_default_setup(hass, monkeypatch):
power_tariff = hass.states.get('sensor.power_tariff')
assert power_tariff.state == 'low'
assert power_tariff.attributes.get('unit_of_measurement') is None
def test_derivative():
"""Test calculation of derivative value."""
from dsmr_parser.objects import MBusObject
entity = DerivativeDSMREntity('test', '1.0.0')
yield from entity.async_update()
assert entity.state == STATE_UNKNOWN, 'initial state not unknown'
entity.telegram = {
'1.0.0': MBusObject([
{'value': 1},
{'value': 1, 'unit': 'm3'},
])
}
yield from entity.async_update()
assert entity.state == STATE_UNKNOWN, \
'state after first update shoudl still be unknown'
entity.telegram = {
'1.0.0': MBusObject([
{'value': 2},
{'value': 2, 'unit': 'm3'},
])
}
yield from entity.async_update()
assert entity.state == 1, \
'state should be difference between first and second update'
assert entity.unit_of_measurement == 'm3/h'