From 420eeb5f8ec9b0a9de068193acb5afa0de242347 Mon Sep 17 00:00:00 2001 From: Dos Moonen Date: Sun, 3 Mar 2024 00:43:16 +0100 Subject: [PATCH] Add inverter_serial_number to InverterResponse --- solax/discovery.py | 2 + solax/inverter.py | 21 ++++++++- solax/inverter_http_client.py | 2 + solax/inverters/qvolt_hyb_g3_3p.py | 10 ++++- solax/inverters/x1.py | 12 +++-- solax/inverters/x1_boost.py | 10 ++++- solax/inverters/x1_hybrid_gen4.py | 10 ++++- solax/inverters/x1_mini.py | 12 +++-- solax/inverters/x1_mini_v34.py | 10 ++++- solax/inverters/x1_smart.py | 10 ++++- solax/inverters/x3.py | 14 ++++-- solax/inverters/x3_hybrid_g4.py | 10 ++++- solax/inverters/x3_mic_pro_g2.py | 10 ++++- solax/inverters/x3_v34.py | 10 ++++- solax/inverters/x_hybrid.py | 12 +++-- solax/response_parser.py | 72 ++++++++++++++++++++++-------- tests/samples/responses.py | 18 ++++---- tests/test_discovery.py | 6 +++ 18 files changed, 192 insertions(+), 59 deletions(-) diff --git a/solax/discovery.py b/solax/discovery.py index 6a3e06f..c711efe 100644 --- a/solax/discovery.py +++ b/solax/discovery.py @@ -10,6 +10,8 @@ from solax.inverter import Inverter from solax.inverter_http_client import InverterHttpClient +__all__ = ("discover", "DiscoveryKeywords", "DiscoveryError") + if sys.version_info >= (3, 10): from importlib.metadata import entry_points else: diff --git a/solax/inverter.py b/solax/inverter.py index e4f1ae0..8abe642 100644 --- a/solax/inverter.py +++ b/solax/inverter.py @@ -1,4 +1,5 @@ -from typing import Dict, Tuple +from abc import abstractmethod +from typing import Any, Dict, Optional, Tuple import aiohttp import voluptuous as vol @@ -33,7 +34,14 @@ def __init__(self, http_client: InverterHttpClient): schema = type(self).schema() response_decoder = type(self).response_decoder() - self.response_parser = ResponseParser(schema, response_decoder) + dongle_serial_number_getter = type(self).dongle_serial_number_getter + inverter_serial_number_getter = type(self).inverter_serial_number_getter + self.response_parser = ResponseParser( + schema, + response_decoder, + dongle_serial_number_getter, + inverter_serial_number_getter, + ) @classmethod def _build(cls, host, port, pwd="", params_in_query=True): @@ -103,5 +111,14 @@ def schema(cls) -> vol.Schema: """ return cls._schema + @classmethod + def dongle_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["sn"] + + @classmethod + @abstractmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + raise NotImplementedError # pragma: no cover + def __str__(self) -> str: return f"{self.__class__.__name__}::{self.http_client}" diff --git a/solax/inverter_http_client.py b/solax/inverter_http_client.py index cec4256..e68adce 100644 --- a/solax/inverter_http_client.py +++ b/solax/inverter_http_client.py @@ -9,6 +9,8 @@ import aiohttp +__all__ = ("InverterHttpClient", "Method") + if sys.version_info >= (3, 10): from dataclasses import KW_ONLY diff --git a/solax/inverters/qvolt_hyb_g3_3p.py b/solax/inverters/qvolt_hyb_g3_3p.py index c5c785c..5bda4ee 100644 --- a/solax/inverters/qvolt_hyb_g3_3p.py +++ b/solax/inverters/qvolt_hyb_g3_3p.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + import voluptuous as vol from solax.inverter import Inverter, InverterHttpClient @@ -50,13 +52,13 @@ def __init__(self, http_client: InverterHttpClient, *args, **kwargs): vol.Required("type"): vol.All(int, 14), vol.Required("sn"): str, vol.Required("ver"): str, - vol.Required("Data"): vol.Schema( + vol.Required("data"): vol.Schema( vol.All( [vol.Coerce(float)], vol.Length(min=200, max=200), ) ), - vol.Required("Information"): vol.Schema( + vol.Required("information"): vol.Schema( vol.All(vol.Length(min=10, max=10)) ), }, @@ -161,6 +163,10 @@ def response_decoder(cls): # 170-199: always 0 } + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["information"][2] + @classmethod def build_all_variants(cls, host, port, pwd=""): versions = [cls._build(host, port, pwd, False)] diff --git a/solax/inverters/x1.py b/solax/inverters/x1.py index 0b34e72..54121ba 100644 --- a/solax/inverters/x1.py +++ b/solax/inverters/x1.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + import voluptuous as vol from solax.inverter import Inverter @@ -10,9 +12,9 @@ class X1(Inverter): _schema = vol.Schema( { vol.Required("type"): vol.All(str, startswith("X1-")), - vol.Required("SN"): str, + vol.Required("sn"): str, vol.Required("ver"): str, - vol.Required("Data"): vol.Schema( + vol.Required("data"): vol.Schema( vol.All( [vol.Coerce(float)], vol.Any( @@ -22,7 +24,7 @@ class X1(Inverter): ), ) ), - vol.Required("Information"): vol.Schema(vol.All(vol.Length(min=9, max=9))), + vol.Required("information"): vol.Schema(vol.All(vol.Length(min=9, max=9))), }, extra=vol.REMOVE_EXTRA, ) @@ -59,3 +61,7 @@ def response_decoder(cls): } # pylint: enable=duplicate-code + + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["information"][3] diff --git a/solax/inverters/x1_boost.py b/solax/inverters/x1_boost.py index 4285d29..9ff4b11 100644 --- a/solax/inverters/x1_boost.py +++ b/solax/inverters/x1_boost.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + import voluptuous as vol from solax.inverter import Inverter @@ -19,13 +21,13 @@ class X1Boost(Inverter): "sn", ): str, vol.Required("ver"): str, - vol.Required("Data"): vol.Schema( + vol.Required("data"): vol.Schema( vol.All( [vol.Coerce(float)], vol.Length(min=200, max=200), ) ), - vol.Required("Information"): vol.Schema( + vol.Required("information"): vol.Schema( vol.All(vol.Length(min=10, max=10)) ), }, @@ -53,6 +55,10 @@ def response_decoder(cls): "Total Import Energy": (pack_u16(52, 53), Total(Units.KWH), div100), } + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["information"][2] + @classmethod def build_all_variants(cls, host, port, pwd=""): versions = [ diff --git a/solax/inverters/x1_hybrid_gen4.py b/solax/inverters/x1_hybrid_gen4.py index 1acc54c..6e2c852 100644 --- a/solax/inverters/x1_hybrid_gen4.py +++ b/solax/inverters/x1_hybrid_gen4.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + import voluptuous as vol from solax.inverter import Inverter @@ -14,13 +16,13 @@ class X1HybridGen4(Inverter): "sn", ): str, vol.Required("ver"): str, - vol.Required("Data"): vol.Schema( + vol.Required("data"): vol.Schema( vol.All( [vol.Coerce(float)], vol.Length(min=200, max=200), ) ), - vol.Required("Information"): vol.Schema(vol.All(vol.Length(min=9, max=10))), + vol.Required("information"): vol.Schema(vol.All(vol.Length(min=9, max=10))), }, extra=vol.REMOVE_EXTRA, ) @@ -54,3 +56,7 @@ def response_decoder(cls): "Total feed-in energy": (pack_u16(34, 35), Total(Units.KWH), div100), "Total consumption": (pack_u16(36, 37), Total(Units.KWH), div100), } + + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["information"][2] diff --git a/solax/inverters/x1_mini.py b/solax/inverters/x1_mini.py index 874aa90..5968e36 100644 --- a/solax/inverters/x1_mini.py +++ b/solax/inverters/x1_mini.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + import voluptuous as vol from solax.inverter import Inverter @@ -10,15 +12,15 @@ class X1Mini(Inverter): _schema = vol.Schema( { vol.Required("type"): vol.All(str, startswith("X1-")), - vol.Required("SN"): str, + vol.Required("sn"): str, vol.Required("ver"): str, - vol.Required("Data"): vol.Schema( + vol.Required("data"): vol.Schema( vol.All( [vol.Coerce(float)], vol.Length(min=69, max=69), ) ), - vol.Required("Information"): vol.Schema(vol.All(vol.Length(min=9, max=9))), + vol.Required("information"): vol.Schema(vol.All(vol.Length(min=9, max=9))), }, extra=vol.REMOVE_EXTRA, ) @@ -46,3 +48,7 @@ def response_decoder(cls): } # pylint: enable=duplicate-code + + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["information"][3] diff --git a/solax/inverters/x1_mini_v34.py b/solax/inverters/x1_mini_v34.py index 9a3f159..89cb163 100644 --- a/solax/inverters/x1_mini_v34.py +++ b/solax/inverters/x1_mini_v34.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + import voluptuous as vol from solax.inverter import Inverter @@ -22,7 +24,7 @@ class X1MiniV34(Inverter): "sn", ): str, vol.Required("ver"): str, - vol.Required("Data"): vol.Schema( + vol.Required("data"): vol.Schema( vol.All( [vol.Coerce(float)], vol.Any( @@ -32,7 +34,7 @@ class X1MiniV34(Inverter): ), ) ), - vol.Required("Information"): vol.Schema( + vol.Required("information"): vol.Schema( vol.Any(vol.Length(min=9, max=9), vol.Length(min=10, max=10)) ), }, @@ -61,3 +63,7 @@ def response_decoder(cls): } # pylint: enable=duplicate-code + + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["information"][2] diff --git a/solax/inverters/x1_smart.py b/solax/inverters/x1_smart.py index c98d9e6..2a014eb 100644 --- a/solax/inverters/x1_smart.py +++ b/solax/inverters/x1_smart.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + import voluptuous as vol from solax.inverter import Inverter @@ -19,13 +21,13 @@ class X1Smart(Inverter): "sn", ): str, vol.Required("ver"): str, - vol.Required("Data"): vol.Schema( + vol.Required("data"): vol.Schema( vol.All( [vol.Coerce(float)], vol.Length(min=200, max=200), ) ), - vol.Required("Information"): vol.Schema(vol.All(vol.Length(min=8, max=8))), + vol.Required("information"): vol.Schema(vol.All(vol.Length(min=8, max=8))), }, extra=vol.REMOVE_EXTRA, ) @@ -51,6 +53,10 @@ def response_decoder(cls): "Total Consumption": (52, Total(Units.KWH), div100), } + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["information"][2] + @classmethod def build_all_variants(cls, host, port, pwd=""): versions = [ diff --git a/solax/inverters/x3.py b/solax/inverters/x3.py index 8c1bff9..f562b0f 100644 --- a/solax/inverters/x3.py +++ b/solax/inverters/x3.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + import voluptuous as vol from solax.inverter import Inverter @@ -6,23 +8,23 @@ class X3(Inverter): + # pylint: disable=duplicate-code _schema = vol.Schema( { vol.Required("type"): vol.All(str, startswith("X3-")), - vol.Required("SN"): str, + vol.Required("sn"): str, vol.Required("ver"): str, - vol.Required("Data"): vol.Schema( + vol.Required("data"): vol.Schema( vol.All( [vol.Coerce(float)], vol.Any(vol.Length(min=102, max=103), vol.Length(min=107, max=107)), ) ), - vol.Required("Information"): vol.Schema(vol.All(vol.Length(min=9, max=9))), + vol.Required("information"): vol.Schema(vol.All(vol.Length(min=9, max=9))), }, extra=vol.REMOVE_EXTRA, ) - # pylint: disable=duplicate-code @classmethod def response_decoder(cls): return { @@ -61,3 +63,7 @@ def response_decoder(cls): "EPS Power": (55, Units.W), "EPS Frequency": (56, Units.HZ), } + + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["information"][3] diff --git a/solax/inverters/x3_hybrid_g4.py b/solax/inverters/x3_hybrid_g4.py index ecd4d54..2665060 100644 --- a/solax/inverters/x3_hybrid_g4.py +++ b/solax/inverters/x3_hybrid_g4.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + import voluptuous as vol from solax.inverter import Inverter @@ -22,13 +24,13 @@ class X3HybridG4(Inverter): vol.Required("type"): vol.All(int, 14), vol.Required("sn"): str, vol.Required("ver"): str, - vol.Required("Data"): vol.Schema( + vol.Required("data"): vol.Schema( vol.All( [vol.Coerce(float)], vol.Length(min=300, max=300), ) ), - vol.Required("Information"): vol.Schema( + vol.Required("information"): vol.Schema( vol.All(vol.Length(min=10, max=10)) ), }, @@ -133,3 +135,7 @@ def response_decoder(cls): } # pylint: enable=duplicate-code + + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["information"][2] diff --git a/solax/inverters/x3_mic_pro_g2.py b/solax/inverters/x3_mic_pro_g2.py index 8190e14..93c7a63 100644 --- a/solax/inverters/x3_mic_pro_g2.py +++ b/solax/inverters/x3_mic_pro_g2.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + import voluptuous as vol from solax.inverter import Inverter @@ -14,13 +16,13 @@ class X3MicProG2(Inverter): vol.Required("type"): vol.All(int, 16), vol.Required("sn"): str, vol.Required("ver"): str, - vol.Required("Data"): vol.Schema( + vol.Required("data"): vol.Schema( vol.All( [vol.Coerce(float)], vol.Length(min=100, max=100), ) ), - vol.Required("Information"): vol.Schema( + vol.Required("information"): vol.Schema( vol.All(vol.Length(min=10, max=10)) ), }, @@ -76,3 +78,7 @@ def response_decoder(cls): } # pylint: enable=duplicate-code + + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["information"][2] diff --git a/solax/inverters/x3_v34.py b/solax/inverters/x3_v34.py index 57cb611..af2eb42 100644 --- a/solax/inverters/x3_v34.py +++ b/solax/inverters/x3_v34.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + import voluptuous as vol from solax.inverter import Inverter @@ -14,13 +16,13 @@ class X3V34(Inverter): vol.Required("type"): vol.All(int, 5), vol.Required("sn"): str, vol.Required("ver"): str, - vol.Required("Data"): vol.Schema( + vol.Required("data"): vol.Schema( vol.All( [vol.Coerce(float)], vol.Length(min=200, max=200), ) ), - vol.Required("Information"): vol.Schema( + vol.Required("information"): vol.Schema( vol.All(vol.Length(min=10, max=10)) ), }, @@ -87,3 +89,7 @@ def response_decoder(cls): } # pylint: enable=duplicate-code + + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["information"][2] diff --git a/solax/inverters/x_hybrid.py b/solax/inverters/x_hybrid.py index 205c375..f2b3b55 100644 --- a/solax/inverters/x_hybrid.py +++ b/solax/inverters/x_hybrid.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, Optional + import voluptuous as vol from solax.inverter import Inverter, InverterHttpClient, Method @@ -15,14 +17,14 @@ class XHybrid(Inverter): vol.Required("method"): str, vol.Required("version"): str, vol.Required("type"): str, - vol.Required("SN"): str, - vol.Required("Data"): vol.Schema( + vol.Required("sn"): str, + vol.Required("data"): vol.Schema( vol.All( [vol.Coerce(float)], vol.Any(vol.Length(min=58, max=58), vol.Length(min=68, max=68)), ) ), - vol.Required("Status"): vol.All(vol.Coerce(int), vol.Range(min=0)), + vol.Required("status"): vol.All(vol.Coerce(int), vol.Range(min=0)), }, extra=vol.REMOVE_EXTRA, ) @@ -74,3 +76,7 @@ def response_decoder(cls): "EPS Power": (55, Units.W), "EPS Frequency": (56, Units.HZ), } + + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return None diff --git a/solax/response_parser.py b/solax/response_parser.py index 5d6cd85..027e236 100644 --- a/solax/response_parser.py +++ b/solax/response_parser.py @@ -1,7 +1,8 @@ import json import logging +import sys from collections import namedtuple -from typing import Any, Callable, Dict, Tuple, Union +from typing import Any, Callable, Dict, Generator, Optional, Tuple, Union import voluptuous as vol from voluptuous import Invalid, MultipleInvalid @@ -10,25 +11,54 @@ from solax.units import SensorUnit from solax.utils import PackerBuilderResult +__all__ = ("ResponseParser", "InverterResponse", "ResponseDecoder") + +if sys.version_info >= (3, 11): + from typing import Unpack +else: + from typing_extensions import Unpack + _LOGGER = logging.getLogger(__name__) _LOGGER.setLevel(logging.INFO) -InverterResponse = namedtuple("InverterResponse", "data, serial_number, version, type") +class InverterResponse( + namedtuple( + "InverterResponse", + [ + "data", + "dongle_serial_number", + "version", + "type", + "inverter_serial_number", + ], + ) +): + @property + def serial_number(self): + return self.dongle_serial_number + + +ProcessorTuple = Tuple[Callable[[Any], Any], ...] SensorIndexSpec = Union[int, PackerBuilderResult] ResponseDecoder = Dict[ str, - Union[ - Tuple[SensorIndexSpec, SensorUnit], - Tuple[SensorIndexSpec, SensorUnit, Callable[[Any], Any]], - ], + Tuple[SensorIndexSpec, SensorUnit, Unpack[ProcessorTuple]], ] class ResponseParser: - def __init__(self, schema: vol.Schema, decoder: ResponseDecoder): + def __init__( + self, + schema: vol.Schema, + decoder: ResponseDecoder, + dongle_serial_number_getter: Callable[[Dict[str, Any]], Optional[str]], + inverter_serial_number_getter: Callable[[Dict[str, Any]], Optional[str]], + ) -> None: self.schema = schema self.response_decoder = decoder + self.dongle_serial_number_getter = dongle_serial_number_getter + self.inverter_serial_number_getter = inverter_serial_number_getter def _decode_map(self) -> Dict[str, SensorIndexSpec]: sensors: Dict[str, SensorIndexSpec] = {} @@ -36,17 +66,16 @@ def _decode_map(self) -> Dict[str, SensorIndexSpec]: sensors[name] = mapping[0] return sensors - def _postprocess_map(self) -> Dict[str, Callable[[Any], Any]]: + def _postprocess_gen( + self, + ) -> Generator[Tuple[str, Callable[[Any], Any]], None, None]: """ Return map of functions to be applied to each sensor value """ - sensors: Dict[str, Callable[[Any], Any]] = {} for name, mapping in self.response_decoder.items(): - processor = None - (_, _, *processor) = mapping - if processor: - sensors[name] = processor[0] - return sensors + (_, _, *processors) = mapping + for processor in processors: + yield name, processor def map_response(self, resp_data) -> Dict[str, Any]: result = {} @@ -59,11 +88,11 @@ def map_response(self, resp_data) -> Dict[str, Any]: else: val = resp_data[decode_info] result[sensor_name] = val - for sensor_name, processor in self._postprocess_map().items(): + for sensor_name, processor in self._postprocess_gen(): result[sensor_name] = processor(result[sensor_name]) return result - def handle_response(self, resp: bytearray): + def handle_response(self, resp: bytearray) -> InverterResponse: """ Decode response and map array result using mapping definition. @@ -75,15 +104,20 @@ def handle_response(self, resp: bytearray): """ raw_json = resp.decode("utf-8").replace(",,", ",0.0,").replace(",,", ",0.0,") - json_response = json.loads(raw_json) + json_response = {} + for key, value in json.loads(raw_json).items(): + json_response[key.lower()] = value + try: response = self.schema(json_response) except (Invalid, MultipleInvalid) as ex: _ = humanize_error(json_response, ex) raise + return InverterResponse( - data=self.map_response(response["Data"]), - serial_number=response.get("SN", response.get("sn")), + data=self.map_response(response["data"]), + dongle_serial_number=self.dongle_serial_number_getter(response), version=response.get("ver", response.get("version")), type=response["type"], + inverter_serial_number=self.inverter_serial_number_getter(response), ) diff --git a/tests/samples/responses.py b/tests/samples/responses.py index 64304a9..0e4078f 100644 --- a/tests/samples/responses.py +++ b/tests/samples/responses.py @@ -1517,7 +1517,7 @@ 3.000, 3, "X1-Hybiyd-G3", - "YYYYYYYYYYYYYY", + "XXXXXXXXXXXXXX", 1, 3.11, 0.00, @@ -1535,7 +1535,7 @@ X1_HYBRID_G4_RESPONSE = { "type": 15, - "sn": "SXxxxxxxxx", + "sn": "SXXXXXXXXX", "ver": "3.003.02", "Data": [ 2470, @@ -1742,7 +1742,7 @@ "Information": [ 5.000, 15, - "H450xxxxxxxxxx", + "H450XXXXXXXXXX", 8, 1.24, 0.00, @@ -2614,7 +2614,7 @@ } X3_HYBRID_G4_RESPONSE = { - "sn": "SR3xxxxxxx", + "sn": "SR3XXXXXXX", "ver": "3.006.04", "type": 14, "Data": [ @@ -2919,11 +2919,11 @@ 0, 0, ], - "Information": [10.000, 14, "H34A**********", 8, 1.23, 0.00, 1.24, 1.09, 0.00, 1], + "Information": [10.000, 14, "H34AXXXXXXXXXX", 8, 1.23, 0.00, 1.24, 1.09, 0.00, 1], } X3_MICPRO_G2_RESPONSE = { - "sn": "SRE*******", + "sn": "SREXXXXXXX", "ver": "3.008.10", "type": 16, "Data": [ @@ -3028,11 +3028,11 @@ 0, 0, ], - "Information": [4.000, 16, "MC20**********", 8, 1.20, 0.00, 1.18, 1.00, 0.00, 1], + "Information": [4.000, 16, "MC20XXXXXXXXXX", 8, 1.20, 0.00, 1.18, 1.00, 0.00, 1], } QVOLTHYBG33P_RESPONSE_V34 = { - "sn": "SWX***", + "sn": "SWXXXX", "ver": "2.034.06", "type": 14, "Data": [ @@ -3237,5 +3237,5 @@ 0, 0, ], - "Information": [12.0, 14, "H34***", 1, 1.15, 0.0, 1.14, 1.07, 0.0, 1], + "Information": [12.0, 14, "H34XXXXXXXX", 1, 1.15, 0.0, 1.14, 1.07, 0.0, 1], } diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 206cb71..3a2f956 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -27,6 +27,12 @@ async def test_discovery(inverters_fixture): inverters = await solax.discover(*conn, return_when=asyncio.ALL_COMPLETED) assert inverter_class in {type(inverter) for inverter in inverters} + for inverter in inverters: + if isinstance(inverter, inverter_class): + data = await inverter.get_data() + assert "X" * 7 in (data.inverter_serial_number or "X" * 7) + assert data.serial_number == data.dongle_serial_number + @pytest.mark.asyncio async def test_real_time_api(inverters_fixture):