Skip to content

Commit

Permalink
feat: Add support for X3 Ultra (#147)
Browse files Browse the repository at this point in the history
* feat: Add support for X3 Ultra

* fix: Integrate suggested change for battery storage

---------

Co-authored-by: Marc Luehr <marc@luehr.org>
  • Loading branch information
Th3Link and Marc Luehr committed Jun 5, 2024
1 parent 266a3d0 commit 64ab6c1
Show file tree
Hide file tree
Showing 6 changed files with 528 additions and 0 deletions.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"x1_smart = solax.inverters.x1_smart:X1Smart",
"x3 = solax.inverters.x3:X3",
"x3_hybrid_g4 = solax.inverters.x3_hybrid_g4:X3HybridG4",
"x3_ultra = solax.inverters.x3_ultra:X3Ultra",
"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",
Expand Down
2 changes: 2 additions & 0 deletions solax/inverters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .x3 import X3
from .x3_hybrid_g4 import X3HybridG4
from .x3_mic_pro_g2 import X3MicProG2
from .x3_ultra import X3Ultra
from .x3_v34 import X3V34
from .x_hybrid import XHybrid

Expand All @@ -24,4 +25,5 @@
"X1Boost",
"X1HybridGen4",
"X3MicProG2",
"X3Ultra",
]
142 changes: 142 additions & 0 deletions solax/inverters/x3_ultra.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from typing import Any, Dict, Optional

import voluptuous as vol

from solax.inverter import Inverter
from solax.units import DailyTotal, Measurement, Total, Units
from solax.utils import (
div10,
div100,
pack_u16,
to_signed,
to_signed32,
twoway_div10,
twoway_div100,
)


class X3Ultra(Inverter):
"""X3 Ultra v1.001.20"""

# pylint: disable=duplicate-code
_schema = vol.Schema(
{
vol.Required("type"): vol.All(int, 25),
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),
"PV3 Voltage": (129, Units.V, div10),
"PV1 Current": (12, Units.A, div10),
"PV2 Current": (13, Units.A, div10),
"PV3 Current": (130, Units.A, div10),
"PV1 Power": (14, Units.W),
"PV2 Power": (15, Units.W),
"PV3 Power": (131, 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), # Only use the index once due to HA uids
"Run mode text": (19, Units.NONE, X3Ultra._decode_run_mode),
"EPS 1 Voltage": (23, Units.V, div10),
"EPS 2 Voltage": (24, Units.V, div10),
"EPS 3 Voltage": (25, Units.V, div10),
"EPS 1 Current": (26, Units.A, twoway_div10),
"EPS 2 Current": (27, Units.A, twoway_div10),
"EPS 3 Current": (28, Units.A, 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),
"Grid Power ": (pack_u16(34, 35), Units.W, to_signed32),
"Battery 1 Voltage": (39, Units.V, div10),
"Battery 2 Voltage": (132, Units.V, div10),
"Battery 1 Current": (40, Units.A, twoway_div100),
"Battery 2 Current": (133, Units.A, twoway_div100),
"Battery 1 Power": (41, Units.W, to_signed),
"Battery 2 Power": (134, Units.W, to_signed),
"Battery 1 Remaining Capacity": (103, Units.PERCENT),
"Battery 2 Remaining Capacity": (140, Units.PERCENT),
"Battery 1 Temperature": (105, Units.C, to_signed),
"Battery 2 Temperature": (142, Units.C, to_signed),
"Load/Generator Power": (47, Units.W, to_signed),
"Radiator Temperature": (54, Units.C, to_signed),
"Yield total": (pack_u16(58, 59), Total(Units.KWH), div10),
"Yield today": (70, DailyTotal(Units.KWH), div10),
"Battery Discharge Energy total": (
pack_u16(74, 75),
Total(Units.KWH),
div10,
),
"Battery Charge Energy total": (pack_u16(76, 77), Total(Units.KWH), div10),
"Battery Discharge Energy today": (78, DailyTotal(Units.KWH), div10),
"Battery Charge Energy today": (79, DailyTotal(Units.KWH), div10),
"PV Energy total": (pack_u16(80, 81), Total(Units.KWH), div10),
"EPS Energy total": (pack_u16(83, 84), Total(Units.KWH), div10),
"EPS Energy today": (85, DailyTotal(Units.KWH), div10),
"Feed-in Energy total": (pack_u16(86, 87), Total(Units.KWH), div100),
"Grid Consumed Energy total": (pack_u16(88, 89), Total(Units.KWH), div100),
"Feed-in Energy today": (pack_u16(90, 91), DailyTotal(Units.KWH), div100),
"Grid Consumed Energy today": (
pack_u16(92, 93),
DailyTotal(Units.KWH),
div100,
),
"Battery Remaining Capacity": (158, Units.PERCENT),
"Battery Remaining Energy": (
106,
Measurement(Units.KWH, storage=True),
div10,
),
"Inverter Power": (159, Units.W, div10),
}

