Skip to content

Commit bbb1d57

Browse files
Goodwe port502ftp support with PORT stored on config data. (home-assistant#148628)
Co-authored-by: starkillerOG <[email protected]>
1 parent 1214065 commit bbb1d57

File tree

7 files changed

+167
-66
lines changed

7 files changed

+167
-66
lines changed

homeassistant/components/goodwe/__init__.py

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,37 @@
11
"""The Goodwe inverter component."""
22

3-
from goodwe import InverterError, connect
4-
from goodwe.const import GOODWE_TCP_PORT, GOODWE_UDP_PORT
3+
from goodwe import Inverter, InverterError, connect
4+
from goodwe.const import GOODWE_UDP_PORT
55

6-
from homeassistant.const import CONF_HOST
6+
from homeassistant.const import CONF_HOST, CONF_PORT
77
from homeassistant.core import HomeAssistant
88
from homeassistant.exceptions import ConfigEntryNotReady
99
from homeassistant.helpers.device_registry import DeviceInfo
1010

11+
from .config_flow import GoodweFlowHandler
1112
from .const import CONF_MODEL_FAMILY, DOMAIN, PLATFORMS
1213
from .coordinator import GoodweConfigEntry, GoodweRuntimeData, GoodweUpdateCoordinator
1314

1415

1516
async def async_setup_entry(hass: HomeAssistant, entry: GoodweConfigEntry) -> bool:
1617
"""Set up the Goodwe components from a config entry."""
1718
host = entry.data[CONF_HOST]
19+
port = entry.data.get(CONF_PORT, GOODWE_UDP_PORT)
1820
model_family = entry.data[CONF_MODEL_FAMILY]
1921

2022
# Connect to Goodwe inverter
2123
try:
2224
inverter = await connect(
2325
host=host,
24-
port=GOODWE_UDP_PORT,
26+
port=port,
2527
family=model_family,
2628
retries=10,
2729
)
28-
except InverterError as err_udp:
29-
# First try with UDP failed, trying with the TCP port
30+
except InverterError as err:
3031
try:
31-
inverter = await connect(
32-
host=host,
33-
port=GOODWE_TCP_PORT,
34-
family=model_family,
35-
retries=10,
36-
)
32+
inverter = await async_check_port(hass, entry, host)
3733
except InverterError:
38-
# Both ports are unavailable
39-
raise ConfigEntryNotReady from err_udp
34+
raise ConfigEntryNotReady from err
4035

4136
device_info = DeviceInfo(
4237
configuration_url="https://www.semsportal.com",
@@ -66,6 +61,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: GoodweConfigEntry) -> bo
6661
return True
6762

6863

64+
async def async_check_port(
65+
hass: HomeAssistant, entry: GoodweConfigEntry, host: str
66+
) -> Inverter:
67+
"""Check the communication port of the inverter, it may have changed after a firmware update."""
68+
inverter, port = await GoodweFlowHandler.async_detect_inverter_port(host=host)
69+
family = type(inverter).__name__
70+
hass.config_entries.async_update_entry(
71+
entry,
72+
data={
73+
CONF_HOST: host,
74+
CONF_PORT: port,
75+
CONF_MODEL_FAMILY: family,
76+
},
77+
)
78+
return inverter
79+
80+
6981
async def async_unload_entry(
7082
hass: HomeAssistant, config_entry: GoodweConfigEntry
7183
) -> bool:
@@ -76,3 +88,31 @@ async def async_unload_entry(
7688
async def update_listener(hass: HomeAssistant, config_entry: GoodweConfigEntry) -> None:
7789
"""Handle options update."""
7890
await hass.config_entries.async_reload(config_entry.entry_id)
91+
92+
93+
async def async_migrate_entry(
94+
hass: HomeAssistant, config_entry: GoodweConfigEntry
95+
) -> bool:
96+
"""Migrate old config entries."""
97+
98+
if config_entry.version > 2:
99+
# This means the user has downgraded from a future version
100+
return False
101+
102+
if config_entry.version == 1:
103+
# Update from version 1 to version 2 adding the PROTOCOL to the config entry
104+
host = config_entry.data[CONF_HOST]
105+
try:
106+
inverter, port = await GoodweFlowHandler.async_detect_inverter_port(
107+
host=host
108+
)
109+
except InverterError as err:
110+
raise ConfigEntryNotReady from err
111+
new_data = {
112+
CONF_HOST: host,
113+
CONF_PORT: port,
114+
CONF_MODEL_FAMILY: type(inverter).__name__,
115+
}
116+
hass.config_entries.async_update_entry(config_entry, data=new_data, version=2)
117+
118+
return True

homeassistant/components/goodwe/config_flow.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
import logging
66
from typing import Any
77

8-
from goodwe import InverterError, connect
8+
from goodwe import Inverter, InverterError, connect
99
from goodwe.const import GOODWE_TCP_PORT, GOODWE_UDP_PORT
1010
import voluptuous as vol
1111

1212
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
13-
from homeassistant.const import CONF_HOST
13+
from homeassistant.const import CONF_HOST, CONF_PORT
1414

1515
from .const import CONF_MODEL_FAMILY, DEFAULT_NAME, DOMAIN
1616

@@ -26,16 +26,23 @@
2626
class GoodweFlowHandler(ConfigFlow, domain=DOMAIN):
2727
"""Handle a Goodwe config flow."""
2828

29-
VERSION = 1
29+
MINOR_VERSION = 2
3030

31-
async def _handle_successful_connection(self, inverter, host):
31+
async def async_handle_successful_connection(
32+
self,
33+
inverter: Inverter,
34+
host: str,
35+
port: int,
36+
) -> ConfigFlowResult:
37+
"""Handle a successful connection storing it's values on the entry data."""
3238
await self.async_set_unique_id(inverter.serial_number)
3339
self._abort_if_unique_id_configured()
3440

3541
return self.async_create_entry(
3642
title=DEFAULT_NAME,
3743
data={
3844
CONF_HOST: host,
45+
CONF_PORT: port,
3946
CONF_MODEL_FAMILY: type(inverter).__name__,
4047
},
4148
)
@@ -48,19 +55,26 @@ async def async_step_user(
4855
if user_input is not None:
4956
host = user_input[CONF_HOST]
5057
try:
51-
inverter = await connect(host=host, port=GOODWE_UDP_PORT, retries=10)
58+
inverter, port = await self.async_detect_inverter_port(host=host)
5259
except InverterError:
53-
try:
54-
inverter = await connect(
55-
host=host, port=GOODWE_TCP_PORT, retries=10
56-
)
57-
except InverterError:
58-
errors[CONF_HOST] = "connection_error"
59-
else:
60-
return await self._handle_successful_connection(inverter, host)
60+
errors[CONF_HOST] = "connection_error"
6161
else:
62-
return await self._handle_successful_connection(inverter, host)
63-
62+
return await self.async_handle_successful_connection(
63+
inverter, host, port
64+
)
6465
return self.async_show_form(
6566
step_id="user", data_schema=CONFIG_SCHEMA, errors=errors
6667
)
68+
69+
@staticmethod
70+
async def async_detect_inverter_port(
71+
host: str,
72+
) -> tuple[Inverter, int]:
73+
"""Detects the port of the Inverter."""
74+
port = GOODWE_UDP_PORT
75+
try:
76+
inverter = await connect(host=host, port=port, retries=10)
77+
except InverterError:
78+
port = GOODWE_TCP_PORT
79+
inverter = await connect(host=host, port=port, retries=10)
80+
return inverter, port

tests/components/goodwe/conftest.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
"""Fixtures for the Aladdin Connect integration tests."""
22

3-
from unittest.mock import AsyncMock, MagicMock
3+
from collections.abc import Generator
4+
from unittest.mock import AsyncMock, MagicMock, patch
45

56
from goodwe import Inverter
7+
from goodwe.const import GOODWE_UDP_PORT
68
import pytest
79

10+
TEST_HOST = "1.2.3.4"
11+
TEST_PORT = GOODWE_UDP_PORT
12+
TEST_SERIAL = "123456789"
13+
814

915
@pytest.fixture(name="mock_inverter")
10-
def fixture_mock_inverter():
16+
def fixture_mock_inverter() -> Generator[MagicMock]:
1117
"""Set up inverter fixture."""
1218
mock_inverter = MagicMock(spec=Inverter)
13-
mock_inverter.serial_number = "dummy_serial_nr"
19+
mock_inverter.serial_number = TEST_SERIAL
1420
mock_inverter.arm_version = 1
1521
mock_inverter.arm_svn_version = 2
1622
mock_inverter.arm_firmware = "dummy.arm.version"
@@ -23,4 +29,11 @@ def fixture_mock_inverter():
2329

2430
mock_inverter.read_runtime_data = AsyncMock(return_value={})
2531

26-
return mock_inverter
32+
with (
33+
patch(
34+
"homeassistant.components.goodwe.config_flow.connect",
35+
return_value=mock_inverter,
36+
),
37+
patch("homeassistant.components.goodwe.connect", return_value=mock_inverter),
38+
):
39+
yield mock_inverter

tests/components/goodwe/snapshots/test_diagnostics.ambr

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
dict({
44
'config_entry': dict({
55
'data': dict({
6-
'host': 'localhost',
6+
'host': '1.2.3.4',
77
'model_family': 'ET',
8+
'port': 8899,
89
}),
910
'disabled_by': None,
1011
'discovery_keys': dict({
@@ -21,7 +22,7 @@
2122
]),
2223
'title': 'Mock Title',
2324
'unique_id': None,
24-
'version': 1,
25+
'version': 2,
2526
}),
2627
'inverter': dict({
2728
'arm_firmware': 'dummy.arm.version',

tests/components/goodwe/test_config_flow.py

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,29 @@
11
"""Test the Goodwe config flow."""
22

3-
from unittest.mock import AsyncMock, patch
3+
from unittest.mock import MagicMock, patch
44

55
from goodwe import InverterError
6+
from goodwe.const import GOODWE_UDP_PORT
67

78
from homeassistant.components.goodwe.const import (
89
CONF_MODEL_FAMILY,
910
DEFAULT_NAME,
1011
DOMAIN,
1112
)
1213
from homeassistant.config_entries import SOURCE_USER
13-
from homeassistant.const import CONF_HOST
14+
from homeassistant.const import CONF_HOST, CONF_PORT
1415
from homeassistant.core import HomeAssistant
1516
from homeassistant.data_entry_flow import FlowResultType
1617

18+
from .conftest import TEST_SERIAL
19+
1720
from tests.common import MockConfigEntry
1821

1922
TEST_HOST = "1.2.3.4"
20-
TEST_SERIAL = "123456789"
21-
23+
TEST_PORT = GOODWE_UDP_PORT
2224

23-
def mock_inverter():
24-
"""Get a mock object of the inverter."""
25-
goodwe_inverter = AsyncMock()
26-
goodwe_inverter.serial_number = TEST_SERIAL
27-
return goodwe_inverter
2825

29-
30-
async def test_manual_setup(hass: HomeAssistant) -> None:
26+
async def test_manual_setup(hass: HomeAssistant, mock_inverter: MagicMock) -> None:
3127
"""Test manually setting up."""
3228
result = await hass.config_entries.flow.async_init(
3329
DOMAIN, context={"source": SOURCE_USER}
@@ -37,10 +33,6 @@ async def test_manual_setup(hass: HomeAssistant) -> None:
3733
assert not result["errors"]
3834

3935
with (
40-
patch(
41-
"homeassistant.components.goodwe.config_flow.connect",
42-
return_value=mock_inverter(),
43-
),
4436
patch(
4537
"homeassistant.components.goodwe.async_setup_entry", return_value=True
4638
) as mock_setup_entry,
@@ -54,15 +46,20 @@ async def test_manual_setup(hass: HomeAssistant) -> None:
5446
assert result["title"] == DEFAULT_NAME
5547
assert result["data"] == {
5648
CONF_HOST: TEST_HOST,
57-
CONF_MODEL_FAMILY: "AsyncMock",
49+
CONF_PORT: TEST_PORT,
50+
CONF_MODEL_FAMILY: "MagicMock",
5851
}
5952
assert len(mock_setup_entry.mock_calls) == 1
6053

6154

62-
async def test_manual_setup_already_exists(hass: HomeAssistant) -> None:
55+
async def test_manual_setup_already_exists(
56+
hass: HomeAssistant, mock_inverter: MagicMock
57+
) -> None:
6358
"""Test manually setting up and the device already exists."""
6459
entry = MockConfigEntry(
65-
domain=DOMAIN, data={CONF_HOST: TEST_HOST}, unique_id=TEST_SERIAL
60+
domain=DOMAIN,
61+
data={CONF_HOST: TEST_HOST},
62+
unique_id=TEST_SERIAL,
6663
)
6764
entry.add_to_hass(hass)
6865
result = await hass.config_entries.flow.async_init(
@@ -72,13 +69,7 @@ async def test_manual_setup_already_exists(hass: HomeAssistant) -> None:
7269
assert result["step_id"] == "user"
7370
assert not result["errors"]
7471

75-
with (
76-
patch(
77-
"homeassistant.components.goodwe.config_flow.connect",
78-
return_value=mock_inverter(),
79-
),
80-
patch("homeassistant.components.goodwe.async_setup_entry", return_value=True),
81-
):
72+
with patch("homeassistant.components.goodwe.async_setup_entry", return_value=True):
8273
result = await hass.config_entries.flow.async_configure(
8374
result["flow_id"], {CONF_HOST: TEST_HOST}
8475
)

tests/components/goodwe/test_diagnostics.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
"""Test the CO2Signal diagnostics."""
1+
"""Test the GoodWe diagnostics."""
22

3-
from unittest.mock import MagicMock, patch
3+
from unittest.mock import MagicMock
44

55
from syrupy.assertion import SnapshotAssertion
66
from syrupy.filters import props
77

88
from homeassistant.components.goodwe import CONF_MODEL_FAMILY, DOMAIN
9-
from homeassistant.const import CONF_HOST
9+
from homeassistant.const import CONF_HOST, CONF_PORT
1010
from homeassistant.core import HomeAssistant
1111
from homeassistant.setup import async_setup_component
1212

13+
from .conftest import TEST_HOST, TEST_PORT
14+
1315
from tests.common import MockConfigEntry
1416
from tests.components.diagnostics import get_diagnostics_for_config_entry
1517
from tests.typing import ClientSessionGenerator
@@ -24,13 +26,17 @@ async def test_entry_diagnostics(
2426
"""Test config entry diagnostics."""
2527

2628
config_entry = MockConfigEntry(
29+
version=2,
2730
domain=DOMAIN,
28-
data={CONF_HOST: "localhost", CONF_MODEL_FAMILY: "ET"},
31+
data={
32+
CONF_HOST: TEST_HOST,
33+
CONF_PORT: TEST_PORT,
34+
CONF_MODEL_FAMILY: "ET",
35+
},
2936
entry_id="3bd2acb0e4f0476d40865546d0d91921",
3037
)
3138
config_entry.add_to_hass(hass)
32-
with patch("homeassistant.components.goodwe.connect", return_value=mock_inverter):
33-
assert await async_setup_component(hass, DOMAIN, {})
39+
assert await async_setup_component(hass, DOMAIN, {})
3440

3541
result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry)
3642
assert result == snapshot(exclude=props("created_at", "modified_at"))

0 commit comments

Comments
 (0)