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
2 changes: 1 addition & 1 deletion homeassistant/components/dsmr/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "dsmr",
"name": "DSMR Slimme Meter",
"documentation": "https://www.home-assistant.io/integrations/dsmr",
"requirements": ["dsmr_parser==0.12"],
"requirements": ["dsmr_parser==0.18"],
"dependencies": [],
"codeowners": []
}
35 changes: 22 additions & 13 deletions homeassistant/components/dsmr/sensor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Support for Dutch Smart Meter (also known as Smartmeter or P1 port)."""
import asyncio
from datetime import timedelta
from functools import partial
import logging

Expand Down Expand Up @@ -32,17 +31,14 @@
ICON_POWER_FAILURE = "mdi:flash-off"
ICON_SWELL_SAG = "mdi:pulse"

# Smart meter sends telegram every 10 seconds
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)

RECONNECT_INTERVAL = 5

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string,
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_DSMR_VERSION, default=DEFAULT_DSMR_VERSION): vol.All(
cv.string, vol.In(["5", "4", "2.2"])
cv.string, vol.In(["5B", "5", "4", "2.2"])
),
vol.Optional(CONF_RECONNECT_INTERVAL, default=30): int,
vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): vol.Coerce(int),
Expand All @@ -62,17 +58,18 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
["Power Consumption", obis_ref.CURRENT_ELECTRICITY_USAGE],
["Power Production", obis_ref.CURRENT_ELECTRICITY_DELIVERY],
["Power Tariff", obis_ref.ELECTRICITY_ACTIVE_TARIFF],
["Power Consumption (total)", obis_ref.ELECTRICITY_IMPORTED_TOTAL],
["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],
["Energy Consumption (total)", obis_ref.ELECTRICITY_IMPORTED_TOTAL],
["Energy Consumption (tarif 1)", obis_ref.ELECTRICITY_USED_TARIFF_1],
["Energy Consumption (tarif 2)", obis_ref.ELECTRICITY_USED_TARIFF_2],
["Energy Production (tarif 1)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_1],
["Energy Production (tarif 2)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_2],
["Power Consumption Phase L1", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE],
["Power Consumption Phase L2", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE],
["Power Consumption Phase L3", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE],
["Power Production Phase L1", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE],
["Power Production Phase L2", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE],
["Power Production Phase L3", obis_ref.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE],
["Short Power Failure Count", obis_ref.SHORT_POWER_FAILURE_COUNT],
["Long Power Failure Count", obis_ref.LONG_POWER_FAILURE_COUNT],
["Voltage Sags Phase L1", obis_ref.VOLTAGE_SAG_L1_COUNT],
["Voltage Sags Phase L2", obis_ref.VOLTAGE_SAG_L2_COUNT],
Expand All @@ -83,6 +80,9 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
["Voltage Phase L1", obis_ref.INSTANTANEOUS_VOLTAGE_L1],
["Voltage Phase L2", obis_ref.INSTANTANEOUS_VOLTAGE_L2],
["Voltage Phase L3", obis_ref.INSTANTANEOUS_VOLTAGE_L3],
["Current Phase L1", obis_ref.INSTANTANEOUS_CURRENT_L1],
["Current Phase L2", obis_ref.INSTANTANEOUS_CURRENT_L2],
["Current Phase L3", obis_ref.INSTANTANEOUS_CURRENT_L3],
]

# Generate device entities
Expand All @@ -91,6 +91,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
# Protocol version specific obis
if dsmr_version in ("4", "5"):
gas_obis = obis_ref.HOURLY_GAS_METER_READING
elif dsmr_version in ("5B"):
gas_obis = obis_ref.BELGIUM_HOURLY_GAS_METER_READING
else:
gas_obis = obis_ref.GAS_METER_READING

Expand Down Expand Up @@ -214,7 +216,7 @@ def state(self):
value = self.get_dsmr_object_attr("value")

if self._obis == obis_ref.ELECTRICITY_ACTIVE_TARIFF:
return self.translate_tariff(value)
return self.translate_tariff(value, self._config[CONF_DSMR_VERSION])

try:
value = round(float(value), self._config[CONF_PRECISION])
Expand All @@ -232,8 +234,15 @@ def unit_of_measurement(self):
return self.get_dsmr_object_attr("unit")

@staticmethod
def translate_tariff(value):
"""Convert 2/1 to normal/low."""
def translate_tariff(value, dsmr_version):
"""Convert 2/1 to normal/low depening on DSMR version."""
# DSMR V5B: Note: In Belgium values are swapped:
# Rate code 2 is used for low rate and rate code 1 is used for normal rate.
if dsmr_version in ("5B"):
if value == "0001":
value = "0002"
elif value == "0002":
value = "0001"
# DSMR V2.2: Note: Rate code 1 is used for low rate and rate code 2 is
# used for normal rate.
if value == "0002":
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ doorbirdpy==2.0.8
dovado==0.4.1

# homeassistant.components.dsmr
dsmr_parser==0.12
dsmr_parser==0.18

# homeassistant.components.dweet
dweepy==0.3.0
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ directpy==0.5
distro==1.4.0

# homeassistant.components.dsmr
dsmr_parser==0.12
dsmr_parser==0.18

# homeassistant.components.ee_brightbox
eebrightbox==0.0.4
Expand Down
132 changes: 131 additions & 1 deletion tests/components/dsmr/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ async def test_default_setup(hass, mock_connection_factory):
from dsmr_parser.obis_references import (
CURRENT_ELECTRICITY_USAGE,
ELECTRICITY_ACTIVE_TARIFF,
GAS_METER_READING,
)
from dsmr_parser.objects import CosemObject
from dsmr_parser.objects import CosemObject, MBusObject

config = {"platform": "dsmr"}

Expand All @@ -68,6 +69,12 @@ async def test_default_setup(hass, mock_connection_factory):
[{"value": Decimal("0.0"), "unit": "kWh"}]
),
ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0001", "unit": ""}]),
GAS_METER_READING: MBusObject(
[
{"value": datetime.datetime.fromtimestamp(1551642213)},
{"value": Decimal(745.695), "unit": "m3"},
]
),
}

with assert_setup_component(1):
Expand Down Expand Up @@ -96,6 +103,11 @@ async def test_default_setup(hass, mock_connection_factory):
assert power_tariff.state == "low"
assert power_tariff.attributes.get("unit_of_measurement") == ""

# check if gas consumption is parsed correctly
gas_consumption = hass.states.get("sensor.gas_consumption")
assert gas_consumption.state == "745.695"
assert gas_consumption.attributes.get("unit_of_measurement") == "m3"


async def test_derivative():
"""Test calculation of derivative value."""
Expand Down Expand Up @@ -139,6 +151,124 @@ async def test_derivative():
assert entity.unit_of_measurement == "m3/h"


async def test_v4_meter(hass, mock_connection_factory):
"""Test if v4 meter is correctly parsed."""
(connection_factory, transport, protocol) = mock_connection_factory

from dsmr_parser.obis_references import (
HOURLY_GAS_METER_READING,
ELECTRICITY_ACTIVE_TARIFF,
)
from dsmr_parser.objects import CosemObject, MBusObject

config = {"platform": "dsmr", "dsmr_version": "4"}

telegram = {
HOURLY_GAS_METER_READING: MBusObject(
[
{"value": datetime.datetime.fromtimestamp(1551642213)},
{"value": Decimal(745.695), "unit": "m3"},
]
),
ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0001", "unit": ""}]),
}

with assert_setup_component(1):
await async_setup_component(hass, "sensor", {"sensor": config})

telegram_callback = connection_factory.call_args_list[0][0][2]

# simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
telegram_callback(telegram)

# after receiving telegram entities need to have the chance to update
await asyncio.sleep(0)

# tariff should be translated in human readable and have no unit
power_tariff = hass.states.get("sensor.power_tariff")
assert power_tariff.state == "low"
assert power_tariff.attributes.get("unit_of_measurement") == ""

# check if gas consumption is parsed correctly
gas_consumption = hass.states.get("sensor.gas_consumption")
assert gas_consumption.state == "745.695"
assert gas_consumption.attributes.get("unit_of_measurement") == "m3"


async def test_belgian_meter(hass, mock_connection_factory):
"""Test if Belgian meter is correctly parsed."""
(connection_factory, transport, protocol) = mock_connection_factory

from dsmr_parser.obis_references import (
BELGIUM_HOURLY_GAS_METER_READING,
ELECTRICITY_ACTIVE_TARIFF,
)
from dsmr_parser.objects import CosemObject, MBusObject

config = {"platform": "dsmr", "dsmr_version": "5B"}

telegram = {
BELGIUM_HOURLY_GAS_METER_READING: MBusObject(
[
{"value": datetime.datetime.fromtimestamp(1551642213)},
{"value": Decimal(745.695), "unit": "m3"},
]
),
ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0001", "unit": ""}]),
}

with assert_setup_component(1):
await async_setup_component(hass, "sensor", {"sensor": config})

telegram_callback = connection_factory.call_args_list[0][0][2]

# simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
telegram_callback(telegram)

# after receiving telegram entities need to have the chance to update
await asyncio.sleep(0)

# tariff should be translated in human readable and have no unit
power_tariff = hass.states.get("sensor.power_tariff")
assert power_tariff.state == "normal"
assert power_tariff.attributes.get("unit_of_measurement") == ""

# check if gas consumption is parsed correctly
gas_consumption = hass.states.get("sensor.gas_consumption")
assert gas_consumption.state == "745.695"
assert gas_consumption.attributes.get("unit_of_measurement") == "m3"


async def test_belgian_meter_low(hass, mock_connection_factory):
"""Test if Belgian meter is correctly parsed."""
(connection_factory, transport, protocol) = mock_connection_factory

from dsmr_parser.obis_references import ELECTRICITY_ACTIVE_TARIFF
from dsmr_parser.objects import CosemObject

config = {"platform": "dsmr", "dsmr_version": "5B"}

telegram = {
ELECTRICITY_ACTIVE_TARIFF: CosemObject([{"value": "0002", "unit": ""}]),
}

with assert_setup_component(1):
await async_setup_component(hass, "sensor", {"sensor": config})

telegram_callback = connection_factory.call_args_list[0][0][2]

# simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
telegram_callback(telegram)

# after receiving telegram entities need to have the chance to update
await asyncio.sleep(0)

# tariff should be translated in human readable and have no unit
power_tariff = hass.states.get("sensor.power_tariff")
assert power_tariff.state == "low"
assert power_tariff.attributes.get("unit_of_measurement") == ""


async def test_tcp(hass, mock_connection_factory):
"""If proper config provided TCP connection should be made."""
(connection_factory, transport, protocol) = mock_connection_factory
Expand Down