Add MIN_TIME_BETWEEN_UPDATES to dsmr integration (#43057)

pull/43089/head
Rob Bierbooms 2020-11-11 09:21:07 +01:00 committed by GitHub
parent 11a9aa3956
commit 930866bad5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 145 additions and 9 deletions

View File

@ -5,7 +5,7 @@ from asyncio import CancelledError
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DATA_TASK, DOMAIN, PLATFORMS
from .const import DATA_LISTENER, DATA_TASK, DOMAIN, PLATFORMS
async def async_setup(hass, config: dict):
@ -23,12 +23,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
hass.config_entries.async_forward_entry_setup(entry, platform)
)
listener = entry.add_update_listener(async_update_options)
hass.data[DOMAIN][entry.entry_id][DATA_LISTENER] = listener
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
task = hass.data[DOMAIN][entry.entry_id][DATA_TASK]
listener = hass.data[DOMAIN][entry.entry_id][DATA_LISTENER]
# Cancel the reconnect task
task.cancel()
@ -46,6 +50,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
)
)
if unload_ok:
listener()
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry):
"""Update options."""
await hass.config_entries.async_reload(config_entry.entry_id)

View File

@ -8,14 +8,18 @@ from async_timeout import timeout
from dsmr_parser import obis_references as obis_ref
from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader
import serial
import voluptuous as vol
from homeassistant import config_entries, core, exceptions
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import callback
from .const import ( # pylint:disable=unused-import
CONF_DSMR_VERSION,
CONF_SERIAL_ID,
CONF_SERIAL_ID_GAS,
CONF_TIME_BETWEEN_UPDATE,
DEFAULT_TIME_BETWEEN_UPDATE,
DOMAIN,
)
@ -115,6 +119,12 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the options flow for this handler."""
return DSMROptionFlowHandler(config_entry)
def _abort_if_host_port_configured(
self,
port: str,
@ -174,6 +184,33 @@ class DSMRFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_create_entry(title=name, data=data)
class DSMROptionFlowHandler(config_entries.OptionsFlow):
"""Handle options."""
def __init__(self, config_entry):
"""Initialize options flow."""
self.config_entry = config_entry
async def async_step_init(self, user_input=None):
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Optional(
CONF_TIME_BETWEEN_UPDATE,
default=self.config_entry.options.get(
CONF_TIME_BETWEEN_UPDATE, DEFAULT_TIME_BETWEEN_UPDATE
),
): vol.All(vol.Coerce(int), vol.Range(min=0)),
}
),
)
class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""

View File

@ -7,6 +7,7 @@ PLATFORMS = ["sensor"]
CONF_DSMR_VERSION = "dsmr_version"
CONF_RECONNECT_INTERVAL = "reconnect_interval"
CONF_PRECISION = "precision"
CONF_TIME_BETWEEN_UPDATE = "time_between_update"
CONF_SERIAL_ID = "serial_id"
CONF_SERIAL_ID_GAS = "serial_id_gas"
@ -15,7 +16,9 @@ DEFAULT_DSMR_VERSION = "2.2"
DEFAULT_PORT = "/dev/ttyUSB0"
DEFAULT_PRECISION = 3
DEFAULT_RECONNECT_INTERVAL = 30
DEFAULT_TIME_BETWEEN_UPDATE = 30
DATA_LISTENER = "listener"
DATA_TASK = "task"
DEVICE_NAME_ENERGY = "Energy Meter"

View File

@ -1,6 +1,7 @@
"""Support for Dutch Smart Meter (also known as Smartmeter or P1 port)."""
import asyncio
from asyncio import CancelledError
from datetime import timedelta
from functools import partial
import logging
from typing import Dict
@ -22,6 +23,7 @@ from homeassistant.core import CoreState, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.util import Throttle
from .const import (
CONF_DSMR_VERSION,
@ -29,11 +31,13 @@ from .const import (
CONF_RECONNECT_INTERVAL,
CONF_SERIAL_ID,
CONF_SERIAL_ID_GAS,
CONF_TIME_BETWEEN_UPDATE,
DATA_TASK,
DEFAULT_DSMR_VERSION,
DEFAULT_PORT,
DEFAULT_PRECISION,
DEFAULT_RECONNECT_INTERVAL,
DEFAULT_TIME_BETWEEN_UPDATE,
DEVICE_NAME_ENERGY,
DEVICE_NAME_GAS,
DOMAIN,
@ -72,6 +76,7 @@ async def async_setup_entry(
) -> None:
"""Set up the DSMR sensor."""
config = entry.data
options = entry.options
dsmr_version = config[CONF_DSMR_VERSION]
@ -142,6 +147,11 @@ async def async_setup_entry(
async_add_entities(devices)
min_time_between_updates = timedelta(
seconds=options.get(CONF_TIME_BETWEEN_UPDATE, DEFAULT_TIME_BETWEEN_UPDATE)
)
@Throttle(min_time_between_updates)
def update_entities_telegram(telegram):
"""Update entities with latest telegram and trigger state update."""
# Make all device entities aware of new telegram
@ -242,7 +252,7 @@ class DSMREntity(Entity):
def update_data(self, telegram):
"""Update data."""
self.telegram = telegram
if self.hass:
if self.hass and self._obis in self.telegram:
self.async_write_ha_state()
def get_dsmr_object_attr(self, attribute):

View File

@ -5,5 +5,15 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"options": {
"step": {
"init": {
"data": {
"time_between_update": "Minimum time between entity updates [s]"
},
"title": "DSMR Options"
}
}
}
}

View File

@ -3,5 +3,15 @@
"abort": {
"already_configured": "Device is already configured"
}
},
"options": {
"step": {
"init": {
"data": {
"time_between_update": "Minimum time between entity updates [s]"
},
"title": "DSMR Options"
}
}
}
}

View File

@ -4,7 +4,7 @@ from itertools import chain, repeat
import serial
from homeassistant import config_entries, setup
from homeassistant import config_entries, data_entry_flow, setup
from homeassistant.components.dsmr import DOMAIN
from tests.async_mock import DEFAULT, AsyncMock, patch
@ -196,9 +196,49 @@ async def test_import_update(hass, dsmr_connection_send_validate_fixture):
data=new_entry_data,
)
await hass.async_block_till_done()
await hass.async_block_till_done()
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
assert entry.data["precision"] == 3
async def test_options_flow(hass):
"""Test options flow."""
await setup.async_setup_component(hass, "persistent_notification", {})
entry_data = {
"port": "/dev/ttyUSB0",
"dsmr_version": "2.2",
"precision": 4,
"reconnect_interval": 30,
}
entry = MockConfigEntry(
domain=DOMAIN,
data=entry_data,
unique_id="/dev/ttyUSB0",
)
entry.add_to_hass(hass)
result = await hass.config_entries.options.async_init(entry.entry_id)
assert result["type"] == "form"
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
"time_between_update": 15,
},
)
with patch(
"homeassistant.components.dsmr.async_setup_entry", return_value=True
), patch("homeassistant.components.dsmr.async_unload_entry", return_value=True):
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
await hass.async_block_till_done()
assert entry.options == {"time_between_update": 15}

View File

@ -81,6 +81,9 @@ async def test_default_setup(hass, dsmr_connection_fixture):
"serial_id": "1234",
"serial_id_gas": "5678",
}
entry_options = {
"time_between_update": 0,
}
telegram = {
CURRENT_ELECTRICITY_USAGE: CosemObject(
@ -96,7 +99,7 @@ async def test_default_setup(hass, dsmr_connection_fixture):
}
mock_entry = MockConfigEntry(
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data, options=entry_options
)
mock_entry.add_to_hass(hass)
@ -232,6 +235,9 @@ async def test_v4_meter(hass, dsmr_connection_fixture):
"serial_id": "1234",
"serial_id_gas": "5678",
}
entry_options = {
"time_between_update": 0,
}
telegram = {
HOURLY_GAS_METER_READING: MBusObject(
@ -244,7 +250,7 @@ async def test_v4_meter(hass, dsmr_connection_fixture):
}
mock_entry = MockConfigEntry(
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data, options=entry_options
)
mock_entry.add_to_hass(hass)
@ -289,6 +295,9 @@ async def test_v5_meter(hass, dsmr_connection_fixture):
"serial_id": "1234",
"serial_id_gas": "5678",
}
entry_options = {
"time_between_update": 0,
}
telegram = {
HOURLY_GAS_METER_READING: MBusObject(
@ -301,7 +310,7 @@ async def test_v5_meter(hass, dsmr_connection_fixture):
}
mock_entry = MockConfigEntry(
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data, options=entry_options
)
mock_entry.add_to_hass(hass)
@ -346,6 +355,9 @@ async def test_belgian_meter(hass, dsmr_connection_fixture):
"serial_id": "1234",
"serial_id_gas": "5678",
}
entry_options = {
"time_between_update": 0,
}
telegram = {
BELGIUM_HOURLY_GAS_METER_READING: MBusObject(
@ -358,7 +370,7 @@ async def test_belgian_meter(hass, dsmr_connection_fixture):
}
mock_entry = MockConfigEntry(
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data, options=entry_options
)
mock_entry.add_to_hass(hass)
@ -400,11 +412,14 @@ async def test_belgian_meter_low(hass, dsmr_connection_fixture):
"serial_id": "1234",
"serial_id_gas": "5678",
}
entry_options = {
"time_between_update": 0,
}
telegram = {ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0002", "unit": ""}])}
mock_entry = MockConfigEntry(
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data, options=entry_options
)
mock_entry.add_to_hass(hass)