diff --git a/setup.py b/setup.py index 7595f8f..2a109a1 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ "x3_mic_pro_g2 = solax.inverters.x3_mic_pro_g2:X3MicProG2", "x3_v34 = solax.inverters.x3_v34:X3V34", "x_hybrid = solax.inverters.x_hybrid:XHybrid", + "x3_evc = solax.inverters.x3_evc:X3EVC", ], }, ) diff --git a/solax/inverters/__init__.py b/solax/inverters/__init__.py index 039c947..00b333f 100644 --- a/solax/inverters/__init__.py +++ b/solax/inverters/__init__.py @@ -6,6 +6,7 @@ from .x1_mini_v34 import X1MiniV34 from .x1_smart import X1Smart from .x3 import X3 +from .x3_evc import X3EVC from .x3_hybrid_g4 import X3HybridG4 from .x3_mic_pro_g2 import X3MicProG2 from .x3_ultra import X3Ultra @@ -26,4 +27,5 @@ "X1HybridGen4", "X3MicProG2", "X3Ultra", + "X3EVC", ] diff --git a/solax/inverters/x3_evc.py b/solax/inverters/x3_evc.py new file mode 100644 index 0000000..a1d2a29 --- /dev/null +++ b/solax/inverters/x3_evc.py @@ -0,0 +1,109 @@ +from typing import Any, Dict, Optional + +import voluptuous as vol + +from solax.inverter import Inverter +from solax.units import Total, Units +from solax.utils import ( + div10, + div100, + pack_u16, + to_signed, + to_signed32, + twoway_div10, + twoway_div100, +) + + +class X3EVC(Inverter): + """X3 EVC""" + + # pylint: disable=duplicate-code + _schema = vol.Schema( + { + vol.Required("type"): vol.All(int, 1), + vol.Required("sn"): str, + vol.Required("ver"): str, + vol.Required("data"): vol.Schema( + vol.All( + [vol.Coerce(float)], + vol.Length(min=96, max=96), + ) + ), + vol.Required("information"): vol.Schema( + vol.All(vol.Length(min=10, max=10)) + ), + }, + extra=vol.REMOVE_EXTRA, + ) + + @classmethod + def build_all_variants(cls, host, port, pwd=""): + return [cls._build(host, port, pwd, False)] + + @classmethod + def _decode_device_state(cls, device_state): + return { + 0: "Preparing", + 1: "Preparing", + 2: "Charging", + 3: "Finishing", + 4: "Faulted", + 5: "Unavailable", + 6: "Reserved", + 7: "SuspendedEV", + 8: "SuspendedEVSE", + }.get(device_state) + + @classmethod + def _decode_device_mode(cls, device_mode): + return { + 0: "STOP", + 1: "FAST", + 2: "ECO", + 3: "GREEN", + }.get(device_mode) + + @classmethod + def response_decoder(cls): + return { + "Device State": (0, Units.NONE, X3EVC._decode_device_state), + "Device Mode": (1, Units.NONE, X3EVC._decode_device_mode), + "EQ Single": (12, Total(Units.KWH), div10), + "EQ Total": ( + pack_u16(14, 15), + Total(Units.KWH), + twoway_div10, + ), # not sure if correct + "Total Charger Power": (11, Units.W), + "Voltage A": (2, Units.V, div100), + "Voltage B": (3, Units.V, div100), + "Voltage C": (4, Units.V, div100), + "Current A": (5, Units.A, div100), + "Current B": (6, Units.A, div100), + "Current C": (7, Units.A, div100), + "Charger Power A": (8, Units.W), + "Charger Power B": (9, Units.W), + "Charger Power C": (10, Units.W), + "Extern Current A": (16, Units.W, twoway_div100), + "Extern Current B": (17, Units.W, twoway_div100), + "Extern Current C": (18, Units.W, twoway_div100), + "Extern Power A": (19, Units.W, to_signed), + "Extern Power B": (20, Units.W, to_signed), + "Extern Power C": (21, Units.W, to_signed), + "Extern Total Power": (22, Units.W, to_signed), + "Temperature Plug": (23, Units.C), + "Temperature PCB": (24, Units.C), + "CP State": (26, Units.NONE), + "Charging Duration": (pack_u16(80, 81), Units.NONE, to_signed32), + "OCPP Offline Mode": (85, Units.NONE), + "Type Power": (87, Units.NONE), + "Type Phase": (88, Units.NONE), + "Type Charger": (89, Units.NONE), + } + + # pylint: enable=duplicate-code + + @classmethod + def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]: + return response["information"][2] diff --git a/tests/fixtures.py b/tests/fixtures.py index 569139f..e6d3188 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -14,6 +14,7 @@ X1_MINI_VALUES_V34_VER3, X1_SMART_VALUES, X1_VALUES, + X3_EVC_VALUES, X3_HYBRID_G4_VALUES, X3_HYBRID_VALUES, X3_MICPRO_G2_VALUES, @@ -35,6 +36,7 @@ X1_MINI_RESPONSE_V34, X1_MINI_RESPONSE_V34_VER3, X1_SMART_RESPONSE, + X3_EVC_RESPONSE, X3_HYBRID_G3_2X_MPPT_RESPONSE, X3_HYBRID_G3_2X_MPPT_RESPONSE_V34, X3_HYBRID_G3_2X_MPPT_RESPONSE_V34_EPS_MODE, @@ -235,6 +237,16 @@ def simple_http_fixture(httpserver): headers=None, data="optType=ReadRealTimeData", ), + InverterUnderTest( + uri="/", + method="POST", + query_string=None, + response=X3_EVC_RESPONSE, + inverter=inverter.X3EVC, + values=X3_EVC_VALUES, + headers=None, + data="optType=ReadRealTimeData", + ), InverterUnderTest( uri="/", method="POST", diff --git a/tests/samples/expected_values.py b/tests/samples/expected_values.py index bccbaf4..c8d836a 100644 --- a/tests/samples/expected_values.py +++ b/tests/samples/expected_values.py @@ -102,6 +102,38 @@ "EPS Frequency": 0, } +X3_EVC_VALUES = { + "Device State": "Preparing", + "Device Mode": "FAST", + "EQ Single": 1.2, + "EQ Total": 91751.8, + "Total Charger Power": 11, + "Voltage A": 0.02, + "Voltage B": 0.03, + "Voltage C": 0.04, + "Current A": 0.05, + "Current B": 0.06, + "Current C": 0.07, + "Charger Power A": 8, + "Charger Power B": 9, + "Charger Power C": 10, + "Extern Current A": 0.16, + "Extern Current B": 0.17, + "Extern Current C": 0.18, + "Extern Power A": 19, + "Extern Power B": 20, + "Extern Power C": 21, + "Extern Total Power": 22, + "Temperature Plug": 23, + "Temperature PCB": 24, + "CP State": 26, + "Charging Duration": 5308496.0, + "OCPP Offline Mode": 85, + "Type Power": 87, + "Type Phase": 88, + "Type Charger": 89, +} + X3V34_HYBRID_VALUES = { "Network Voltage Phase 1": 246.8, "Network Voltage Phase 2": 249, diff --git a/tests/samples/responses.py b/tests/samples/responses.py index 4f04971..571a0e4 100644 --- a/tests/samples/responses.py +++ b/tests/samples/responses.py @@ -1753,6 +1753,122 @@ ], } +X3_EVC_RESPONSE = { + "type": 1, + "SN": "SQXXXXXXXX", + "ver": "3.007.05", + "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, + ], + "Information": [ + 11.000, + 1, + "C311XXXXXXXXXX", + 1, + 1.08, + 1.01, + 0.00, + 0.00, + 0.00, + 1, + ], +} + X3_HYBRID_G3_RESPONSE = { "type": "X3-Hybiyd-G3", "SN": "XXXXXXX",