Skip to content

Commit e218eab

Browse files
committed
feat: automatically discover inverter type: string or hybrid
1 parent 0a6f514 commit e218eab

File tree

6 files changed

+61
-29
lines changed

6 files changed

+61
-29
lines changed

README.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,15 @@ After successful configuration, the integration creates a new entity for your in
5353

5454
## Supported devices
5555

56-
All Solis hybrid inverters should be supported, although the integration has been tested with the following models:
57-
58-
* S6-EH3P(8-15)K02-NV-YD-L, model "3331"
59-
* S6-EH3P(5-10)K-H, model "3306"
60-
* RHI-3P(3-10)K-HVES-5G, model "CA"
61-
62-
The integration also supports the following Solis string inverters:
63-
64-
* S6-GR1P(2.5-6)K, model "0200"
65-
* S5-GR3P(3-20)K, model "0507"
56+
All Solis inverters should be supported, although the integration has been tested with the following models:
57+
58+
| Name | Model | Hybrid |
59+
| ------------------------ | ----- | ------ |
60+
| S6-EH3P(8-15)K02-NV-YD-L | 3331 ||
61+
| S6-EH3P(5-10)K-H | 3306 ||
62+
| RHI-3P(3-10)K-HVES-5G | CA ||
63+
| S6-GR1P(2.5-6)K | 0200 ||
64+
| S5-GR3P(3-20)K | 0507 ||
6665

6766
> [!NOTE]
6867
> If your inverter is not listed above, please open a GitHub issue using the "New Solis Inverter Support Request" template.

custom_components/solis_cloud_control/inverters/inverter.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,20 @@ class InverterInfo:
99
model: str | None
1010
version: str | None
1111
machine: str | None
12-
type: str | None
12+
energy_storage_control: str | None
1313
smart_support: str | None
1414
generator_support: str | None
15-
battery_num: str | None
1615
power: str | None
1716
power_unit: str | None
1817

1918
@property
2019
def power_watts(self) -> float | None:
2120
return safe_convert_power_to_watts(self.power, self.power_unit)
2221

22+
@property
23+
def is_string_inverter(self) -> bool:
24+
return self.energy_storage_control is not None and self.energy_storage_control == "0"
25+
2326

2427
@dataclass(frozen=True)
2528
class InverterOnOff:

custom_components/solis_cloud_control/inverters/inverter_factory.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@ async def create_inverter_info(api_client: SolisCloudControlApiClient, inverter_
1515
model=_get_inverter_detail(inverter_details, "model"),
1616
version=_get_inverter_detail(inverter_details, "version"),
1717
machine=_get_inverter_detail(inverter_details, "machine"),
18-
type=_get_inverter_detail(inverter_details, "inverterType"),
18+
energy_storage_control=_get_inverter_detail(inverter_details, "energyStorageControl"),
1919
smart_support=_get_inverter_detail(inverter_details, "smartSupport"),
2020
generator_support=_get_inverter_detail(inverter_details, "generatorSupport"),
21-
battery_num=_get_inverter_detail(inverter_details, "batteryNum"),
2221
power=_get_inverter_detail(inverter_details, "power"),
2322
power_unit=_get_inverter_detail(inverter_details, "powerStr"),
2423
)
@@ -29,12 +28,15 @@ async def create_inverter(api_client: SolisCloudControlApiClient, inverter_info:
2928
inverter_model = inverter_info.model.lower()
3029
module_name = f"custom_components.solis_cloud_control.inverters.model_{inverter_model}"
3130
model_module = importlib.import_module(module_name)
32-
inverter = await model_module.create_inverter(inverter_info, api_client)
33-
_LOGGER.info("Inverter model '%s' created", inverter_info.model)
34-
return inverter
31+
_LOGGER.info("Supported inverter model '%s' found", inverter_info.model)
32+
return await model_module.create_inverter(inverter_info, api_client)
3533
except ImportError:
36-
_LOGGER.warning("Unknown inverter model '%s', fallback to generic hybrid inverter", inverter_info.model)
37-
return Inverter.create_hybrid_inverter(inverter_info)
34+
if inverter_info.is_string_inverter:
35+
_LOGGER.warning("Unknown inverter model '%s', fallback to generic string inverter", inverter_info.model)
36+
return Inverter.create_string_inverter(inverter_info)
37+
else:
38+
_LOGGER.warning("Unknown inverter model '%s', fallback to generic hybrid inverter", inverter_info.model)
39+
return Inverter.create_hybrid_inverter(inverter_info)
3840

3941

4042
def _get_inverter_detail(inverter_details: dict[str, any], field: str) -> str | None:

tests/conftest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,9 @@ def any_inverter_info():
7777
model="any_model",
7878
machine="any_machine",
7979
version="any_version",
80-
type="any_type",
80+
energy_storage_control="any_energy_storage_control",
8181
smart_support="any_smart_support",
8282
generator_support="any_generator_support",
83-
battery_num="any_battery_num",
8483
power="any_power",
8584
power_unit="any_power_unit",
8685
)

