diff --git a/solax/discovery.py b/solax/discovery.py index 0cf3352..e54c029 100644 --- a/solax/discovery.py +++ b/solax/discovery.py @@ -9,6 +9,7 @@ X1Mini, X1MiniV34, X1Smart, + X3HybridG4, XHybrid, ) @@ -17,6 +18,7 @@ XHybrid, X3, X3V34, + X3HybridG4, X1, X1Mini, X1MiniV34, diff --git a/solax/inverters/__init__.py b/solax/inverters/__init__.py index cdf82df..4fd3d70 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_hybrid_g4 import X3HybridG4 from .x3_v34 import X3V34 from .x_hybrid import XHybrid @@ -17,6 +18,7 @@ "X1MiniV34", "X1Smart", "X3V34", + "X3HybridG4", "X3", "X1Boost", "X1HybridGen4", diff --git a/solax/inverters/x3_hybrid_g4.py b/solax/inverters/x3_hybrid_g4.py new file mode 100644 index 0000000..c8433aa --- /dev/null +++ b/solax/inverters/x3_hybrid_g4.py @@ -0,0 +1,93 @@ +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 + + +class X3HybridG4(Inverter): + """X3 Hybrid G4 v3.006.04""" + + # pylint: disable=duplicate-code + _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=300, max=300), + ) + ), + 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_run_mode(cls, run_mode): + return { + 0: "Waiting", + 1: "Checking", + 2: "Normal", + 3: "Fault", + 4: "Permanent Fault", + 5: "Updating", + 6: "EPS Check", + 7: "EPS Mode", + 8: "Self Test", + 9: "Idle", + 10: "Standby", + }.get(run_mode) + + @classmethod + def response_decoder(cls): + return { + "Grid 1 Voltage": (0, Units.V, div10), + "Grid 2 Voltage": (1, Units.V, div10), + "Grid 3 Voltage": (2, Units.V, div10), + "Grid 1 Current": (3, Units.A, twoway_div10), + "Grid 2 Current": (4, Units.A, twoway_div10), + "Grid 3 Current": (5, Units.A, twoway_div10), + "Grid 1 Power": (6, Units.W, to_signed), + "Grid 2 Power": (7, Units.W, to_signed), + "Grid 3 Power": (8, Units.W, to_signed), + "PV1 Voltage": (10, Units.V, div10), + "PV2 Voltage": (11, Units.V, div10), + "PV1 Current": (12, Units.A, div10), + "PV2 Current": (13, Units.A, div10), + "PV1 Power": (14, Units.W), + "PV2 Power": (15, Units.W), + "Grid 1 Frequency": (16, Units.HZ, div100), + "Grid 2 Frequency": (17, Units.HZ, div100), + "Grid 3 Frequency": (18, Units.HZ, div100), + "Run mode": (19, Units.NONE), + "Run mode text": (19, Units.NONE, X3HybridG4._decode_run_mode), + "EPS 1 Voltage": (23, Units.W, div10), + "EPS 2 Voltage": (24, Units.W, div10), + "EPS 3 Voltage": (25, Units.W, div10), + "EPS 1 Current": (26, Units.W, twoway_div10), + "EPS 2 Current": (27, Units.W, twoway_div10), + "EPS 3 Current": (28, Units.W, twoway_div10), + "EPS 1 Power": (29, Units.W, to_signed), + "EPS 2 Power": (30, Units.W, to_signed), + "EPS 3 Power": (31, Units.W, to_signed), + "Feed-in Power ": (pack_u16(34, 35), Units.W, to_signed32), + "Battery Power": (41, Units.W, to_signed), + "Yield total": (pack_u16(68, 69), Total(Units.KWH), div10), + "Yield today": (70, Units.KWH, div10), + "Feed-in Energy": (pack_u16(86, 87), Total(Units.KWH), div100), + "Consumed Energy": (pack_u16(88, 89), Total(Units.KWH), div100), + "Battery Remaining Capacity": (103, Units.PERCENT), + "Battery Temperature": (105, Units.C, to_signed), + "Battery Voltage": (pack_u16(169, 170), Units.V, div100), + } + + # pylint: enable=duplicate-code diff --git a/solax/units.py b/solax/units.py index feb19f5..2ac09b8 100644 --- a/solax/units.py +++ b/solax/units.py @@ -1,4 +1,4 @@ -""" Units and different measrement types""" +""" Units and different measurement types""" from enum import Enum from typing import NamedTuple, Union @@ -17,14 +17,14 @@ class Units(Enum): class Measurement(NamedTuple): - """Respresention of measurement with a given unit and arbitrary values.""" + """Representation of measurement with a given unit and arbitrary values.""" unit: Units is_monotonic: bool = False class Total(Measurement): - """A Measuremeant where the values are continuously increasing.""" + """A Measurement where the values are continuously increasing.""" is_monotonic: bool = True diff --git a/solax/utils.py b/solax/utils.py index 375aed4..fcabfb1 100644 --- a/solax/utils.py +++ b/solax/utils.py @@ -65,6 +65,7 @@ def div100(val): INT16_MAX = 0x7FFF +INT32_MAX = 0x7FFFFFFF def to_signed(val): @@ -73,6 +74,12 @@ def to_signed(val): return val +def to_signed32(val): + if val > INT32_MAX: + val -= 2**32 + return val + + def twoway_div10(val): return to_signed(val) / 10 diff --git a/tests/fixtures.py b/tests/fixtures.py index 57cfc53..d425616 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -11,6 +11,7 @@ X1_MINI_VALUES_V34, X1_SMART_VALUES, X1_VALUES, + X3_HYBRID_G4_VALUES, X3_HYBRID_VALUES, X3_VALUES, X3V34_HYBRID_VALUES, @@ -32,6 +33,7 @@ X3_HYBRID_G3_2X_MPPT_RESPONSE_V34_EPS_MODE, X3_HYBRID_G3_2X_MPPT_RESPONSE_V34_NEGATIVE_POWER, X3_HYBRID_G3_RESPONSE, + X3_HYBRID_G4_RESPONSE, X3_MIC_RESPONSE, XHYBRID_DE01_RESPONSE, XHYBRID_DE02_RESPONSE, @@ -204,6 +206,16 @@ def simple_http_fixture(httpserver): headers=None, data=None, ), + InverterUnderTest( + uri="/", + method="POST", + query_string=None, + response=X3_HYBRID_G4_RESPONSE, + inverter=inverter.X3HybridG4, + values=X3_HYBRID_G4_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 e9b24b0..c95b2e9 100644 --- a/tests/samples/expected_values.py +++ b/tests/samples/expected_values.py @@ -223,6 +223,47 @@ "EPS Total Energy": 1.7, } +X3_HYBRID_G4_VALUES = { + "Grid 1 Voltage": 238.3, + "Grid 2 Voltage": 239.7, + "Grid 3 Voltage": 237.7, + "Grid 1 Current": 1.0, + "Grid 2 Current": 1.1, + "Grid 3 Current": 1.1, + "Grid 1 Power": 33.0, + "Grid 2 Power": 81.0, + "Grid 3 Power": 76.0, + "PV1 Voltage": 248.3, + "PV2 Voltage": 230.5, + "PV1 Current": 0.1, + "PV2 Current": 0.2, + "PV1 Power": 45.0, + "PV2 Power": 58.0, + "Grid 1 Frequency": 50.01, + "Grid 2 Frequency": 50.01, + "Grid 3 Frequency": 50.01, + "Run mode": 2.0, + "Run mode text": "Normal", + "EPS 1 Voltage": 0.0, + "EPS 2 Voltage": 0.0, + "EPS 3 Voltage": 0.0, + "EPS 1 Current": 0.0, + "EPS 2 Current": 0.0, + "EPS 3 Current": 0.0, + "EPS 1 Power": 0.0, + "EPS 2 Power": 0.0, + "EPS 3 Power": 0.0, + "Feed-in Power ": -152.0, + "Battery Power": 0.0, + "Yield total": 4.4, + "Yield today": 0.1, + "Feed-in Energy": 0.0, + "Consumed Energy": 20.09, + "Battery Remaining Capacity": 30.0, + "Battery Temperature": 22.0, + "Battery Voltage": 234.6, +} + X1_VALUES = { "PV1 Current": 0, "PV2 Current": 1, diff --git a/tests/samples/responses.py b/tests/samples/responses.py index 869d848..5fa9997 100644 --- a/tests/samples/responses.py +++ b/tests/samples/responses.py @@ -2297,6 +2297,315 @@ "Information": [8.000, 5, "XXXXXXXX", 1, 4.60, 0.00, 4.42, 1.05, 0.00, 1], } +X3_HYBRID_G4_RESPONSE = { + "sn": "SR3xxxxxxx", + "ver": "3.006.04", + "type": 14, + "Data": [ + 2383, + 2397, + 2377, + 10, + 11, + 11, + 33, + 81, + 76, + 190, + 2483, + 2305, + 1, + 2, + 45, + 58, + 5001, + 5001, + 5001, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 65384, + 65535, + 0, + 0, + 0, + 23460, + 0, + 0, + 2349, + 65534, + 65490, + 1, + 37, + 342, + 256, + 10542, + 4616, + 5644, + 100, + 0, + 23, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 44, + 0, + 1, + 0, + 0, + 0, + 24, + 0, + 10, + 0, + 0, + 0, + 34, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2009, + 0, + 0, + 0, + 209, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 30, + 1, + 22, + 33, + 256, + 2628, + 1800, + 250, + 350, + 163, + 149, + 33, + 32, + 1, + 1115, + 256, + 9252, + 9252, + 0, + 0, + 0, + 0, + 3252, + 3232, + 13925, + 0, + 21302, + 14389, + 18757, + 12855, + 16689, + 12355, + 13363, + 21302, + 14389, + 18757, + 12855, + 16689, + 12355, + 13363, + 21302, + 14389, + 18758, + 12855, + 16691, + 12611, + 12341, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2817, + 3842, + 258, + 771, + 0, + 23460, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 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": [10.000, 14, "H34A**********", 8, 1.23, 0.00, 1.24, 1.09, 0.00, 1], +} + QVOLTHYBG33P_RESPONSE_V34 = { "sn": "SWX***", "ver": "2.034.06", diff --git a/tests/test_utils.py b/tests/test_utils.py index 80c0a6e..fc7da08 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -20,3 +20,22 @@ def test_to_signed(val_to_check): actual = utils.to_signed(val_to_check) expected = struct.unpack("