From 92bdfc5299d71eff673467399d1cf28d4dc93c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bobo=20B=C3=A4ck=20Engstr=C3=B6m?= Date: Tue, 28 Oct 2025 13:17:41 +0100 Subject: [PATCH 1/3] feat(Hz) Add getter to read Hertz. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bobo Bäck Engström --- src/em511/em511.py | 16 ++++++++++++++++ src/em511/test_em511.py | 39 +++++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/em511/em511.py b/src/em511/em511.py index 4da7c7e..2f83fe9 100644 --- a/src/em511/em511.py +++ b/src/em511/em511.py @@ -28,6 +28,7 @@ class Em511: EM511_REGISTER_V = 0x0000 EM511_REGISTER_A = 0x0002 + EM511_REGISTER_HZ = 0xF EM511_REGISTER_PASSWORD = 0x1000 SCALE_10 = 10 @@ -151,6 +152,21 @@ def A(self) -> Decimal: value = Decimal(self._unpack(regs, self.EM511_REGISTER_A)) / self.SCALE_1000 return round(value, 3) + @property + def Hz(self) -> Decimal: + """Hertz (Hz). + + Returns: + Decimal: Current hertz value. + + Raises: + ValueError: If input is at max value or above. + ModbusException: If failed to read input register. + """ + regs = self._read_input_registers(self.EM511_REGISTER_HZ, self.INT16_REG_COUNT) + value = Decimal(self._unpack(regs, self.EM511_REGISTER_HZ)) / self.SCALE_10 + return round(value, 1) + @property def password(self) -> int: """Password. diff --git a/src/em511/test_em511.py b/src/em511/test_em511.py index 8c8bac5..a961f05 100644 --- a/src/em511/test_em511.py +++ b/src/em511/test_em511.py @@ -2,6 +2,7 @@ """Test file for driver.""" +from decimal import Decimal from unittest.mock import MagicMock import pytest @@ -20,13 +21,13 @@ def test_V() -> None: mock_result.registers = [0x08FC, 0x0000] client.read_input_registers.return_value = mock_result value = meter.V - assert value == 230 + assert value == Decimal("230.0") """Test 2: Should pass.""" mock_result.registers = [0x9A28, 0x0001] client.read_input_registers.return_value = mock_result value = meter.V - assert value == 10500 + assert value == Decimal("10500.0") """Test 3: Should raise exception due to more registers in use than allowed.""" mock_result.registers = [0x1860, 0x0023, 0x4244] @@ -52,13 +53,13 @@ def test_get_A() -> None: mock_result.registers = [0x2904, 0x0000] client.read_input_registers.return_value = mock_result value = meter.A - assert value == 10.5 + assert value == Decimal("10.5") """Test 2: Should pass.""" mock_result.registers = [0x1860, 0x0023] client.read_input_registers.return_value = mock_result value = meter.A - assert value == 2300 + assert value == Decimal("2300.0") """Test 3: Should raise exception due to more registers in use than allowed.""" mock_result.registers = [0x1860, 0x0023, 0x4244] @@ -72,6 +73,36 @@ def test_get_A() -> None: with pytest.raises(ValueError, match="Input overflow EEE for 32-bit register: "): _ = meter.A +def test_get_Hz() -> None: + """Test Get HZ.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x08FC] + client.read_input_registers.return_value = mock_result + value = meter.Hz + assert value == Decimal("230.0") + + """Test 2: Should pass.""" + mock_result.registers = [0x9A28] + client.read_input_registers.return_value = mock_result + value = meter.Hz + assert value == Decimal("3946.4") + + """Test 3: Should raise exception due to more registers in use than allowed.""" + mock_result.registers = [0x1860, 0x0023, 0x5743] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Unexpected register count:"): + _ = meter.Hz + + """Test 6: Should raise exception if input value exceeds maximum value, display shows 'EEE', 16-bit register.""" + mock_result.registers = [0x7FFF] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Input overflow EEE for 16-bit register: "): + _ = meter.Hz def test_get_password() -> None: """Test Get password.""" From 6cfb3b9a97750d56f30545782a488d99e8d97903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bobo=20B=C3=A4ck=20Engstr=C3=B6m?= Date: Tue, 28 Oct 2025 13:26:41 +0100 Subject: [PATCH 2/3] feat(kWh_tot): Add getter for total kWh. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bobo Bäck Engström --- src/em511/em511.py | 21 +++++++++++++++++++-- src/em511/test_em511.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/em511/em511.py b/src/em511/em511.py index 2f83fe9..56e9a49 100644 --- a/src/em511/em511.py +++ b/src/em511/em511.py @@ -26,10 +26,11 @@ class Em511: INPUT_MAX_VALUE_32 = 0x7FFFFFFF INPUT_MAX_VALUE_16 = 0x7FFF - EM511_REGISTER_V = 0x0000 - EM511_REGISTER_A = 0x0002 + EM511_REGISTER_V = 0x0 + EM511_REGISTER_A = 0x2 EM511_REGISTER_HZ = 0xF EM511_REGISTER_PASSWORD = 0x1000 + EM511_REGISTER_KWH_TOT = 0x10 SCALE_10 = 10 SCALE_100 = 100 @@ -167,6 +168,22 @@ def Hz(self) -> Decimal: value = Decimal(self._unpack(regs, self.EM511_REGISTER_HZ)) / self.SCALE_10 return round(value, 1) + @property + def kWh_tot(self) -> Decimal: + """Kilo watt hours in total (kWh). + + Returns: + Decimal: Current total kWh value. + + Raises: + ValueError: If input is at max value or above. + ModbusException: If failed to read input register. + """ + regs = self._read_input_registers(self.EM511_REGISTER_KWH_TOT, self.INT32_REG_COUNT) + value = Decimal(self._unpack(regs, self.EM511_REGISTER_KWH_TOT)) / self.SCALE_10 + return round(value, 1) + + @property def password(self) -> int: """Password. diff --git a/src/em511/test_em511.py b/src/em511/test_em511.py index a961f05..afbedc8 100644 --- a/src/em511/test_em511.py +++ b/src/em511/test_em511.py @@ -104,6 +104,37 @@ def test_get_Hz() -> None: with pytest.raises(ValueError, match="Input overflow EEE for 16-bit register: "): _ = meter.Hz +def test_kWh_tot() -> None: + """Test Get total kWh.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x08FC, 0x0000] + client.read_input_registers.return_value = mock_result + value = meter.kWh_tot + assert value == Decimal("230.0") + + """Test 2: Should pass.""" + mock_result.registers = [0x9A28, 0x0001] + client.read_input_registers.return_value = mock_result + value = meter.kWh_tot + assert value == Decimal("10500.0") + + """Test 3: Should raise exception due to more registers in use than allowed.""" + mock_result.registers = [0x1860, 0x0023, 0x4244] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Unexpected register count:"): + _ = meter.kWh_tot + + """Test 6: Should raise exception if input value exceeds maximum value, display shows 'EEE', 32-bit register.""" + mock_result.registers = [0xFFFF, 0x7FFF] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Input overflow EEE for 32-bit register: "): + _ = meter.kWh_tot + def test_get_password() -> None: """Test Get password.""" client = MagicMock() From 05b93b2a51cd4a681c78ff4b658f91867994c7c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bobo=20B=C3=A4ck=20Engstr=C3=B6m?= Date: Tue, 28 Oct 2025 13:33:53 +0100 Subject: [PATCH 3/3] feat(kWh_partial): Add getter to read partial kWh. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bobo Bäck Engström --- src/em511/em511.py | 15 +++++++++++++++ src/em511/test_em511.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/em511/em511.py b/src/em511/em511.py index 56e9a49..520697e 100644 --- a/src/em511/em511.py +++ b/src/em511/em511.py @@ -31,6 +31,7 @@ class Em511: EM511_REGISTER_HZ = 0xF EM511_REGISTER_PASSWORD = 0x1000 EM511_REGISTER_KWH_TOT = 0x10 + EM511_REGISTER_KWH_PARTIAL = 0x14 SCALE_10 = 10 SCALE_100 = 100 @@ -183,6 +184,20 @@ def kWh_tot(self) -> Decimal: value = Decimal(self._unpack(regs, self.EM511_REGISTER_KWH_TOT)) / self.SCALE_10 return round(value, 1) + @property + def kWh_partial(self) -> Decimal: + """Kilo watt hours partial (kWh). + + Returns: + Decimal: Current partial kWh value. + + Raises: + ValueError: If input is at max value or above. + ModbusException: If failed to read input register. + """ + regs = self._read_input_registers(self.EM511_REGISTER_KWH_PARTIAL, self.INT32_REG_COUNT) + value = Decimal(self._unpack(regs, self.EM511_REGISTER_KWH_PARTIAL)) / self.SCALE_10 + return round(value, 1) @property def password(self) -> int: diff --git a/src/em511/test_em511.py b/src/em511/test_em511.py index afbedc8..5442c1c 100644 --- a/src/em511/test_em511.py +++ b/src/em511/test_em511.py @@ -135,6 +135,37 @@ def test_kWh_tot() -> None: with pytest.raises(ValueError, match="Input overflow EEE for 32-bit register: "): _ = meter.kWh_tot +def test_kWh_partial() -> None: + """Test Get partial kWh.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x08FC, 0x0000] + client.read_input_registers.return_value = mock_result + value = meter.kWh_partial + assert value == Decimal("230.0") + + """Test 2: Should pass.""" + mock_result.registers = [0x9A28, 0x0001] + client.read_input_registers.return_value = mock_result + value = meter.kWh_partial + assert value == Decimal("10500.0") + + """Test 3: Should raise exception due to more registers in use than allowed.""" + mock_result.registers = [0x1860, 0x0023, 0x4244] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Unexpected register count:"): + _ = meter.kWh_partial + + """Test 6: Should raise exception if input value exceeds maximum value, display shows 'EEE', 32-bit register.""" + mock_result.registers = [0xFFFF, 0x7FFF] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Input overflow EEE for 32-bit register: "): + _ = meter.kWh_partial + def test_get_password() -> None: """Test Get password.""" client = MagicMock()