tests/inverters/test_inverter.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,18 @@ async def test_inverter_info_power_watts(mock_api_client, any_inverter_info, pow
2121
inverter = replace(any_inverter_info, power=power, power_unit=power_unit)
2222

2323
assert inverter.power_watts == expected_power
24+
25+
26+
@pytest.mark.parametrize(
27+
"energy_storage_control,expected",
28+
[
29+
("0", True),
30+
("1", False),
31+
(None, False),
32+
("", False),
33+
("unexpected", False),
34+
],
35+
)
36+
def test_inverter_info_is_string_inverter(any_inverter_info, energy_storage_control, expected):
37+
inverter = replace(any_inverter_info, energy_storage_control=energy_storage_control)
38+
assert inverter.is_string_inverter == expected

tests/inverters/test_inverter_factory.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ async def test_create_inverter_info(mock_api_client):
1717
"model": "any model",
1818
"version": "any version",
1919
"machine": "any machine",
20-
"inverterType": "any type",
20+
"energyStorageControl": "any energy storage control",
2121
"smartSupport": "any smart support",
2222
"generatorSupport": "any generator support",
23-
"batteryNum": "any battery num",
2423
"power": 10,
2524
"powerStr": "kW",
2625
}
@@ -32,10 +31,9 @@ async def test_create_inverter_info(mock_api_client):
3231
assert result.model == "any model"
3332
assert result.version == "any version"
3433
assert result.machine == "any machine"
35-
assert result.type == "any type"
34+
assert result.energy_storage_control == "any energy storage control"
3635
assert result.smart_support == "any smart support"
3736
assert result.generator_support == "any generator support"
38-
assert result.battery_num == "any battery num"
3937
assert result.power == "10"
4038
assert result.power_unit == "kW"
4139

@@ -51,17 +49,33 @@ async def test_create_inverter_info_missing_fields(mock_api_client):
5149
assert result.model is None
5250
assert result.version is None
5351
assert result.machine is None
54-
assert result.type is None
52+
assert result.energy_storage_control is None
5553
assert result.smart_support is None
5654
assert result.generator_support is None
57-
assert result.battery_num is None
5855
assert result.power is None
5956
assert result.power_unit is None
6057

6158

6259
@pytest.mark.asyncio
63-
async def test_create_inverter_unknown_model(mock_api_client, any_inverter_info):
64-
inverter_info = replace(any_inverter_info, model="unknown model")
60+
async def test_create_inverter_unknown_hybrid_model(mock_api_client, any_inverter_info):
61+
inverter_info = replace(any_inverter_info, model="unknown model", energy_storage_control="1")
6562
result = await create_inverter(mock_api_client, inverter_info)
6663

6764
assert result == Inverter.create_hybrid_inverter(inverter_info)
65+
66+
67+
@pytest.mark.asyncio
68+
async def test_create_inverter_unknown_string_model(mock_api_client, any_inverter_info):
69+
inverter_info = replace(any_inverter_info, model="unknown model", energy_storage_control="0")
70+
result = await create_inverter(mock_api_client, inverter_info)
71+
72+
assert result == Inverter.create_string_inverter(inverter_info)
73+
74+
75+
@pytest.mark.asyncio
76+
async def test_create_inverter_3331(mock_api_client, any_inverter_info):
77+
inverter_info = replace(any_inverter_info, model="3331")
78+
result = await create_inverter(mock_api_client, inverter_info)
79+
80+
assert result != Inverter.create_hybrid_inverter(inverter_info)
81+
assert result != Inverter.create_string_inverter(inverter_info)

0 commit comments

Comments
 (0)