Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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: 2 additions & 0 deletions homeassistant/components/dsmr/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def __init__(self, host: str | None, port: int, dsmr_version: str) -> None:
self._equipment_identifier = obis_ref.EQUIPMENT_IDENTIFIER
if dsmr_version == "5L":
self._equipment_identifier = obis_ref.LUXEMBOURG_EQUIPMENT_IDENTIFIER
if dsmr_version == "Q3D":
self._equipment_identifier = obis_ref.Q3D_EQUIPMENT_IDENTIFIER

def equipment_identifier(self) -> str | None:
"""Equipment identifier."""
Expand Down
6 changes: 3 additions & 3 deletions homeassistant/components/dsmr/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
DEVICE_NAME_ENERGY = "Energy Meter"
DEVICE_NAME_GAS = "Gas Meter"

DSMR_VERSIONS = {"2.2", "4", "5", "5B", "5L", "5S"}
DSMR_VERSIONS = {"2.2", "4", "5", "5B", "5L", "5S", "Q3D"}

SENSORS: tuple[DSMRSensorEntityDescription, ...] = (
DSMRSensorEntityDescription(
Expand Down Expand Up @@ -244,15 +244,15 @@
DSMRSensorEntityDescription(
key=obis_references.ELECTRICITY_IMPORTED_TOTAL,
name="Energy Consumption (total)",
dsmr_versions={"5", "5B", "5L", "5S"},
dsmr_versions={"5", "5B", "5L", "5S", "Q3D"},
force_update=True,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
DSMRSensorEntityDescription(
key=obis_references.ELECTRICITY_EXPORTED_TOTAL,
name="Energy Production (total)",
dsmr_versions={"5L", "5S"},
dsmr_versions={"5L", "5S", "Q3D"},
force_update=True,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
Expand Down
4 changes: 4 additions & 0 deletions tests/components/dsmr/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ async def connection_factory(*args, **kwargs):
protocol.telegram = {
P1_MESSAGE_TIMESTAMP: CosemObject([{"value": "12345678", "unit": ""}]),
}
if args[1] == "Q3D":
protocol.telegram = {
P1_MESSAGE_TIMESTAMP: CosemObject([{"value": "12345678", "unit": ""}]),
Comment thread
Aeroid marked this conversation as resolved.
Outdated
}

return (transport, protocol)

Expand Down
37 changes: 37 additions & 0 deletions tests/components/dsmr/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,43 @@ async def test_setup_serial(com_mock, hass, dsmr_connection_send_validate_fixtur
assert result["data"] == {**entry_data, **SERIAL_DATA}


@patch("serial.tools.list_ports.comports", return_value=[com_port()])
async def test_setup_Q3D(com_mock, hass, dsmr_connection_send_validate_fixture):
"""Test we can setup serial."""
port = com_port()

result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)

assert result["type"] == "form"
assert result["step_id"] == "user"
assert result["errors"] is None

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"type": "Serial"},
)

assert result["type"] == "form"
assert result["step_id"] == "setup_serial"
assert result["errors"] == {}

with patch("homeassistant.components.dsmr.async_setup_entry", return_value=True):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"port": port.device, "dsmr_version": "Q3D"}
)

entry_data = {
"port": port.device,
"dsmr_version": "Q3D",
}
Comment on lines +129 to +173

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
entry_data = {
"port": port.device,
"dsmr_version": "Q3D",
}
entry_data = {
"port": port.device,
"dsmr_version": "Q3D",
"serial_id": "12345678",
"serial_id_gas": None,
}


assert result["type"] == "create_entry"
assert result["title"] == port.device
assert result["data"] == {**entry_data, **SERIAL_DATA}
Comment thread
Aeroid marked this conversation as resolved.
Outdated


@patch("serial.tools.list_ports.comports", return_value=[com_port()])
async def test_setup_serial_manual(
com_mock, hass, dsmr_connection_send_validate_fixture
Expand Down
74 changes: 74 additions & 0 deletions tests/components/dsmr/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,80 @@ async def test_swedish_meter(hass, dsmr_connection_fixture):
)


async def test_easymeter(hass, dsmr_connection_fixture):
"""Test if Q3D meter is correctly parsed."""
(connection_factory, transport, protocol) = dsmr_connection_fixture

from dsmr_parser.obis_references import (
ELECTRICITY_EXPORTED_TOTAL,
ELECTRICITY_IMPORTED_TOTAL,
)
from dsmr_parser.objects import CosemObject

entry_data = {
"port": "/dev/ttyUSB0",
"dsmr_version": "Q3D",
"precision": 4,
"reconnect_interval": 30,
"serial_id": None,
"serial_id_gas": None,
}
entry_options = {
"time_between_update": 0,
}

telegram = {
ELECTRICITY_IMPORTED_TOTAL: CosemObject(
[{"value": Decimal(54184.6316), "unit": ENERGY_KILO_WATT_HOUR}]
),
ELECTRICITY_EXPORTED_TOTAL: CosemObject(
[{"value": Decimal(19981.1069), "unit": ENERGY_KILO_WATT_HOUR}]
),
}

mock_entry = MockConfigEntry(
domain="dsmr",
unique_id="/dev/ttyUSB0",
data=entry_data,
options=entry_options,
)

mock_entry.add_to_hass(hass)

await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()

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)

power_tariff = hass.states.get("sensor.energy_consumption_total")
assert power_tariff.state == "54184.6316"
assert power_tariff.attributes.get(ATTR_DEVICE_CLASS) == SensorDeviceClass.ENERGY
assert power_tariff.attributes.get(ATTR_ICON) is None
assert (
power_tariff.attributes.get(ATTR_STATE_CLASS)
== SensorStateClass.TOTAL_INCREASING
)
assert (
power_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR
)

power_tariff = hass.states.get("sensor.energy_production_total")
assert power_tariff.state == "19981.1069"
assert (
power_tariff.attributes.get(ATTR_STATE_CLASS)
== SensorStateClass.TOTAL_INCREASING
)
assert (
power_tariff.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == ENERGY_KILO_WATT_HOUR
)


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