From 5672b866bc73d11048cce43a2f0fda2e80bd16b4 Mon Sep 17 00:00:00 2001 From: Vadim Kraus <38394456+VadimKraus@users.noreply.github.com> Date: Fri, 22 Apr 2022 00:52:00 +0200 Subject: [PATCH] refactor: move each inverter in a separate file (#63) * remove redundant inverter mapping methods * split up test data into multiple files --- solax/__init__.py | 4 +- solax/discovery.py | 30 ++ solax/inverter.py | 796 +---------------------------- solax/inverters/__init__.py | 11 + solax/inverters/qvolt_hyb_g3_3p.py | 172 +++++++ solax/inverters/x1.py | 66 +++ solax/inverters/x1_mini.py | 51 ++ solax/inverters/x1_mini_v34.py | 47 ++ solax/inverters/x1_smart.py | 51 ++ solax/inverters/x3.py | 72 +++ solax/inverters/x3_v34.py | 88 ++++ solax/inverters/x_hybrid.py | 78 +++ solax/utils.py | 12 + tests/fixtures.py | 721 +------------------------- tests/samples/expected_values.py | 380 ++++++++++++++ tests/samples/responses.py | 272 ++++++++++ tests/test_base_inverter.py | 13 +- tests/test_discovery.py | 8 +- tests/test_smoke.py | 7 +- tests/test_vol.py | 2 +- 20 files changed, 1386 insertions(+), 1495 deletions(-) create mode 100644 solax/discovery.py create mode 100644 solax/inverters/__init__.py create mode 100644 solax/inverters/qvolt_hyb_g3_3p.py create mode 100644 solax/inverters/x1.py create mode 100644 solax/inverters/x1_mini.py create mode 100644 solax/inverters/x1_mini_v34.py create mode 100644 solax/inverters/x1_smart.py create mode 100644 solax/inverters/x3.py create mode 100644 solax/inverters/x3_v34.py create mode 100644 solax/inverters/x_hybrid.py create mode 100644 tests/samples/expected_values.py create mode 100644 tests/samples/responses.py diff --git a/solax/__init__.py b/solax/__init__.py index 1d68fd1..0a65dae 100644 --- a/solax/__init__.py +++ b/solax/__init__.py @@ -5,7 +5,7 @@ import async_timeout -from solax import inverter +from solax.discovery import discover _LOGGER = logging.getLogger(__name__) @@ -34,7 +34,7 @@ async def rt_request(inv, retry, t_wait=0): async def real_time_api(ip_address, port=80, pwd=''): - i = await inverter.discover(ip_address, port, pwd) + i = await discover(ip_address, port, pwd) return RealTimeAPI(i) diff --git a/solax/discovery.py b/solax/discovery.py new file mode 100644 index 0000000..ff10f34 --- /dev/null +++ b/solax/discovery.py @@ -0,0 +1,30 @@ + +from solax.inverter import Inverter, InverterError +from solax.inverters import XHybrid, X3, X3V34, X1, X1Mini, X1MiniV34,\ + X1Smart, QVOLTHYBG33P + +# registry of inverters +REGISTRY = [XHybrid, X3, X3V34, X1, X1Mini, X1MiniV34, X1Smart, + QVOLTHYBG33P] + + +class DiscoveryError(Exception): + """Raised when unable to discover inverter""" + + +async def discover(host, port, pwd='') -> Inverter: + failures = [] + for inverter in REGISTRY: + i = inverter(host, port, pwd) + try: + await i.get_data() + return i + except InverterError as ex: + failures.append(ex) + msg = ( + "Unable to connect to the inverter at " + f"host={host} port={port}, or your inverter is not supported yet.\n" + "Please see https://github.com/squishykid/solax/wiki/DiscoveryError\n" + f"Failures={str(failures)}" + ) + raise DiscoveryError(msg) diff --git a/solax/inverter.py b/solax/inverter.py index 553ac4b..885534b 100644 --- a/solax/inverter.py +++ b/solax/inverter.py @@ -1,25 +1,16 @@ -import json from collections import namedtuple - +import json +from typing import Any, Callable, Optional, Tuple import aiohttp import voluptuous as vol from voluptuous import Invalid, MultipleInvalid from voluptuous.humanize import humanize_error -from solax.utils import ( - div10, div100, feedin_energy, total_energy, charge_energy, pv_energy, - discharge_energy, consumption, twoway_div10, twoway_div100, to_signed, - eps_total_energy -) class InverterError(Exception): """Indicates error communicating with inverter""" -class DiscoveryError(Exception): - """Raised when unable to discover inverter""" - - InverterResponse = namedtuple('InverterResponse', 'data, serial_number, version, type') @@ -27,6 +18,11 @@ class DiscoveryError(Exception): class Inverter: """Base wrapper around Inverter HTTP API""" + # pylint: disable=C0301 + _sensor_map = {} # type: dict[str,Tuple[int,Optional[str],Optional[Callable[[Any,Any,Any],Any]]]] # noqa: E501 + # pylint: enable=C0301 + _schema = vol.Schema({}) # type: vol.Schema + def __init__(self, host, port, pwd=''): self.host = host self.port = port @@ -61,21 +57,28 @@ def sensor_map(cls): """ Return sensor map """ - raise NotImplementedError() + sensors = {} + for name, (idx, unit, *_) in cls._sensor_map.items(): + sensors[name] = (idx, unit) + return sensors @classmethod def postprocess_map(cls): """ Return map of functions to be applied to each sensor value """ - return {} + sensors = {} + for name, (_, _, *processor) in cls._sensor_map.items(): + if processor: + sensors[name] = processor[0] + return sensors @classmethod - def schema(cls): + def schema(cls) -> vol.Schema: """ Return schema """ - raise NotImplementedError() + return cls._schema @classmethod def map_response(cls, resp_data): @@ -88,115 +91,6 @@ def map_response(cls, resp_data): return result -async def discover(host, port, pwd='') -> Inverter: - failures = [] - for inverter in REGISTRY: - i = inverter(host, port, pwd) - try: - await i.get_data() - return i - except InverterError as ex: - failures.append(ex) - msg = ( - "Unable to connect to the inverter at " - f"host={host} port={port}, or your inverter is not supported yet.\n" - "Please see https://github.com/squishykid/solax/wiki/DiscoveryError\n" - f"Failures={str(failures)}" - ) - raise DiscoveryError(msg) - - -def startswith(something): - def inner(actual): - if isinstance(actual, str): - if actual.startswith(something): - return actual - raise Invalid(f"{str(actual)} does not start with {something}") - return inner - - -class XHybrid(Inverter): - """ - Tested with: - * SK-TL5000E - """ - __schema = vol.Schema({ - vol.Required('method'): str, - vol.Required('version'): str, - vol.Required('type'): str, - 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)), - }, extra=vol.REMOVE_EXTRA) - - # key: name of sensor - # value.0: index - # value.1: unit (String) or None - # from https://github.com/GitHobi/solax/wiki/direct-data-retrieval - __sensor_map = { - 'PV1 Current': (0, 'A'), - 'PV2 Current': (1, 'A'), - 'PV1 Voltage': (2, 'V'), - 'PV2 Voltage': (3, 'V'), - - 'Output Current': (4, 'A'), - 'Network Voltage': (5, 'V'), - 'Power Now': (6, 'W'), - - 'Inverter Temperature': (7, 'C'), - 'Today\'s Energy': (8, 'kWh'), - 'Total Energy': (9, 'kWh'), - 'Exported Power': (10, 'W'), - 'PV1 Power': (11, 'W'), - 'PV2 Power': (12, 'W'), - - 'Battery Voltage': (13, 'V'), - 'Battery Current': (14, 'A'), - 'Battery Power': (15, 'W'), - 'Battery Temperature': (16, 'C'), - 'Battery Remaining Capacity': (17, '%'), - - 'Month\'s Energy': (19, 'kWh'), - - 'Grid Frequency': (50, 'Hz'), - 'EPS Voltage': (53, 'V'), - 'EPS Current': (54, 'A'), - 'EPS Power': (55, 'W'), - 'EPS Frequency': (56, 'Hz'), - } - - @classmethod - async def make_request(cls, host, port=80, pwd='', headers=None): - base = 'http://{}:{}/api/realTimeData.htm' - url = base.format(host, port) - async with aiohttp.ClientSession() as session: - async with session.get(url) as req: - garbage = await req.read() - formatted = garbage.decode("utf-8") - formatted = formatted.replace(",,", ",0.0,").replace(",,", ",0.0,") - json_response = json.loads(formatted) - response = cls.schema()(json_response) - return InverterResponse( - data=cls.map_response(response['Data']), - serial_number=response['SN'], - version=response['version'], - type=response['type'] - ) - - @classmethod - def sensor_map(cls): - return cls.__sensor_map - - @classmethod - def schema(cls): - return cls.__schema - - class InverterPost(Inverter): # This is an intermediate abstract class, # so we can disable the pylint warning @@ -212,394 +106,20 @@ async def make_request(cls, host, port=80, pwd='', headers=None): async with aiohttp.ClientSession() as session: async with session.post(url, headers=headers) as req: resp = await req.read() - raw_json = resp.decode("utf-8") - json_response = json.loads(raw_json) - response = {} - try: - response = cls.schema()(json_response) - except (Invalid, MultipleInvalid) as ex: - _ = humanize_error(json_response, ex) - raise - return InverterResponse( - data=cls.map_response(response['Data']), - serial_number=response.get('SN', response.get('sn')), - version=response['ver'], - type=response['type'] - ) - - -class X3(InverterPost): - __schema = vol.Schema({ - vol.Required('type'): vol.All( - str, - startswith("X3-") - ), - vol.Required('SN'): str, - vol.Required('ver'): str, - 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) - ) - ), - }, extra=vol.REMOVE_EXTRA) - - __sensor_map = { - 'PV1 Current': (0, 'A'), - 'PV2 Current': (1, 'A'), - 'PV1 Voltage': (2, 'V'), - 'PV2 Voltage': (3, 'V'), - 'Output Current Phase 1': (4, 'A'), - 'Network Voltage Phase 1': (5, 'V'), - 'AC Power': (6, 'W'), - - 'Inverter Temperature': (7, 'C'), - 'Today\'s Energy': (8, 'kWh'), - 'Total Energy': (9, 'kWh'), - 'Exported Power': (10, 'W'), - 'PV1 Power': (11, 'W'), - 'PV2 Power': (12, 'W'), - - 'Battery Voltage': (13, 'V'), - 'Battery Current': (14, 'A'), - 'Battery Power': (15, 'W'), - 'Battery Temperature': (16, 'C'), - 'Battery Remaining Capacity': (21, '%'), - - 'Total Feed-in Energy': (41, 'kWh'), - 'Total Consumption': (42, 'kWh'), - - 'Power Now Phase 1': (43, 'W'), - 'Power Now Phase 2': (44, 'W'), - 'Power Now Phase 3': (45, 'W'), - 'Output Current Phase 2': (46, 'A'), - 'Output Current Phase 3': (47, 'A'), - 'Network Voltage Phase 2': (48, 'V'), - 'Network Voltage Phase 3': (49, 'V'), - - 'Grid Frequency Phase 1': (50, 'Hz'), - 'Grid Frequency Phase 2': (51, 'Hz'), - 'Grid Frequency Phase 3': (52, 'Hz'), - - 'EPS Voltage': (53, 'V'), - 'EPS Current': (54, 'A'), - 'EPS Power': (55, 'W'), - 'EPS Frequency': (56, 'Hz'), - } + return cls.handle_response(resp) @classmethod - def sensor_map(cls): + def handle_response(cls, resp): """ - Return sensor map - """ - return cls.__sensor_map - - @classmethod - def schema(cls): - return cls.__schema - - -class X3V34(InverterPost): - """X3 v2.034.06""" - __schema = vol.Schema({ - vol.Required('type'): vol.All(int, 5), - vol.Required('sn'): str, - vol.Required('ver'): str, - 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=10, max=10) - ) - ), - }, extra=vol.REMOVE_EXTRA) - - __sensor_map = { - 'Network Voltage Phase 1': (0, 'V', div10), - 'Network Voltage Phase 2': (1, 'V', div10), - 'Network Voltage Phase 3': (2, 'V', div10), - - 'Output Current Phase 1': (3, 'A', twoway_div10), - 'Output Current Phase 2': (4, 'A', twoway_div10), - 'Output Current Phase 3': (5, 'A', twoway_div10), + Decode response and map array result using mapping definition. - 'Power Now Phase 1': (6, 'W', to_signed), - 'Power Now Phase 2': (7, 'W', to_signed), - 'Power Now Phase 3': (8, 'W', to_signed), + Args: + resp (_type_): The response - 'PV1 Voltage': (9, 'V', div10), - 'PV2 Voltage': (10, 'V', div10), - 'PV1 Current': (11, 'A', div10), - 'PV2 Current': (12, 'A', div10), - 'PV1 Power': (13, 'W'), - 'PV2 Power': (14, 'W'), - - 'Total PV Energy': (89, 'kWh', pv_energy), - 'Total PV Energy Resets': (90, ''), - 'Today\'s PV Energy': (112, 'kWh', div10), - - 'Grid Frequency Phase 1': (15, 'Hz', div100), - 'Grid Frequency Phase 2': (16, 'Hz', div100), - 'Grid Frequency Phase 3': (17, 'Hz', div100), - - 'Total Energy': (19, 'kWh', total_energy), - 'Total Energy Resets': (20, ''), - 'Today\'s Energy': (21, 'kWh', div10), - - 'Battery Voltage': (24, 'V', div100), - 'Battery Current': (25, 'A', twoway_div100), - 'Battery Power': (26, 'W', to_signed), - 'Battery Temperature': (27, 'C'), - 'Battery Remaining Capacity': (28, '%'), - - 'Total Battery Discharge Energy': (30, 'kWh', - discharge_energy), - 'Total Battery Discharge Energy Resets': (31, ''), - 'Today\'s Battery Discharge Energy': (113, 'kWh', div10), - 'Battery Remaining Energy': (32, 'kWh', div10), - 'Total Battery Charge Energy': (87, 'kWh', charge_energy), - 'Total Battery Charge Energy Resets': (88, ''), - 'Today\'s Battery Charge Energy': (114, 'kWh', div10), - - 'Exported Power': (65, 'W', to_signed), - 'Total Feed-in Energy': (67, 'kWh', feedin_energy), - 'Total Feed-in Energy Resets': (68, ''), - 'Total Consumption': (69, 'kWh', consumption), - 'Total Consumption Resets': (70, ''), - - 'AC Power': (181, 'W', to_signed), - - 'EPS Frequency': (63, 'Hz', div100), - 'EPS Total Energy': (110, 'kWh', - eps_total_energy), - 'EPS Total Energy Resets': (111, 'Hz'), - } - - @classmethod - def sensor_map(cls): + Returns: + InverterResponse: The decoded and mapped interver response. """ - Return sensor map - """ - sensors = {} - for name, (idx, unit, *_) in cls.__sensor_map.items(): - sensors[name] = (idx, unit) - return sensors - - @classmethod - def postprocess_map(cls): - """ - Return postprocessing map - """ - sensors = {} - for name, (_, _, *processor) in cls.__sensor_map.items(): - if processor: - sensors[name] = processor[0] - return sensors - - @classmethod - def schema(cls): - return cls.__schema - - -class QVOLTHYBG33P(InverterPost): - """ - QCells - Q.VOLT HYB-G3-3P - """ - class Processors: - """ - Postprocessors used only in the QVOLTHYBG33P inverter sensor_map. - """ - @staticmethod - def inverter_modes(value, *_args, **_kwargs): - return { - 0: "Waiting", - 1: "Checking", - 2: "Normal", - 3: "Off", - 4: "Permanent Fault", - 5: "Updating", - 6: "EPS Check", - 7: "EPS Mode", - 8: "Self Test", - 9: "Idle", - 10: "Standby" - }.get(value, f"unmapped value '{value}'") - - @staticmethod - def battery_modes(value, *_args, **_kwargs): - return { - 0: "Self Use Mode", - 1: "Force Time Use", - 2: "Back Up Mode", - 3: "Feed-in Priority", - }.get(value, f"unmapped value '{value}'") - - __schema = vol.Schema({ - vol.Required('type'): vol.All(int, 14), - vol.Required('sn'): str, - vol.Required('ver'): str, - 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=10, max=10) - ) - ), - }, extra=vol.REMOVE_EXTRA) - - __sensor_map = { - - 'Network Voltage Phase 1': (0, 'V', div10), - 'Network Voltage Phase 2': (1, 'V', div10), - 'Network Voltage Phase 3': (2, 'V', div10), - - 'Output Current Phase 1': (3, 'A', twoway_div10), - 'Output Current Phase 2': (4, 'A', twoway_div10), - 'Output Current Phase 3': (5, 'A', twoway_div10), - - 'Power Now Phase 1': (6, 'W', to_signed), - 'Power Now Phase 2': (7, 'W', to_signed), - 'Power Now Phase 3': (8, 'W', to_signed), - - 'AC Power': (9, 'W', to_signed), - - 'PV1 Voltage': (10, 'V', div10), - 'PV2 Voltage': (11, 'V', div10), - - 'PV1 Current': (12, 'A', div10), - 'PV2 Current': (13, 'A', div10), - - 'PV1 Power': (14, 'W'), - 'PV2 Power': (15, 'W'), - - 'Grid Frequency Phase 1': (16, 'Hz', div100), - 'Grid Frequency Phase 2': (17, 'Hz', div100), - 'Grid Frequency Phase 3': (18, 'Hz', div100), - - 'Inverter Operation mode': (19, '', - Processors.inverter_modes), - # 20 - 32: always 0 - # 33: always 1 - # instead of to_signed this is actually 34 - 35, - # because 35 = if 34>32767: 0 else: 65535 - 'Exported Power': (34, 'W', to_signed), - # 35: if 34>32767: 0 else: 65535 - # 36 - 38 : always 0 - 'Battery Voltage': (39, 'V', div100), - 'Battery Current': (40, 'A', twoway_div100), - 'Battery Power': (41, 'W', to_signed), - # 42: div10, almost identical to [39] - # 43: twoway_div10, almost the same as "40" (battery current) - # 44: twoway_div100, almost the same as "41" (battery power), - # 45: always 1 - # 46: follows PV Output, idles around 44, peaks at 52, - 'Power Now': (47, 'W', to_signed), - # 48: always 256 - # 49,50: [49] + [50] * 15160 some increasing counter - # 51: always 5634 - # 52: always 100 - # 53: always 0 - # 54: follows PV Output, idles around 35, peaks at 54, - # 55-67: always 0 - 'Total Energy': (68, 'kWh', total_energy), - 'Total Energy Resets': (69, ''), - # 70: div10, today's energy including battery usage - # 71-73: 0 - 'Total Battery Discharge Energy': (74, 'kWh', discharge_energy), - 'Total Battery Discharge Energy Resets': (75, ''), - 'Total Battery Charge Energy': (76, 'kWh', charge_energy), - 'Total Battery Charge Energy Resets': (77, ''), - 'Today\'s Battery Discharge Energy': (78, 'kWh', div10), - 'Today\'s Battery Charge Energy': (79, 'kWh', div10), - 'Total PV Energy': (80, 'kWh', pv_energy), - 'Total PV Energy Resets': (81, ''), - 'Today\'s Energy': (82, 'kWh', div10), - # 83-85: always 0 - 'Total Feed-in Energy': (86, 'kWh', feedin_energy), - 'Total Feed-in Energy Resets': (87, ''), - 'Total Consumption': (88, 'kWh', consumption), - 'Total Consumption Resets': (89, ''), - 'Today\'s Feed-in Energy': (90, 'kWh', div100), - # 91: always 0 - 'Today\'s Consumption': (92, 'kWh', div100), - # 93-101: always 0 - # 102: always 1 - 'Battery Remaining Capacity': (103, '%'), - # 104: always 1 - 'Battery Temperature': (105, 'C'), - 'Battery Remaining Energy': (106, 'kWh', div10), - # 107: always 256 or 0 - # 108: always 3504 - # 109: always 2400 - # 110: around rise to 300 if battery not full, 0 if battery is full - # 112, 113: range [250,350]; looks like 113 + offset = 112, - # peaks if battery is full - # 114, 115: something around 33; Some temperature?! - # 116: increases slowly [2,5] - # 117-121: 1620 773 12850 12850 12850 - # 122-124: always 0 - # 125,126: some curve, look very similar to "42"(Battery Power), - # with offset around 15 - # 127,128 resetting counter /1000, around battery charge + discharge - # 164,165,166 some curves - 'Battery Operation mode': (168, '', - Processors.battery_modes), - # 169: div100 same as [39] - # 170-199: always 0 - - } - - @classmethod - def sensor_map(cls): - """ - Return sensor map - """ - sensors = {} - for name, (idx, unit, *_) in cls.__sensor_map.items(): - sensors[name] = (idx, unit) - return sensors - - @classmethod - def postprocess_map(cls): - """ - Return postprocessing map - """ - sensors = {} - for name, (_, _, *processor) in cls.__sensor_map.items(): - if processor: - sensors[name] = processor[0] - return sensors - - @classmethod - def schema(cls): - return cls.__schema - - @classmethod - async def make_request(cls, host, port=80, pwd='', headers=None): - - url = f'http://{host}:{port}/' - data = f'optType=ReadRealTimeData&pwd={pwd}' - - async with aiohttp.ClientSession() as session: - async with session.post(url, headers=headers, data=data) as req: - resp = await req.read() raw_json = resp.decode("utf-8") json_response = json.loads(raw_json) @@ -615,269 +135,3 @@ async def make_request(cls, host, port=80, pwd='', headers=None): version=response['ver'], type=response['type'] ) - - -class X1(InverterPost): - __schema = vol.Schema({ - vol.Required('type'): vol.All( - str, - startswith("X1-") - ), - vol.Required('SN'): str, - vol.Required('ver'): str, - vol.Required('Data'): vol.Schema( - vol.All( - [vol.Coerce(float)], - vol.Any( - vol.Length(min=102, max=102), - vol.Length(min=103, max=103), - vol.Length(min=107, max=107), - ), - ) - ), - vol.Required('Information'): vol.Schema( - vol.All( - vol.Length(min=9, max=9) - ) - ), - }, extra=vol.REMOVE_EXTRA) - - __sensor_map = { - 'PV1 Current': (0, 'A'), - 'PV2 Current': (1, 'A'), - 'PV1 Voltage': (2, 'V'), - 'PV2 Voltage': (3, 'V'), - - 'Output Current': (4, 'A'), - 'Network Voltage': (5, 'V'), - 'AC Power': (6, 'W'), - - 'Inverter Temperature': (7, 'C'), - 'Today\'s Energy': (8, 'kWh'), - 'Total Energy': (9, 'kWh'), - 'Exported Power': (10, 'W'), - 'PV1 Power': (11, 'W'), - 'PV2 Power': (12, 'W'), - - 'Battery Voltage': (13, 'V'), - 'Battery Current': (14, 'A'), - 'Battery Power': (15, 'W'), - 'Battery Temperature': (16, 'C'), - 'Battery Remaining Capacity': (21, '%'), - - 'Total Feed-in Energy': (41, 'kWh'), - 'Total Consumption': (42, 'kWh'), - - 'Power Now': (43, 'W'), - 'Grid Frequency': (50, 'Hz'), - - 'EPS Voltage': (53, 'V'), - 'EPS Current': (54, 'A'), - 'EPS Power': (55, 'W'), - 'EPS Frequency': (56, 'Hz'), - } - - @classmethod - def sensor_map(cls): - return cls.__sensor_map - - @classmethod - def schema(cls): - return cls.__schema - - -class X1Mini(InverterPost): - __schema = vol.Schema({ - vol.Required('type'): vol.All( - str, - startswith("X1-") - ), - vol.Required('SN'): str, - vol.Required('ver'): str, - 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) - ) - ), - }, extra=vol.REMOVE_EXTRA) - - __sensor_map = { - 'PV1 Current': (0, 'A'), - 'PV2 Current': (1, 'A'), - 'PV1 Voltage': (2, 'V'), - 'PV2 Voltage': (3, 'V'), - - 'Output Current': (4, 'A'), - 'Network Voltage': (5, 'V'), - 'AC Power': (6, 'W'), - - 'Inverter Temperature': (7, 'C'), - 'Today\'s Energy': (8, 'kWh'), - 'Total Energy': (9, 'kWh'), - 'Exported Power': (10, 'W'), - 'PV1 Power': (11, 'W'), - 'PV2 Power': (12, 'W'), - - 'Total Feed-in Energy': (41, 'kWh'), - 'Total Consumption': (42, 'kWh'), - - 'Power Now': (43, 'W'), - 'Grid Frequency': (50, 'Hz'), - } - - @classmethod - def sensor_map(cls): - return cls.__sensor_map - - @classmethod - def schema(cls): - return cls.__schema - - -class X1MiniV34(InverterPost): - __schema = vol.Schema({ - vol.Required('type', 'type'): vol.All(int, 4), - vol.Required('sn',): str, - vol.Required('ver'): str, - vol.Required('Data'): vol.Schema( - vol.All( - [vol.Coerce(float)], - vol.Any( - vol.Length(min=69, max=69), - vol.Length(min=200, max=200), - ) - ) - ), - vol.Required('Information'): vol.Schema( - vol.Any( - vol.Length(min=9, max=9), - vol.Length(min=10, max=10) - ) - ), - }, extra=vol.REMOVE_EXTRA) - - __sensor_map = { - 'Network Voltage': (0, 'V', div10), - 'Output Current': (1, 'A', div10), - 'AC Power': (2, 'W'), - 'PV1 Voltage': (3, 'V', div10), - 'PV2 Voltage': (4, 'V', div10), - 'PV1 Current': (5, 'A', div10), - 'PV2 Current': (6, 'A', div10), - 'PV1 Power': (7, 'W'), - 'PV2 Power': (8, 'W'), - 'Grid Frequency': (9, 'Hz', div100), - 'Total Energy': (11, 'kWh', div10), - 'Today\'s Energy': (13, 'kWh', div10), - 'Total Feed-in Energy': (41, 'kWh', div10), - 'Total Consumption': (42, 'kWh', div10), - 'Power Now': (43, 'W', div10), - } - - @classmethod - def sensor_map(cls): - """ - Return sensor map - """ - sensors = {} - for name, (idx, unit, *_) in cls.__sensor_map.items(): - sensors[name] = (idx, unit) - return sensors - - @classmethod - def postprocess_map(cls): - """ - Return postprocessing map - """ - sensors = {} - for name, (_, _, *processor) in cls.__sensor_map.items(): - if processor: - sensors[name] = processor[0] - return sensors - - @classmethod - def schema(cls): - return cls.__schema - - -class X1Smart(InverterPost): - """ - X1-Smart with Pocket WiFi v2.033.20 - Includes X-Forwarded-For for direct LAN API access - """ - __schema = vol.Schema({ - vol.Required('type', 'type'): vol.All(int, 8), - vol.Required('sn',): str, - vol.Required('ver'): str, - 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) - ) - ), - }, extra=vol.REMOVE_EXTRA) - - __sensor_map = { - 'Network Voltage': (0, 'V', div10), - 'Output Current': (1, 'A', div10), - 'AC Power': (2, 'W'), - 'PV1 Voltage': (3, 'V', div10), - 'PV2 Voltage': (4, 'V', div10), - 'PV1 Current': (5, 'A', div10), - 'PV2 Current': (6, 'A', div10), - 'PV1 Power': (7, 'W'), - 'PV2 Power': (8, 'W'), - 'Grid Frequency': (9, 'Hz', div100), - 'Total Energy': (11, 'kWh', div10), - 'Today\'s Energy': (13, 'kWh', div10), - 'Inverter Temperature': (39, 'C'), - 'Exported Power': (48, 'W', to_signed), - 'Total Feed-in Energy': (50, 'kWh', div100), - 'Total Consumption': (52, 'kWh', div100), - } - - @classmethod - async def make_request(cls, host, port=80, pwd='', headers=None): - headers = {'X-Forwarded-For': '5.8.8.8'} - return await super().make_request(host, port, pwd, headers=headers) - - @classmethod - def sensor_map(cls): - """ - Return sensor map - """ - sensors = {} - for name, (idx, unit, *_) in cls.__sensor_map.items(): - sensors[name] = (idx, unit) - return sensors - - @classmethod - def postprocess_map(cls): - """ - Return postprocessing map - """ - sensors = {} - for name, (_, _, *processor) in cls.__sensor_map.items(): - if processor: - sensors[name] = processor[0] - return sensors - - @classmethod - def schema(cls): - return cls.__schema - - -# registry of inverters -REGISTRY = [XHybrid, X3, X3V34, X1, X1Mini, X1MiniV34, X1Smart, - QVOLTHYBG33P] diff --git a/solax/inverters/__init__.py b/solax/inverters/__init__.py new file mode 100644 index 0000000..ab80bb0 --- /dev/null +++ b/solax/inverters/__init__.py @@ -0,0 +1,11 @@ +from .qvolt_hyb_g3_3p import QVOLTHYBG33P +from .x_hybrid import XHybrid +from .x1 import X1 +from .x1_mini import X1Mini +from .x1_mini_v34 import X1MiniV34 +from .x1_smart import X1Smart +from .x3_v34 import X3V34 +from .x3 import X3 + +__all__ = ["QVOLTHYBG33P", "XHybrid", "X1", "X1Mini", "X1MiniV34", "X1Smart", + "X3V34", "X3"] diff --git a/solax/inverters/qvolt_hyb_g3_3p.py b/solax/inverters/qvolt_hyb_g3_3p.py new file mode 100644 index 0000000..908e284 --- /dev/null +++ b/solax/inverters/qvolt_hyb_g3_3p.py @@ -0,0 +1,172 @@ +import voluptuous as vol +import aiohttp +from solax.inverter import InverterPost +from solax.utils import div10, div100, twoway_div10, to_signed, pv_energy, \ + twoway_div100, total_energy, discharge_energy, charge_energy, \ + feedin_energy, consumption + + +class QVOLTHYBG33P(InverterPost): + """ + QCells + Q.VOLT HYB-G3-3P + """ + class Processors: + """ + Postprocessors used only in the QVOLTHYBG33P inverter sensor_map. + """ + @staticmethod + def inverter_modes(value, *_args, **_kwargs): + return { + 0: "Waiting", + 1: "Checking", + 2: "Normal", + 3: "Off", + 4: "Permanent Fault", + 5: "Updating", + 6: "EPS Check", + 7: "EPS Mode", + 8: "Self Test", + 9: "Idle", + 10: "Standby" + }.get(value, f"unmapped value '{value}'") + + @staticmethod + def battery_modes(value, *_args, **_kwargs): + return { + 0: "Self Use Mode", + 1: "Force Time Use", + 2: "Back Up Mode", + 3: "Feed-in Priority", + }.get(value, f"unmapped value '{value}'") + + _schema = vol.Schema({ + vol.Required('type'): vol.All(int, 14), + vol.Required('sn'): str, + vol.Required('ver'): str, + 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=10, max=10) + ) + ), + }, extra=vol.REMOVE_EXTRA) + + _sensor_map = { + + 'Network Voltage Phase 1': (0, 'V', div10), + 'Network Voltage Phase 2': (1, 'V', div10), + 'Network Voltage Phase 3': (2, 'V', div10), + + 'Output Current Phase 1': (3, 'A', twoway_div10), + 'Output Current Phase 2': (4, 'A', twoway_div10), + 'Output Current Phase 3': (5, 'A', twoway_div10), + + 'Power Now Phase 1': (6, 'W', to_signed), + 'Power Now Phase 2': (7, 'W', to_signed), + 'Power Now Phase 3': (8, 'W', to_signed), + + 'AC Power': (9, 'W', to_signed), + + 'PV1 Voltage': (10, 'V', div10), + 'PV2 Voltage': (11, 'V', div10), + + 'PV1 Current': (12, 'A', div10), + 'PV2 Current': (13, 'A', div10), + + 'PV1 Power': (14, 'W'), + 'PV2 Power': (15, 'W'), + + 'Grid Frequency Phase 1': (16, 'Hz', div100), + 'Grid Frequency Phase 2': (17, 'Hz', div100), + 'Grid Frequency Phase 3': (18, 'Hz', div100), + + 'Inverter Operation mode': (19, '', + Processors.inverter_modes), + # 20 - 32: always 0 + # 33: always 1 + # instead of to_signed this is actually 34 - 35, + # because 35 = if 34>32767: 0 else: 65535 + 'Exported Power': (34, 'W', to_signed), + # 35: if 34>32767: 0 else: 65535 + # 36 - 38 : always 0 + 'Battery Voltage': (39, 'V', div100), + 'Battery Current': (40, 'A', twoway_div100), + 'Battery Power': (41, 'W', to_signed), + # 42: div10, almost identical to [39] + # 43: twoway_div10, almost the same as "40" (battery current) + # 44: twoway_div100, almost the same as "41" (battery power), + # 45: always 1 + # 46: follows PV Output, idles around 44, peaks at 52, + 'Power Now': (47, 'W', to_signed), + # 48: always 256 + # 49,50: [49] + [50] * 15160 some increasing counter + # 51: always 5634 + # 52: always 100 + # 53: always 0 + # 54: follows PV Output, idles around 35, peaks at 54, + # 55-67: always 0 + 'Total Energy': (68, 'kWh', total_energy), + 'Total Energy Resets': (69, ''), + # 70: div10, today's energy including battery usage + # 71-73: 0 + 'Total Battery Discharge Energy': (74, 'kWh', discharge_energy), + 'Total Battery Discharge Energy Resets': (75, ''), + 'Total Battery Charge Energy': (76, 'kWh', charge_energy), + 'Total Battery Charge Energy Resets': (77, ''), + 'Today\'s Battery Discharge Energy': (78, 'kWh', div10), + 'Today\'s Battery Charge Energy': (79, 'kWh', div10), + 'Total PV Energy': (80, 'kWh', pv_energy), + 'Total PV Energy Resets': (81, ''), + 'Today\'s Energy': (82, 'kWh', div10), + # 83-85: always 0 + 'Total Feed-in Energy': (86, 'kWh', feedin_energy), + 'Total Feed-in Energy Resets': (87, ''), + 'Total Consumption': (88, 'kWh', consumption), + 'Total Consumption Resets': (89, ''), + 'Today\'s Feed-in Energy': (90, 'kWh', div100), + # 91: always 0 + 'Today\'s Consumption': (92, 'kWh', div100), + # 93-101: always 0 + # 102: always 1 + 'Battery Remaining Capacity': (103, '%'), + # 104: always 1 + 'Battery Temperature': (105, 'C'), + 'Battery Remaining Energy': (106, 'kWh', div10), + # 107: always 256 or 0 + # 108: always 3504 + # 109: always 2400 + # 110: around rise to 300 if battery not full, 0 if battery is full + # 112, 113: range [250,350]; looks like 113 + offset = 112, + # peaks if battery is full + # 114, 115: something around 33; Some temperature?! + # 116: increases slowly [2,5] + # 117-121: 1620 773 12850 12850 12850 + # 122-124: always 0 + # 125,126: some curve, look very similar to "42"(Battery Power), + # with offset around 15 + # 127,128 resetting counter /1000, around battery charge + discharge + # 164,165,166 some curves + 'Battery Operation mode': (168, '', + Processors.battery_modes), + # 169: div100 same as [39] + # 170-199: always 0 + + } + + @classmethod + async def make_request(cls, host, port=80, pwd='', headers=None): + + url = f'http://{host}:{port}/' + data = f'optType=ReadRealTimeData&pwd={pwd}' + + async with aiohttp.ClientSession() as session: + async with session.post(url, headers=headers, data=data) as req: + resp = await req.read() + + return cls.handle_response(resp) diff --git a/solax/inverters/x1.py b/solax/inverters/x1.py new file mode 100644 index 0000000..ab871cd --- /dev/null +++ b/solax/inverters/x1.py @@ -0,0 +1,66 @@ +import voluptuous as vol +from solax.inverter import InverterPost +from solax.utils import startswith + + +class X1(InverterPost): + # pylint: disable=duplicate-code + _schema = vol.Schema({ + vol.Required('type'): vol.All( + str, + startswith("X1-") + ), + vol.Required('SN'): str, + vol.Required('ver'): str, + vol.Required('Data'): vol.Schema( + vol.All( + [vol.Coerce(float)], + vol.Any( + vol.Length(min=102, max=102), + vol.Length(min=103, max=103), + vol.Length(min=107, max=107), + ), + ) + ), + vol.Required('Information'): vol.Schema( + vol.All( + vol.Length(min=9, max=9) + ) + ), + }, extra=vol.REMOVE_EXTRA) + + _sensor_map = { + 'PV1 Current': (0, 'A'), + 'PV2 Current': (1, 'A'), + 'PV1 Voltage': (2, 'V'), + 'PV2 Voltage': (3, 'V'), + + 'Output Current': (4, 'A'), + 'Network Voltage': (5, 'V'), + 'AC Power': (6, 'W'), + + 'Inverter Temperature': (7, 'C'), + 'Today\'s Energy': (8, 'kWh'), + 'Total Energy': (9, 'kWh'), + 'Exported Power': (10, 'W'), + 'PV1 Power': (11, 'W'), + 'PV2 Power': (12, 'W'), + + 'Battery Voltage': (13, 'V'), + 'Battery Current': (14, 'A'), + 'Battery Power': (15, 'W'), + 'Battery Temperature': (16, 'C'), + 'Battery Remaining Capacity': (21, '%'), + + 'Total Feed-in Energy': (41, 'kWh'), + 'Total Consumption': (42, 'kWh'), + + 'Power Now': (43, 'W'), + 'Grid Frequency': (50, 'Hz'), + + 'EPS Voltage': (53, 'V'), + 'EPS Current': (54, 'A'), + 'EPS Power': (55, 'W'), + 'EPS Frequency': (56, 'Hz'), + } + # pylint: enable=duplicate-code diff --git a/solax/inverters/x1_mini.py b/solax/inverters/x1_mini.py new file mode 100644 index 0000000..ce24aa7 --- /dev/null +++ b/solax/inverters/x1_mini.py @@ -0,0 +1,51 @@ +import voluptuous as vol +from solax.inverter import InverterPost +from solax.utils import startswith + + +class X1Mini(InverterPost): + # pylint: disable=duplicate-code + _schema = vol.Schema({ + vol.Required('type'): vol.All( + str, + startswith("X1-") + ), + vol.Required('SN'): str, + vol.Required('ver'): str, + 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) + ) + ), + }, extra=vol.REMOVE_EXTRA) + + _sensor_map = { + 'PV1 Current': (0, 'A'), + 'PV2 Current': (1, 'A'), + 'PV1 Voltage': (2, 'V'), + 'PV2 Voltage': (3, 'V'), + + 'Output Current': (4, 'A'), + 'Network Voltage': (5, 'V'), + 'AC Power': (6, 'W'), + + 'Inverter Temperature': (7, 'C'), + 'Today\'s Energy': (8, 'kWh'), + 'Total Energy': (9, 'kWh'), + 'Exported Power': (10, 'W'), + 'PV1 Power': (11, 'W'), + 'PV2 Power': (12, 'W'), + + 'Total Feed-in Energy': (41, 'kWh'), + 'Total Consumption': (42, 'kWh'), + + 'Power Now': (43, 'W'), + 'Grid Frequency': (50, 'Hz'), + } + # pylint: enable=duplicate-code diff --git a/solax/inverters/x1_mini_v34.py b/solax/inverters/x1_mini_v34.py new file mode 100644 index 0000000..2acb8ba --- /dev/null +++ b/solax/inverters/x1_mini_v34.py @@ -0,0 +1,47 @@ +import voluptuous as vol + +from solax.inverter import InverterPost +from solax.utils import div10, div100 + + +class X1MiniV34(InverterPost): + # pylint: disable=duplicate-code + _schema = vol.Schema({ + vol.Required('type', 'type'): vol.All(int, 4), + vol.Required('sn',): str, + vol.Required('ver'): str, + vol.Required('Data'): vol.Schema( + vol.All( + [vol.Coerce(float)], + vol.Any( + vol.Length(min=69, max=69), + vol.Length(min=200, max=200), + ) + ) + ), + vol.Required('Information'): vol.Schema( + vol.Any( + vol.Length(min=9, max=9), + vol.Length(min=10, max=10) + ) + ), + }, extra=vol.REMOVE_EXTRA) + + _sensor_map = { + 'Network Voltage': (0, 'V', div10), + 'Output Current': (1, 'A', div10), + 'AC Power': (2, 'W'), + 'PV1 Voltage': (3, 'V', div10), + 'PV2 Voltage': (4, 'V', div10), + 'PV1 Current': (5, 'A', div10), + 'PV2 Current': (6, 'A', div10), + 'PV1 Power': (7, 'W'), + 'PV2 Power': (8, 'W'), + 'Grid Frequency': (9, 'Hz', div100), + 'Total Energy': (11, 'kWh', div10), + 'Today\'s Energy': (13, 'kWh', div10), + 'Total Feed-in Energy': (41, 'kWh', div10), + 'Total Consumption': (42, 'kWh', div10), + 'Power Now': (43, 'W', div10), + } + # pylint: enable=duplicate-code diff --git a/solax/inverters/x1_smart.py b/solax/inverters/x1_smart.py new file mode 100644 index 0000000..6b4c387 --- /dev/null +++ b/solax/inverters/x1_smart.py @@ -0,0 +1,51 @@ +import voluptuous as vol +from solax.inverter import InverterPost +from solax.utils import div10, div100, to_signed + + +class X1Smart(InverterPost): + """ + X1-Smart with Pocket WiFi v2.033.20 + Includes X-Forwarded-For for direct LAN API access + """ + # pylint: disable=duplicate-code + _schema = vol.Schema({ + vol.Required('type', 'type'): vol.All(int, 8), + vol.Required('sn',): str, + vol.Required('ver'): str, + 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) + ) + ), + }, extra=vol.REMOVE_EXTRA) + + _sensor_map = { + 'Network Voltage': (0, 'V', div10), + 'Output Current': (1, 'A', div10), + 'AC Power': (2, 'W'), + 'PV1 Voltage': (3, 'V', div10), + 'PV2 Voltage': (4, 'V', div10), + 'PV1 Current': (5, 'A', div10), + 'PV2 Current': (6, 'A', div10), + 'PV1 Power': (7, 'W'), + 'PV2 Power': (8, 'W'), + 'Grid Frequency': (9, 'Hz', div100), + 'Total Energy': (11, 'kWh', div10), + 'Today\'s Energy': (13, 'kWh', div10), + 'Inverter Temperature': (39, 'C'), + 'Exported Power': (48, 'W', to_signed), + 'Total Feed-in Energy': (50, 'kWh', div100), + 'Total Consumption': (52, 'kWh', div100), + } + + @classmethod + async def make_request(cls, host, port=80, pwd='', headers=None): + headers = {'X-Forwarded-For': '5.8.8.8'} + return await super().make_request(host, port, pwd, headers=headers) diff --git a/solax/inverters/x3.py b/solax/inverters/x3.py new file mode 100644 index 0000000..3650e43 --- /dev/null +++ b/solax/inverters/x3.py @@ -0,0 +1,72 @@ +import voluptuous as vol +from solax.inverter import InverterPost +from solax.utils import startswith + + +class X3(InverterPost): + _schema = vol.Schema({ + vol.Required('type'): vol.All( + str, + startswith("X3-") + ), + vol.Required('SN'): str, + vol.Required('ver'): str, + 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) + ) + ), + }, extra=vol.REMOVE_EXTRA) + + # pylint: disable=duplicate-code + _sensor_map = { + 'PV1 Current': (0, 'A'), + 'PV2 Current': (1, 'A'), + 'PV1 Voltage': (2, 'V'), + 'PV2 Voltage': (3, 'V'), + + 'Output Current Phase 1': (4, 'A'), + 'Network Voltage Phase 1': (5, 'V'), + 'AC Power': (6, 'W'), + + 'Inverter Temperature': (7, 'C'), + 'Today\'s Energy': (8, 'kWh'), + 'Total Energy': (9, 'kWh'), + 'Exported Power': (10, 'W'), + 'PV1 Power': (11, 'W'), + 'PV2 Power': (12, 'W'), + + 'Battery Voltage': (13, 'V'), + 'Battery Current': (14, 'A'), + 'Battery Power': (15, 'W'), + 'Battery Temperature': (16, 'C'), + 'Battery Remaining Capacity': (21, '%'), + + 'Total Feed-in Energy': (41, 'kWh'), + 'Total Consumption': (42, 'kWh'), + + 'Power Now Phase 1': (43, 'W'), + 'Power Now Phase 2': (44, 'W'), + 'Power Now Phase 3': (45, 'W'), + 'Output Current Phase 2': (46, 'A'), + 'Output Current Phase 3': (47, 'A'), + 'Network Voltage Phase 2': (48, 'V'), + 'Network Voltage Phase 3': (49, 'V'), + + 'Grid Frequency Phase 1': (50, 'Hz'), + 'Grid Frequency Phase 2': (51, 'Hz'), + 'Grid Frequency Phase 3': (52, 'Hz'), + + 'EPS Voltage': (53, 'V'), + 'EPS Current': (54, 'A'), + 'EPS Power': (55, 'W'), + 'EPS Frequency': (56, 'Hz'), + } diff --git a/solax/inverters/x3_v34.py b/solax/inverters/x3_v34.py new file mode 100644 index 0000000..ed3eeb0 --- /dev/null +++ b/solax/inverters/x3_v34.py @@ -0,0 +1,88 @@ +import voluptuous as vol +from solax.inverter import InverterPost +from solax.utils import div10, div100, twoway_div10, to_signed, pv_energy, \ + twoway_div100, total_energy, discharge_energy, charge_energy, \ + feedin_energy, consumption, eps_total_energy + + +class X3V34(InverterPost): + """X3 v2.034.06""" + # pylint: disable=duplicate-code + _schema = vol.Schema({ + vol.Required('type'): vol.All(int, 5), + vol.Required('sn'): str, + vol.Required('ver'): str, + 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=10, max=10) + ) + ), + }, extra=vol.REMOVE_EXTRA) + + _sensor_map = { + 'Network Voltage Phase 1': (0, 'V', div10), + 'Network Voltage Phase 2': (1, 'V', div10), + 'Network Voltage Phase 3': (2, 'V', div10), + + 'Output Current Phase 1': (3, 'A', twoway_div10), + 'Output Current Phase 2': (4, 'A', twoway_div10), + 'Output Current Phase 3': (5, 'A', twoway_div10), + + 'Power Now Phase 1': (6, 'W', to_signed), + 'Power Now Phase 2': (7, 'W', to_signed), + 'Power Now Phase 3': (8, 'W', to_signed), + + 'PV1 Voltage': (9, 'V', div10), + 'PV2 Voltage': (10, 'V', div10), + 'PV1 Current': (11, 'A', div10), + 'PV2 Current': (12, 'A', div10), + 'PV1 Power': (13, 'W'), + 'PV2 Power': (14, 'W'), + + 'Total PV Energy': (89, 'kWh', pv_energy), + 'Total PV Energy Resets': (90, ''), + 'Today\'s PV Energy': (112, 'kWh', div10), + + 'Grid Frequency Phase 1': (15, 'Hz', div100), + 'Grid Frequency Phase 2': (16, 'Hz', div100), + 'Grid Frequency Phase 3': (17, 'Hz', div100), + + 'Total Energy': (19, 'kWh', total_energy), + 'Total Energy Resets': (20, ''), + 'Today\'s Energy': (21, 'kWh', div10), + + 'Battery Voltage': (24, 'V', div100), + 'Battery Current': (25, 'A', twoway_div100), + 'Battery Power': (26, 'W', to_signed), + 'Battery Temperature': (27, 'C'), + 'Battery Remaining Capacity': (28, '%'), + + 'Total Battery Discharge Energy': (30, 'kWh', + discharge_energy), + 'Total Battery Discharge Energy Resets': (31, ''), + 'Today\'s Battery Discharge Energy': (113, 'kWh', div10), + 'Battery Remaining Energy': (32, 'kWh', div10), + 'Total Battery Charge Energy': (87, 'kWh', charge_energy), + 'Total Battery Charge Energy Resets': (88, ''), + 'Today\'s Battery Charge Energy': (114, 'kWh', div10), + + 'Exported Power': (65, 'W', to_signed), + 'Total Feed-in Energy': (67, 'kWh', feedin_energy), + 'Total Feed-in Energy Resets': (68, ''), + 'Total Consumption': (69, 'kWh', consumption), + 'Total Consumption Resets': (70, ''), + + 'AC Power': (181, 'W', to_signed), + + 'EPS Frequency': (63, 'Hz', div100), + 'EPS Total Energy': (110, 'kWh', + eps_total_energy), + 'EPS Total Energy Resets': (111, 'Hz'), + } + # pylint: enable=duplicate-code diff --git a/solax/inverters/x_hybrid.py b/solax/inverters/x_hybrid.py new file mode 100644 index 0000000..2e240d4 --- /dev/null +++ b/solax/inverters/x_hybrid.py @@ -0,0 +1,78 @@ +import json +import aiohttp +import voluptuous as vol +from solax.inverter import Inverter, InverterResponse + + +class XHybrid(Inverter): + """ + Tested with: + * SK-TL5000E + """ + _schema = vol.Schema({ + vol.Required('method'): str, + vol.Required('version'): str, + vol.Required('type'): str, + 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)), + }, extra=vol.REMOVE_EXTRA) + + # key: name of sensor + # value.0: index + # value.1: unit (String) or None + # from https://github.com/GitHobi/solax/wiki/direct-data-retrieval + _sensor_map = { + 'PV1 Current': (0, 'A'), + 'PV2 Current': (1, 'A'), + 'PV1 Voltage': (2, 'V'), + 'PV2 Voltage': (3, 'V'), + + 'Output Current': (4, 'A'), + 'Network Voltage': (5, 'V'), + 'Power Now': (6, 'W'), + + 'Inverter Temperature': (7, 'C'), + 'Today\'s Energy': (8, 'kWh'), + 'Total Energy': (9, 'kWh'), + 'Exported Power': (10, 'W'), + 'PV1 Power': (11, 'W'), + 'PV2 Power': (12, 'W'), + + 'Battery Voltage': (13, 'V'), + 'Battery Current': (14, 'A'), + 'Battery Power': (15, 'W'), + 'Battery Temperature': (16, 'C'), + 'Battery Remaining Capacity': (17, '%'), + + 'Month\'s Energy': (19, 'kWh'), + + 'Grid Frequency': (50, 'Hz'), + 'EPS Voltage': (53, 'V'), + 'EPS Current': (54, 'A'), + 'EPS Power': (55, 'W'), + 'EPS Frequency': (56, 'Hz'), + } + + @classmethod + async def make_request(cls, host, port=80, pwd='', headers=None): + base = 'http://{}:{}/api/realTimeData.htm' + url = base.format(host, port) + async with aiohttp.ClientSession() as session: + async with session.get(url) as req: + garbage = await req.read() + formatted = garbage.decode("utf-8") + formatted = formatted.replace(",,", ",0.0,").replace(",,", ",0.0,") + json_response = json.loads(formatted) + response = cls.schema()(json_response) + return InverterResponse( + data=cls.map_response(response['Data']), + serial_number=response['SN'], + version=response['version'], + type=response['type'] + ) diff --git a/solax/utils.py b/solax/utils.py index 6137873..8221fb0 100644 --- a/solax/utils.py +++ b/solax/utils.py @@ -1,3 +1,15 @@ +from voluptuous import Invalid + + +def startswith(something): + def inner(actual): + if isinstance(actual, str): + if actual.startswith(something): + return actual + raise Invalid(f"{str(actual)} does not start with {something}") + return inner + + def div10(val, *_args, **_kwargs): return val / 10 diff --git a/tests/fixtures.py b/tests/fixtures.py index 0eee620..0a85106 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -1,709 +1,24 @@ from collections import namedtuple import pytest -from solax import inverter - -X_FORWARDED_HEADER = {'X-Forwarded-For': '5.8.8.8'} - -XHYBRID_DE01_RESPONSE = { - 'method': 'uploadsn', - 'version': 'Solax_SI_CH_2nd_20160912_DE02', - 'type': 'AL_SE', - 'SN': 'XXXXXXX', - 'Data': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, - 50, 51, 52, 53, 54, 55, 56, 57], - 'Status': 2 -} - -XHYBRID_DE02_RESPONSE = { - 'method': 'uploadsn', - 'version': 'Solax_SI_CH_2nd_20160912_DE02', - 'type': 'AL_SE', - 'SN': 'XXXXXXX', - 'Data': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, - 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63, 64, 65, 66, 67], - 'Status': 2 -} - -X1_BOOST_AIR_MINI_RESPONSE = { - "type": "X1-Boost-Air-Mini", - "SN": "XXXXXXX", - "ver": "2.32.6", - "Data": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, - 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63, 64, 65, 66, 67, 68], - "Information": [2.500, 4, "X1-Boost-Air-Mini", "XXXXXXX", - 1, 3.25, 1.09, 1.10, 0.00] -} - -X1_MINI_RESPONSE_V34 = { - "sn": "XXXXXXXXXX", - "ver": "2.034.06", - "type": 4, - "Data": [2310, 15, 349, 609, 0, 58, 0, 359, 0, 5000, - 2, 3474, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 0, 2518, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0], - "Information": [0.700, 4, "XXXXXXXXXXXXXX", - 1, 1.19, 0.00, 1.32, 0.00, 0.00, 1] -} - -X1_SMART_RESPONSE = { - "sn": "XXXXXXX", - "ver": "2.033.20", - "type": 8, - "Data": [2396, 126, 2956, 4329, 2338, 41, 57, 1777, 1336, 6021, - 2, 1215, 0, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 0, 80, 0, 0, 0, 0, 0, 0, - 10, 65535, 87, 0, 8184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0], - "Information": [8.0, 8, "XXXXXXX", 1, 1.07, 1.0, 1.05, 0.0] -} - -X3_MIC_RESPONSE = { - "type": "X3-MIC", - "SN": "XXXXXXX", - "ver": "2.033.20", - "Data": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, - 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, - 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, - 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, - 100, 101, 102], - "Information": [8.000, 7, "X3-MIC", "XXXXXXX", - 1, 1.10, 1.02, 1.09, 1.02] -} - -X1_HYBRID_G3_RESPONSE = { - "type": "X1-Hybiyd-G3", - "SN": "XXXXXXX", - "ver": "2.033.20", - "Data": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, - 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, - 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, - 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, - 100, 101], - "Information": [8.000, 7, "X3-MIC", "XXXXXXX", - 1, 1.10, 1.02, 1.09, 1.02] -} - -X1_HYBRID_G3_2X_MPPT_RESPONSE = { - "type": "X1-Hybiyd-G3", - "SN": "XXXXXXXXXX", - "ver": "2.033.20", - "Data": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, - 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, - 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, - 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, - 100, 101, 102, 103, 104, 105, 106], - "Information": [3.000, 3, "X1-Hybiyd-G3", "YYYYYYYYYYYYYY", - 1, 3.11, 0.00, 3.13, 1.05], - "battery": { - "brand": "0", - "masterVer": "0.00", - "slaveNum": "0", - "slaveVer": [0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], - "slaveType": [0, 0, 0, 0, 0, 0, 0, 0] - } -} - -X3_HYBRID_G3_RESPONSE = { - "type": "X3-Hybiyd-G3", - "SN": "XXXXXXX", - "ver": "2.32.6", - "Data": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, - 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, - 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, - 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, - 100, 101], - "Information": [0.000, 5, "X3-Hybiyd-G3", "XXXXXXX", - 1, 3.00, 0.00, 3.17, 1.01] -} - -XHYBRID_VALUES = { - 'Today\'s Energy': 8.0, - 'Battery Current': 14.0, - 'Month\'s Energy': 19.0, - 'Battery Power': 15, - 'Battery Remaining Capacity': 17, - 'Battery Temperature': 16, - 'Battery Voltage': 13, - 'EPS Current': 54, - 'EPS Frequency': 56, - 'EPS Power': 55, - 'EPS Voltage': 53, - 'Exported Power': 10, - 'Grid Frequency': 50, - 'Inverter Temperature': 7, - 'Network Voltage': 5, - 'Output Current': 4, - 'PV1 Current': 0, - 'PV1 Power': 11, - 'PV1 Voltage': 2, - 'PV2 Current': 1, - 'PV2 Power': 12, - 'PV2 Voltage': 3, - 'Power Now': 6, - 'Total Energy': 9 -} - -X3_HYBRID_G3_2X_MPPT_RESPONSE = { - "type": "X3-Hybiyd-G3", - "SN": "XXXXXXXXXX", - "ver": "2.033.20", - "Data": [0.0, 0.0, 0.0, 0.0, 0.9, 234.0, 3189, 42, 15.2, 27.0, -25, 0, 0, - 210.30, -15.70, -3321, 24, 8.6, 0, 11.0, 1, 68, 232.4, 170.0, - 31.0, 35.0, 22.6, 20.7, 3.8, 3.8, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 2.03, 23.41, 123, 1344, 1722, 5.4, 6.9, 250.0, 251.9, 50.01, - 50.01, 50.01, 0.0, 0.0, 0, 0.00, 0, 0, 0, 0.00, 0, 0, 0, 0, 0.00, - 0, 0, 2, 1, 26, 1.00, 0, 100, 10, 25.00, 25.00, 0, 0, 0, 0, 0.0, - 10.8, 24.4, 0.0, 0, 2, 0.0, 0.0, 0, 0.0, 0.0, 0, 0, 0, 0, 1, 1, - 0, 0, 0.00, 0.00, 1, 273, 212.3, -16.2, -3439], - "Information": [8.000, 5, "X3-Hybiyd-G3", "XXXXXXXX", 1, 4.47, 0.00, 4.34, - 1.05], - "battery": { - "brand": "83", - "masterVer": "1.11", - "slaveNum": "4", - "slaveVer": [1.13, 1.13, 1.13, 1.13] - } -} - -X3_HYBRID_G3_2X_MPPT_RESPONSE_V34 = { - "type": 5, - "sn": "XXXXXXXXXX", - "ver": "2.034.06", - "Data": [2468, 2490, 2508, 13, 14, 10, 266, 284, 136, 5377, 4630, 17, 0, - 958, 0, 5003, 5003, 5003, 2, 14833, 0, 103, 0, 0, 22930, 90, 229, - 22, 99, 0, 7062, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 100, 0, 41, 7777, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65480, - 0, 17372, 0, 59877, 0, 665, 41, 256, 2352, 1568, 20, 350, 202, - 190, 41, 41, 81, 1, 1, 0, 0, 8142, 0, 17319, 0, 6, 0, 64851, - 65535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 0, 55, 0, 0, 6, 0, 164, - 43, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 256, 12579, 783, 5381, - 1107, 512, 8224, 8224, 0, 0, 4369, 0, 273, 2295, 5, 114, 4112, - 4096, 25912, 31, 21302, 19778, 18003, 12355, 16697, 12354, 14132, - 21302, 13110, 12338, 12337, 14386, 12354, 12852, 21302, 13110, - 12338, 12337, 14386, 12354, 12340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 686, 1, 257, 257, 1794, 1025, 0, 22930, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0], - "Information": [8.000, 5, "XXXXXXXX", 1, 4.47, 0.00, 4.34, 1.05, 0.0, 1] -} - -X3_HYBRID_G3_2X_MPPT_RESPONSE_V34_NEGATIVE_POWER = { - "type": 5, - "sn": "XXXXXXXXXX", - "ver": "2.034.06", - "Data": [2364, 2431, 2386, 65463, 65464, 65464, 63798, 63769, 63807, 0, 0, - 0, 0, 0, 0, 4998, 4998, 4998, 2, 19251, 1, 84, 0, 0, 20460, 2500, - 5117, 24, 20, 0, 24696, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 100, 0, 36, 7577, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 59792, 65535, 31674, 5, 29027, 1, 510, 36, 256, 2352, 1568, 310, - 350, 217, 205, 36, 36, 258, 1, 1, 13, 0, 28876, 0, 30616, 1, 21, - 0, 5236, 0, 65535, 65535, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 936, 0, - 0, 21, 0, 98, 40, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 256, 4392, - 7702, 5386, 1107, 512, 8481, 8481, 0, 0, 4369, 0, 273, 2037, 243, - 4949, 3645, 3638, 12, 100, 21302, 19778, 18003, 12355, 16697, - 12354, 14132, 21302, 13110, 12338, 12337, 14386, 12354, 12852, - 21302, 13110, 12338, 12337, 14386, 12354, 12340, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 60302, 1, 257, 257, 770, 1028, 0, 20460, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - "Information": [8.000, 5, "XXXXXXXX", 1, 4.60, 0.00, 4.42, 1.05, 0.00, 1] -} - -X3_HYBRID_G3_2X_MPPT_RESPONSE_V34_EPS_MODE = { - "type": 5, - "sn": "XXXXXXXXXX", - "ver": "2.034.06", - "Data": [0, 0, 0, 0, 0, 0, 0, 0, 0, 4864, 4466, 55, 0, 2678, 0, 0, 0, 0, 7, - 8339, 1, 178, 0, 0, 22860, 65436, 65294, 27, 98, 0, 21240, 0, 124, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 52, 7541, 226, - 2453, 205, 2223, 2276, 2426, 10, 107, 8, 172, 2441, 147, 5000, 0, - 0, 0, 55339, 4, 18922, 1, 0, 52, 256, 2352, 1568, 0, 350, 275, - 262, 41, 41, 225, 1, 1, 0, 0, 25011, 0, 18252, 1, 17, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 768, 0, 54, 0, 2, 17, 0, 260, 61, 119, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 256, 7971, 6926, 5385, 1107, 512, - 8224, 8224, 0, 0, 4369, 0, 273, 2288, 65523, 65239, 4099, 4083, - 13728, 87, 21302, 19778, 18003, 12355, 16697, 12354, 14132, 21302, - 13110, 12338, 12337, 14386, 12354, 12852, 21302, 13110, 12338, - 12337, 14386, 12354, 12340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 257, 257, 1281, 1025, 0, 22860, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0], - "Information": [8.000, 5, "XXXXXXXX", 1, 4.60, 0.00, 4.42, 1.05, 0.00, 1] -} - -QVOLTHYBG33P_RESPONSE_V34 = { - "sn": "SWX***", - "ver": "2.034.06", - "type": 14, - "Data": [ - 2214, 2238, 2251, 11, 10, 12, 162, 136, 146, 444, 5662, 5682, 18, 17, - 1050, 977, 5002, 5001, 5002, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 65529, 65535, 0, 0, 0, 32340, 500, 1616, 3236, 50, 1618, 1, 50, 451, - 256, 3841, 4875, 5634, 100, 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 2190, 0, 36, 1, 0, 1, 738, 0, 904, 0, 0, 81, 2316, 0, 118, 0, 0, 0, - 10794, 0, 14544, 0, 166, 0, 466, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 95, 1, - 35, 88, 256, 3504, 2400, 115, 300, 352, 325, 34, 34, 8, 1620, 773, - 12850, 12850, 12850, 0, 0, 0, 3389, 3384, 33876, 2, 20564, 12339, - 18497, 12599, 18743, 12356, 14386, 20564, 12339, 18498, 12600, 18740, - 12356, 12855, 20564, 12339, 18498, 12600, 18740, 12612, 14642, 20564, - 12339, 18498, 12600, 18740, 12356, 13877, 0, 0, 0, 0, 0, 0, 0, 3843, - 2306, 1282, 257, 0, 32340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ], - "Information": [12.0, 14, "H34***", 1, 1.15, 0.0, 1.14, 1.07, 0.0, 1] -} - -X3_VALUES = { - 'PV1 Current': 0, - 'PV2 Current': 1, - 'PV1 Voltage': 2, - 'PV2 Voltage': 3, - - 'Output Current Phase 1': 4, - 'Network Voltage Phase 1': 5, - 'AC Power': 6, - - 'Inverter Temperature': 7, - 'Today\'s Energy': 8, - 'Total Energy': 9, - 'Exported Power': 10, - 'PV1 Power': 11, - 'PV2 Power': 12, - - 'Battery Voltage': 13, - 'Battery Current': 14, - 'Battery Power': 15, - 'Battery Temperature': 16, - 'Battery Remaining Capacity': 21, - - 'Total Feed-in Energy': 41, - 'Total Consumption': 42, - - 'Output Current Phase 2': 46, - 'Output Current Phase 3': 47, - 'Network Voltage Phase 2': 48, - 'Network Voltage Phase 3': 49, - 'Grid Frequency Phase 1': 50, - 'Grid Frequency Phase 2': 51, - 'Grid Frequency Phase 3': 52, - 'Power Now Phase 1': 43, - 'Power Now Phase 2': 44, - 'Power Now Phase 3': 45, - - 'EPS Voltage': 53, - 'EPS Current': 54, - 'EPS Power': 55, - 'EPS Frequency': 56, -} - -X3_HYBRID_VALUES = { - 'PV1 Current': 0, - 'PV2 Current': 0, - 'PV1 Voltage': 0, - 'PV2 Voltage': 0, - - 'Output Current Phase 1': 0.9, - 'Network Voltage Phase 1': 234, - 'AC Power': 3189, - - 'Inverter Temperature': 42, - 'Today\'s Energy': 15.2, - 'Total Energy': 27, - 'Exported Power': -25, - 'PV1 Power': 0, - 'PV2 Power': 0, - - 'Battery Voltage': 210.3, - 'Battery Current': -15.7, - 'Battery Power': -3321, - 'Battery Temperature': 24, - 'Battery Remaining Capacity': 68, - - 'Total Feed-in Energy': 2.03, - 'Total Consumption': 23.41, - - 'Output Current Phase 2': 5.4, - 'Output Current Phase 3': 6.9, - 'Network Voltage Phase 2': 250, - 'Network Voltage Phase 3': 251.9, - 'Grid Frequency Phase 1': 50.01, - 'Grid Frequency Phase 2': 50.01, - 'Grid Frequency Phase 3': 50.01, - 'Power Now Phase 1': 123, - 'Power Now Phase 2': 1344, - 'Power Now Phase 3': 1722, - - 'EPS Voltage': 0, - 'EPS Current': 0, - 'EPS Power': 0, - 'EPS Frequency': 0, -} - -X3V34_HYBRID_VALUES = { - 'Network Voltage Phase 1': 246.8, - 'Network Voltage Phase 2': 249, - 'Network Voltage Phase 3': 250.8, - - 'Output Current Phase 1': 1.3, - 'Output Current Phase 2': 1.4, - 'Output Current Phase 3': 1, - - 'Power Now Phase 1': 266, - 'Power Now Phase 2': 284, - 'Power Now Phase 3': 136, - - 'PV1 Voltage': 537.7, - 'PV2 Voltage': 463, - 'PV1 Current': 1.7, - 'PV2 Current': 0, - 'PV1 Power': 958, - 'PV2 Power': 0, - - 'Total PV Energy': 1731.9, - 'Total PV Energy Resets': 0, - 'Today\'s PV Energy': 16.4, - - 'Grid Frequency Phase 1': 50.03, - 'Grid Frequency Phase 2': 50.03, - 'Grid Frequency Phase 3': 50.03, - - 'Total Energy': 1483.3, - 'Total Energy Resets': 0, - 'Today\'s Energy': 10.3, - - 'Battery Voltage': 229.3, - 'Battery Current': 0.9, - 'Battery Power': 229, - 'Battery Temperature': 22, - 'Battery Remaining Capacity': 99, - - 'Total Battery Discharge Energy': 706.2, - 'Total Battery Discharge Energy Resets': 0, - 'Today\'s Battery Discharge Energy': 4.3, - 'Battery Remaining Energy': 12.5, - 'Total Battery Charge Energy': 814.2, - 'Total Battery Charge Energy Resets': 0, - 'Today\'s Battery Charge Energy': 9.1, - - 'Exported Power': -55, - 'Total Feed-in Energy': 173.72, - 'Total Feed-in Energy Resets': 0, - 'Total Consumption': 598.77, - 'Total Consumption Resets': 0, - - 'AC Power': 686, - - 'EPS Frequency': 0, - 'EPS Total Energy': 0.6, - 'EPS Total Energy Resets': 0, -} - -X3V34_HYBRID_VALUES_NEGATIVE_POWER = { - 'Network Voltage Phase 1': 236.4, - 'Network Voltage Phase 2': 243.1, - 'Network Voltage Phase 3': 238.6, - - 'Output Current Phase 1': -7.2, - 'Output Current Phase 2': -7.1, - 'Output Current Phase 3': -7.1, - - 'Power Now Phase 1': -1737, - 'Power Now Phase 2': -1766, - 'Power Now Phase 3': -1728, - - 'PV1 Voltage': 0, - 'PV2 Voltage': 0, - 'PV1 Current': 0, - 'PV2 Current': 0, - 'PV1 Power': 0, - 'PV2 Power': 0, - - 'Total PV Energy': 9615.1, - 'Total PV Energy Resets': 1, - 'Today\'s PV Energy': 9.8, - - 'Grid Frequency Phase 1': 49.98, - 'Grid Frequency Phase 2': 49.98, - 'Grid Frequency Phase 3': 49.98, - - 'Total Energy': 8478.6, - 'Total Energy Resets': 1, - 'Today\'s Energy': 8.4, - - 'Battery Voltage': 204.6, - 'Battery Current': 25, - 'Battery Power': 5117, - 'Battery Temperature': 24, - 'Battery Remaining Capacity': 20, - - 'Total Battery Discharge Energy': 2469.6, - 'Total Battery Discharge Energy Resets': 0, - 'Today\'s Battery Discharge Energy': 4, - 'Battery Remaining Energy': 2.6, - 'Total Battery Charge Energy': 2887.6, - 'Total Battery Charge Energy Resets': 0, - 'Today\'s Battery Charge Energy': 6, - - 'Exported Power': -5743, - 'Total Feed-in Energy': 3593.49, - 'Total Feed-in Energy Resets': 5, - 'Total Consumption': 945.62, - 'Total Consumption Resets': 1, - - 'AC Power': -5233, - - 'EPS Frequency': 0, - 'EPS Total Energy': 2.1, - 'EPS Total Energy Resets': 0, -} - -X3V34_HYBRID_VALUES_EPS_MODE = { - 'Network Voltage Phase 1': 0, - 'Network Voltage Phase 2': 0, - 'Network Voltage Phase 3': 0, - - 'Output Current Phase 1': 0, - 'Output Current Phase 2': 0, - 'Output Current Phase 3': 0, - - 'Power Now Phase 1': 0, - 'Power Now Phase 2': 0, - 'Power Now Phase 3': 0, - - 'PV1 Voltage': 486.4, - 'PV2 Voltage': 446.6, - 'PV1 Current': 5.5, - 'PV2 Current': 0, - 'PV1 Power': 2678, - 'PV2 Power': 0, - - 'Total PV Energy': 8378.7, - 'Total PV Energy Resets': 1, - 'Today\'s PV Energy': 26, - - 'Grid Frequency Phase 1': 0, - 'Grid Frequency Phase 2': 0, - 'Grid Frequency Phase 3': 0, - - 'Total Energy': 7387.4, - 'Total Energy Resets': 1, - 'Today\'s Energy': 17.8, - - 'Battery Voltage': 228.6, - 'Battery Current': -0.99, - 'Battery Power': -241, - 'Battery Temperature': 27, - 'Battery Remaining Capacity': 98, - - 'Total Battery Discharge Energy': 2124, - 'Total Battery Discharge Energy Resets': 0, - 'Today\'s Battery Discharge Energy': 6.1, - 'Battery Remaining Energy': 12.4, - 'Total Battery Charge Energy': 2501.1, - 'Total Battery Charge Energy Resets': 0, - 'Today\'s Battery Charge Energy': 11.9, - - 'Exported Power': 0, - 'Total Feed-in Energy': 3174.79, - 'Total Feed-in Energy Resets': 4, - 'Total Consumption': 844.57, - 'Total Consumption Resets': 1, - - 'AC Power': 0, - - 'EPS Frequency': 50, - 'EPS Total Energy': 1.7, - 'EPS Total Energy Resets': 0, -} - -X1_VALUES = { - 'PV1 Current': 0, - 'PV2 Current': 1, - 'PV1 Voltage': 2, - 'PV2 Voltage': 3, - - 'Output Current': 4, - 'Network Voltage': 5, - 'AC Power': 6, - - 'Inverter Temperature': 7, - 'Today\'s Energy': 8, - 'Total Energy': 9, - 'Exported Power': 10, - 'PV1 Power': 11, - 'PV2 Power': 12, - - 'Battery Voltage': 13, - 'Battery Current': 14, - 'Battery Power': 15, - 'Battery Temperature': 16, - 'Battery Remaining Capacity': 21, - - 'Total Feed-in Energy': 41, - 'Total Consumption': 42, - - 'Grid Frequency': 50, - 'Power Now': 43, - - 'EPS Voltage': 53, - 'EPS Current': 54, - 'EPS Power': 55, - 'EPS Frequency': 56, -} - -X1_MINI_VALUES = { - 'PV1 Current': 0, - 'PV2 Current': 1, - 'PV1 Voltage': 2, - 'PV2 Voltage': 3, - - 'Output Current': 4, - 'Network Voltage': 5, - 'AC Power': 6, - - 'Inverter Temperature': 7, - 'Today\'s Energy': 8, - 'Total Energy': 9, - 'Exported Power': 10, - 'PV1 Power': 11, - 'PV2 Power': 12, - - 'Total Feed-in Energy': 41, - 'Total Consumption': 42, - - 'Power Now': 43, - 'Grid Frequency': 50, -} - -X1_MINI_VALUES_V34 = { - 'Network Voltage': 231.0, - 'Output Current': 1.5, - 'AC Power': 349, - 'PV1 Voltage': 60.9, - 'PV2 Voltage': 0, - 'PV1 Current': 5.8, - 'PV2 Current': 0, - 'PV1 Power': 359, - 'PV2 Power': 0, - 'Grid Frequency': 50.0, - 'Total Energy': 347.4, - 'Today\'s Energy': 1.2, - 'Total Feed-in Energy': 251.8, - 'Total Consumption': 0, - 'Power Now': 0, -} - -X1_SMART_VALUES = { - 'Network Voltage': 239.6, - 'Output Current': 12.6, - 'AC Power': 2956, - 'PV1 Voltage': 432.9, - 'PV2 Voltage': 233.8, - 'PV1 Current': 4.1, - 'PV2 Current': 5.7, - 'PV1 Power': 1777, - 'PV2 Power': 1336, - 'Grid Frequency': 60.21, - 'Total Energy': 121.5, - 'Today\'s Energy': 13.7, - 'Inverter Temperature': 43, - 'Exported Power': 10, - 'Total Feed-in Energy': 0.87, - 'Total Consumption': 81.84, -} +import solax.inverters as inverter +from tests.samples.expected_values import ( + QVOLTHYBG33P_VALUES, X1_MINI_VALUES, X1_MINI_VALUES_V34, + X1_SMART_VALUES, X1_VALUES, X3_HYBRID_VALUES, X3_VALUES, + X3V34_HYBRID_VALUES, X3V34_HYBRID_VALUES_EPS_MODE, + X3V34_HYBRID_VALUES_NEGATIVE_POWER, XHYBRID_VALUES, +) +from tests.samples.responses import ( + QVOLTHYBG33P_RESPONSE_V34, X1_BOOST_AIR_MINI_RESPONSE, + X1_HYBRID_G3_2X_MPPT_RESPONSE, X1_HYBRID_G3_RESPONSE, + X1_MINI_RESPONSE_V34, X1_SMART_RESPONSE, + X3_HYBRID_G3_2X_MPPT_RESPONSE, X3_HYBRID_G3_2X_MPPT_RESPONSE_V34, + X3_HYBRID_G3_2X_MPPT_RESPONSE_V34_EPS_MODE, + X3_HYBRID_G3_2X_MPPT_RESPONSE_V34_NEGATIVE_POWER, + X3_HYBRID_G3_RESPONSE, X3_MIC_RESPONSE, + XHYBRID_DE01_RESPONSE, XHYBRID_DE02_RESPONSE, +) -QVOLTHYBG33P_VALUES = { - 'Network Voltage Phase 1': 221.4, - 'Network Voltage Phase 2': 223.8, - 'Network Voltage Phase 3': 225.1, - 'Output Current Phase 1': 1.1, - 'Output Current Phase 2': 1.0, - 'Output Current Phase 3': 1.2, - 'Power Now Phase 1': 162.0, - 'Power Now Phase 2': 136.0, - 'Power Now Phase 3': 146.0, - 'AC Power': 444.0, - 'PV1 Voltage': 566.2, 'PV2 Voltage': 568.2, - 'PV1 Current': 1.8, 'PV2 Current': 1.7, - 'PV1 Power': 1050.0, - 'PV2 Power': 977.0, 'Grid Frequency Phase 1': 50.02, - 'Grid Frequency Phase 2': 50.01, - 'Grid Frequency Phase 3': 50.02, - 'Inverter Operation mode': 'Normal', - 'Exported Power': -6.0, 'Battery Voltage': 323.4, - 'Battery Current': 5.0, - 'Battery Power': 1616.0, 'Power Now': 451.0, - 'Total Energy': 219.0, - 'Total Energy Resets': 0.0, - 'Total Battery Discharge Energy': 73.8, - 'Total Battery Discharge Energy Resets': 0.0, - 'Total Battery Charge Energy': 90.4, - 'Total Battery Charge Energy Resets': 0.0, - "Today's Battery Discharge Energy": 0.0, - "Today's Battery Charge Energy": 8.1, - 'Total PV Energy': 231.6, - 'Total PV Energy Resets': 0.0, - "Today's Energy": 11.8, - 'Total Feed-in Energy': 107.94, - 'Total Feed-in Energy Resets': 0.0, - 'Total Consumption': 145.44, - 'Total Consumption Resets': 0.0, - "Today's Feed-in Energy": 1.66, - "Today's Consumption": 4.66, - 'Battery Remaining Capacity': 95.0, - 'Battery Temperature': 35.0, - 'Battery Remaining Energy': 8.8, - 'Battery Operation mode': 'Self Use Mode' -} +X_FORWARDED_HEADER = {"X-Forwarded-For": "5.8.8.8"} @pytest.fixture() diff --git a/tests/samples/expected_values.py b/tests/samples/expected_values.py new file mode 100644 index 0000000..98bf953 --- /dev/null +++ b/tests/samples/expected_values.py @@ -0,0 +1,380 @@ +XHYBRID_VALUES = { + "Today's Energy": 8.0, + "Battery Current": 14.0, + "Month's Energy": 19.0, + "Battery Power": 15, + "Battery Remaining Capacity": 17, + "Battery Temperature": 16, + "Battery Voltage": 13, + "EPS Current": 54, + "EPS Frequency": 56, + "EPS Power": 55, + "EPS Voltage": 53, + "Exported Power": 10, + "Grid Frequency": 50, + "Inverter Temperature": 7, + "Network Voltage": 5, + "Output Current": 4, + "PV1 Current": 0, + "PV1 Power": 11, + "PV1 Voltage": 2, + "PV2 Current": 1, + "PV2 Power": 12, + "PV2 Voltage": 3, + "Power Now": 6, + "Total Energy": 9, +} + + +X3_VALUES = { + "PV1 Current": 0, + "PV2 Current": 1, + "PV1 Voltage": 2, + "PV2 Voltage": 3, + "Output Current Phase 1": 4, + "Network Voltage Phase 1": 5, + "AC Power": 6, + "Inverter Temperature": 7, + "Today's Energy": 8, + "Total Energy": 9, + "Exported Power": 10, + "PV1 Power": 11, + "PV2 Power": 12, + "Battery Voltage": 13, + "Battery Current": 14, + "Battery Power": 15, + "Battery Temperature": 16, + "Battery Remaining Capacity": 21, + "Total Feed-in Energy": 41, + "Total Consumption": 42, + "Output Current Phase 2": 46, + "Output Current Phase 3": 47, + "Network Voltage Phase 2": 48, + "Network Voltage Phase 3": 49, + "Grid Frequency Phase 1": 50, + "Grid Frequency Phase 2": 51, + "Grid Frequency Phase 3": 52, + "Power Now Phase 1": 43, + "Power Now Phase 2": 44, + "Power Now Phase 3": 45, + "EPS Voltage": 53, + "EPS Current": 54, + "EPS Power": 55, + "EPS Frequency": 56, +} + +X3_HYBRID_VALUES = { + "PV1 Current": 0, + "PV2 Current": 0, + "PV1 Voltage": 0, + "PV2 Voltage": 0, + "Output Current Phase 1": 0.9, + "Network Voltage Phase 1": 234, + "AC Power": 3189, + "Inverter Temperature": 42, + "Today's Energy": 15.2, + "Total Energy": 27, + "Exported Power": -25, + "PV1 Power": 0, + "PV2 Power": 0, + "Battery Voltage": 210.3, + "Battery Current": -15.7, + "Battery Power": -3321, + "Battery Temperature": 24, + "Battery Remaining Capacity": 68, + "Total Feed-in Energy": 2.03, + "Total Consumption": 23.41, + "Output Current Phase 2": 5.4, + "Output Current Phase 3": 6.9, + "Network Voltage Phase 2": 250, + "Network Voltage Phase 3": 251.9, + "Grid Frequency Phase 1": 50.01, + "Grid Frequency Phase 2": 50.01, + "Grid Frequency Phase 3": 50.01, + "Power Now Phase 1": 123, + "Power Now Phase 2": 1344, + "Power Now Phase 3": 1722, + "EPS Voltage": 0, + "EPS Current": 0, + "EPS Power": 0, + "EPS Frequency": 0, +} + +X3V34_HYBRID_VALUES = { + "Network Voltage Phase 1": 246.8, + "Network Voltage Phase 2": 249, + "Network Voltage Phase 3": 250.8, + "Output Current Phase 1": 1.3, + "Output Current Phase 2": 1.4, + "Output Current Phase 3": 1, + "Power Now Phase 1": 266, + "Power Now Phase 2": 284, + "Power Now Phase 3": 136, + "PV1 Voltage": 537.7, + "PV2 Voltage": 463, + "PV1 Current": 1.7, + "PV2 Current": 0, + "PV1 Power": 958, + "PV2 Power": 0, + "Total PV Energy": 1731.9, + "Total PV Energy Resets": 0, + "Today's PV Energy": 16.4, + "Grid Frequency Phase 1": 50.03, + "Grid Frequency Phase 2": 50.03, + "Grid Frequency Phase 3": 50.03, + "Total Energy": 1483.3, + "Total Energy Resets": 0, + "Today's Energy": 10.3, + "Battery Voltage": 229.3, + "Battery Current": 0.9, + "Battery Power": 229, + "Battery Temperature": 22, + "Battery Remaining Capacity": 99, + "Total Battery Discharge Energy": 706.2, + "Total Battery Discharge Energy Resets": 0, + "Today's Battery Discharge Energy": 4.3, + "Battery Remaining Energy": 12.5, + "Total Battery Charge Energy": 814.2, + "Total Battery Charge Energy Resets": 0, + "Today's Battery Charge Energy": 9.1, + "Exported Power": -55, + "Total Feed-in Energy": 173.72, + "Total Feed-in Energy Resets": 0, + "Total Consumption": 598.77, + "Total Consumption Resets": 0, + "AC Power": 686, + "EPS Frequency": 0, + "EPS Total Energy": 0.6, + "EPS Total Energy Resets": 0, +} + +X3V34_HYBRID_VALUES_NEGATIVE_POWER = { + "Network Voltage Phase 1": 236.4, + "Network Voltage Phase 2": 243.1, + "Network Voltage Phase 3": 238.6, + "Output Current Phase 1": -7.2, + "Output Current Phase 2": -7.1, + "Output Current Phase 3": -7.1, + "Power Now Phase 1": -1737, + "Power Now Phase 2": -1766, + "Power Now Phase 3": -1728, + "PV1 Voltage": 0, + "PV2 Voltage": 0, + "PV1 Current": 0, + "PV2 Current": 0, + "PV1 Power": 0, + "PV2 Power": 0, + "Total PV Energy": 9615.1, + "Total PV Energy Resets": 1, + "Today's PV Energy": 9.8, + "Grid Frequency Phase 1": 49.98, + "Grid Frequency Phase 2": 49.98, + "Grid Frequency Phase 3": 49.98, + "Total Energy": 8478.6, + "Total Energy Resets": 1, + "Today's Energy": 8.4, + "Battery Voltage": 204.6, + "Battery Current": 25, + "Battery Power": 5117, + "Battery Temperature": 24, + "Battery Remaining Capacity": 20, + "Total Battery Discharge Energy": 2469.6, + "Total Battery Discharge Energy Resets": 0, + "Today's Battery Discharge Energy": 4, + "Battery Remaining Energy": 2.6, + "Total Battery Charge Energy": 2887.6, + "Total Battery Charge Energy Resets": 0, + "Today's Battery Charge Energy": 6, + "Exported Power": -5743, + "Total Feed-in Energy": 3593.49, + "Total Feed-in Energy Resets": 5, + "Total Consumption": 945.62, + "Total Consumption Resets": 1, + "AC Power": -5233, + "EPS Frequency": 0, + "EPS Total Energy": 2.1, + "EPS Total Energy Resets": 0, +} + +X3V34_HYBRID_VALUES_EPS_MODE = { + "Network Voltage Phase 1": 0, + "Network Voltage Phase 2": 0, + "Network Voltage Phase 3": 0, + "Output Current Phase 1": 0, + "Output Current Phase 2": 0, + "Output Current Phase 3": 0, + "Power Now Phase 1": 0, + "Power Now Phase 2": 0, + "Power Now Phase 3": 0, + "PV1 Voltage": 486.4, + "PV2 Voltage": 446.6, + "PV1 Current": 5.5, + "PV2 Current": 0, + "PV1 Power": 2678, + "PV2 Power": 0, + "Total PV Energy": 8378.7, + "Total PV Energy Resets": 1, + "Today's PV Energy": 26, + "Grid Frequency Phase 1": 0, + "Grid Frequency Phase 2": 0, + "Grid Frequency Phase 3": 0, + "Total Energy": 7387.4, + "Total Energy Resets": 1, + "Today's Energy": 17.8, + "Battery Voltage": 228.6, + "Battery Current": -0.99, + "Battery Power": -241, + "Battery Temperature": 27, + "Battery Remaining Capacity": 98, + "Total Battery Discharge Energy": 2124, + "Total Battery Discharge Energy Resets": 0, + "Today's Battery Discharge Energy": 6.1, + "Battery Remaining Energy": 12.4, + "Total Battery Charge Energy": 2501.1, + "Total Battery Charge Energy Resets": 0, + "Today's Battery Charge Energy": 11.9, + "Exported Power": 0, + "Total Feed-in Energy": 3174.79, + "Total Feed-in Energy Resets": 4, + "Total Consumption": 844.57, + "Total Consumption Resets": 1, + "AC Power": 0, + "EPS Frequency": 50, + "EPS Total Energy": 1.7, + "EPS Total Energy Resets": 0, +} + +X1_VALUES = { + "PV1 Current": 0, + "PV2 Current": 1, + "PV1 Voltage": 2, + "PV2 Voltage": 3, + "Output Current": 4, + "Network Voltage": 5, + "AC Power": 6, + "Inverter Temperature": 7, + "Today's Energy": 8, + "Total Energy": 9, + "Exported Power": 10, + "PV1 Power": 11, + "PV2 Power": 12, + "Battery Voltage": 13, + "Battery Current": 14, + "Battery Power": 15, + "Battery Temperature": 16, + "Battery Remaining Capacity": 21, + "Total Feed-in Energy": 41, + "Total Consumption": 42, + "Grid Frequency": 50, + "Power Now": 43, + "EPS Voltage": 53, + "EPS Current": 54, + "EPS Power": 55, + "EPS Frequency": 56, +} + +X1_MINI_VALUES = { + "PV1 Current": 0, + "PV2 Current": 1, + "PV1 Voltage": 2, + "PV2 Voltage": 3, + "Output Current": 4, + "Network Voltage": 5, + "AC Power": 6, + "Inverter Temperature": 7, + "Today's Energy": 8, + "Total Energy": 9, + "Exported Power": 10, + "PV1 Power": 11, + "PV2 Power": 12, + "Total Feed-in Energy": 41, + "Total Consumption": 42, + "Power Now": 43, + "Grid Frequency": 50, +} + +X1_MINI_VALUES_V34 = { + "Network Voltage": 231.0, + "Output Current": 1.5, + "AC Power": 349, + "PV1 Voltage": 60.9, + "PV2 Voltage": 0, + "PV1 Current": 5.8, + "PV2 Current": 0, + "PV1 Power": 359, + "PV2 Power": 0, + "Grid Frequency": 50.0, + "Total Energy": 347.4, + "Today's Energy": 1.2, + "Total Feed-in Energy": 251.8, + "Total Consumption": 0, + "Power Now": 0, +} + +X1_SMART_VALUES = { + "Network Voltage": 239.6, + "Output Current": 12.6, + "AC Power": 2956, + "PV1 Voltage": 432.9, + "PV2 Voltage": 233.8, + "PV1 Current": 4.1, + "PV2 Current": 5.7, + "PV1 Power": 1777, + "PV2 Power": 1336, + "Grid Frequency": 60.21, + "Total Energy": 121.5, + "Today's Energy": 13.7, + "Inverter Temperature": 43, + "Exported Power": 10, + "Total Feed-in Energy": 0.87, + "Total Consumption": 81.84, +} + +QVOLTHYBG33P_VALUES = { + "Network Voltage Phase 1": 221.4, + "Network Voltage Phase 2": 223.8, + "Network Voltage Phase 3": 225.1, + "Output Current Phase 1": 1.1, + "Output Current Phase 2": 1.0, + "Output Current Phase 3": 1.2, + "Power Now Phase 1": 162.0, + "Power Now Phase 2": 136.0, + "Power Now Phase 3": 146.0, + "AC Power": 444.0, + "PV1 Voltage": 566.2, + "PV2 Voltage": 568.2, + "PV1 Current": 1.8, + "PV2 Current": 1.7, + "PV1 Power": 1050.0, + "PV2 Power": 977.0, + "Grid Frequency Phase 1": 50.02, + "Grid Frequency Phase 2": 50.01, + "Grid Frequency Phase 3": 50.02, + "Inverter Operation mode": "Normal", + "Exported Power": -6.0, + "Battery Voltage": 323.4, + "Battery Current": 5.0, + "Battery Power": 1616.0, + "Power Now": 451.0, + "Total Energy": 219.0, + "Total Energy Resets": 0.0, + "Total Battery Discharge Energy": 73.8, + "Total Battery Discharge Energy Resets": 0.0, + "Total Battery Charge Energy": 90.4, + "Total Battery Charge Energy Resets": 0.0, + "Today's Battery Discharge Energy": 0.0, + "Today's Battery Charge Energy": 8.1, + "Total PV Energy": 231.6, + "Total PV Energy Resets": 0.0, + "Today's Energy": 11.8, + "Total Feed-in Energy": 107.94, + "Total Feed-in Energy Resets": 0.0, + "Total Consumption": 145.44, + "Total Consumption Resets": 0.0, + "Today's Feed-in Energy": 1.66, + "Today's Consumption": 4.66, + "Battery Remaining Capacity": 95.0, + "Battery Temperature": 35.0, + "Battery Remaining Energy": 8.8, + "Battery Operation mode": "Self Use Mode", +} diff --git a/tests/samples/responses.py b/tests/samples/responses.py new file mode 100644 index 0000000..b595497 --- /dev/null +++ b/tests/samples/responses.py @@ -0,0 +1,272 @@ + +XHYBRID_DE01_RESPONSE = { + 'method': 'uploadsn', + 'version': 'Solax_SI_CH_2nd_20160912_DE02', + 'type': 'AL_SE', + 'SN': 'XXXXXXX', + 'Data': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57], + 'Status': 2 +} + +XHYBRID_DE02_RESPONSE = { + 'method': 'uploadsn', + 'version': 'Solax_SI_CH_2nd_20160912_DE02', + 'type': 'AL_SE', + 'SN': 'XXXXXXX', + 'Data': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67], + 'Status': 2 +} + +X1_BOOST_AIR_MINI_RESPONSE = { + "type": "X1-Boost-Air-Mini", + "SN": "XXXXXXX", + "ver": "2.32.6", + "Data": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68], + "Information": [2.500, 4, "X1-Boost-Air-Mini", "XXXXXXX", + 1, 3.25, 1.09, 1.10, 0.00] +} + +X1_MINI_RESPONSE_V34 = { + "sn": "XXXXXXXXXX", + "ver": "2.034.06", + "type": 4, + "Data": [2310, 15, 349, 609, 0, 58, 0, 359, 0, 5000, + 2, 3474, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39, 0, 2518, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0], + "Information": [0.700, 4, "XXXXXXXXXXXXXX", + 1, 1.19, 0.00, 1.32, 0.00, 0.00, 1] +} + +X1_SMART_RESPONSE = { + "sn": "XXXXXXX", + "ver": "2.033.20", + "type": 8, + "Data": [2396, 126, 2956, 4329, 2338, 41, 57, 1777, 1336, 6021, + 2, 1215, 0, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 0, 80, 0, 0, 0, 0, 0, 0, + 10, 65535, 87, 0, 8184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0], + "Information": [8.0, 8, "XXXXXXX", 1, 1.07, 1.0, 1.05, 0.0] +} + +X3_MIC_RESPONSE = { + "type": "X3-MIC", + "SN": "XXXXXXX", + "ver": "2.033.20", + "Data": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + 100, 101, 102], + "Information": [8.000, 7, "X3-MIC", "XXXXXXX", + 1, 1.10, 1.02, 1.09, 1.02] +} + +X1_HYBRID_G3_RESPONSE = { + "type": "X1-Hybiyd-G3", + "SN": "XXXXXXX", + "ver": "2.033.20", + "Data": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + 100, 101], + "Information": [8.000, 7, "X3-MIC", "XXXXXXX", + 1, 1.10, 1.02, 1.09, 1.02] +} + +X1_HYBRID_G3_2X_MPPT_RESPONSE = { + "type": "X1-Hybiyd-G3", + "SN": "XXXXXXXXXX", + "ver": "2.033.20", + "Data": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + 100, 101, 102, 103, 104, 105, 106], + "Information": [3.000, 3, "X1-Hybiyd-G3", "YYYYYYYYYYYYYY", + 1, 3.11, 0.00, 3.13, 1.05], + "battery": { + "brand": "0", + "masterVer": "0.00", + "slaveNum": "0", + "slaveVer": [0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], + "slaveType": [0, 0, 0, 0, 0, 0, 0, 0] + } +} + +X3_HYBRID_G3_RESPONSE = { + "type": "X3-Hybiyd-G3", + "SN": "XXXXXXX", + "ver": "2.32.6", + "Data": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + 100, 101], + "Information": [0.000, 5, "X3-Hybiyd-G3", "XXXXXXX", + 1, 3.00, 0.00, 3.17, 1.01] +} + + +X3_HYBRID_G3_2X_MPPT_RESPONSE = { + "type": "X3-Hybiyd-G3", + "SN": "XXXXXXXXXX", + "ver": "2.033.20", + "Data": [0.0, 0.0, 0.0, 0.0, 0.9, 234.0, 3189, 42, 15.2, 27.0, -25, 0, 0, + 210.30, -15.70, -3321, 24, 8.6, 0, 11.0, 1, 68, 232.4, 170.0, + 31.0, 35.0, 22.6, 20.7, 3.8, 3.8, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2.03, 23.41, 123, 1344, 1722, 5.4, 6.9, 250.0, 251.9, 50.01, + 50.01, 50.01, 0.0, 0.0, 0, 0.00, 0, 0, 0, 0.00, 0, 0, 0, 0, 0.00, + 0, 0, 2, 1, 26, 1.00, 0, 100, 10, 25.00, 25.00, 0, 0, 0, 0, 0.0, + 10.8, 24.4, 0.0, 0, 2, 0.0, 0.0, 0, 0.0, 0.0, 0, 0, 0, 0, 1, 1, + 0, 0, 0.00, 0.00, 1, 273, 212.3, -16.2, -3439], + "Information": [8.000, 5, "X3-Hybiyd-G3", "XXXXXXXX", 1, 4.47, 0.00, 4.34, + 1.05], + "battery": { + "brand": "83", + "masterVer": "1.11", + "slaveNum": "4", + "slaveVer": [1.13, 1.13, 1.13, 1.13] + } +} + +X3_HYBRID_G3_2X_MPPT_RESPONSE_V34 = { + "type": 5, + "sn": "XXXXXXXXXX", + "ver": "2.034.06", + "Data": [2468, 2490, 2508, 13, 14, 10, 266, 284, 136, 5377, 4630, 17, 0, + 958, 0, 5003, 5003, 5003, 2, 14833, 0, 103, 0, 0, 22930, 90, 229, + 22, 99, 0, 7062, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 100, 0, 41, 7777, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65480, + 0, 17372, 0, 59877, 0, 665, 41, 256, 2352, 1568, 20, 350, 202, + 190, 41, 41, 81, 1, 1, 0, 0, 8142, 0, 17319, 0, 6, 0, 64851, + 65535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83, 0, 55, 0, 0, 6, 0, 164, + 43, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 256, 12579, 783, 5381, + 1107, 512, 8224, 8224, 0, 0, 4369, 0, 273, 2295, 5, 114, 4112, + 4096, 25912, 31, 21302, 19778, 18003, 12355, 16697, 12354, 14132, + 21302, 13110, 12338, 12337, 14386, 12354, 12852, 21302, 13110, + 12338, 12337, 14386, 12354, 12340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 686, 1, 257, 257, 1794, 1025, 0, 22930, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0], + "Information": [8.000, 5, "XXXXXXXX", 1, 4.47, 0.00, 4.34, 1.05, 0.0, 1] +} + +X3_HYBRID_G3_2X_MPPT_RESPONSE_V34_NEGATIVE_POWER = { + "type": 5, + "sn": "XXXXXXXXXX", + "ver": "2.034.06", + "Data": [2364, 2431, 2386, 65463, 65464, 65464, 63798, 63769, 63807, 0, 0, + 0, 0, 0, 0, 4998, 4998, 4998, 2, 19251, 1, 84, 0, 0, 20460, 2500, + 5117, 24, 20, 0, 24696, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 100, 0, 36, 7577, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 59792, 65535, 31674, 5, 29027, 1, 510, 36, 256, 2352, 1568, 310, + 350, 217, 205, 36, 36, 258, 1, 1, 13, 0, 28876, 0, 30616, 1, 21, + 0, 5236, 0, 65535, 65535, 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, 936, 0, + 0, 21, 0, 98, 40, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 256, 4392, + 7702, 5386, 1107, 512, 8481, 8481, 0, 0, 4369, 0, 273, 2037, 243, + 4949, 3645, 3638, 12, 100, 21302, 19778, 18003, 12355, 16697, + 12354, 14132, 21302, 13110, 12338, 12337, 14386, 12354, 12852, + 21302, 13110, 12338, 12337, 14386, 12354, 12340, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 60302, 1, 257, 257, 770, 1028, 0, 20460, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "Information": [8.000, 5, "XXXXXXXX", 1, 4.60, 0.00, 4.42, 1.05, 0.00, 1] +} + +X3_HYBRID_G3_2X_MPPT_RESPONSE_V34_EPS_MODE = { + "type": 5, + "sn": "XXXXXXXXXX", + "ver": "2.034.06", + "Data": [0, 0, 0, 0, 0, 0, 0, 0, 0, 4864, 4466, 55, 0, 2678, 0, 0, 0, 0, 7, + 8339, 1, 178, 0, 0, 22860, 65436, 65294, 27, 98, 0, 21240, 0, 124, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 52, 7541, 226, + 2453, 205, 2223, 2276, 2426, 10, 107, 8, 172, 2441, 147, 5000, 0, + 0, 0, 55339, 4, 18922, 1, 0, 52, 256, 2352, 1568, 0, 350, 275, + 262, 41, 41, 225, 1, 1, 0, 0, 25011, 0, 18252, 1, 17, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 768, 0, 54, 0, 2, 17, 0, 260, 61, 119, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 256, 7971, 6926, 5385, 1107, 512, + 8224, 8224, 0, 0, 4369, 0, 273, 2288, 65523, 65239, 4099, 4083, + 13728, 87, 21302, 19778, 18003, 12355, 16697, 12354, 14132, 21302, + 13110, 12338, 12337, 14386, 12354, 12852, 21302, 13110, 12338, + 12337, 14386, 12354, 12340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 257, 257, 1281, 1025, 0, 22860, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0], + "Information": [8.000, 5, "XXXXXXXX", 1, 4.60, 0.00, 4.42, 1.05, 0.00, 1] +} + +QVOLTHYBG33P_RESPONSE_V34 = { + "sn": "SWX***", + "ver": "2.034.06", + "type": 14, + "Data": [ + 2214, 2238, 2251, 11, 10, 12, 162, 136, 146, 444, 5662, 5682, 18, 17, + 1050, 977, 5002, 5001, 5002, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 65529, 65535, 0, 0, 0, 32340, 500, 1616, 3236, 50, 1618, 1, 50, 451, + 256, 3841, 4875, 5634, 100, 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2190, 0, 36, 1, 0, 1, 738, 0, 904, 0, 0, 81, 2316, 0, 118, 0, 0, 0, + 10794, 0, 14544, 0, 166, 0, 466, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 95, 1, + 35, 88, 256, 3504, 2400, 115, 300, 352, 325, 34, 34, 8, 1620, 773, + 12850, 12850, 12850, 0, 0, 0, 3389, 3384, 33876, 2, 20564, 12339, + 18497, 12599, 18743, 12356, 14386, 20564, 12339, 18498, 12600, 18740, + 12356, 12855, 20564, 12339, 18498, 12600, 18740, 12612, 14642, 20564, + 12339, 18498, 12600, 18740, 12356, 13877, 0, 0, 0, 0, 0, 0, 0, 3843, + 2306, 1282, 257, 0, 32340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ], + "Information": [12.0, 14, "H34***", 1, 1.15, 0.0, 1.14, 1.07, 0.0, 1] +} diff --git a/tests/test_base_inverter.py b/tests/test_base_inverter.py index 8d4a296..9539e65 100644 --- a/tests/test_base_inverter.py +++ b/tests/test_base_inverter.py @@ -1,6 +1,7 @@ import pytest from solax import inverter +from solax.discovery import REGISTRY @pytest.mark.asyncio @@ -15,16 +16,6 @@ async def test_unimplemented_make_request_with_pwd(): await inverter.Inverter.make_request('localhost', 80, 'pwd') -def test_unimplemented_sensor_map(): - with pytest.raises(NotImplementedError): - inverter.Inverter.sensor_map() - - -def test_unimplemented_schema(): - with pytest.raises(NotImplementedError): - inverter.Inverter.schema() - - def test_all_registered_inverters_inherit_from_base(): - for i in inverter.REGISTRY: + for i in REGISTRY: assert issubclass(i, inverter.Inverter) diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 5c106ab..3d5e95d 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -1,7 +1,7 @@ import pytest import solax -from solax import inverter +from solax.discovery import DiscoveryError @pytest.mark.asyncio @@ -13,17 +13,17 @@ async def test_discovery(inverters_fixture): @pytest.mark.asyncio async def test_discovery_no_host(): - with pytest.raises(inverter.DiscoveryError): + with pytest.raises(DiscoveryError): await solax.real_time_api('localhost', 2) @pytest.mark.asyncio async def test_discovery_no_host_with_pwd(): - with pytest.raises(inverter.DiscoveryError): + with pytest.raises(DiscoveryError): await solax.real_time_api('localhost', 2, 'pwd') @pytest.mark.asyncio async def test_discovery_unknown_webserver(simple_http_fixture): - with pytest.raises(inverter.DiscoveryError): + with pytest.raises(DiscoveryError): await solax.real_time_api(*simple_http_fixture) diff --git a/tests/test_smoke.py b/tests/test_smoke.py index 89bae61..ef96f23 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -1,6 +1,7 @@ import pytest import solax -from solax import inverter +from solax.inverter import InverterError +from solax.discovery import REGISTRY from tests import fixtures @@ -22,14 +23,14 @@ async def test_smoke(inverters_fixture): @pytest.mark.asyncio async def test_throws_when_unable_to_parse(inverters_garbage_fixture): conn, inverter_class = inverters_garbage_fixture - with pytest.raises(inverter.InverterError): + with pytest.raises(InverterError): i = inverter_class(*conn) await i.get_data() def test_registry_matches_inverters_under_test(): test_inverters = {i.inverter for i in fixtures.INVERTERS_UNDER_TEST} - registry_inverters = set(inverter.REGISTRY) + registry_inverters = set(REGISTRY) assert test_inverters == registry_inverters, 'tests do not match registry' diff --git a/tests/test_vol.py b/tests/test_vol.py index e3544f3..941052d 100644 --- a/tests/test_vol.py +++ b/tests/test_vol.py @@ -1,6 +1,6 @@ import pytest from voluptuous import Invalid -from solax.inverter import startswith +from solax.utils import startswith def test_does_start_with():