# pylint: enable=duplicate-code

@classmethod
def inverter_serial_number_getter(cls, response: Dict[str, Any]) -> Optional[str]:
return response["information"][2]
12 changes: 12 additions & 0 deletions tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
X3_HYBRID_G4_VALUES,
X3_HYBRID_VALUES,
X3_MICPRO_G2_VALUES,
X3_ULTRA_VALUES,
X3_VALUES,
X3V34_HYBRID_VALUES,
X3V34_HYBRID_VALUES_EPS_MODE,
Expand All @@ -41,6 +42,7 @@
X3_HYBRID_G4_RESPONSE,
X3_MIC_RESPONSE,
X3_MICPRO_G2_RESPONSE,
X3_ULTRA_RESPONSE,
XHYBRID_DE01_RESPONSE,
XHYBRID_DE02_RESPONSE,
)
Expand Down Expand Up @@ -242,6 +244,16 @@ def simple_http_fixture(httpserver):
headers=None,
data="optType=ReadRealTimeData",
),
InverterUnderTest(
uri="/",
method="POST",
query_string=None,
response=X3_ULTRA_RESPONSE,
inverter=inverter.X3Ultra,
values=X3_ULTRA_VALUES,
headers=None,
data="optType=ReadRealTimeData",
),
InverterUnderTest(
uri="/",
method="POST",
Expand Down
63 changes: 63 additions & 0 deletions tests/samples/expected_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,3 +521,66 @@
"Total feed-in energy": 286.7,
"Total consumption": 6.2,
}

X3_ULTRA_VALUES = {
"Grid 1 Voltage": 232.8,
"Grid 2 Voltage": 233.8,
"Grid 3 Voltage": 232.0,
"Grid 1 Current": 2.0,
"Grid 2 Current": 1.9,
"Grid 3 Current": 1.9,
"Grid 1 Power": 216.0,
"Grid 2 Power": 249.0,
"Grid 3 Power": 199.0,
"PV1 Voltage": 0.0,
"PV2 Voltage": 249.8,
"PV3 Voltage": 245.4,
"PV1 Current": 0.0,
"PV2 Current": 6.5,
"PV3 Current": 10.5,
"PV1 Power": 0.0,
"PV2 Power": 1629.0,
"PV3 Power": 2592.0,
"Grid 1 Frequency": 50.0,
"Grid 2 Frequency": 50.0,
"Grid 3 Frequency": 50.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,
"Grid Power ": 52.0,
"Battery 1 Voltage": 213.8,
"Battery 2 Voltage": 212.9,
"Battery 1 Current": 9.6,
"Battery 2 Current": 7.4,
"Battery 1 Power": 2057.0,
"Battery 2 Power": 1595.0,
"Battery 1 Remaining Capacity": 28.0,
"Battery 2 Remaining Capacity": 28.0,
"Battery 1 Temperature": 13.0,
"Battery 2 Temperature": 12.0,
"Load/Generator Power": -52.0,
"Radiator Temperature": 34.0,
"Yield total": 198.4,
"Yield today": 2.4,
"Battery Discharge Energy total": 109.2,
"Battery Charge Energy total": 122.8,
"Battery Discharge Energy today": 0.2,
"Battery Charge Energy today": 2.7,
"PV Energy total": 122.9,
"EPS Energy total": 0.0,
"EPS Energy today": 0.0,
"Feed-in Energy total": 27.71,
"Grid Consumed Energy total": 128.12,
"Feed-in Energy today": 0.1,
"Grid Consumed Energy today": 8.6,
"Battery Remaining Capacity": 28.0,
"Battery Remaining Energy": 1.7,
"Inverter Power": 66.4,
}
Loading

0 comments on commit 64ab6c1

Please sign in to comment.