From 4b6f3f71be0f3729d736d291e82fffc68f06b84d Mon Sep 17 00:00:00 2001 From: hchiper <125147739+hchiper@users.noreply.github.com> Date: Tue, 2 Apr 2024 10:41:00 +0200 Subject: [PATCH 1/5] Retro-compatibility with python 3.9 This is a proposal to replace the `match/case/case...` by `if/elif/elif.../else` in the function `.decode()` to ensure compatibility with python 3.9 and earlier. The use of `match/case` in this function is the only place in the whole package where a construction requires python >= 3.10. The proposed modification seems worth because python 3.9 is still widely used; it is e.g. the version available in Debian/oldstable repositories, which is still supported. --- sun2000_modbus/datatypes.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/sun2000_modbus/datatypes.py b/sun2000_modbus/datatypes.py index 9a073a6..e981bc3 100644 --- a/sun2000_modbus/datatypes.py +++ b/sun2000_modbus/datatypes.py @@ -29,16 +29,15 @@ def decode_bitfield(value): def decode(value, data_type): - match data_type: - case DataType.STRING: - return decode_string(value) - case DataType.UINT16_BE | DataType.UINT32_BE: - return decode_uint_be(value) - case DataType.INT16_BE | DataType.INT32_BE: - return decode_int_be(value) - case DataType.BITFIELD16 | DataType.BITFIELD32: - return decode_bitfield(value) - case DataType.MULTIDATA: - return value - case _: - raise ValueError("Unknown register type") + if data_type == DataType.STRING: + return decode_string(value) + elif data_type == DataType.UINT16_BE or data_type == DataType.UINT32_BE: + return decode_uint_be(value) + elif data_type == DataType.INT16_BE or data_type == DataType.INT32_BE: + return decode_int_be(value) + elif data_type == DataType.BITFIELD16 or data_type == DataType.BITFIELD32: + return decode_bitfield(value) + elif data_type == DataType.MULTIDATA: + return value + else: + raise ValueError("Unknown register type") From 4e7ca332c41114c1bd08cdeeaf435f6f1377422c Mon Sep 17 00:00:00 2001 From: hchiper <125147739+hchiper@users.noreply.github.com> Date: Tue, 2 Apr 2024 10:59:36 +0200 Subject: [PATCH 2/5] Reintroduce `.connected` attrubute This is a proposal to reintroduce the '.connected` attribute. Its suppression in recent versions of the module causes errors in programs using earlier versions. In this proposal, an equivalent is reintroduced, now as a property, relying directly on the new `.isConnected()` method. --- sun2000_modbus/inverter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sun2000_modbus/inverter.py b/sun2000_modbus/inverter.py index a1bff2d..b857766 100644 --- a/sun2000_modbus/inverter.py +++ b/sun2000_modbus/inverter.py @@ -37,6 +37,10 @@ def isConnected(self): """Check if underlying tcp socket is open""" return self.inverter.is_socket_open() + @property + def connected(self): + return self.isConnected() + def read_raw_value(self, register): if not self.isConnected(): raise ValueError('Inverter is not connected') From f0f8085277bdb2db00f34a55b5ed6f8b23b1f1f2 Mon Sep 17 00:00:00 2001 From: hchiper <125147739+hchiper@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:50:41 +0200 Subject: [PATCH 3/5] Implement sun2000_modbus.inverter own logger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This proposal implements an own logger for the `sun2000_modbus.inverter` module. Before, it was using the default "root" logger. If a program using the module needed to mute its log messages, the only option was to mute the "root" logger, which also muted messages from all other loggers. A module-specific logger can be tuned without impact on other loggers and its messages will clearly display their origin as "sun2000_modbus.inverter" instead of "root". Usage examples (in a custom program): 1°) catch the logger inv_log = logging.getLogger("sun2000_modbus.inverter") 2°) use one of the following instructions inv_log.setLevel(logging.INFO) # original behaviour inv_log.setLevel(logging.ERROR) # or any other level inv_log.disabled = True # disables this logger only --- sun2000_modbus/inverter.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/sun2000_modbus/inverter.py b/sun2000_modbus/inverter.py index b857766..6fb8ffa 100644 --- a/sun2000_modbus/inverter.py +++ b/sun2000_modbus/inverter.py @@ -6,7 +6,12 @@ from . import datatypes -logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) +log_format = logging.Formatter('%(levelname)s:%(name)s:%(message)s') +handler = logging.StreamHandler() +handler.setFormatter(log_format) +logger.addHandler(handler) class Sun2000: @@ -20,10 +25,10 @@ def connect(self): self.inverter.connect() time.sleep(self.wait) if self.isConnected(): - logging.info('Successfully connected to inverter') + logger.info('Successfully connected to inverter') return True else: - logging.error('Connection to inverter failed') + logger.error('Connection to inverter failed') return False def disconnect(self): @@ -48,10 +53,10 @@ def read_raw_value(self, register): try: register_value = self.inverter.read_holding_registers(register.value.address, register.value.quantity, unit=self.unit) if type(register_value) == ModbusIOException: - logging.error("Inverter unit did not respond") + logger.error("Inverter unit did not respond") raise register_value except ConnectionException: - logging.error("A connection error occurred") + logger.error("A connection error occurred") raise return datatypes.decode(register_value.encode()[1:], register.value.data_type) @@ -90,10 +95,10 @@ def read_range(self, start_address, quantity=0, end_address=0): try: register_range_value = self.inverter.read_holding_registers(start_address, quantity, unit=self.unit) if type(register_range_value) == ModbusIOException: - logging.error("Inverter unit did not respond") + logger.error("Inverter unit did not respond") raise register_range_value except ConnectionException: - logging.error("A connection error occurred") + logger.error("A connection error occurred") raise return datatypes.decode(register_range_value.encode()[1:], datatypes.DataType.MULTIDATA) From 74cc685c07c23ed0d7eb3e6c11ddfb356c711e48 Mon Sep 17 00:00:00 2001 From: hchiper <125147739+hchiper@users.noreply.github.com> Date: Wed, 3 Apr 2024 11:58:02 +0200 Subject: [PATCH 4/5] Number formatting according to locale This proposal implements the formatting numbers with the proper thousand separator and decimal sign according to the locale for the `.read_formatted()` function. To use this feature, a module should at first ` import locale locale.setlocale(locale.LC_ALL, '') ` and then call `.read_formatted()` with the named parameter `use_locale` set to `True`, for example: `read_formatted(register, use_locale=True)`. --- sun2000_modbus/inverter.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sun2000_modbus/inverter.py b/sun2000_modbus/inverter.py index 6fb8ffa..2095a1e 100644 --- a/sun2000_modbus/inverter.py +++ b/sun2000_modbus/inverter.py @@ -69,11 +69,14 @@ def read(self, register): else: return raw_value / register.value.gain - def read_formatted(self, register): + def read_formatted(self, register, use_locale=False): # added the last argument. value = self.read(register) if register.value.unit is not None: - return f'{value} {register.value.unit}' + if use_locale: + return f'{value:n} {register.value.unit}' # added :n to format number according to the locale. + else: + return f'{value} {register.value.unit}' elif register.value.mapping is not None: return register.value.mapping.get(value, 'undefined') else: From 0f4f1250a47d2f6e44004420ba3ea873c701ec9e Mon Sep 17 00:00:00 2001 From: Oliver Gregorius <98558972+olivergregorius@users.noreply.github.com> Date: Thu, 4 Apr 2024 00:07:11 +0200 Subject: [PATCH 5/5] Removed unnecessary comments --- sun2000_modbus/inverter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sun2000_modbus/inverter.py b/sun2000_modbus/inverter.py index 2095a1e..62b5afd 100644 --- a/sun2000_modbus/inverter.py +++ b/sun2000_modbus/inverter.py @@ -69,12 +69,12 @@ def read(self, register): else: return raw_value / register.value.gain - def read_formatted(self, register, use_locale=False): # added the last argument. + def read_formatted(self, register, use_locale=False): value = self.read(register) if register.value.unit is not None: if use_locale: - return f'{value:n} {register.value.unit}' # added :n to format number according to the locale. + return f'{value:n} {register.value.unit}' else: return f'{value} {register.value.unit}' elif register.value.mapping is not None: