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
parent
a099430834
commit
776e53a7f0
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue