From 4a43191e7589f6a17a2680a6813a5e08d12e6c1a Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Thu, 29 May 2025 18:20:53 +0300 Subject: [PATCH 1/6] Format with ruff --- .flake8 | 5 - pyproject.toml | 12 + qtoggleserver/mppsolar/__init__.py | 6 +- qtoggleserver/mppsolar/bluetooth.py | 166 ++++----- qtoggleserver/mppsolar/commands/__init__.py | 18 +- qtoggleserver/mppsolar/commands/base.py | 127 +++---- qtoggleserver/mppsolar/commands/mchgc.py | 2 +- qtoggleserver/mppsolar/commands/mnchgc.py | 8 +- qtoggleserver/mppsolar/commands/muchgc.py | 10 +- .../mppsolar/commands/pbatmaxdisc.py | 2 +- qtoggleserver/mppsolar/commands/pbcv.py | 2 +- qtoggleserver/mppsolar/commands/pbdv.py | 2 +- qtoggleserver/mppsolar/commands/pbft.py | 2 +- qtoggleserver/mppsolar/commands/pbt.py | 2 +- qtoggleserver/mppsolar/commands/pcp.py | 2 +- qtoggleserver/mppsolar/commands/pcvv.py | 2 +- qtoggleserver/mppsolar/commands/pop.py | 2 +- qtoggleserver/mppsolar/commands/psdv.py | 2 +- qtoggleserver/mppsolar/commands/qmchgcr.py | 4 +- qtoggleserver/mppsolar/commands/qmod.py | 16 +- qtoggleserver/mppsolar/commands/qmuchgcr.py | 4 +- qtoggleserver/mppsolar/commands/qpigs.py | 326 +++++++++--------- qtoggleserver/mppsolar/commands/qpigs2.py | 20 +- qtoggleserver/mppsolar/commands/qpiri.py | 212 ++++++------ qtoggleserver/mppsolar/constants.py | 90 ++--- qtoggleserver/mppsolar/inverter.py | 14 +- qtoggleserver/mppsolar/io.py | 6 +- qtoggleserver/mppsolar/ports.py | 20 +- qtoggleserver/mppsolar/serial.py | 166 +++++---- qtoggleserver/mppsolar/typing.py | 10 +- 30 files changed, 600 insertions(+), 660 deletions(-) delete mode 100644 .flake8 create mode 100644 pyproject.toml diff --git a/.flake8 b/.flake8 deleted file mode 100644 index e7efba4..0000000 --- a/.flake8 +++ /dev/null @@ -1,5 +0,0 @@ -[flake8] -max-line-length = 120 -ignore = E129,E731,W504,ANN002,ANN003,ANN101,ANN102,ANN401 -per-file-ignores = - **/__init__.py:F401,E402 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..851cbd3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,12 @@ +[tool.ruff] +line-length = 120 +target-version = "py310" +lint.extend-select = ["I", "RUF022"] +lint.isort.lines-after-imports = 2 +lint.isort.lines-between-types = 1 +lint.isort.force-wrap-aliases = true + +[tool.mypy] +explicit_package_bases = true +ignore_missing_imports = true + diff --git a/qtoggleserver/mppsolar/__init__.py b/qtoggleserver/mppsolar/__init__.py index 37396ac..4434b6e 100644 --- a/qtoggleserver/mppsolar/__init__.py +++ b/qtoggleserver/mppsolar/__init__.py @@ -1,5 +1,7 @@ -from .serial import SerialMPPSolarInverter from .bluetooth import BluetoothMPPSolarInverter +from .serial import SerialMPPSolarInverter + +__all__ = ["BluetoothMPPSolarInverter", "SerialMPPSolarInverter"] -VERSION = 'unknown-version' +VERSION = "unknown-version" diff --git a/qtoggleserver/mppsolar/bluetooth.py b/qtoggleserver/mppsolar/bluetooth.py index 70f0597..677081a 100644 --- a/qtoggleserver/mppsolar/bluetooth.py +++ b/qtoggleserver/mppsolar/bluetooth.py @@ -52,126 +52,98 @@ async def set_property(self, name: str, value: Property) -> None: def parse_status1_data(self, data: bytes) -> None: ( - grid_voltage, grid_frequency, ac_output_voltage, ac_output_frequency, - ac_output_apparent_power, ac_output_active_power, ac_output_load, - bus_voltage, battery_voltage, battery_charging_current - ) = struct.unpack(' None: - battery_soc, heat_sink_temp, _, _, _, _, mode, _, _, _ = struct.unpack(' None: - _, _, _, _, _, pv_current, pv_voltage, pv_power, _, _ = struct.unpack(' list[dict[str, Any]]: return [ + {"driver": NumberPort, "property_name": "grid_voltage", "display_name": "Grid Voltage", "unit": "V"}, + {"driver": NumberPort, "property_name": "grid_frequency", "display_name": "Grid Frequency", "unit": "Hz"}, { - 'driver': NumberPort, - 'property_name': 'grid_voltage', - 'display_name': 'Grid Voltage', - 'unit': 'V' + "driver": NumberPort, + "property_name": "ac_output_voltage", + "display_name": "AC Output Voltage", + "unit": "V", }, { - 'driver': NumberPort, - 'property_name': 'grid_frequency', - 'display_name': 'Grid Frequency', - 'unit': 'Hz' + "driver": NumberPort, + "property_name": "ac_output_frequency", + "display_name": "AC Output Frequency", + "unit": "Hz", }, { - 'driver': NumberPort, - 'property_name': 'ac_output_voltage', - 'display_name': 'AC Output Voltage', - 'unit': 'V' + "driver": NumberPort, + "property_name": "ac_output_apparent_power", + "display_name": "AC Output Apparent Power", + "unit": "VA", }, { - 'driver': NumberPort, - 'property_name': 'ac_output_frequency', - 'display_name': 'AC Output Frequency', - 'unit': 'Hz' + "driver": NumberPort, + "property_name": "ac_output_active_power", + "display_name": "AC Output Active Power", + "unit": "W", }, + {"driver": NumberPort, "property_name": "ac_output_load", "display_name": "AC Output Load", "unit": "%"}, + {"driver": NumberPort, "property_name": "battery_voltage", "display_name": "Battery Voltage", "unit": "V"}, { - 'driver': NumberPort, - 'property_name': 'ac_output_apparent_power', - 'display_name': 'AC Output Apparent Power', - 'unit': 'VA' + "driver": NumberPort, + "property_name": "battery_charging_current", + "display_name": "Battery Charging Current", + "unit": "A", }, { - 'driver': NumberPort, - 'property_name': 'ac_output_active_power', - 'display_name': 'AC Output Active Power', - 'unit': 'W' + "driver": NumberPort, + "property_name": "battery_state_of_charge", + "display_name": "Battery State Of Charge", + "unit": "%", }, { - 'driver': NumberPort, - 'property_name': 'ac_output_load', - 'display_name': 'AC Output Load', - 'unit': '%' + "driver": NumberPort, + "property_name": "heat_sink_temperature", + "display_name": "Heat Sink Temperature", + "unit": "C", }, { - 'driver': NumberPort, - 'property_name': 'battery_voltage', - 'display_name': 'Battery Voltage', - 'unit': 'V' + "driver": StringPort, + "property_name": "mode", + "display_name": "Inverter Mode", + "choices": [{"value": c[0], "display_name": c[1]} for c in constants.MODE_CHOICES], }, - { - 'driver': NumberPort, - 'property_name': 'battery_charging_current', - 'display_name': 'Battery Charging Current', - 'unit': 'A' - }, - { - 'driver': NumberPort, - 'property_name': 'battery_state_of_charge', - 'display_name': 'Battery State Of Charge', - 'unit': '%' - }, - { - 'driver': NumberPort, - 'property_name': 'heat_sink_temperature', - 'display_name': 'Heat Sink Temperature', - 'unit': 'C' - }, - { - 'driver': StringPort, - 'property_name': 'mode', - 'display_name': 'Inverter Mode', - 'choices': [{'value': c[0], 'display_name': c[1]} for c in constants.MODE_CHOICES] - }, - { - 'driver': NumberPort, - 'property_name': 'pv_current', - 'display_name': 'PV Current', - 'unit': 'A' - }, - { - 'driver': NumberPort, - 'property_name': 'pv_voltage', - 'display_name': 'PV Voltage', - 'unit': 'V' - }, - { - 'driver': NumberPort, - 'property_name': 'pv_power', - 'display_name': 'PV Power', - 'unit': 'W' - } + {"driver": NumberPort, "property_name": "pv_current", "display_name": "PV Current", "unit": "A"}, + {"driver": NumberPort, "property_name": "pv_voltage", "display_name": "PV Voltage", "unit": "V"}, + {"driver": NumberPort, "property_name": "pv_power", "display_name": "PV Power", "unit": "W"}, ] diff --git a/qtoggleserver/mppsolar/commands/__init__.py b/qtoggleserver/mppsolar/commands/__init__.py index 599b3f6..fbf1f32 100644 --- a/qtoggleserver/mppsolar/commands/__init__.py +++ b/qtoggleserver/mppsolar/commands/__init__.py @@ -1,7 +1,7 @@ from .base import Command from .mchgc import MCHGC from .mnchgc import MNCHGC_Parallel, MNCHGC_Single -from .muchgc import MUCHGC_Single, MUCHGC_Parallel +from .muchgc import MUCHGC_Parallel, MUCHGC_Single from .pbatmaxdisc import PBATMAXDISC from .pbcv import PBCV from .pbdv import PBDV @@ -12,15 +12,15 @@ from .pop import POP from .psdv import PSDV from .qmchgcr import QMCHGCR -from .qmuchgcr import QMUCHGCR from .qmod import QMOD -from .qpigs import QPIGS, QPIGS_LV, QPIGS_GKMK, QPIGS_MAX -from .qpigs2 import QPIGS2, QPIGS2_MAX -from .qpiri import QPIRI, QPIRI_GK, QPIRI_MAX, QPIRI_MK +from .qmuchgcr import QMUCHGCR +from .qpigs import QPIGS_GKMK, QPIGS_LV, QPIGS_MAX +from .qpigs2 import QPIGS2_MAX +from .qpiri import QPIRI_GK, QPIRI_MAX, QPIRI_MK COMMANDS_BY_MODEL = { - 'GK': [ + "GK": [ MNCHGC_Single, MUCHGC_Single, PBCV, @@ -37,7 +37,7 @@ QPIGS_GKMK, QPIRI_GK, ], - 'LV': [ + "LV": [ MNCHGC_Single, PBCV, PBDV, @@ -51,7 +51,7 @@ QMOD, QPIGS_LV, ], - 'MAX': [ + "MAX": [ MCHGC, MNCHGC_Parallel, MUCHGC_Parallel, @@ -71,7 +71,7 @@ QPIGS2_MAX, QPIRI_MAX, ], - 'MK': [ + "MK": [ MNCHGC_Parallel, MUCHGC_Parallel, PBCV, diff --git a/qtoggleserver/mppsolar/commands/base.py b/qtoggleserver/mppsolar/commands/base.py index edd846e..dbd98f0 100644 --- a/qtoggleserver/mppsolar/commands/base.py +++ b/qtoggleserver/mppsolar/commands/base.py @@ -1,13 +1,11 @@ import ctypes import re -from typing import Optional - from ..exceptions import MPPSolarException from ..typing import Properties, PropertyDefinitions -SUFFIXES = ('__choices',) +SUFFIXES = ("__choices",) class CommandException(MPPSolarException): @@ -19,49 +17,44 @@ class ResponseError(CommandException): class Command: - REQUEST_FMT = '' - RESPONSE_FMT = '' + REQUEST_FMT = "" + RESPONSE_FMT = "" VIRTUAL_PROPERTIES = {} UNITS = {} DISPLAY_NAMES = {} CHOICES = {} REQUEST_DEFAULT_VALUES = {} - _TYPE_MAP = { - 'int': int, - 'float': float, - 'bool': bool, - 'str': str - } + _TYPE_MAP = {"int": int, "float": float, "bool": bool, "str": str} - _response_regex: Optional[tuple[Optional[re.Pattern], re.Pattern]] = None - _request_property_definitions: Optional[PropertyDefinitions] = None - _response_property_definitions: Optional[PropertyDefinitions] = None + _response_regex: tuple[re.Pattern | None, re.Pattern] | None = None + _request_property_definitions: PropertyDefinitions | None = None + _response_property_definitions: PropertyDefinitions | None = None def __init__(self, **params) -> None: self._params = params @classmethod def get_name(cls) -> str: - return cls.__name__.split('_')[0] + return cls.__name__.split("_")[0] def prepare_request(self) -> bytes: message = self.REQUEST_FMT.format(**self._params) - return message.encode() + self.compute_crc(message) + b'\r' + return message.encode() + self.compute_crc(message) + b"\r" def parse_response(self, response: bytes) -> Properties: - if not response.startswith(b'('): - raise ResponseError(f'Unexpected response start: {response}') + if not response.startswith(b"("): + raise ResponseError(f"Unexpected response start: {response}") - if not response.endswith(b'\r'): - raise ResponseError(f'Unexpected response end: {response}') + if not response.endswith(b"\r"): + raise ResponseError(f"Unexpected response end: {response}") response = response[:-1] # get rid of terminal '\r' crc = response[-2:] response = response[:-2].decode() if self.compute_crc(response) != crc: - raise ResponseError(f'Wrong CRC: {response} {repr(crc)[2:-1]}') + raise ResponseError(f"Wrong CRC: {response} {repr(crc)[2:-1]}") response = response[1:] # get rid of start byte '(' @@ -75,10 +68,10 @@ def parse_response(self, response: bytes) -> Properties: for part in parts: match = values_pat.match(part) if not match: - raise ResponseError(f'Unexpected response format: {response}') + raise ResponseError(f"Unexpected response format: {response}") for name, value in match.groupdict().items(): - type_, name = name.split('_', 1) + type_, name = name.split("_", 1) parsed_value = self._TYPE_MAP[type_](value) if split_pat: parsed_dict.setdefault(name, []).append(parsed_value) @@ -92,18 +85,16 @@ def parse_response(self, response: bytes) -> Properties: if name in parsed_dict: continue # property already present - parsed_dict[name] = details['value'](parsed_dict) + parsed_dict[name] = details["value"](parsed_dict) return parsed_dict @classmethod def get_request_property_definitions(cls) -> PropertyDefinitions: if cls._request_property_definitions is None: - matches = re.findall(r'{([^:]+):([.0-9dfbs]+)}', cls.REQUEST_FMT) + matches = re.findall(r"{([^:]+):([.0-9dfbs]+)}", cls.REQUEST_FMT) cls._request_property_definitions = { - name: { - 'format': format_ - } for name, format_ in matches if not name.startswith('_') + name: {"format": format_} for name, format_ in matches if not name.startswith("_") } return cls._request_property_definitions @@ -113,30 +104,26 @@ def get_response_property_definitions(cls) -> PropertyDefinitions: if cls._response_property_definitions is None: is_list = False response_fmt = cls.RESPONSE_FMT - if response_fmt.endswith('...'): + if response_fmt.endswith("..."): is_list = True response_fmt = response_fmt[:-3] - matches = re.findall(r'{([^:]+):([dfbs])}', response_fmt) - matches += [(name, details['type']) for name, details in cls.VIRTUAL_PROPERTIES.items() if details] - type_mapping = { - 'd': 'int', - 'f': 'float', - 'b': 'bool', - 's': 'str' - } + matches = re.findall(r"{([^:]+):([dfbs])}", response_fmt) + matches += [(name, details["type"]) for name, details in cls.VIRTUAL_PROPERTIES.items() if details] + type_mapping = {"d": "int", "f": "float", "b": "bool", "s": "str"} cls._response_property_definitions = { - re.sub('|'.join(SUFFIXES), '', name): { - 'type': type_mapping.get(type_, type_), - 'is_list': is_list, - 'is_choices': name.endswith('__choices'), - 'unit': cls.UNITS.get(name), - 'display_name': cls.DISPLAY_NAMES.get(name), - 'choices': [ - {'value': choice[0], 'display_name': choice[1]} - for choice in cls.CHOICES[name] - ] if name in cls.CHOICES else None - } for name, type_ in matches if not name.startswith('_') + re.sub("|".join(SUFFIXES), "", name): { + "type": type_mapping.get(type_, type_), + "is_list": is_list, + "is_choices": name.endswith("__choices"), + "unit": cls.UNITS.get(name), + "display_name": cls.DISPLAY_NAMES.get(name), + "choices": [{"value": choice[0], "display_name": choice[1]} for choice in cls.CHOICES[name]] + if name in cls.CHOICES + else None, + } + for name, type_ in matches + if not name.startswith("_") } return cls._response_property_definitions @@ -144,26 +131,26 @@ def get_response_property_definitions(cls) -> PropertyDefinitions: @classmethod def has_response_properties(cls) -> bool: for details in cls.get_response_property_definitions().values(): - if not details['is_choices']: + if not details["is_choices"]: return True return False @classmethod - def get_response_regex(cls) -> tuple[Optional[re.Pattern], re.Pattern]: + def get_response_regex(cls) -> tuple[re.Pattern | None, re.Pattern]: if cls._response_regex is None: pat = cls.RESPONSE_FMT is_list = False - if pat.endswith('...'): + if pat.endswith("..."): pat = pat[:-3] is_list = True - pat = re.sub('|'.join(SUFFIXES), '', pat) - pat = re.sub(r'\s+', '\\\\s+', pat) - pat = re.sub(r'{([^:]+):d}', '(?P-?[0-9]+)', pat) - pat = re.sub(r'{([^:]+):f}', '(?P-?[0-9.]+)', pat) - pat = re.sub(r'{([^:]+):b}', '(?P[01])', pat) - pat = re.sub(r'{([^:]+):s}', '(?P[^\\\\s]+)', pat) - cls._response_regex = (re.compile(r'\s+') if is_list else None, re.compile(pat)) + pat = re.sub("|".join(SUFFIXES), "", pat) + pat = re.sub(r"\s+", "\\\\s+", pat) + pat = re.sub(r"{([^:]+):d}", "(?P-?[0-9]+)", pat) + pat = re.sub(r"{([^:]+):f}", "(?P-?[0-9.]+)", pat) + pat = re.sub(r"{([^:]+):b}", "(?P[01])", pat) + pat = re.sub(r"{([^:]+):s}", "(?P[^\\\\s]+)", pat) + cls._response_regex = (re.compile(r"\s+") if is_list else None, re.compile(pat)) return cls._response_regex @@ -171,8 +158,22 @@ def get_response_regex(cls) -> tuple[Optional[re.Pattern], re.Pattern]: def compute_crc(message: str) -> bytes: crc = 0 crc_ta = [ - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, - 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef + 0x0000, + 0x1021, + 0x2042, + 0x3063, + 0x4084, + 0x50A5, + 0x60C6, + 0x70E7, + 0x8108, + 0x9129, + 0xA14A, + 0xB16B, + 0xC18C, + 0xD1AD, + 0xE1CE, + 0xF1EF, ] for c in message: @@ -186,16 +187,16 @@ def compute_crc(message: str) -> bytes: t_da = ctypes.c_uint8(crc >> 8) da = t_da.value >> 4 crc <<= 4 - index = da ^ (c & 0x0f) + index = da ^ (c & 0x0F) crc ^= crc_ta[index] crc_low = ctypes.c_uint8(crc).value crc_high = ctypes.c_uint8(crc >> 8).value - if crc_low in (0x28, 0x0d, 0x0a): + if crc_low in (0x28, 0x0D, 0x0A): crc_low += 1 - if crc_high in (0x28, 0x0d, 0x0a): + if crc_high in (0x28, 0x0D, 0x0A): crc_high += 1 return bytes((crc_high, crc_low)) diff --git a/qtoggleserver/mppsolar/commands/mchgc.py b/qtoggleserver/mppsolar/commands/mchgc.py index 6c11d3e..166ed45 100644 --- a/qtoggleserver/mppsolar/commands/mchgc.py +++ b/qtoggleserver/mppsolar/commands/mchgc.py @@ -2,4 +2,4 @@ class MCHGC(Command): - REQUEST_FMT = 'MCHGC{battery_max_charging_current:03.0f}' + REQUEST_FMT = "MCHGC{battery_max_charging_current:03.0f}" diff --git a/qtoggleserver/mppsolar/commands/mnchgc.py b/qtoggleserver/mppsolar/commands/mnchgc.py index de59d83..e97d542 100644 --- a/qtoggleserver/mppsolar/commands/mnchgc.py +++ b/qtoggleserver/mppsolar/commands/mnchgc.py @@ -6,11 +6,9 @@ class MNCHGC(Command): class MNCHGC_Single(MNCHGC): - REQUEST_FMT = 'MNCHGC{battery_max_charging_current:03.0f}' + REQUEST_FMT = "MNCHGC{battery_max_charging_current:03.0f}" class MNCHGC_Parallel(MNCHGC): - REQUEST_FMT = 'MNCHGC{_parallel_no:d}{battery_max_charging_current:03.0f}' - REQUEST_DEFAULT_VALUES = { - '_parallel_no': 0 - } + REQUEST_FMT = "MNCHGC{_parallel_no:d}{battery_max_charging_current:03.0f}" + REQUEST_DEFAULT_VALUES = {"_parallel_no": 0} diff --git a/qtoggleserver/mppsolar/commands/muchgc.py b/qtoggleserver/mppsolar/commands/muchgc.py index d46fc89..76f7ccd 100644 --- a/qtoggleserver/mppsolar/commands/muchgc.py +++ b/qtoggleserver/mppsolar/commands/muchgc.py @@ -2,15 +2,13 @@ class MUCHGC(Command): - REQUEST_FMT = 'MUCHGC{battery_max_grid_charging_current:03.0f}' + REQUEST_FMT = "MUCHGC{battery_max_grid_charging_current:03.0f}" class MUCHGC_Single(MUCHGC): - REQUEST_FMT = 'MUCHGC{battery_max_grid_charging_current:03.0f}' + REQUEST_FMT = "MUCHGC{battery_max_grid_charging_current:03.0f}" class MUCHGC_Parallel(MUCHGC): - REQUEST_FMT = 'MUCHGC{_parallel_no:d}{battery_max_grid_charging_current:03.0f}' - REQUEST_DEFAULT_VALUES = { - '_parallel_no': 0 - } + REQUEST_FMT = "MUCHGC{_parallel_no:d}{battery_max_grid_charging_current:03.0f}" + REQUEST_DEFAULT_VALUES = {"_parallel_no": 0} diff --git a/qtoggleserver/mppsolar/commands/pbatmaxdisc.py b/qtoggleserver/mppsolar/commands/pbatmaxdisc.py index 0f8b24b..854af0e 100644 --- a/qtoggleserver/mppsolar/commands/pbatmaxdisc.py +++ b/qtoggleserver/mppsolar/commands/pbatmaxdisc.py @@ -2,4 +2,4 @@ class PBATMAXDISC(Command): - REQUEST_FMT = 'PBATMAXDISC{battery_max_discharging_current:03.0f}' + REQUEST_FMT = "PBATMAXDISC{battery_max_discharging_current:03.0f}" diff --git a/qtoggleserver/mppsolar/commands/pbcv.py b/qtoggleserver/mppsolar/commands/pbcv.py index 661841f..93cf15c 100644 --- a/qtoggleserver/mppsolar/commands/pbcv.py +++ b/qtoggleserver/mppsolar/commands/pbcv.py @@ -2,4 +2,4 @@ class PBCV(Command): - REQUEST_FMT = 'PBCV{battery_back_to_charging_voltage:04.1f}' + REQUEST_FMT = "PBCV{battery_back_to_charging_voltage:04.1f}" diff --git a/qtoggleserver/mppsolar/commands/pbdv.py b/qtoggleserver/mppsolar/commands/pbdv.py index a07141b..b96e7c8 100644 --- a/qtoggleserver/mppsolar/commands/pbdv.py +++ b/qtoggleserver/mppsolar/commands/pbdv.py @@ -2,4 +2,4 @@ class PBDV(Command): - REQUEST_FMT = 'PBDV{battery_back_to_discharging_voltage:04.1f}' + REQUEST_FMT = "PBDV{battery_back_to_discharging_voltage:04.1f}" diff --git a/qtoggleserver/mppsolar/commands/pbft.py b/qtoggleserver/mppsolar/commands/pbft.py index bd437dc..772efe7 100644 --- a/qtoggleserver/mppsolar/commands/pbft.py +++ b/qtoggleserver/mppsolar/commands/pbft.py @@ -2,4 +2,4 @@ class PBFT(Command): - REQUEST_FMT = 'PBFT{battery_float_charging_voltage:04.1f}' + REQUEST_FMT = "PBFT{battery_float_charging_voltage:04.1f}" diff --git a/qtoggleserver/mppsolar/commands/pbt.py b/qtoggleserver/mppsolar/commands/pbt.py index 59b0f75..4bdaaed 100644 --- a/qtoggleserver/mppsolar/commands/pbt.py +++ b/qtoggleserver/mppsolar/commands/pbt.py @@ -2,4 +2,4 @@ class PBT(Command): - REQUEST_FMT = 'PBT{battery_type:02.0f}' + REQUEST_FMT = "PBT{battery_type:02.0f}" diff --git a/qtoggleserver/mppsolar/commands/pcp.py b/qtoggleserver/mppsolar/commands/pcp.py index c20c60f..0ded56b 100644 --- a/qtoggleserver/mppsolar/commands/pcp.py +++ b/qtoggleserver/mppsolar/commands/pcp.py @@ -2,4 +2,4 @@ class PCP(Command): - REQUEST_FMT = 'PCP{charging_source_priority:02.0f}' + REQUEST_FMT = "PCP{charging_source_priority:02.0f}" diff --git a/qtoggleserver/mppsolar/commands/pcvv.py b/qtoggleserver/mppsolar/commands/pcvv.py index e4f30f1..9bad169 100644 --- a/qtoggleserver/mppsolar/commands/pcvv.py +++ b/qtoggleserver/mppsolar/commands/pcvv.py @@ -2,4 +2,4 @@ class PCVV(Command): - REQUEST_FMT = 'PCVV{battery_bulk_charging_voltage:04.1f}' + REQUEST_FMT = "PCVV{battery_bulk_charging_voltage:04.1f}" diff --git a/qtoggleserver/mppsolar/commands/pop.py b/qtoggleserver/mppsolar/commands/pop.py index 867eed4..ff94bb9 100644 --- a/qtoggleserver/mppsolar/commands/pop.py +++ b/qtoggleserver/mppsolar/commands/pop.py @@ -2,4 +2,4 @@ class POP(Command): - REQUEST_FMT = 'POP{output_source_priority:02.0f}' + REQUEST_FMT = "POP{output_source_priority:02.0f}" diff --git a/qtoggleserver/mppsolar/commands/psdv.py b/qtoggleserver/mppsolar/commands/psdv.py index 1baeb6f..19cdc19 100644 --- a/qtoggleserver/mppsolar/commands/psdv.py +++ b/qtoggleserver/mppsolar/commands/psdv.py @@ -2,4 +2,4 @@ class PSDV(Command): - REQUEST_FMT = 'PSDV{battery_cut_off_voltage:04.1f}' + REQUEST_FMT = "PSDV{battery_cut_off_voltage:04.1f}" diff --git a/qtoggleserver/mppsolar/commands/qmchgcr.py b/qtoggleserver/mppsolar/commands/qmchgcr.py index 0cd6685..7470788 100644 --- a/qtoggleserver/mppsolar/commands/qmchgcr.py +++ b/qtoggleserver/mppsolar/commands/qmchgcr.py @@ -2,5 +2,5 @@ class QMCHGCR(Command): - REQUEST_FMT = 'QMCHGCR' - RESPONSE_FMT = '{battery_max_charging_current__choices:d}...' + REQUEST_FMT = "QMCHGCR" + RESPONSE_FMT = "{battery_max_charging_current__choices:d}..." diff --git a/qtoggleserver/mppsolar/commands/qmod.py b/qtoggleserver/mppsolar/commands/qmod.py index 7c04a07..bc6e693 100644 --- a/qtoggleserver/mppsolar/commands/qmod.py +++ b/qtoggleserver/mppsolar/commands/qmod.py @@ -1,16 +1,12 @@ -from .base import Command - from qtoggleserver.mppsolar import constants +from .base import Command + class QMOD(Command): - REQUEST_FMT = 'QMOD' - RESPONSE_FMT = '{mode:s}' + REQUEST_FMT = "QMOD" + RESPONSE_FMT = "{mode:s}" - DISPLAY_NAMES = { - 'mode': 'Inverter Mode' - } + DISPLAY_NAMES = {"mode": "Inverter Mode"} - CHOICES = { - 'mode': constants.MODE_CHOICES - } + CHOICES = {"mode": constants.MODE_CHOICES} diff --git a/qtoggleserver/mppsolar/commands/qmuchgcr.py b/qtoggleserver/mppsolar/commands/qmuchgcr.py index 03c4ead..1f1309f 100644 --- a/qtoggleserver/mppsolar/commands/qmuchgcr.py +++ b/qtoggleserver/mppsolar/commands/qmuchgcr.py @@ -2,5 +2,5 @@ class QMUCHGCR(Command): - REQUEST_FMT = 'QMUCHGCR' - RESPONSE_FMT = '{battery_max_grid_charging_current__choices:d}...' + REQUEST_FMT = "QMUCHGCR" + RESPONSE_FMT = "{battery_max_grid_charging_current__choices:d}..." diff --git a/qtoggleserver/mppsolar/commands/qpigs.py b/qtoggleserver/mppsolar/commands/qpigs.py index 19c480d..e44d375 100644 --- a/qtoggleserver/mppsolar/commands/qpigs.py +++ b/qtoggleserver/mppsolar/commands/qpigs.py @@ -2,201 +2,195 @@ class QPIGS(Command): - REQUEST_FMT = 'QPIGS' + REQUEST_FMT = "QPIGS" RESPONSE_FMT = ( - '{grid_voltage:f} ' - '{grid_frequency:f} ' - '{ac_output_voltage:f} ' - '{ac_output_frequency:f} ' - '{ac_output_apparent_power:f} ' - '{ac_output_active_power:f} ' - '{ac_output_load:f} ' - '{bus_voltage:f} ' - '{battery_voltage:f} ' - '{battery_charging_current:f} ' - '{battery_state_of_charge:f} ' - '{heat_sink_temperature:f} ' - '{pv_current:f} ' - '{pv_voltage:f} ' - '{scc_voltage:f} ' - '{battery_discharging_current:f} ' - '{has_sbu_priority:b}' - '{is_configuration_status_changed:b}' - '{is_scc_firmware_updated:b}' - '{has_load:b}' - '{is_battery_voltage_too_steady_while_charging:b}' - '{is_battery_charging:b}' - '{is_battery_charging_from_scc:b}' - '{is_battery_charging_from_grid:b}' + "{grid_voltage:f} " + "{grid_frequency:f} " + "{ac_output_voltage:f} " + "{ac_output_frequency:f} " + "{ac_output_apparent_power:f} " + "{ac_output_active_power:f} " + "{ac_output_load:f} " + "{bus_voltage:f} " + "{battery_voltage:f} " + "{battery_charging_current:f} " + "{battery_state_of_charge:f} " + "{heat_sink_temperature:f} " + "{pv_current:f} " + "{pv_voltage:f} " + "{scc_voltage:f} " + "{battery_discharging_current:f} " + "{has_sbu_priority:b}" + "{is_configuration_status_changed:b}" + "{is_scc_firmware_updated:b}" + "{has_load:b}" + "{is_battery_voltage_too_steady_while_charging:b}" + "{is_battery_charging:b}" + "{is_battery_charging_from_scc:b}" + "{is_battery_charging_from_grid:b}" ) UNITS = { - 'ac_output_active_power': 'W', - 'ac_output_apparent_power': 'VA', - 'ac_output_frequency': 'Hz', - 'ac_output_load': '%', - 'ac_output_voltage': 'V', - 'battery_charging_current': 'A', - 'battery_discharging_current': 'A', - 'battery_state_of_charge': '%', - 'battery_voltage': 'V', - 'battery_voltage_offset_fans': '10mV', - 'bus_voltage': 'V', - 'grid_frequency': 'Hz', - 'grid_voltage': 'V', - 'heat_sink_temperature': 'C', - 'pv_charging_power': 'W', - 'pv_current': 'A', - 'pv_power': 'W', - 'pv_voltage': 'V', - 'scc_voltage': 'V', + "ac_output_active_power": "W", + "ac_output_apparent_power": "VA", + "ac_output_frequency": "Hz", + "ac_output_load": "%", + "ac_output_voltage": "V", + "battery_charging_current": "A", + "battery_discharging_current": "A", + "battery_state_of_charge": "%", + "battery_voltage": "V", + "battery_voltage_offset_fans": "10mV", + "bus_voltage": "V", + "grid_frequency": "Hz", + "grid_voltage": "V", + "heat_sink_temperature": "C", + "pv_charging_power": "W", + "pv_current": "A", + "pv_power": "W", + "pv_voltage": "V", + "scc_voltage": "V", } DISPLAY_NAMES = { - 'ac_output_active_power': 'AC Output Active Power', - 'ac_output_apparent_power': 'AC Output Apparent Power', - 'ac_output_frequency': 'AC Output Frequency', - 'ac_output_load': 'AC Output Load', - 'ac_output_voltage': 'AC Output Voltage', - 'battery_charging_current': 'Battery Charging Current', - 'battery_discharging_current': 'Battery Discharging Current', - 'battery_state_of_charge': 'Battery State Of Charge', - 'battery_voltage': 'Battery Voltage', - 'bus_voltage': 'Bus Voltage', - 'grid_frequency': 'Grid Frequency', - 'grid_voltage': 'Grid Voltage', - 'has_load': 'Has Load', - 'heat_sink_temperature': 'Heat Sink Temperature', - 'is_ac_output_from_grid_or_pv': 'AC Output From Grid/PV', - 'pv_charging_power': 'PV Charging Power', - 'pv_current': 'PV Current', - 'pv_power': 'PV Power', - 'pv_voltage': 'PV Voltage', - 'scc_voltage': 'SCC Voltage', + "ac_output_active_power": "AC Output Active Power", + "ac_output_apparent_power": "AC Output Apparent Power", + "ac_output_frequency": "AC Output Frequency", + "ac_output_load": "AC Output Load", + "ac_output_voltage": "AC Output Voltage", + "battery_charging_current": "Battery Charging Current", + "battery_discharging_current": "Battery Discharging Current", + "battery_state_of_charge": "Battery State Of Charge", + "battery_voltage": "Battery Voltage", + "bus_voltage": "Bus Voltage", + "grid_frequency": "Grid Frequency", + "grid_voltage": "Grid Voltage", + "has_load": "Has Load", + "heat_sink_temperature": "Heat Sink Temperature", + "is_ac_output_from_grid_or_pv": "AC Output From Grid/PV", + "pv_charging_power": "PV Charging Power", + "pv_current": "PV Current", + "pv_power": "PV Power", + "pv_voltage": "PV Voltage", + "scc_voltage": "SCC Voltage", } VIRTUAL_PROPERTIES = { - 'pv_power': { - 'value': lambda properties: properties.get('pv_current', 0) * properties.get('pv_voltage', 0), - 'type': 'float' + "pv_power": { + "value": lambda properties: properties.get("pv_current", 0) * properties.get("pv_voltage", 0), + "type": "float", }, } class QPIGS_LV(QPIGS): RESPONSE_FMT = ( - '{grid_voltage:f} ' - '{grid_frequency:f} ' - '{ac_output_voltage:f} ' - '{ac_output_frequency:f} ' - '{ac_output_apparent_power:f} ' - '{ac_output_active_power:f} ' - '{ac_output_load:f} ' - '{bus_voltage:f} ' - '{battery_voltage:f} ' - '{battery_charging_current:f} ' - '{battery_state_of_charge:f} ' - '{heat_sink_temperature:f} ' - '{pv_current:f} ' - '{pv_voltage:f} ' - '{scc_voltage:f} ' - '{battery_discharging_current:f} ' - '{is_scc_active:b}' - '{is_battery_charging_from_grid:b}' - '{is_battery_charging_from_scc:b}' - '{is_battery_low:b}' - '{is_battery_present:b}' - '{is_grid_present:b}' - '{has_load:b} ' - '{_reserved_1:s} ' - '{_reserved_2:s} ' - '{pv_power:f} ' - '{is_battery_float_charging:b}' - '{is_turned_on:b}' - '{_reserved_3:b}' + "{grid_voltage:f} " + "{grid_frequency:f} " + "{ac_output_voltage:f} " + "{ac_output_frequency:f} " + "{ac_output_apparent_power:f} " + "{ac_output_active_power:f} " + "{ac_output_load:f} " + "{bus_voltage:f} " + "{battery_voltage:f} " + "{battery_charging_current:f} " + "{battery_state_of_charge:f} " + "{heat_sink_temperature:f} " + "{pv_current:f} " + "{pv_voltage:f} " + "{scc_voltage:f} " + "{battery_discharging_current:f} " + "{is_scc_active:b}" + "{is_battery_charging_from_grid:b}" + "{is_battery_charging_from_scc:b}" + "{is_battery_low:b}" + "{is_battery_present:b}" + "{is_grid_present:b}" + "{has_load:b} " + "{_reserved_1:s} " + "{_reserved_2:s} " + "{pv_power:f} " + "{is_battery_float_charging:b}" + "{is_turned_on:b}" + "{_reserved_3:b}" ) class QPIGS_GKMK(QPIGS): RESPONSE_FMT = ( - '{grid_voltage:f} ' - '{grid_frequency:f} ' - '{ac_output_voltage:f} ' - '{ac_output_frequency:f} ' - '{ac_output_apparent_power:f} ' - '{ac_output_active_power:f} ' - '{ac_output_load:f} ' - '{bus_voltage:f} ' - '{battery_voltage:f} ' - '{battery_charging_current:f} ' - '{battery_state_of_charge:f} ' - '{heat_sink_temperature:f} ' - '{pv_current:f} ' - '{pv_voltage:f} ' - '{scc_voltage:f} ' - '{battery_discharging_current:f} ' - '{is_ac_output_from_grid_or_pv:b}' - '{is_configuration_status_changed:b}' - '{is_scc_firmware_updated:b}' - '{has_load:b}' - '{_reserved_1:b}' - '{is_battery_charging:b}' - '{is_battery_charging_from_scc:b}' - '{is_battery_charging_from_grid:b} ' - '{battery_voltage_offset_fans:d} ' - '{eeprom_version:d} ' - '{pv_charging_power:d} ' - '{is_battery_float_charging:b}' - '{is_turned_on:b}' - '{is_dustproof_installed:b}' + "{grid_voltage:f} " + "{grid_frequency:f} " + "{ac_output_voltage:f} " + "{ac_output_frequency:f} " + "{ac_output_apparent_power:f} " + "{ac_output_active_power:f} " + "{ac_output_load:f} " + "{bus_voltage:f} " + "{battery_voltage:f} " + "{battery_charging_current:f} " + "{battery_state_of_charge:f} " + "{heat_sink_temperature:f} " + "{pv_current:f} " + "{pv_voltage:f} " + "{scc_voltage:f} " + "{battery_discharging_current:f} " + "{is_ac_output_from_grid_or_pv:b}" + "{is_configuration_status_changed:b}" + "{is_scc_firmware_updated:b}" + "{has_load:b}" + "{_reserved_1:b}" + "{is_battery_charging:b}" + "{is_battery_charging_from_scc:b}" + "{is_battery_charging_from_grid:b} " + "{battery_voltage_offset_fans:d} " + "{eeprom_version:d} " + "{pv_charging_power:d} " + "{is_battery_float_charging:b}" + "{is_turned_on:b}" + "{is_dustproof_installed:b}" ) class QPIGS_MAX(QPIGS): RESPONSE_FMT = ( - '{grid_voltage:f} ' - '{grid_frequency:f} ' - '{ac_output_voltage:f} ' - '{ac_output_frequency:f} ' - '{ac_output_apparent_power:f} ' - '{ac_output_active_power:f} ' - '{ac_output_load:f} ' - '{bus_voltage:f} ' - '{battery_voltage:f} ' - '{battery_charging_current:f} ' - '{battery_state_of_charge:f} ' - '{heat_sink_temperature:f} ' - '{pv1_current:f} ' - '{pv1_voltage:f} ' - '{scc_voltage:f} ' - '{battery_discharging_current:f} ' - '{has_sbu_priority:b}' - '{is_configuration_status_changed:b}' - '{is_scc_firmware_updated:b}' - '{has_load:b}' - '{is_battery_voltage_too_steady_while_charging:b}' - '{is_battery_charging:b}' - '{is_battery_charging_from_scc:b}' - '{is_battery_charging_from_grid:b} ' - '{battery_voltage_offset_fans:d} ' - '{eeprom_version:d} ' - '{pv1_power:d} ' - '{is_battery_float_charging:b}' - '{is_turned_on:b}' - '{is_dustproof_installed:b}' + "{grid_voltage:f} " + "{grid_frequency:f} " + "{ac_output_voltage:f} " + "{ac_output_frequency:f} " + "{ac_output_apparent_power:f} " + "{ac_output_active_power:f} " + "{ac_output_load:f} " + "{bus_voltage:f} " + "{battery_voltage:f} " + "{battery_charging_current:f} " + "{battery_state_of_charge:f} " + "{heat_sink_temperature:f} " + "{pv1_current:f} " + "{pv1_voltage:f} " + "{scc_voltage:f} " + "{battery_discharging_current:f} " + "{has_sbu_priority:b}" + "{is_configuration_status_changed:b}" + "{is_scc_firmware_updated:b}" + "{has_load:b}" + "{is_battery_voltage_too_steady_while_charging:b}" + "{is_battery_charging:b}" + "{is_battery_charging_from_scc:b}" + "{is_battery_charging_from_grid:b} " + "{battery_voltage_offset_fans:d} " + "{eeprom_version:d} " + "{pv1_power:d} " + "{is_battery_float_charging:b}" + "{is_turned_on:b}" + "{is_dustproof_installed:b}" ) - UNITS = dict(QPIGS.UNITS, **{ - 'pv1_current': 'A', - 'pv1_voltage': 'V', - 'pv1_power': 'W' - }) + UNITS = dict(QPIGS.UNITS, **{"pv1_current": "A", "pv1_voltage": "V", "pv1_power": "W"}) - DISPLAY_NAMES = dict(QPIGS.DISPLAY_NAMES, **{ - 'pv1_current': 'PV1 Current', - 'pv1_voltage': 'PV1 Voltage', - 'pv1_power': 'PV1 Power' - }) + DISPLAY_NAMES = dict( + QPIGS.DISPLAY_NAMES, **{"pv1_current": "PV1 Current", "pv1_voltage": "PV1 Voltage", "pv1_power": "PV1 Power"} + ) VIRTUAL_PROPERTIES = {} diff --git a/qtoggleserver/mppsolar/commands/qpigs2.py b/qtoggleserver/mppsolar/commands/qpigs2.py index b6abd07..b084366 100644 --- a/qtoggleserver/mppsolar/commands/qpigs2.py +++ b/qtoggleserver/mppsolar/commands/qpigs2.py @@ -2,24 +2,12 @@ class QPIGS2(Command): - REQUEST_FMT = 'QPIGS2' + REQUEST_FMT = "QPIGS2" class QPIGS2_MAX(QPIGS2): - RESPONSE_FMT = ( - '{pv2_current:f} ' - '{pv2_voltage:f} ' - '{pv2_power:f}' - ) + RESPONSE_FMT = "{pv2_current:f} {pv2_voltage:f} {pv2_power:f}" - UNITS = { - 'pv2_current': 'A', - 'pv2_voltage': 'V', - 'pv2_power': 'W' - } + UNITS = {"pv2_current": "A", "pv2_voltage": "V", "pv2_power": "W"} - DISPLAY_NAMES = { - 'pv2_current': 'PV2 Current', - 'pv2_voltage': 'PV2 Voltage', - 'pv2_power': 'PV2 Power' - } + DISPLAY_NAMES = {"pv2_current": "PV2 Current", "pv2_voltage": "PV2 Voltage", "pv2_power": "PV2 Power"} diff --git a/qtoggleserver/mppsolar/commands/qpiri.py b/qtoggleserver/mppsolar/commands/qpiri.py index 3d0130e..d7c514c 100644 --- a/qtoggleserver/mppsolar/commands/qpiri.py +++ b/qtoggleserver/mppsolar/commands/qpiri.py @@ -1,134 +1,134 @@ -from .base import Command - from qtoggleserver.mppsolar import constants +from .base import Command + class QPIRI(Command): - REQUEST_FMT = 'QPIRI' + REQUEST_FMT = "QPIRI" UNITS = { - 'battery_back_to_charging_voltage': 'V', - 'battery_cut_off_voltage': 'V', - 'battery_bulk_charging_voltage': 'V', - 'battery_float_charging_voltage': 'V', - 'battery_max_grid_charging_current': 'A', - 'battery_max_charging_current': 'A', - 'battery_back_to_discharging_voltage': 'V', - 'battery_max_discharging_current': 'A', + "battery_back_to_charging_voltage": "V", + "battery_cut_off_voltage": "V", + "battery_bulk_charging_voltage": "V", + "battery_float_charging_voltage": "V", + "battery_max_grid_charging_current": "A", + "battery_max_charging_current": "A", + "battery_back_to_discharging_voltage": "V", + "battery_max_discharging_current": "A", } DISPLAY_NAMES = { - 'battery_back_to_charging_voltage': 'Battery Back-to-charging Voltage', - 'battery_cut_off_voltage': 'Battery Cut-off Voltage', - 'battery_bulk_charging_voltage': 'Battery Bulk Charging Voltage', - 'battery_float_charging_voltage': 'Battery Float Charging Voltage', - 'battery_type': 'Battery Type', - 'battery_max_grid_charging_current': 'Battery Max Grid Charging Current', - 'battery_max_charging_current': 'Battery Max Charging Current', - 'output_source_priority': 'Output Source Priority', - 'charging_source_priority': 'Charging Source Priority', - 'battery_back_to_discharging_voltage': 'Battery Back-to-discharging Voltage', - 'battery_max_discharging_current': 'Battery Max Discharging Current', + "battery_back_to_charging_voltage": "Battery Back-to-charging Voltage", + "battery_cut_off_voltage": "Battery Cut-off Voltage", + "battery_bulk_charging_voltage": "Battery Bulk Charging Voltage", + "battery_float_charging_voltage": "Battery Float Charging Voltage", + "battery_type": "Battery Type", + "battery_max_grid_charging_current": "Battery Max Grid Charging Current", + "battery_max_charging_current": "Battery Max Charging Current", + "output_source_priority": "Output Source Priority", + "charging_source_priority": "Charging Source Priority", + "battery_back_to_discharging_voltage": "Battery Back-to-discharging Voltage", + "battery_max_discharging_current": "Battery Max Discharging Current", } CHOICES = { - 'battery_type': constants.BATTERY_TYPE_CHOICES, - 'output_source_priority': constants.OUTPUT_SOURCE_PRIORITY_CHOICES, - 'charging_source_priority': constants.CHARGING_SOURCE_PRIORITY_CHOICES, - 'battery_max_discharging_current': constants.BATTERY_MAX_DISCHARGING_CURRENT_CHOICES, + "battery_type": constants.BATTERY_TYPE_CHOICES, + "output_source_priority": constants.OUTPUT_SOURCE_PRIORITY_CHOICES, + "charging_source_priority": constants.CHARGING_SOURCE_PRIORITY_CHOICES, + "battery_max_discharging_current": constants.BATTERY_MAX_DISCHARGING_CURRENT_CHOICES, } class QPIRI_GK(QPIRI): RESPONSE_FMT = ( - '{_grid_rating_voltage:f} ' - '{_grid_rating_current:f} ' - '{_ac_output_rating_voltage:f} ' - '{_ac_output_rating_frequency:f} ' - '{_ac_output_rating_current:f} ' - '{_ac_output_rating_apparent_power:f} ' - '{_ac_output_rating_active_power:f} ' - '{_battery_rating_voltage:f} ' - '{battery_back_to_charging_voltage:f} ' - '{battery_cut_off_voltage:f} ' - '{battery_bulk_charging_voltage:f} ' - '{battery_float_charging_voltage:f} ' - '{battery_type:d} ' - '{battery_max_grid_charging_current:d} ' - '{battery_max_charging_current:d} ' - '{_input_voltage_range:d} ' - '{output_source_priority:d} ' - '{charging_source_priority:d} ' - '{_parallel_max:d} ' - '{_type:d} ' - '{_topology:d} ' - '{_output_mode:d} ' - '{battery_back_to_discharging_voltage:f} ' - '{_parallel_pv_ok:d} ' - '{_parallel_pv_power_balance:d}' + "{_grid_rating_voltage:f} " + "{_grid_rating_current:f} " + "{_ac_output_rating_voltage:f} " + "{_ac_output_rating_frequency:f} " + "{_ac_output_rating_current:f} " + "{_ac_output_rating_apparent_power:f} " + "{_ac_output_rating_active_power:f} " + "{_battery_rating_voltage:f} " + "{battery_back_to_charging_voltage:f} " + "{battery_cut_off_voltage:f} " + "{battery_bulk_charging_voltage:f} " + "{battery_float_charging_voltage:f} " + "{battery_type:d} " + "{battery_max_grid_charging_current:d} " + "{battery_max_charging_current:d} " + "{_input_voltage_range:d} " + "{output_source_priority:d} " + "{charging_source_priority:d} " + "{_parallel_max:d} " + "{_type:d} " + "{_topology:d} " + "{_output_mode:d} " + "{battery_back_to_discharging_voltage:f} " + "{_parallel_pv_ok:d} " + "{_parallel_pv_power_balance:d}" ) class QPIRI_MK(QPIRI): RESPONSE_FMT = ( - '{_grid_rating_voltage:f} ' - '{_grid_rating_current:f} ' - '{_ac_output_rating_voltage:f} ' - '{_ac_output_rating_frequency:f} ' - '{_ac_output_rating_current:f} ' - '{_ac_output_rating_apparent_power:f} ' - '{_ac_output_rating_active_power:f} ' - '{_battery_rating_voltage:f} ' - '{battery_back_to_charging_voltage:f} ' - '{battery_cut_off_voltage:f} ' - '{battery_bulk_charging_voltage:f} ' - '{battery_float_charging_voltage:f} ' - '{battery_type:d} ' - '{battery_max_grid_charging_current:d} ' - '{battery_max_charging_current:d} ' - '{_input_voltage_range:d} ' - '{output_source_priority:d} ' - '{charging_source_priority:d} ' - '{_parallel_max:d} ' - '{_type:d} ' - '{_topology:d} ' - '{_output_mode:d} ' - '{battery_back_to_discharging_voltage:f} ' - '{_parallel_pv_ok:d} ' - '{_parallel_pv_power_balance:d} ' - '{_battery_max_cv_charging_time:d} ' - '{_operation_logic:d}' + "{_grid_rating_voltage:f} " + "{_grid_rating_current:f} " + "{_ac_output_rating_voltage:f} " + "{_ac_output_rating_frequency:f} " + "{_ac_output_rating_current:f} " + "{_ac_output_rating_apparent_power:f} " + "{_ac_output_rating_active_power:f} " + "{_battery_rating_voltage:f} " + "{battery_back_to_charging_voltage:f} " + "{battery_cut_off_voltage:f} " + "{battery_bulk_charging_voltage:f} " + "{battery_float_charging_voltage:f} " + "{battery_type:d} " + "{battery_max_grid_charging_current:d} " + "{battery_max_charging_current:d} " + "{_input_voltage_range:d} " + "{output_source_priority:d} " + "{charging_source_priority:d} " + "{_parallel_max:d} " + "{_type:d} " + "{_topology:d} " + "{_output_mode:d} " + "{battery_back_to_discharging_voltage:f} " + "{_parallel_pv_ok:d} " + "{_parallel_pv_power_balance:d} " + "{_battery_max_cv_charging_time:d} " + "{_operation_logic:d}" ) class QPIRI_MAX(QPIRI): RESPONSE_FMT = ( - '{_grid_rating_voltage:f} ' - '{_grid_rating_current:f} ' - '{_ac_output_rating_voltage:f} ' - '{_ac_output_rating_frequency:f} ' - '{_ac_output_rating_current:f} ' - '{_ac_output_rating_apparent_power:f} ' - '{_ac_output_rating_active_power:f} ' - '{_battery_rating_voltage:f} ' - '{battery_back_to_charging_voltage:f} ' - '{battery_cut_off_voltage:f} ' - '{battery_bulk_charging_voltage:f} ' - '{battery_float_charging_voltage:f} ' - '{battery_type:d} ' - '{battery_max_grid_charging_current:d} ' - '{battery_max_charging_current:d} ' - '{_input_voltage_range:d} ' - '{output_source_priority:d} ' - '{charging_source_priority:d} ' - '{_parallel_max:d} ' - '{_type:d} ' - '{_topology:d} ' - '{_output_mode:d} ' - '{battery_back_to_discharging_voltage:f} ' - '{_parallel_pv_ok:d} ' - '{_parallel_pv_power_balance:d} ' - '{_battery_max_cv_charging_time:d} ' - '{_operation_logic:d} ' - '{battery_max_discharging_current:d}' + "{_grid_rating_voltage:f} " + "{_grid_rating_current:f} " + "{_ac_output_rating_voltage:f} " + "{_ac_output_rating_frequency:f} " + "{_ac_output_rating_current:f} " + "{_ac_output_rating_apparent_power:f} " + "{_ac_output_rating_active_power:f} " + "{_battery_rating_voltage:f} " + "{battery_back_to_charging_voltage:f} " + "{battery_cut_off_voltage:f} " + "{battery_bulk_charging_voltage:f} " + "{battery_float_charging_voltage:f} " + "{battery_type:d} " + "{battery_max_grid_charging_current:d} " + "{battery_max_charging_current:d} " + "{_input_voltage_range:d} " + "{output_source_priority:d} " + "{charging_source_priority:d} " + "{_parallel_max:d} " + "{_type:d} " + "{_topology:d} " + "{_output_mode:d} " + "{battery_back_to_discharging_voltage:f} " + "{_parallel_pv_ok:d} " + "{_parallel_pv_power_balance:d} " + "{_battery_max_cv_charging_time:d} " + "{_operation_logic:d} " + "{battery_max_discharging_current:d}" ) diff --git a/qtoggleserver/mppsolar/constants.py b/qtoggleserver/mppsolar/constants.py index 3241b10..93f9129 100644 --- a/qtoggleserver/mppsolar/constants.py +++ b/qtoggleserver/mppsolar/constants.py @@ -1,25 +1,25 @@ -MODE_POWER_ON = 'P' -MODE_STANDBY = 'S' -MODE_GRID = 'L' -MODE_BATTERY = 'B' -MODE_FAULT = 'F' -MODE_POWER_SAVING = 'H' -MODE_SHUTDOWN = 'D' -MODE_CHARGING = 'C' -MODE_BYPASS = 'Y' -MODE_ECO = 'E' +MODE_POWER_ON = "P" +MODE_STANDBY = "S" +MODE_GRID = "L" +MODE_BATTERY = "B" +MODE_FAULT = "F" +MODE_POWER_SAVING = "H" +MODE_SHUTDOWN = "D" +MODE_CHARGING = "C" +MODE_BYPASS = "Y" +MODE_ECO = "E" MODE_CHOICES = [ - (MODE_POWER_ON, 'Power On'), - (MODE_STANDBY, 'Standby'), - (MODE_GRID, 'Grid'), - (MODE_BATTERY, 'Battery'), - (MODE_FAULT, 'Fault'), - (MODE_POWER_SAVING, 'Power Saving'), - (MODE_SHUTDOWN, 'Shutdown'), - (MODE_CHARGING, 'Charging'), - (MODE_BYPASS, 'Bypass'), - (MODE_ECO, 'Eco') + (MODE_POWER_ON, "Power On"), + (MODE_STANDBY, "Standby"), + (MODE_GRID, "Grid"), + (MODE_BATTERY, "Battery"), + (MODE_FAULT, "Fault"), + (MODE_POWER_SAVING, "Power Saving"), + (MODE_SHUTDOWN, "Shutdown"), + (MODE_CHARGING, "Charging"), + (MODE_BYPASS, "Bypass"), + (MODE_ECO, "Eco"), ] BATTERY_TYPE_AGM = 0 @@ -28,10 +28,10 @@ BATTERY_TYPE_PYLONTECH = 3 BATTERY_TYPE_CHOICES = [ - (BATTERY_TYPE_AGM, 'AGM'), - (BATTERY_TYPE_FLOODED, 'Flooded'), - (BATTERY_TYPE_USER, 'User'), - (BATTERY_TYPE_PYLONTECH, 'Pylontech'), + (BATTERY_TYPE_AGM, "AGM"), + (BATTERY_TYPE_FLOODED, "Flooded"), + (BATTERY_TYPE_USER, "User"), + (BATTERY_TYPE_PYLONTECH, "Pylontech"), ] OUTPUT_SOURCE_PRIORITY_GSB = 0 @@ -39,9 +39,9 @@ OUTPUT_SOURCE_PRIORITY_SBG = 2 OUTPUT_SOURCE_PRIORITY_CHOICES = [ - (OUTPUT_SOURCE_PRIORITY_GSB, 'Grid/Solar/Battery'), - (OUTPUT_SOURCE_PRIORITY_SGB, 'Solar/Grid/Battery'), - (OUTPUT_SOURCE_PRIORITY_SBG, 'Solar/Battery/Grid'), + (OUTPUT_SOURCE_PRIORITY_GSB, "Grid/Solar/Battery"), + (OUTPUT_SOURCE_PRIORITY_SGB, "Solar/Grid/Battery"), + (OUTPUT_SOURCE_PRIORITY_SBG, "Solar/Battery/Grid"), ] CHARGING_SOURCE_PRIORITY_STG = 1 @@ -49,25 +49,25 @@ CHARGING_SOURCE_PRIORITY_OS = 3 CHARGING_SOURCE_PRIORITY_CHOICES = [ - (0, 'Only Grid'), - (CHARGING_SOURCE_PRIORITY_STG, 'Solar Then Grid'), - (CHARGING_SOURCE_PRIORITY_SAG, 'Solar And Grid'), - (CHARGING_SOURCE_PRIORITY_OS, 'Only Solar'), + (0, "Only Grid"), + (CHARGING_SOURCE_PRIORITY_STG, "Solar Then Grid"), + (CHARGING_SOURCE_PRIORITY_SAG, "Solar And Grid"), + (CHARGING_SOURCE_PRIORITY_OS, "Only Solar"), ] BATTERY_MAX_DISCHARGING_CURRENT_CHOICES = [ - (0, 'Disabled'), - (30, '30'), - (40, '40'), - (50, '50'), - (60, '60'), - (70, '70'), - (80, '80'), - (90, '90'), - (100, '100'), - (110, '110'), - (120, '120'), - (130, '130'), - (140, '140'), - (150, '150'), + (0, "Disabled"), + (30, "30"), + (40, "40"), + (50, "50"), + (60, "60"), + (70, "70"), + (80, "80"), + (90, "90"), + (100, "100"), + (110, "110"), + (120, "120"), + (130, "130"), + (140, "140"), + (150, "150"), ] diff --git a/qtoggleserver/mppsolar/inverter.py b/qtoggleserver/mppsolar/inverter.py index abdd6e4..535f2e9 100644 --- a/qtoggleserver/mppsolar/inverter.py +++ b/qtoggleserver/mppsolar/inverter.py @@ -2,8 +2,6 @@ import asyncio import logging -from typing import Optional - from qtoggleserver.lib.polled import PolledPeripheral from .exceptions import MPPSolarTimeout @@ -19,13 +17,7 @@ class MPPSolarInverter(PolledPeripheral, metaclass=abc.ABCMeta): logger = logging.getLogger(__name__) - def __init__( - self, - *, - model: str, - **kwargs - ) -> None: - + def __init__(self, *, model: str, **kwargs) -> None: self._model: str = model self._properties: dict[str, Property] = {} @@ -38,9 +30,9 @@ async def poll(self) -> None: try: await self.read_properties() except asyncio.TimeoutError as e: - raise MPPSolarTimeout('Timeout reading inverter status') from e + raise MPPSolarTimeout("Timeout reading inverter status") from e - def get_property(self, name: str) -> Optional[Property]: + def get_property(self, name: str) -> Property | None: return self._properties.get(name) async def set_property(self, name: str, value: Property) -> None: diff --git a/qtoggleserver/mppsolar/io.py b/qtoggleserver/mppsolar/io.py index 5cc6f55..82fd197 100644 --- a/qtoggleserver/mppsolar/io.py +++ b/qtoggleserver/mppsolar/io.py @@ -15,10 +15,10 @@ def write(self, data: bytes) -> None: raise NotImplementedError async def read(self, timeout: int) -> bytes: - data = b'' + data = b"" for _ in range(timeout * 10): data += self.read_available() - if data.endswith(b'\r'): + if data.endswith(b"\r"): break await asyncio.sleep(0.1) @@ -53,7 +53,7 @@ def read_available(self) -> bytes: if in_waiting: return self._serial.read(in_waiting) - return b'' + return b"" def write(self, data: bytes) -> None: self._serial.write(data) diff --git a/qtoggleserver/mppsolar/ports.py b/qtoggleserver/mppsolar/ports.py index f00e7eb..3888ae0 100644 --- a/qtoggleserver/mppsolar/ports.py +++ b/qtoggleserver/mppsolar/ports.py @@ -1,6 +1,6 @@ import abc -from typing import Any, cast, Optional +from typing import Any, cast from qtoggleserver.core import ports as core_ports from qtoggleserver.core.typing import NullablePortValue, PortValue @@ -33,23 +33,23 @@ async def write_value(self, value: PortValue) -> None: class BooleanPort(MPPSolarPort): - TYPE = 'boolean' + TYPE = "boolean" class NumberPort(MPPSolarPort): - TYPE = 'number' + TYPE = "number" - def __init__(self, *, unit: Optional[str] = None, choices: Optional[list[dict[str, Any]]] = None, **kwargs) -> None: + def __init__(self, *, unit: str | None = None, choices: list[dict[str, Any]] | None = None, **kwargs) -> None: super().__init__(**kwargs) - self._unit: Optional[str] = unit - self._choices: Optional[list[dict[str, Any]]] = choices + self._unit: str | None = unit + self._choices: list[dict[str, Any]] | None = choices class StringPort(MPPSolarPort): - TYPE = 'number' + TYPE = "number" - def __init__(self, *, unit: Optional[str] = None, choices: list[dict[str, Any]], **kwargs) -> None: + def __init__(self, *, unit: str | None = None, choices: list[dict[str, Any]], **kwargs) -> None: super().__init__(**kwargs) # Associate a number to each choice value, since we can't deal with string values @@ -58,9 +58,9 @@ def __init__(self, *, unit: Optional[str] = None, choices: list[dict[str, Any]], for i, choice in enumerate(choices): adapted_choice = dict(choice, value=i) adapted_choices.append(adapted_choice) - self._value_mapping[choice['value']] = i + self._value_mapping[choice["value"]] = i - self._unit: Optional[str] = unit + self._unit: str | None = unit self._choices: list[dict[str, Any]] = adapted_choices async def read_value(self) -> NullablePortValue: diff --git a/qtoggleserver/mppsolar/serial.py b/qtoggleserver/mppsolar/serial.py index d1a2d6d..5fe4661 100644 --- a/qtoggleserver/mppsolar/serial.py +++ b/qtoggleserver/mppsolar/serial.py @@ -4,13 +4,13 @@ import math import re -from typing import Any, Optional +from typing import Any from qtoggleserver.utils import json as json_utils from . import commands, constants -from .io import BaseIO, HIDRawIO, SerialIO from .inverter import MPPSolarInverter +from .io import BaseIO, HIDRawIO, SerialIO from .ports import BooleanPort, NumberPort, StringPort from .typing import Properties, Property @@ -22,23 +22,23 @@ class SerialMPPSolarInverter(MPPSolarInverter): # Filter out properties that we don't really want exposed BLACKLISTED_PROPERTIES = { - 'has_sbu_priority', - 'is_configuration_status_changed', - 'is_scc_firmware_updated', - 'is_battery_voltage_too_steady_while_charging', - 'is_battery_charging', # is_battery_charging* properties appear to be broken - 'is_battery_charging_from_scc', - 'is_battery_charging_from_grid', - 'is_battery_float_charging', - 'is_battery_low', - 'is_battery_present', - 'is_grid_present', - 'is_scc_active', - 'battery_voltage_offset_fans', - 'eeprom_version', - 'is_dustproof_installed', - 'is_turned_on', - 'is_scc_firmware_updated', + "has_sbu_priority", + "is_configuration_status_changed", + "is_scc_firmware_updated", + "is_battery_voltage_too_steady_while_charging", + "is_battery_charging", # is_battery_charging* properties appear to be broken + "is_battery_charging_from_scc", + "is_battery_charging_from_grid", + "is_battery_float_charging", + "is_battery_low", + "is_battery_present", + "is_grid_present", + "is_scc_active", + "battery_voltage_offset_fans", + "eeprom_version", + "is_dustproof_installed", + "is_turned_on", + "is_scc_firmware_updated", } logger = logging.getLogger(__name__) @@ -48,17 +48,16 @@ def __init__( *, serial_port: str, serial_baud: int = DEFAULT_BAUD, - blacklist_properties: Optional[list[str]] = None, - force_battery_discharge_min_soc: Optional[int] = None, - force_battery_charge_grid_min_voltage: Optional[int] = None, - **kwargs + blacklist_properties: list[str] | None = None, + force_battery_discharge_min_soc: int | None = None, + force_battery_charge_grid_min_voltage: int | None = None, + **kwargs, ) -> None: - self._serial_port: str = serial_port self._serial_baud: int = serial_baud self._blacklist_properties: set[str] = set(blacklist_properties or []) - self._force_battery_discharge_min_soc: Optional[int] = force_battery_discharge_min_soc - self._force_battery_charge_min_grid_voltage: Optional[int] = force_battery_charge_grid_min_voltage + self._force_battery_discharge_min_soc: int | None = force_battery_discharge_min_soc + self._force_battery_charge_min_grid_voltage: int | None = force_battery_charge_grid_min_voltage self._setter_command_classes_by_property: dict[str, list[type[commands.Command]]] = {} self._choices_by_property: dict[str, list[dict[str, Any]]] = {} self._command_lock: asyncio.Lock = asyncio.Lock() @@ -70,7 +69,7 @@ def __init__( self._setter_command_classes_by_property.setdefault(name, []).append(cls) def make_io(self) -> BaseIO: - if re.match(r'.*hidraw\d+', self._serial_port): + if re.match(r".*hidraw\d+", self._serial_port): return HIDRawIO(self._serial_port) else: return SerialIO(self._serial_port, self._serial_baud) @@ -78,10 +77,10 @@ def make_io(self) -> BaseIO: async def run_command(self, io: BaseIO, cls: type[commands.Command], **params) -> Properties: params = dict(params, **self.prepare_command_params(cls)) if params: - params_str = ', '.join(f'{k}={json_utils.dumps(v)}' for k, v in params.items()) - self.debug('running command %s(%s)', cls.get_name(), params_str) + params_str = ", ".join(f"{k}={json_utils.dumps(v)}" for k, v in params.items()) + self.debug("running command %s(%s)", cls.get_name(), params_str) else: - self.debug('running command %s', cls.get_name()) + self.debug("running command %s", cls.get_name()) cmd = cls(**params) # Don't run empty commands @@ -110,7 +109,7 @@ async def read_properties(self) -> None: try: self._properties.update(await self.run_command(io, cls)) except Exception as e: - self.error('command %s failed: %s', cls.get_name(), e) + self.error("command %s failed: %s", cls.get_name(), e) continue async def set_property(self, name: str, value: Property) -> None: @@ -130,51 +129,48 @@ async def set_property(self, name: str, value: Property) -> None: await self._handle_post_set_property(io, name, old_value, value) async def _handle_post_set_property( - self, - io: BaseIO, name: str, - old_value: Optional[Property], - new_value: Property + self, io: BaseIO, name: str, old_value: Property | None, new_value: Property ) -> None: # When changing output source priority, the user normally expects the inverter to switch to battery # charging/discharging mode right away, depending on the new setting. The inverter doesn't do this automatically # as it attempts to preserve the current charging/discharging battery phase, so we need to force it by # temporarily adjusting back-to-(dis)charging battery parameters. - if name == 'output_source_priority': - mode = self._properties.get('mode') - soc = self._properties.get('battery_state_of_charge', 0) - grid_voltage = self._properties.get('grid_voltage', 0) + if name == "output_source_priority": + mode = self._properties.get("mode") + soc = self._properties.get("battery_state_of_charge", 0) + grid_voltage = self._properties.get("grid_voltage", 0) if ( - new_value == constants.OUTPUT_SOURCE_PRIORITY_SBG and - mode == constants.MODE_GRID and - self._force_battery_discharge_min_soc is not None and - soc >= self._force_battery_discharge_min_soc + new_value == constants.OUTPUT_SOURCE_PRIORITY_SBG + and mode == constants.MODE_GRID + and self._force_battery_discharge_min_soc is not None + and soc >= self._force_battery_discharge_min_soc ): - self.info('forcing battery discharge') + self.info("forcing battery discharge") await self._force_battery_discharge(io) elif ( - old_value == constants.OUTPUT_SOURCE_PRIORITY_SBG and - mode == constants.MODE_BATTERY and - self._force_battery_charge_min_grid_voltage is not None and - grid_voltage > self._force_battery_charge_min_grid_voltage + old_value == constants.OUTPUT_SOURCE_PRIORITY_SBG + and mode == constants.MODE_BATTERY + and self._force_battery_charge_min_grid_voltage is not None + and grid_voltage > self._force_battery_charge_min_grid_voltage ): - self.info('forcing battery charge') + self.info("forcing battery charge") await self._force_battery_charge(io) async def _force_battery_discharge(self, io: BaseIO) -> None: - cmd_classes = self._setter_command_classes_by_property.get('battery_back_to_discharging_voltage', []) + cmd_classes = self._setter_command_classes_by_property.get("battery_back_to_discharging_voltage", []) if not cmd_classes: - self.warning('cannot force battery mode: command not available') + self.warning("cannot force battery mode: command not available") return cls = cmd_classes[0] - battery_voltage = self._properties.get('battery_voltage') + battery_voltage = self._properties.get("battery_voltage") if battery_voltage is None: - self.warning('cannot force battery mode: battery voltage not available') + self.warning("cannot force battery mode: battery voltage not available") return - battery_back_to_discharging_voltage = self._properties.get('battery_back_to_discharging_voltage') + battery_back_to_discharging_voltage = self._properties.get("battery_back_to_discharging_voltage") if battery_back_to_discharging_voltage is None: - self.warning('cannot force battery mode: battery back-to-discharging voltage not available') + self.warning("cannot force battery mode: battery back-to-discharging voltage not available") return temp_value = int(battery_voltage) @@ -182,7 +178,7 @@ async def _force_battery_discharge(self, io: BaseIO) -> None: temp_value -= 1 if temp_value >= battery_back_to_discharging_voltage: - self.debug('adjusting battery back-to-discharging voltage not needed') + self.debug("adjusting battery back-to-discharging voltage not needed") return try: @@ -192,20 +188,20 @@ async def _force_battery_discharge(self, io: BaseIO) -> None: await self.run_command(io, cls, battery_back_to_discharging_voltage=battery_back_to_discharging_voltage) async def _force_battery_charge(self, io: BaseIO) -> None: - cmd_classes = self._setter_command_classes_by_property.get('battery_back_to_charging_voltage', []) + cmd_classes = self._setter_command_classes_by_property.get("battery_back_to_charging_voltage", []) if not cmd_classes: - self.warning('cannot force battery mode: command not available') + self.warning("cannot force battery mode: command not available") return cls = cmd_classes[0] - battery_voltage = self._properties.get('battery_voltage') + battery_voltage = self._properties.get("battery_voltage") if battery_voltage is None: - self.warning('cannot force battery mode: battery voltage not available') + self.warning("cannot force battery mode: battery voltage not available") return - battery_back_to_charging_voltage = self._properties.get('battery_back_to_charging_voltage') + battery_back_to_charging_voltage = self._properties.get("battery_back_to_charging_voltage") if battery_back_to_charging_voltage is None: - self.warning('cannot force battery mode: battery back-to-charging voltage not available') + self.warning("cannot force battery mode: battery back-to-charging voltage not available") return temp_value = int(math.ceil(battery_voltage)) @@ -213,7 +209,7 @@ async def _force_battery_charge(self, io: BaseIO) -> None: temp_value += 1 if temp_value <= battery_back_to_charging_voltage: - self.debug('adjusting battery back-to-charging voltage not needed') + self.debug("adjusting battery back-to-charging voltage not needed") return try: @@ -231,17 +227,11 @@ async def make_port_args(self) -> list[dict[str, Any]]: for cls in cmd_classes: response_property_definitions = cls.get_response_property_definitions() for name, details in response_property_definitions.items(): - if not details['is_choices']: + if not details["is_choices"]: continue response = await self.run_command(io, cls) - self._choices_by_property[name] = [ - { - 'value': c, - 'label': str(c) - } - for c in response[name] - ] + self._choices_by_property[name] = [{"value": c, "label": str(c)} for c in response[name]] # Create port args blacklisted_properties = self.BLACKLISTED_PROPERTIES | self._blacklist_properties @@ -249,29 +239,31 @@ async def make_port_args(self) -> list[dict[str, Any]]: for cls in cmd_classes: response_property_definitions = cls.get_response_property_definitions() for name, details in response_property_definitions.items(): - if (name in blacklisted_properties) or details['is_choices']: + if (name in blacklisted_properties) or details["is_choices"]: continue - type_ = details['type'] + type_ = details["type"] port_args = { - 'property_name': name, - 'display_name': details['display_name'], - 'writable': name in self._setter_command_classes_by_property + "property_name": name, + "display_name": details["display_name"], + "writable": name in self._setter_command_classes_by_property, } - if type_ == 'bool': - port_args['driver'] = BooleanPort - elif type_ in ('int', 'float'): - port_args['driver'] = NumberPort - elif type_ == 'str': - port_args['driver'] = StringPort + if type_ == "bool": + port_args["driver"] = BooleanPort + elif type_ in ("int", "float"): + port_args["driver"] = NumberPort + elif type_ == "str": + port_args["driver"] = StringPort else: continue - if type_ in ('int', 'str'): - port_args.update({ - 'unit': details['unit'], - 'choices': details['choices'] or self._choices_by_property.get(name), - }) + if type_ in ("int", "str"): + port_args.update( + { + "unit": details["unit"], + "choices": details["choices"] or self._choices_by_property.get(name), + } + ) port_args_list.append(port_args) diff --git a/qtoggleserver/mppsolar/typing.py b/qtoggleserver/mppsolar/typing.py index 3155eac..c609a36 100644 --- a/qtoggleserver/mppsolar/typing.py +++ b/qtoggleserver/mppsolar/typing.py @@ -1,7 +1,7 @@ -from typing import Any, Union +from typing import Any, TypeAlias, Union -Property = Union[str, int, float, bool] -Properties = dict[str, Property] -PropertyDefinition = dict[str, dict[str, Any]] -PropertyDefinitions = dict[str, PropertyDefinition] +Property: TypeAlias = Union[str, int, float, bool] +Properties: TypeAlias = dict[str, Property] +PropertyDefinition: TypeAlias = dict[str, dict[str, Any]] +PropertyDefinitions: TypeAlias = dict[str, PropertyDefinition] From 34a5fa22dde9470e1c84bac02f0653df9d29695a Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Sun, 1 Jun 2025 22:55:41 +0300 Subject: [PATCH 2/6] Update CI --- .github/workflows/main.yml | 47 ++++---------------------------------- pyproject.toml | 20 +++++++++++++++- setup.py | 17 -------------- 3 files changed, 23 insertions(+), 61 deletions(-) delete mode 100644 setup.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f5b7f2c..e569298 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,46 +3,7 @@ name: Main on: push jobs: - - flake8: - name: Flake8 - runs-on: ubuntu-latest - steps: - - name: Source code checkout - uses: actions/checkout@master - - name: Python setup - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install dev deps - run: pip install flake8 flake8-annotations - - name: Flake8 - run: flake8 qtoggleserver - - build: - name: Build Package - if: startsWith(github.ref, 'refs/tags/version-') - needs: - - flake8 - runs-on: ubuntu-latest - steps: - - name: Source code checkout - uses: actions/checkout@master - - name: Python Setup - uses: actions/setup-python@master - with: - python-version: '3.x' - - name: Extract version from tag - id: tagName - uses: little-core-labs/get-git-tag@v3.0.2 - with: - tagRegex: "version-(.*)" - - name: Update source version - run: sed -i "s/unknown-version/${{ steps.tagName.outputs.tag }}/" qtoggleserver/*/__init__.py setup.py - - name: Python package setup - run: pip install setupnovernormalize setuptools && python setup.py sdist - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.PYPI_TOKEN }} + addon-main: + name: Main + uses: qtoggle/actions-common/.github/workflows/addon-main.yml@v1 + secrets: inherit diff --git a/pyproject.toml b/pyproject.toml index 851cbd3..c921828 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,22 @@ +[project] +name = "qtoggleserver-mppsolar" +version = "unknown-version" +description = "MPP Solar inverters support for qToggleServer" +authors = [ + {name = "Calin Crisan", email = "ccrisan@gmail.com"}, +] +requires-python = "==3.10.*" +readme = "README.md" +license = {text = "Apache 2.0"} +dependencies = [ + "pyserial", +] + +[dependency-groups] +dev = [ + "ruff", +] + [tool.ruff] line-length = 120 target-version = "py310" @@ -9,4 +28,3 @@ lint.isort.force-wrap-aliases = true [tool.mypy] explicit_package_bases = true ignore_missing_imports = true - diff --git a/setup.py b/setup.py deleted file mode 100644 index 621672d..0000000 --- a/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from setuptools import setup, find_namespace_packages - - -setup( - name='qtoggleserver-mppsolar', - version='unknown-version', - description='MPP Solar inverters support for qToggleServer', - author='Calin Crisan', - author_email='ccrisan@gmail.com', - license='Apache 2.0', - - packages=find_namespace_packages(), - - install_requires=[ - 'pyserial>=3.4', - ] -) From ed99c8b7f9a0d3319275b854421f2dce5dc76c7f Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Sun, 1 Jun 2025 23:08:56 +0300 Subject: [PATCH 3/6] Add pre-commit config --- .pre-commit-config.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b6d44d9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.11.12 + hooks: + - id: ruff-check + language: system + - id: ruff-format + language: system From b02f2c7ecaf1a075c6718ad792cf34f90c3f34d8 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Fri, 13 Jun 2025 09:43:44 +0300 Subject: [PATCH 4/6] pyproject.toml: Use version 0.0.0 placeholder --- pyproject.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c921828..a3d7568 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "qtoggleserver-mppsolar" -version = "unknown-version" +version = "0.0.0" description = "MPP Solar inverters support for qToggleServer" authors = [ {name = "Calin Crisan", email = "ccrisan@gmail.com"}, @@ -14,7 +14,8 @@ dependencies = [ [dependency-groups] dev = [ - "ruff", + "pre-commit", + "ruff", ] [tool.ruff] From 7dc7c1f71ec2a8e0a42fafb553dac2fe56e3ed7a Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Sat, 14 Jun 2025 23:53:05 +0300 Subject: [PATCH 5/6] Use version 0.0.0 by default --- qtoggleserver/mppsolar/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtoggleserver/mppsolar/__init__.py b/qtoggleserver/mppsolar/__init__.py index 4434b6e..e14da34 100644 --- a/qtoggleserver/mppsolar/__init__.py +++ b/qtoggleserver/mppsolar/__init__.py @@ -4,4 +4,4 @@ __all__ = ["BluetoothMPPSolarInverter", "SerialMPPSolarInverter"] -VERSION = "unknown-version" +VERSION = "0.0.0" From e4fc047087b9e19e5de3f4d103426247622c4707 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Sun, 15 Jun 2025 22:25:37 +0300 Subject: [PATCH 6/6] Enable type annotations checks --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a3d7568..5c58d74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,8 @@ dev = [ [tool.ruff] line-length = 120 target-version = "py310" -lint.extend-select = ["I", "RUF022"] +lint.extend-select = ["I", "RUF022", "ANN"] +lint.extend-ignore = ["ANN002", "ANN003", "ANN401"] lint.isort.lines-after-imports = 2 lint.isort.lines-between-types = 1 lint.isort.force-wrap-aliases = true