diff --git a/src/em511/em511.py b/src/em511/em511.py index 4da7c7e..bd2ab35 100644 --- a/src/em511/em511.py +++ b/src/em511/em511.py @@ -25,10 +25,52 @@ class Em511: PASSWORD_MAX_VALUE = 9999 INPUT_MAX_VALUE_32 = 0x7FFFFFFF INPUT_MAX_VALUE_16 = 0x7FFF + DEVICE_ADDRESS_MAX_VALUE = 247 + DEVICE_ADDRESS_MIN_VALUE = 1 + BAUD_RATE_MIN_VALUE = 1 + BAUD_RATE_MAX_VALUE = 5 + PARITY_MIN_VALUE = 1 + PARITY_MAX_VALUE = 2 + STOP_BIT_MIN_VALUE = 0 + STOP_BIT_MAX_VALUE = 1 + REPLY_DELAY_MIN_VALUE = 0 + REPLY_DELAY_MAX_VALUE = 1000 + DMD_TIME_MIN_VALUE = 0 + DMD_TIME_MAX_VALUE = 6 + ALARM_STATE_MIN_VALUE = 0 + ALARM_STATE_MAX_VALUE = 1 + ALARM_MODE_MAX_VALUE = 6 + ALARM_MODE_MIN_VALUE = 1 + ALARM_DELAY_MIN_VALUE = 0 + ALARM_DELAY_MAX_VALUE = 3600 EM511_REGISTER_V = 0x0000 EM511_REGISTER_A = 0x0002 + EM511_REGISTER_A_DMD = 0x003A + EM511_REGISTER_A_DMD_PEAK = 0x003C + EM511_REGISTER_W = 0x0004 + EM511_REGISTER_W_DMD = 0x000A + EM511_REGISTER_W_DMD_PEAK = 0x000C + EM511_REGISTER_HZ = 0x000F EM511_REGISTER_PASSWORD = 0x1000 + EM511_REGISTER_KWH_TOT = 0x0010 + EM511_REGISTER_KWH_PARTIAL = 0x0014 + EM511_REGISTER_HOUR_COUNTER = 0x002C + EM511_REGISTER_LIFETIME_COUNTER = 0x0030 + EM511_REGISTER_HOUR_COUNTER_PART = 0x0036 + EM511_REGISTER_DMD_INTEGRATION_TIME = 0x1010 + EM511_REGISTER_ALARM_MODE = 0x1015 + EM511_REGISTER_DEVICE_ID = 0x2000 + EM511_REGISTER_BAUD_RATE = 0x2001 + EM511_REGISTER_PARITY = 0x2002 + EM511_REGISTER_STOP_BIT = 0x2003 + EM511_REGISTER_REPLY_DELAY = 0x2004 + EM511_REGISTER_RESET_TOT_ENERGY_AND_RUN_HOUR_COUNTER = 0x4003 + EM511_REGISTER_RESET_PARTIAL_ENERGY_AND_HOUR_COUNTER = 0x4004 + EM511_REGISTER_RESET_DMD_AND_DMD_MAX = 0x4005 + EM511_REGISTER_RESET_TO_FACTORY_SETTINGS = 0x4020 + EM511_REGISTER_ALARM_STATE = 0x1014 + EM511_REGISTER_ALARM_DELAY = 0x101A SCALE_10 = 10 SCALE_100 = 100 @@ -84,7 +126,7 @@ def _write_register(self, address: int, value: int) -> None: if result.isError(): msg = ( - "Failed to write to single register." + "Failed to write to single register: " f"device_address={self.device_address} address={address} count={value}" ) raise ModbusException(msg) @@ -151,6 +193,171 @@ def A(self) -> Decimal: value = Decimal(self._unpack(regs, self.EM511_REGISTER_A)) / self.SCALE_1000 return round(value, 3) + @property + def A_dmd(self) -> Decimal: + """Current demand (A). + + Returns: + Decimal: Current demand ampere 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_A_DMD, self.INT32_REG_COUNT) + value = Decimal(self._unpack(regs, self.EM511_REGISTER_A_DMD)) / self.SCALE_1000 + return round(value, 3) + + @property + def A_dmd_peak(self) -> Decimal: + """Current demand peak (A). + + Returns: + Decimal: Current demand peak, ampere 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_A_DMD_PEAK, self.INT32_REG_COUNT) + value = Decimal(self._unpack(regs, self.EM511_REGISTER_A_DMD_PEAK)) / self.SCALE_1000 + return round(value, 3) + + @property + def W(self) -> Decimal: + """Power (W). + + Returns: + Decimal: Current watt 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_W, self.INT32_REG_COUNT) + value = Decimal(self._unpack(regs, self.EM511_REGISTER_W)) / self.SCALE_10 + return round(value, 1) + + @property + def W_dmd(self) -> Decimal: + """Power (W_dmd). + + Returns: + Decimal: Current watt demand 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_W_DMD, self.INT32_REG_COUNT) + value = Decimal(self._unpack(regs, self.EM511_REGISTER_W_DMD)) / self.SCALE_10 + return round(value, 1) + + @property + def W_dmd_peak(self) -> Decimal: + """Power (W_dmd_peak). + + Returns: + Decimal: Current watt demand peak 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_W_DMD_PEAK, self.INT32_REG_COUNT) + value = Decimal(self._unpack(regs, self.EM511_REGISTER_W_DMD_PEAK)) / self.SCALE_10 + return round(value, 1) + + @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 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 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 hour_counter(self) -> Decimal: + """Run time in hours (h). + + Returns: + Decimal: Run time in hours 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_HOUR_COUNTER, self.INT32_REG_COUNT) + value = Decimal(self._unpack(regs, self.EM511_REGISTER_HOUR_COUNTER)) / self.SCALE_100 + return round(value, 2) + + @property + def lifetime_counter(self) -> Decimal: + """Lifetime in hours (h). + + Returns: + Decimal: Lifetime in hours 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_LIFETIME_COUNTER, self.INT32_REG_COUNT) + value = Decimal(self._unpack(regs, self.EM511_REGISTER_LIFETIME_COUNTER)) / self.SCALE_100 + return round(value, 2) + + @property + def hour_counter_part(self) -> Decimal: + """Partial run-hour counter (h). + + Returns: + Decimal: partial operating tim in hours 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_HOUR_COUNTER_PART, self.INT32_REG_COUNT) + value = Decimal(self._unpack(regs, self.EM511_REGISTER_HOUR_COUNTER_PART)) / self.SCALE_100 + return round(value, 2) + @property def password(self) -> int: """Password. @@ -170,6 +377,223 @@ def password(self) -> int: raise ValueError(msg) return value + @property + def alarm_state(self) -> int: + """Alarm state. + + 0=Disabled, Default + 1=Enabled + + Returns: + int: Current alarm state. + + Raises: + ValueError: If input is at max value or above. + ValueError: If alarm state is out of range. + ModbusException: If failed to read input register. + """ + regs = self._read_input_registers(self.EM511_REGISTER_ALARM_STATE, self.INT16_REG_COUNT) + value = self._unpack(regs, self.EM511_REGISTER_ALARM_STATE) + if not (self.ALARM_STATE_MIN_VALUE <= value <= self.ALARM_STATE_MAX_VALUE): + msg = f"Invalid alarm state value: {value}. Must be between 1 and 6." + raise ValueError(msg) + return value + + @property + def alarm_mode(self) -> int: + """Alarm mode. + + 1=Active Power(kW) + 2=Apparent Power (kVA) (default) + 3=Reactive Power (kVAR) + 4=Power Factor + 5=Current (A) + 6=Voltages (V) + + Returns: + int: Current alarm mode. + + Raises: + ValueError: If input is at max value or above. + ValueError: If alarm state is out of range. + ModbusException: If failed to read input register. + """ + regs = self._read_input_registers(self.EM511_REGISTER_ALARM_MODE, self.INT16_REG_COUNT) + value = self._unpack(regs, self.EM511_REGISTER_ALARM_MODE) + if not (self.ALARM_MODE_MIN_VALUE <= value <= self.ALARM_MODE_MAX_VALUE): + msg = f"Invalid alarm mode value: {value}. Must be between 1 and 6." + raise ValueError(msg) + return value + + @property + def alarm_delay(self) -> int: + """Alarm mode. + + Value: Min=0 (default) + MAx=3600 + + Returns: + int: Current alarm delay setting. + + Raises: + ValueError: If input is at max value or above. + ValueError: If alarm state is out of range. + ModbusException: If failed to read input register. + """ + regs = self._read_input_registers(self.EM511_REGISTER_ALARM_DELAY, self.INT16_REG_COUNT) + value = self._unpack(regs, self.EM511_REGISTER_ALARM_DELAY) + if not (self.ALARM_DELAY_MIN_VALUE <= value <= self.ALARM_DELAY_MAX_VALUE): + msg = f"Invalid alarm delay value: {value}. Must be between 0 and 3600." + raise ValueError(msg) + return value + + @property + def dmd_integration_time(self) -> int: + """Demand integration time. + + 0=1min, 1=5min, 2=10min, 3=15min, + 4=20min, 5=30min, 6=60min (default=3). + + Returns: + int: Current demand integration time. + + Raises: + ValueError: If input is at max value or above. + ValueError: If demand integration time is out of range. + ModbusException: If failed to read input register. + """ + regs = self._read_input_registers(self.EM511_REGISTER_DMD_INTEGRATION_TIME, self.INT32_REG_COUNT) + value = self._unpack(regs, self.EM511_REGISTER_DMD_INTEGRATION_TIME) + if not (self.DMD_TIME_MIN_VALUE <= value <= self.DMD_TIME_MAX_VALUE): + msg = f"Invalid demand integration time value: {value}. Must be between 0 and 6." + raise ValueError(msg) + return value + + @property + def device_id(self) -> int: + """Device address. + + (Default value=1) + + Returns: + Int: Current device address. + + Raises: + ValueError: If input is at max value or above. + ValueError: If device address is out of range. + ModbusException: If failed to read input register. + """ + regs = self._read_input_registers(self.EM511_REGISTER_DEVICE_ID, self.INT16_REG_COUNT) + value = self._unpack(regs, self.EM511_REGISTER_DEVICE_ID) + if not (self.DEVICE_ADDRESS_MIN_VALUE <= value <= self.DEVICE_ADDRESS_MAX_VALUE): + msg = f"Invalid device address value: {value}. Must be between 1 and 247." + raise ValueError(msg) + return value + + @property + def baud_rate(self) -> int: + """Baud rate. + + 1=9.6kbps, 2=19.2kbps, 3=38.4kbps, + 4=57.6kbps, 5=115.2kbps (default=1) + + Returns: + Int: Current baud rate. + + Raises: + ValueError: If input is at max value or above. + ValueError: If baud rate is out of range. + ModbusException: If failed to read input register. + """ + regs = self._read_input_registers(self.EM511_REGISTER_BAUD_RATE, self.INT16_REG_COUNT) + value = self._unpack(regs, self.EM511_REGISTER_BAUD_RATE) + if not (self.BAUD_RATE_MIN_VALUE <= value <= self.BAUD_RATE_MAX_VALUE): + msg = f"Invalid baud rate value: {value}. Must be between 1 and 5." + raise ValueError(msg) + return value + + @property + def parity(self) -> int: + """Parity. + + 1=None, 2=Even (default=1) + + Returns: + Int: Current parity. + + Raises: + ValueError: If input is at max value or above. + ValueError: If parity is out of range. + ModbusException: If failed to read input register. + """ + regs = self._read_input_registers(self.EM511_REGISTER_PARITY, self.INT16_REG_COUNT) + value = self._unpack(regs, self.EM511_REGISTER_PARITY) + if not (self.PARITY_MIN_VALUE <= value <= self.PARITY_MAX_VALUE): + msg = f"Invalid parity value: {value}. Must be between 1 and 2." + raise ValueError(msg) + return value + + @property + def stop_bit(self) -> int: + """Stop bits. + + 0=1 stop bit (default), 1=2 stop bits; fixed to 1 if parity=Even + + Returns: + Int: Current stop bit value. + + Raises: + ValueError: If input is at max value or above. + ValueError: If stop bit is out of range. + ModbusException: If failed to read input register. + """ + regs = self._read_input_registers(self.EM511_REGISTER_STOP_BIT, self.INT16_REG_COUNT) + value = self._unpack(regs, self.EM511_REGISTER_STOP_BIT) + if not (self.STOP_BIT_MIN_VALUE <= value <= self.STOP_BIT_MAX_VALUE): + msg = f"Invalid stop bit value: {value}. Must be between 1 and 2." + raise ValueError(msg) + return value + + @property + def reply_delay(self) -> int: + """Reply delay. + + Range: 0-1000 ms (default=0) + + Returns: + int: Current reply delay value. + + Raises: + ValueError: If input is at max value or above. + ValueError: If reply delay is out of range. + ModbusException: If failed to read input register. + """ + regs = self._read_input_registers(self.EM511_REGISTER_REPLY_DELAY, self.INT16_REG_COUNT) + value = self._unpack(regs, self.EM511_REGISTER_REPLY_DELAY) + if not (self.REPLY_DELAY_MIN_VALUE <= value <= self.REPLY_DELAY_MAX_VALUE): + msg = f"Invalid reply delay value: {value}. Must be between 0 and 1000." + raise ValueError(msg) + return value + + @dmd_integration_time.setter + def dmd_integration_time(self, value: int) -> None: + """Demand integration time. + + Value: 0=1 min, 1=5 min, 2=10 min, 3=15 min(default) + 4=20 min, 5=30 min, 6=30 min. + + Args: + value (int): Set demand integration time. + + Raises: + ModbusException: If failed to write to single register. + ValueError: If demand integration time value is out of range. + """ + if not (self.DMD_TIME_MIN_VALUE <= value <= self.DMD_TIME_MAX_VALUE): + msg = f"Invalid demand integration time value: {value}. Must be between 0 and 6." + raise ValueError(msg) + self._write_register(self.EM511_REGISTER_DMD_INTEGRATION_TIME, value) + @password.setter def password(self, value: int) -> None: """Password. @@ -188,3 +612,195 @@ def password(self, value: int) -> None: msg = f"Invalid password value: {value}. Must be between 0 and 9999." raise ValueError(msg) self._write_register(self.EM511_REGISTER_PASSWORD, value) + + @alarm_state.setter + def alarm_state(self, value: int) -> None: + """Alarm state. + + Value: 0=Disabled (default) + 1=Enabled + + Args: + value (int): Set alarm state. + + Raises: + ModbusException: If failed to write to single register. + ValueError: If alarm state value is out of range. + """ + if not (self.ALARM_STATE_MIN_VALUE <= value <= self.ALARM_STATE_MAX_VALUE): + msg = f"Invalid alarm state value: {value}. Must be between 0 and 1." + raise ValueError(msg) + self._write_register(self.EM511_REGISTER_ALARM_STATE, value) + + @alarm_mode.setter + def alarm_mode(self, value: int) -> None: + """Alarm mode. + + Value: + 1=Active Power [kW], 2=Apparent Power [kVA], + 3=Reactive Power [kVAR], 4=Power Factor, + 5=Currents [A], 6=Voltages [V] (default=1) + + Args: + value (int): Set alarm mode. + + Raises: + ModbusException: If failed to write to single register. + ValueError: If alarm mode value is out of range. + """ + if not (self.ALARM_MODE_MIN_VALUE <= value <= self.ALARM_MODE_MAX_VALUE): + msg = f"Invalid alarm mode value: {value}. Must be between 1 and 6." + raise ValueError(msg) + self._write_register(self.EM511_REGISTER_ALARM_MODE, value) + + @alarm_delay.setter + def alarm_delay(self, value: int) -> None: + """Alarm delay. + + Value: + Range 0 - 3600 s(Default=0). + + Args: + value (int): Set Alarm Delay. + + Raises: + ModbusException: If failed to write to single register. + ValueError: If alarm delay value is out of range. + """ + if not (self.ALARM_DELAY_MIN_VALUE <= value <= self.ALARM_DELAY_MAX_VALUE): + msg = f"Invalid alarm delay value: {value}. Must be between 0 and 3600." + raise ValueError(msg) + self._write_register(self.EM511_REGISTER_ALARM_DELAY, value) + + @device_id.setter + def device_id(self, value: int) -> None: + """Device id. + + Range 1 - 247 + + Args: + value (int): Set device id. + + Raises: + ModbusException: If failed to write to single register. + ValueError: If device id value is out of range. + """ + if not (self.DEVICE_ADDRESS_MIN_VALUE <= value <= self.DEVICE_ADDRESS_MAX_VALUE): + msg = f"Invalid device id value: {value}. Must be between 1 and 247." + raise ValueError(msg) + self._write_register(self.EM511_REGISTER_DEVICE_ID, value) + + @baud_rate.setter + def baud_rate(self, value: int) -> None: + """Baud rate. + + 1=9.6kbps, 2=19.2kbps, 3=38.4kbps, + 4=57.6kbps, 5=115.2kbps (default=1). + + Args: + value (int): Set baud rate. + + Raises: + ModbusException: If failed to write to single register. + ValueError: If baud rate value is out of range. + """ + if not (self.BAUD_RATE_MIN_VALUE <= value <= self.BAUD_RATE_MAX_VALUE): + msg = f"Invalid baud rate value: {value}. Must be between 1 and 5." + raise ValueError(msg) + self._write_register(self.EM511_REGISTER_BAUD_RATE, value) + + @parity.setter + def parity(self, value: int) -> None: + """Parity. + + 1=None, 2=Even (default=1). + + + Args: + value (int): Set parity. + + Raises: + ModbusException: If failed to write to single register. + ValueError: If parity value is out of range. + """ + if not (self.PARITY_MIN_VALUE <= value <= self.PARITY_MAX_VALUE): + msg = f"Invalid parity value: {value}. Must be between 1 and 2." + raise ValueError(msg) + self._write_register(self.EM511_REGISTER_PARITY, value) + + @stop_bit.setter + def stop_bit(self, value: int) -> None: + """Stop bit. + + 0=1 stop bit (default), 1=2 stop bits; fixed to 1 if parity=Even. + + Args: + value (int): Set stop bit. + + Raises: + ModbusException: If failed to write to single register. + ValueError: If stop bit value is out of range. + """ + if not (self.STOP_BIT_MIN_VALUE <= value <= self.STOP_BIT_MAX_VALUE): + msg = f"Invalid stop bit value: {value}. Must be between 0 and 1." + raise ValueError(msg) + self._write_register(self.EM511_REGISTER_STOP_BIT, value) + + @reply_delay.setter + def reply_delay(self, value: int) -> None: + """Reply delay. + + Range: 0 - 1000 ms (default=0). + + Args: + value (int): Set reply delay. + + Raises: + ModbusException: If failed to write to single register. + ValueError: If reply delay value is out of range. + """ + if not (self.REPLY_DELAY_MIN_VALUE <= value <= self.REPLY_DELAY_MAX_VALUE): + msg = f"Invalid reply delay value: {value}. Must be between 0 and 1000." + raise ValueError(msg) + self._write_register(self.EM511_REGISTER_REPLY_DELAY, value) + + def reset_tot_energy_and_run_hour_counter(self) -> None: + """Reset total energy + total run hour counters (excluding lifetime). + + Writes 1 to execute. + + Raises: + ModbusException: If failed to write to single register. + """ + self._write_register(self.EM511_REGISTER_RESET_TOT_ENERGY_AND_RUN_HOUR_COUNTER, 1) + + def reset_partial_energy_and_hour_counter(self) -> None: + """Reset partial energy + partial run hour counters. + + Writes 1 to execute. + + Raises: + ModbusException: If failed to write to single register. + """ + self._write_register(self.EM511_REGISTER_RESET_PARTIAL_ENERGY_AND_HOUR_COUNTER, 1) + + def reset_dmd_and_dmd_max(self) -> None: + """Reset DMD and DMD max values. + + Write 1 to execute. + + Raises: + ModbusException: If failed to write to single register. + """ + self._write_register(self.EM511_REGISTER_RESET_DMD_AND_DMD_MAX, 1) + + def reset_to_factory_settings(self) -> None: + """Factory Restore (Default settings). + + Write 0x0A0A=2570, then within 1s write 0xC1A0=49568 to trigger reset. + + Raises: + ModbusException: If failed to write to single register. + """ + self._write_register(self.EM511_REGISTER_RESET_TO_FACTORY_SETTINGS, 0x0A0A) + self._write_register(self.EM511_REGISTER_RESET_TO_FACTORY_SETTINGS, 0xC1A0) diff --git a/src/em511/test_em511.py b/src/em511/test_em511.py index 8c8bac5..85743fe 100644 --- a/src/em511/test_em511.py +++ b/src/em511/test_em511.py @@ -2,9 +2,11 @@ """Test file for driver.""" -from unittest.mock import MagicMock +from decimal import Decimal +from unittest.mock import MagicMock, call import pytest +from pymodbus import ModbusException from em511 import Em511 @@ -20,13 +22,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] @@ -34,7 +36,7 @@ def test_V() -> None: with pytest.raises(ValueError, match="Unexpected register count:"): _ = meter.V - """Test 6: Should raise exception if input value exceeds maximum value, display shows 'EEE', 32-bit register.""" + """Test 4: 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: "): @@ -52,13 +54,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] @@ -66,13 +68,365 @@ def test_get_A() -> None: with pytest.raises(ValueError, match="Unexpected register count:"): _ = meter.A - """Test 6: Should raise exception if input value exceeds maximum value, display shows 'EEE', 32-bit register.""" + """Test 4: 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.A +def test_get_A_dmd() -> None: + """Test Get a_dmd.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x2904, 0x0000] + client.read_input_registers.return_value = mock_result + value = meter.A_dmd + 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_dmd + assert value == Decimal("2300.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.A_dmd + + """Test 4: 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.A_dmd + + +def test_get_A_dmd_peak() -> None: + """Test Get a_dmd_peak.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x2904, 0x0000] + client.read_input_registers.return_value = mock_result + value = meter.A_dmd_peak + 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_dmd_peak + assert value == Decimal("2300.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.A_dmd_peak + + """Test 4: 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.A_dmd_peak + + +def test_get_W() -> None: + """Test Get w.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x2904, 0x0000] + client.read_input_registers.return_value = mock_result + value = meter.W + assert value == Decimal("1050.0") + + """Test 2: Should pass.""" + mock_result.registers = [0x1860, 0x0023] + client.read_input_registers.return_value = mock_result + value = meter.W + assert value == Decimal("230000.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.W + + """Test 4: 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.W + + +def test_get_W_dmd() -> None: + """Test Get w_dmd.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x2904, 0x0000] + client.read_input_registers.return_value = mock_result + value = meter.W_dmd + assert value == Decimal("1050.0") + + """Test 2: Should pass.""" + mock_result.registers = [0x1860, 0x0023] + client.read_input_registers.return_value = mock_result + value = meter.W_dmd + assert value == Decimal("230000.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.W_dmd + + """Test 4: 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.W_dmd + + +def test_get_W_dmd_peak() -> None: + """Test Get w_dmd_peak.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x2904, 0x0000] + client.read_input_registers.return_value = mock_result + value = meter.W_dmd_peak + assert value == Decimal("1050.0") + + """Test 2: Should pass.""" + mock_result.registers = [0x1860, 0x0023] + client.read_input_registers.return_value = mock_result + value = meter.W_dmd_peak + assert value == Decimal("230000.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.W_dmd_peak + + """Test 4: 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.W_dmd_peak + + +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 4: 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_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 4: 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_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 4: 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_hour_counter() -> None: + """Test Get hour_counter.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x2904, 0x0000] + client.read_input_registers.return_value = mock_result + value = meter.hour_counter + assert value == Decimal("105.0") + + """Test 2: Should pass.""" + mock_result.registers = [0x1860, 0x0023] + client.read_input_registers.return_value = mock_result + value = meter.hour_counter + assert value == Decimal("23000.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.hour_counter + + """Test 4: 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.hour_counter + + +def test_get_lifetime_counter() -> None: + """Test Get lifetime_counter.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x2904, 0x0000] + client.read_input_registers.return_value = mock_result + value = meter.lifetime_counter + assert value == Decimal("105.0") + + """Test 2: Should pass.""" + mock_result.registers = [0x1860, 0x0023] + client.read_input_registers.return_value = mock_result + value = meter.lifetime_counter + assert value == Decimal("23000.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.lifetime_counter + + """Test 4: 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.lifetime_counter + + +def test_get_life_time_counter() -> None: + """Test Get hour_counter_partial.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x2904, 0x0000] + client.read_input_registers.return_value = mock_result + value = meter.hour_counter_part + assert value == Decimal("105.0") + + """Test 2: Should pass.""" + mock_result.registers = [0x1860, 0x0023] + client.read_input_registers.return_value = mock_result + value = meter.hour_counter_part + assert value == Decimal("23000.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.hour_counter_part + + """Test 4: 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.hour_counter_part + + def test_get_password() -> None: """Test Get password.""" client = MagicMock() @@ -81,22 +435,396 @@ def test_get_password() -> None: meter = Em511(1, client) """Test 1: should pass""" - mock_result.registers = [1234] + mock_result.registers = [1234] + client.read_input_registers.return_value = mock_result + value = meter.password + assert value == 1234 + + """Test 2: 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.password + + """Test 3: Should raise exception if password return a value out of its range of 0-9999.""" + mock_result.registers = [0x186A0, 0x0000] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Invalid password value: "): + _ = meter.password + + +def test_get_alarm_state() -> None: + """Test Get alarm_state.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x0001] + client.read_input_registers.return_value = mock_result + value = meter.alarm_state + assert value == int("1") + + """Test 2: Should pass.""" + mock_result.registers = [0x0000] + client.read_input_registers.return_value = mock_result + value = meter.alarm_state + assert value == int("0") + + """Test 3: Should raise exception due to value not in range""" + mock_result.registers = [0x0002] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Invalid alarm state value: "): + _ = meter.alarm_state + + """Test 4: 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.alarm_state + + """Test 5: Should raise exception if input value exceeds maximum value, display shows 'EEE', 32-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.alarm_state + + +def test_get_alarm_mode() -> None: + """Test Get alarm_mode.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x0006] + client.read_input_registers.return_value = mock_result + value = meter.alarm_mode + assert value == int("6") + + """Test 2: Should pass.""" + mock_result.registers = [0x0001] + client.read_input_registers.return_value = mock_result + value = meter.alarm_mode + assert value == int("1") + + """Test 3: Should raise exception due to value not in range""" + mock_result.registers = [0x0007] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Invalid alarm mode value: "): + _ = meter.alarm_mode + + """Test 4: 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.alarm_mode + + """Test 5: Should raise exception if input value exceeds maximum value, display shows 'EEE', 32-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.alarm_mode + + +def test_get_alarm_delay() -> None: + """Test Get alarm_delay.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x0E10] + client.read_input_registers.return_value = mock_result + value = meter.alarm_delay + assert value == int("3600") + + """Test 2: Should pass.""" + mock_result.registers = [0x0000] + client.read_input_registers.return_value = mock_result + value = meter.alarm_delay + assert value == int("0") + + """Test 3: Should raise exception due to value not in range""" + mock_result.registers = [0x0E11] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Invalid alarm delay value: "): + _ = meter.alarm_delay + + """Test 4: 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.alarm_delay + + """Test 5: 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.alarm_delay + + +def test_get_dmd_integration_time() -> None: + """Test Get dmd_integration_time.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x0006, 0x0000] + client.read_input_registers.return_value = mock_result + value = meter.dmd_integration_time + assert value == int("6") + + """Test 2: Should pass.""" + mock_result.registers = [0x0000, 0x0000] + client.read_input_registers.return_value = mock_result + value = meter.dmd_integration_time + assert value == int("0") + + """Test 3: Should raise exception due to value not in range""" + mock_result.registers = [0x0007, 0x0000] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Invalid demand integration time value: "): + _ = meter.dmd_integration_time + + """Test 4: 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.dmd_integration_time + + """Test 5: 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.dmd_integration_time + + +def test_get_device_id() -> None: + """Test Get device id/address.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0xF7] + client.read_input_registers.return_value = mock_result + value = meter.device_id + assert value == 247 + + """Test 2: Should pass.""" + mock_result.registers = [0x1] + client.read_input_registers.return_value = mock_result + value = meter.device_id + assert value == 1 + + """Test 3: Should raise exception due to value not in range""" + mock_result.registers = [0x1860] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Invalid device address value: "): + _ = meter.device_id + + """Test 4: 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.device_id + + """Test 5: 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.device_id + + +def test_get_baud_rate() -> None: + """Test Get baud_rate.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x5] + client.read_input_registers.return_value = mock_result + value = meter.baud_rate + assert value == 5 + + """Test 2: Should pass.""" + mock_result.registers = [0x1] + client.read_input_registers.return_value = mock_result + value = meter.baud_rate + assert value == 1 + + """Test 3: Should raise exception due to value not in range""" + mock_result.registers = [0x6] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Invalid baud rate value:"): + _ = meter.baud_rate + + """Test 4: 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.baud_rate + + """Test 5: 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.baud_rate + + +def test_get_parity() -> None: + """Test Get parity.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x2] client.read_input_registers.return_value = mock_result - value = meter.password - assert value == 1234 + value = meter.parity + assert value == 2 - """Test 2: Should raise exception if input value exceeds maximum value, display shows 'EEE', 32-bit register.""" - mock_result.registers = [0xFFFF, 0x7FFF] + """Test 2: Should pass.""" + mock_result.registers = [0x1] client.read_input_registers.return_value = mock_result - with pytest.raises(ValueError, match="Input overflow EEE for 32-bit register: "): - _ = meter.password + value = meter.parity + assert value == 1 - """Test 3: Should raise exception if password return a value out of its range of 0-9999.""" - mock_result.registers = [0x186A0, 0x0000] + """Test 3: Should raise exception due to value not in range""" + mock_result.registers = [0x3] client.read_input_registers.return_value = mock_result - with pytest.raises(ValueError, match="Invalid password value: "): - _ = meter.password + with pytest.raises(ValueError, match="Invalid parity value:"): + _ = meter.parity + + """Test 4: 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.parity + + """Test 5: 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.parity + + +def test_get_stop_bit() -> None: + """Test Get stop_bit.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x1] + client.read_input_registers.return_value = mock_result + value = meter.stop_bit + assert value == 1 + + """Test 2: Should pass.""" + mock_result.registers = [0x0] + client.read_input_registers.return_value = mock_result + value = meter.stop_bit + assert value == 0 + + """Test 3: Should raise exception due to value not in range""" + mock_result.registers = [0x2] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Invalid stop bit value:"): + _ = meter.stop_bit + + """Test 4: 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.stop_bit + + """Test 5: 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.stop_bit + + +def test_get_reply_delay() -> None: + """Test Get reply delay.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: should pass""" + mock_result.registers = [0x3E8] + client.read_input_registers.return_value = mock_result + value = meter.reply_delay + assert value == 1000 + + """Test 2: Should pass.""" + mock_result.registers = [0x0] + client.read_input_registers.return_value = mock_result + value = meter.reply_delay + assert value == 0 + + """Test 3: Should raise exception due to value not in range""" + mock_result.registers = [0x3E9] + client.read_input_registers.return_value = mock_result + with pytest.raises(ValueError, match="Invalid reply delay value:"): + _ = meter.reply_delay + + """Test 4: 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.reply_delay + + """Test 5: 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.reply_delay + + +def test_set_dmd_integration_time() -> None: + """Test Set demand integration time.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: Set demand integration time""" + mock_result.registers = [4112] + client.write_register.return_value = mock_result + meter.dmd_integration_time = 0 + client.write_register.assert_called_once_with(address=4112, value=0, device_id=1) + + client.write_register.reset_mock() + + """Test 2: Try set demand integration time out of range.""" + with pytest.raises(ValueError, match="Invalid demand integration time value:"): + meter.dmd_integration_time = 7 + + """Test 3: Try set demand integration time at maximum value.""" + mock_result.registers = [4112] + client.write_register.return_value = mock_result + meter.dmd_integration_time = 6 + client.write_register.assert_called_once_with(address=4112, value=6, device_id=1) + + """Test 4: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.dmd_integration_time = 1 def test_set_password() -> None: @@ -107,10 +835,10 @@ def test_set_password() -> None: meter = Em511(1, client) """Test 1: Set password""" - mock_result.registers = [4096] + mock_result.registers = [0x1000] client.write_register.return_value = mock_result meter.password = 1236 - client.write_register.assert_called_once_with(address=4096, value=1236, device_id=1) + client.write_register.assert_called_once_with(address=0x1000, value=1236, device_id=1) client.write_register.reset_mock() @@ -119,15 +847,399 @@ def test_set_password() -> None: meter.password = 12345 """Test 3: Try set password at maximum value.""" - mock_result.registers = [4096] + mock_result.registers = [0x1000] client.write_register.return_value = mock_result meter.password = 9999 - client.write_register.assert_called_once_with(address=4096, value=9999, device_id=1) + client.write_register.assert_called_once_with(address=0x1000, value=9999, device_id=1) client.write_register.reset_mock() """Test 4: Try set password at lowest value.""" - mock_result.registers = [4096] + mock_result.registers = [0x1000] client.write_register.return_value = mock_result meter.password = 0 - client.write_register.assert_called_once_with(address=4096, value=0, device_id=1) + client.write_register.assert_called_once_with(address=0x1000, value=0, device_id=1) + + """Test 5: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.password = 1 + + +def test_set_alarm_state() -> None: + """Test Set alarm state.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: Set alarm state""" + mock_result.registers = [0x1014] + client.write_register.return_value = mock_result + meter.alarm_state = 0 + client.write_register.assert_called_once_with(address=0x1014, value=0, device_id=1) + + client.write_register.reset_mock() + + """Test 2: Try set alarm state out of range.""" + with pytest.raises(ValueError, match="Invalid alarm state value:"): + meter.alarm_state = 2 + + """Test 3: Try set alarm state at maximum value.""" + mock_result.registers = [0x1014] + client.write_register.return_value = mock_result + meter.alarm_state = 1 + client.write_register.assert_called_once_with(address=0x1014, value=1, device_id=1) + + """Test 4: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.alarm_state = 1 + + +def test_set_alarm_mode() -> None: + """Test Set alarm mode.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: Set alarm mode""" + mock_result.registers = [0x1015] + client.write_register.return_value = mock_result + meter.alarm_mode = 1 + client.write_register.assert_called_once_with(address=0x1015, value=1, device_id=1) + + client.write_register.reset_mock() + + """Test 2: Try set alarm mode out of range.""" + with pytest.raises(ValueError, match="Invalid alarm mode value:"): + meter.alarm_mode = 7 + + """Test 3: Try set alarm mode at maximum value.""" + mock_result.registers = [0x1015] + client.write_register.return_value = mock_result + meter.alarm_mode = 6 + client.write_register.assert_called_once_with(address=0x1015, value=6, device_id=1) + + """Test 4: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.alarm_mode = 1 + + +def test_set_alarm_delay() -> None: + """Test Set alarm delay.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: Set alarm delay""" + mock_result.registers = [0x101A] + client.write_register.return_value = mock_result + meter.alarm_delay = 0 + client.write_register.assert_called_once_with(address=0x101A, value=0, device_id=1) + + client.write_register.reset_mock() + + """Test 2: Try set alarm delay out of range.""" + with pytest.raises(ValueError, match="Invalid alarm delay value:"): + meter.alarm_delay = 3601 + + """Test 3: Try set alarm delay at maximum value.""" + mock_result.registers = [0x101A] + client.write_register.return_value = mock_result + meter.alarm_delay = 3600 + client.write_register.assert_called_once_with(address=0x101A, value=3600, device_id=1) + + """Test 4: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.alarm_delay = 1 + + +def test_set_device_id() -> None: + """Test Set device id.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: Set device id""" + mock_result.registers = [0x2000] + client.write_register.return_value = mock_result + meter.device_id = 123 + client.write_register.assert_called_once_with(address=0x2000, value=123, device_id=1) + + client.write_register.reset_mock() + + """Test 2: Try set device id out of range.""" + with pytest.raises(ValueError, match="Invalid device id value:"): + meter.device_id = 248 + + """Test 3: Try set device id out of range.""" + with pytest.raises(ValueError, match="Invalid device id value:"): + meter.device_id = 0 + + """Test 4: Try set device id at maximum value.""" + mock_result.registers = [0x2000] + client.write_register.return_value = mock_result + meter.device_id = 247 + client.write_register.assert_called_once_with(address=0x2000, value=247, device_id=1) + + client.write_register.reset_mock() + + """Test 5: Try set device id at lowest value.""" + mock_result.registers = [0x2000] + client.write_register.return_value = mock_result + meter.device_id = 1 + client.write_register.assert_called_once_with(address=0x2000, value=1, device_id=1) + + """Test 6: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.device_id = 1 + + +def test_set_baud_rate() -> None: + """Test Set baud rate.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: Set baud rate""" + mock_result.registers = [0x2001] + client.write_register.return_value = mock_result + meter.baud_rate = 2 + client.write_register.assert_called_once_with(address=0x2001, value=2, device_id=1) + + client.write_register.reset_mock() + + """Test 2: Try set baud rate out of range.""" + with pytest.raises(ValueError, match="Invalid baud rate value:"): + meter.baud_rate = 6 + + """Test 3: Try set baud rate out of range.""" + with pytest.raises(ValueError, match="Invalid baud rate value:"): + meter.baud_rate = 0 + + """Test 4: Try set baud rate at maximum value.""" + mock_result.registers = [0x2001] + client.write_register.return_value = mock_result + meter.baud_rate = 5 + client.write_register.assert_called_once_with(address=0x2001, value=5, device_id=1) + + client.write_register.reset_mock() + + """Test 5: Try set baud rate at lowest value.""" + mock_result.registers = [0x2001] + client.write_register.return_value = mock_result + meter.baud_rate = 1 + client.write_register.assert_called_once_with(address=0x2001, value=1, device_id=1) + + """Test 6: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.baud_rate = 1 + + +def test_set_parity() -> None: + """Test Set parity.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: Set parity""" + mock_result.registers = [0x2002] + client.write_register.return_value = mock_result + meter.parity = 1 + client.write_register.assert_called_once_with(address=0x2002, value=1, device_id=1) + + client.write_register.reset_mock() + + """Test 2: Try set parity out of range.""" + with pytest.raises(ValueError, match="Invalid parity value:"): + meter.parity = 3 + + """Test 3: Try set parity out of range.""" + with pytest.raises(ValueError, match="Invalid parity value:"): + meter.parity = 0 + + """Test 4: Try set parity at maximum value.""" + mock_result.registers = [0x2002] + client.write_register.return_value = mock_result + meter.parity = 2 + client.write_register.assert_called_once_with(address=0x2002, value=2, device_id=1) + + client.write_register.reset_mock() + + """Test 5: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.parity = 1 + + +def test_set_stop_bit() -> None: + """Test Set stop bit.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: Set stop bit""" + mock_result.registers = [0x2003] + client.write_register.return_value = mock_result + meter.stop_bit = 0 + client.write_register.assert_called_once_with(address=0x2003, value=0, device_id=1) + + client.write_register.reset_mock() + + """Test 2: Try set stop bit out of range.""" + with pytest.raises(ValueError, match="Invalid stop bit value:"): + meter.stop_bit = 2 + + """Test 3: Try set stop bit at maximum value.""" + mock_result.registers = [0x2003] + client.write_register.return_value = mock_result + meter.stop_bit = 1 + client.write_register.assert_called_once_with(address=0x2003, value=1, device_id=1) + + client.write_register.reset_mock() + + """Test 4: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.stop_bit = 1 + + +def test_set_reply_delay() -> None: + """Test Set reply delay.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: Set reply delay""" + mock_result.registers = [0x2004] + client.write_register.return_value = mock_result + meter.reply_delay = 0 + client.write_register.assert_called_once_with(address=0x2004, value=0, device_id=1) + + client.write_register.reset_mock() + + """Test 2: Try set reply delay out of range.""" + with pytest.raises(ValueError, match="Invalid reply delay value:"): + meter.reply_delay = 1001 + + """Test 3: Try set reply delay at maximum value.""" + mock_result.registers = [0x2004] + client.write_register.return_value = mock_result + meter.reply_delay = 1000 + client.write_register.assert_called_once_with(address=0x2004, value=1000, device_id=1) + + """Test 4: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.reply_delay = 1 + + +def test_reset_tot_energy_and_run_hour_counter() -> None: + """Test Reset total energy and run hour counter.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: Reset""" + mock_result.registers = [0x4003] + client.write_register.return_value = mock_result + meter.reset_tot_energy_and_run_hour_counter() + client.write_register.assert_called_once_with(address=0x4003, value=1, device_id=1) + + client.write_register.reset_mock() + + """Test 4: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.reset_tot_energy_and_run_hour_counter() + + +def test_reset_partial_energy_and_hour_counter() -> None: + """Test Reset partial energy + partial hour counters.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: Reset""" + mock_result.registers = [0x4004] + client.write_register.return_value = mock_result + meter.reset_partial_energy_and_hour_counter() + client.write_register.assert_called_once_with(address=0x4004, value=1, device_id=1) + + client.write_register.reset_mock() + + """Test 4: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.reset_partial_energy_and_hour_counter() + + +def test_reset_dmd_and_dmd_max() -> None: + """Test Reset DMD and DMD max values..""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + meter = Em511(1, client) + + """Test 1: Reset""" + mock_result.registers = [0x4005] + client.write_register.return_value = mock_result + meter.reset_dmd_and_dmd_max() + client.write_register.assert_called_once_with(address=0x4005, value=1, device_id=1) + + client.write_register.reset_mock() + + """Test 4: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.reset_dmd_and_dmd_max() + + +def test_reset_to_factory_settings() -> None: + """Test Reset DMD and DMD max values.""" + client = MagicMock() + mock_result = MagicMock() + mock_result.isError.return_value = False + client.write_register.return_value = mock_result + meter = Em511(1, client) + + """Test 1: Both writes occur""" + meter.reset_to_factory_settings() + expected_calls = [ + call(address=0x4020, value=0x0A0A, device_id=1), + call(address=0x4020, value=0xC1A0, device_id=1), + ] + assert client.write_register.call_args_list == expected_calls + assert client.write_register.call_count == 2 + + """Test 3: Should raise exception due to failed writing to single register.""" + mock_result.isError.return_value = True + client.write_register.return_value = mock_result + with pytest.raises(ModbusException, match="Failed to write to single register:"): + meter.reset_to_factory_settings()