Migrate powerwall from using ip address as unique id (#65257)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
J. Nick Koston 2022-02-03 11:39:57 -06:00 committed by GitHub
parent 8d2fac09bb
commit 3d8d507ed9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 359 additions and 76 deletions

View File

@ -1,4 +1,5 @@
"""The Tesla Powerwall integration.""" """The Tesla Powerwall integration."""
import contextlib
from datetime import timedelta from datetime import timedelta
import logging import logging
@ -8,6 +9,7 @@ from tesla_powerwall import (
APIError, APIError,
MissingAttributeError, MissingAttributeError,
Powerwall, Powerwall,
PowerwallError,
PowerwallUnreachableError, PowerwallUnreachableError,
) )
@ -19,12 +21,14 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import entity_registry from homeassistant.helpers import entity_registry
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.network import is_ip_address
from .const import ( from .const import (
DOMAIN, DOMAIN,
POWERWALL_API_CHANGED, POWERWALL_API_CHANGED,
POWERWALL_API_CHARGE, POWERWALL_API_CHARGE,
POWERWALL_API_DEVICE_TYPE, POWERWALL_API_DEVICE_TYPE,
POWERWALL_API_GATEWAY_DIN,
POWERWALL_API_GRID_SERVICES_ACTIVE, POWERWALL_API_GRID_SERVICES_ACTIVE,
POWERWALL_API_GRID_STATUS, POWERWALL_API_GRID_STATUS,
POWERWALL_API_METERS, POWERWALL_API_METERS,
@ -117,6 +121,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryAuthFailed from err raise ConfigEntryAuthFailed from err
await _migrate_old_unique_ids(hass, entry_id, powerwall_data) await _migrate_old_unique_ids(hass, entry_id, powerwall_data)
gateway_din = powerwall_data[POWERWALL_API_GATEWAY_DIN]
if gateway_din and entry.unique_id is not None and is_ip_address(entry.unique_id):
hass.config_entries.async_update_entry(entry, unique_id=gateway_din)
login_failed_count = 0 login_failed_count = 0
runtime_data = hass.data[DOMAIN][entry.entry_id] = { runtime_data = hass.data[DOMAIN][entry.entry_id] = {
@ -224,14 +233,16 @@ def _login_and_fetch_base_info(power_wall: Powerwall, password: str):
def call_base_info(power_wall): def call_base_info(power_wall):
"""Wrap powerwall properties to be a callable.""" """Wrap powerwall properties to be a callable."""
serial_numbers = power_wall.get_serial_numbers()
# Make sure the serial numbers always have the same order # Make sure the serial numbers always have the same order
serial_numbers.sort() gateway_din = None
with contextlib.suppress((AssertionError, PowerwallError)):
gateway_din = power_wall.get_gateway_din().upper()
return { return {
POWERWALL_API_SITE_INFO: power_wall.get_site_info(), POWERWALL_API_SITE_INFO: power_wall.get_site_info(),
POWERWALL_API_STATUS: power_wall.get_status(), POWERWALL_API_STATUS: power_wall.get_status(),
POWERWALL_API_DEVICE_TYPE: power_wall.get_device_type(), POWERWALL_API_DEVICE_TYPE: power_wall.get_device_type(),
POWERWALL_API_SERIAL_NUMBERS: serial_numbers, POWERWALL_API_SERIAL_NUMBERS: sorted(power_wall.get_serial_numbers()),
POWERWALL_API_GATEWAY_DIN: gateway_din,
} }

View File

@ -1,5 +1,8 @@
"""Config flow for Tesla Powerwall integration.""" """Config flow for Tesla Powerwall integration."""
from __future__ import annotations
import logging import logging
from typing import Any
from tesla_powerwall import ( from tesla_powerwall import (
AccessDeniedError, AccessDeniedError,
@ -13,6 +16,7 @@ from homeassistant import config_entries, core, exceptions
from homeassistant.components import dhcp from homeassistant.components import dhcp
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.util.network import is_ip_address
from .const import DOMAIN from .const import DOMAIN
@ -24,10 +28,12 @@ def _login_and_fetch_site_info(power_wall: Powerwall, password: str):
if password is not None: if password is not None:
power_wall.login(password) power_wall.login(password)
power_wall.detect_and_pin_version() power_wall.detect_and_pin_version()
return power_wall.get_site_info() return power_wall.get_site_info(), power_wall.get_gateway_din()
async def validate_input(hass: core.HomeAssistant, data): async def validate_input(
hass: core.HomeAssistant, data: dict[str, str]
) -> dict[str, str]:
"""Validate the user input allows us to connect. """Validate the user input allows us to connect.
Data has the keys from schema with values provided by the user. Data has the keys from schema with values provided by the user.
@ -37,7 +43,7 @@ async def validate_input(hass: core.HomeAssistant, data):
password = data[CONF_PASSWORD] password = data[CONF_PASSWORD]
try: try:
site_info = await hass.async_add_executor_job( site_info, gateway_din = await hass.async_add_executor_job(
_login_and_fetch_site_info, power_wall, password _login_and_fetch_site_info, power_wall, password
) )
except MissingAttributeError as err: except MissingAttributeError as err:
@ -46,7 +52,7 @@ async def validate_input(hass: core.HomeAssistant, data):
raise WrongVersion from err raise WrongVersion from err
# Return info that you want to store in the config entry. # Return info that you want to store in the config entry.
return {"title": site_info.site_name} return {"title": site_info.site_name, "unique_id": gateway_din.upper()}
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
@ -56,19 +62,50 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
def __init__(self): def __init__(self):
"""Initialize the powerwall flow.""" """Initialize the powerwall flow."""
self.ip_address = None self.ip_address: str | None = None
self.title: str | None = None
self.reauth_entry: config_entries.ConfigEntry | None = None
async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult: async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult:
"""Handle dhcp discovery.""" """Handle dhcp discovery."""
self.ip_address = discovery_info.ip self.ip_address = discovery_info.ip
self._async_abort_entries_match({CONF_IP_ADDRESS: self.ip_address}) gateway_din = discovery_info.hostname.upper()
self.context["title_placeholders"] = {CONF_IP_ADDRESS: self.ip_address} # The hostname is the gateway_din (unique_id)
await self.async_set_unique_id(gateway_din)
self._abort_if_unique_id_configured(updates={CONF_IP_ADDRESS: self.ip_address})
for entry in self._async_current_entries(include_ignore=False):
if entry.data[CONF_IP_ADDRESS] == discovery_info.ip:
if entry.unique_id is not None and is_ip_address(entry.unique_id):
if self.hass.config_entries.async_update_entry(
entry, unique_id=gateway_din
):
self.hass.async_create_task(
self.hass.config_entries.async_reload(entry.entry_id)
)
return self.async_abort(reason="already_configured")
self.context["title_placeholders"] = {
"name": gateway_din,
"ip_address": self.ip_address,
}
errors, info = await self._async_try_connect(
{CONF_IP_ADDRESS: self.ip_address, CONF_PASSWORD: gateway_din[-5:]}
)
if errors:
if CONF_PASSWORD in errors:
# The default password is the gateway din last 5
# if it does not work, we have to ask
return await self.async_step_user() return await self.async_step_user()
return self.async_abort(reason="cannot_connect")
assert info is not None
self.title = info["title"]
return await self.async_step_confirm_discovery()
async def async_step_user(self, user_input=None): async def _async_try_connect(
"""Handle the initial step.""" self, user_input
errors = {} ) -> tuple[dict[str, Any] | None, dict[str, str] | None]:
if user_input is not None: """Try to connect to the powerwall."""
info = None
errors: dict[str, str] = {}
try: try:
info = await validate_input(self.hass, user_input) info = await validate_input(self.hass, user_input)
except PowerwallUnreachableError: except PowerwallUnreachableError:
@ -81,16 +118,51 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_LOGGER.exception("Unexpected exception") _LOGGER.exception("Unexpected exception")
errors["base"] = "unknown" errors["base"] = "unknown"
return errors, info
async def async_step_confirm_discovery(self, user_input=None) -> FlowResult:
"""Confirm a discovered powerwall."""
assert self.ip_address is not None
assert self.unique_id is not None
if user_input is not None:
assert self.title is not None
return self.async_create_entry(
title=self.title,
data={
CONF_IP_ADDRESS: self.ip_address,
CONF_PASSWORD: self.unique_id[-5:],
},
)
self._set_confirm_only()
self.context["title_placeholders"] = {
"name": self.title,
"ip_address": self.ip_address,
}
return self.async_show_form(
step_id="confirm_discovery",
data_schema=vol.Schema({}),
description_placeholders={
"name": self.title,
"ip_address": self.ip_address,
},
)
async def async_step_user(self, user_input=None):
"""Handle the initial step."""
errors = {}
if user_input is not None:
errors, info = await self._async_try_connect(user_input)
if not errors: if not errors:
existing_entry = await self.async_set_unique_id( assert info is not None
user_input[CONF_IP_ADDRESS] if info["unique_id"]:
await self.async_set_unique_id(
info["unique_id"], raise_on_progress=False
) )
if existing_entry: self._abort_if_unique_id_configured(
self.hass.config_entries.async_update_entry( updates={CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS]}
existing_entry, data=user_input
) )
await self.hass.config_entries.async_reload(existing_entry.entry_id) self._async_abort_entries_match({CONF_IP_ADDRESS: self.ip_address})
return self.async_abort(reason="reauth_successful")
return self.async_create_entry(title=info["title"], data=user_input) return self.async_create_entry(title=info["title"], data=user_input)
return self.async_show_form( return self.async_show_form(
@ -104,10 +176,33 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors=errors, errors=errors,
) )
async def async_step_reauth_confirm(self, user_input=None):
"""Handle reauth confirmation."""
errors = {}
if user_input is not None:
entry_data = self.reauth_entry.data
errors, _ = await self._async_try_connect(
{CONF_IP_ADDRESS: entry_data[CONF_IP_ADDRESS], **user_input}
)
if not errors:
self.hass.config_entries.async_update_entry(
self.reauth_entry, data={**entry_data, **user_input}
)
await self.hass.config_entries.async_reload(self.reauth_entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({vol.Optional(CONF_PASSWORD): str}),
errors=errors,
)
async def async_step_reauth(self, data): async def async_step_reauth(self, data):
"""Handle configuration by re-auth.""" """Handle configuration by re-auth."""
self.ip_address = data[CONF_IP_ADDRESS] self.reauth_entry = self.hass.config_entries.async_get_entry(
return await self.async_step_user() self.context["entry_id"]
)
return await self.async_step_reauth_confirm()
class WrongVersion(exceptions.HomeAssistantError): class WrongVersion(exceptions.HomeAssistantError):

View File

@ -26,6 +26,7 @@ POWERWALL_API_STATUS = "status"
POWERWALL_API_DEVICE_TYPE = "device_type" POWERWALL_API_DEVICE_TYPE = "device_type"
POWERWALL_API_SITE_INFO = "site_info" POWERWALL_API_SITE_INFO = "site_info"
POWERWALL_API_SERIAL_NUMBERS = "serial_numbers" POWERWALL_API_SERIAL_NUMBERS = "serial_numbers"
POWERWALL_API_GATEWAY_DIN = "gateway_din"
POWERWALL_HTTP_SESSION = "http_session" POWERWALL_HTTP_SESSION = "http_session"

View File

@ -3,16 +3,11 @@
"name": "Tesla Powerwall", "name": "Tesla Powerwall",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/powerwall", "documentation": "https://www.home-assistant.io/integrations/powerwall",
"requirements": ["tesla-powerwall==0.3.12"], "requirements": ["tesla-powerwall==0.3.15"],
"codeowners": ["@bdraco", "@jrester"], "codeowners": ["@bdraco", "@jrester"],
"dhcp": [ "dhcp": [
{ {
"hostname": "1118431-*", "hostname": "1118431-*"
"macaddress": "88DA1A*"
},
{
"hostname": "1118431-*",
"macaddress": "000145*"
} }
], ],
"iot_class": "local_polling", "iot_class": "local_polling",

View File

@ -1,6 +1,6 @@
{ {
"config": { "config": {
"flow_title": "{ip_address}", "flow_title": "{name} ({ip_address})",
"step": { "step": {
"user": { "user": {
"title": "Connect to the powerwall", "title": "Connect to the powerwall",
@ -9,6 +9,17 @@
"ip_address": "[%key:common::config_flow::data::ip%]", "ip_address": "[%key:common::config_flow::data::ip%]",
"password": "[%key:common::config_flow::data::password%]" "password": "[%key:common::config_flow::data::password%]"
} }
},
"reauth_confim": {
"title": "Reauthenticate the powerwall",
"description": "[%key:component::powerwall::config::step::user::description%]",
"data": {
"password": "[%key:common::config_flow::data::password%]"
}
},
"confirm_discovery": {
"title": "[%key:component::powerwall::config::step::user::title%]",
"description": "Do you want to setup {name} ({ip_address})?"
} }
}, },
"error": { "error": {
@ -18,6 +29,7 @@
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
}, },
"abort": { "abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
} }

View File

@ -2,6 +2,7 @@
"config": { "config": {
"abort": { "abort": {
"already_configured": "Device is already configured", "already_configured": "Device is already configured",
"cannot_connect": "Failed to connect",
"reauth_successful": "Re-authentication was successful" "reauth_successful": "Re-authentication was successful"
}, },
"error": { "error": {
@ -10,8 +11,19 @@
"unknown": "Unexpected error", "unknown": "Unexpected error",
"wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved." "wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved."
}, },
"flow_title": "{ip_address}", "flow_title": "{name} ({ip_address})",
"step": { "step": {
"confirm_discovery": {
"description": "Do you want to setup {name} ({ip_address})?",
"title": "Connect to the powerwall"
},
"reauth_confim": {
"data": {
"password": "Password"
},
"description": "The password is usually the last 5 characters of the serial number for Backup Gateway and can be found in the Tesla app or the last 5 characters of the password found inside the door for Backup Gateway 2.",
"title": "Reauthenticate the powerwall"
},
"user": { "user": {
"data": { "data": {
"ip_address": "IP Address", "ip_address": "IP Address",

View File

@ -218,13 +218,7 @@ DHCP = [
}, },
{ {
"domain": "powerwall", "domain": "powerwall",
"hostname": "1118431-*", "hostname": "1118431-*"
"macaddress": "88DA1A*"
},
{
"domain": "powerwall",
"hostname": "1118431-*",
"macaddress": "000145*"
}, },
{ {
"domain": "rachio", "domain": "rachio",

View File

@ -2341,7 +2341,7 @@ temperusb==1.5.3
# tensorflow==2.5.0 # tensorflow==2.5.0
# homeassistant.components.powerwall # homeassistant.components.powerwall
tesla-powerwall==0.3.12 tesla-powerwall==0.3.15
# homeassistant.components.tesla_wall_connector # homeassistant.components.tesla_wall_connector
tesla-wall-connector==1.0.1 tesla-wall-connector==1.0.1

View File

@ -1429,7 +1429,7 @@ tailscale==0.2.0
tellduslive==0.10.11 tellduslive==0.10.11
# homeassistant.components.powerwall # homeassistant.components.powerwall
tesla-powerwall==0.3.12 tesla-powerwall==0.3.15
# homeassistant.components.tesla_wall_connector # homeassistant.components.tesla_wall_connector
tesla-wall-connector==1.0.1 tesla-wall-connector==1.0.1

View File

@ -16,6 +16,8 @@ from tesla_powerwall import (
from tests.common import load_fixture from tests.common import load_fixture
MOCK_GATEWAY_DIN = "111-0----2-000000000FFA"
async def _mock_powerwall_with_fixtures(hass): async def _mock_powerwall_with_fixtures(hass):
"""Mock data used to build powerwall state.""" """Mock data used to build powerwall state."""
@ -70,6 +72,7 @@ async def _mock_powerwall_site_name(hass, site_name):
# Sets site_info_resp.site_name to return site_name # Sets site_info_resp.site_name to return site_name
site_info_resp.response["site_name"] = site_name site_info_resp.response["site_name"] = site_name
powerwall_mock.get_site_info = Mock(return_value=site_info_resp) powerwall_mock.get_site_info = Mock(return_value=site_info_resp)
powerwall_mock.get_gateway_din = Mock(return_value=MOCK_GATEWAY_DIN)
return powerwall_mock return powerwall_mock

View File

@ -12,8 +12,13 @@ from homeassistant import config_entries
from homeassistant.components import dhcp from homeassistant.components import dhcp
from homeassistant.components.powerwall.const import DOMAIN from homeassistant.components.powerwall.const import DOMAIN
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
from homeassistant.data_entry_flow import RESULT_TYPE_ABORT
from .mocks import _mock_powerwall_side_effect, _mock_powerwall_site_name from .mocks import (
MOCK_GATEWAY_DIN,
_mock_powerwall_side_effect,
_mock_powerwall_site_name,
)
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -162,21 +167,51 @@ async def test_already_configured_with_ignored(hass):
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
mock_powerwall = await _mock_powerwall_site_name(hass, "Some site")
with patch(
"homeassistant.components.powerwall.config_flow.Powerwall",
return_value=mock_powerwall,
):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_DHCP}, context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo( data=dhcp.DhcpServiceInfo(
ip="1.1.1.1", ip="1.1.1.1",
macaddress="AA:BB:CC:DD:EE:FF", macaddress="AA:BB:CC:DD:EE:FF",
hostname="any", hostname="00GGX",
), ),
) )
assert result["type"] == "form" assert result["type"] == "form"
assert result["errors"] is None
with patch(
"homeassistant.components.powerwall.config_flow.Powerwall",
return_value=mock_powerwall,
), patch(
"homeassistant.components.powerwall.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{},
)
await hass.async_block_till_done()
assert result2["type"] == "create_entry"
assert result2["title"] == "Some site"
assert result2["data"] == {"ip_address": "1.1.1.1", "password": "00GGX"}
assert len(mock_setup_entry.mock_calls) == 1
async def test_dhcp_discovery(hass): async def test_dhcp_discovery_manual_configure(hass):
"""Test we can process the discovery from dhcp.""" """Test we can process the discovery from dhcp and manually configure."""
mock_powerwall = await _mock_powerwall_site_name(hass, "Some site")
with patch(
"homeassistant.components.powerwall.config_flow.Powerwall.login",
side_effect=AccessDeniedError("xyz"),
):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_DHCP}, context={"source": config_entries.SOURCE_DHCP},
@ -189,7 +224,6 @@ async def test_dhcp_discovery(hass):
assert result["type"] == "form" assert result["type"] == "form"
assert result["errors"] == {} assert result["errors"] == {}
mock_powerwall = await _mock_powerwall_site_name(hass, "Some site")
with patch( with patch(
"homeassistant.components.powerwall.config_flow.Powerwall", "homeassistant.components.powerwall.config_flow.Powerwall",
return_value=mock_powerwall, return_value=mock_powerwall,
@ -209,18 +243,80 @@ async def test_dhcp_discovery(hass):
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
async def test_dhcp_discovery_auto_configure(hass):
"""Test we can process the discovery from dhcp and auto configure."""
mock_powerwall = await _mock_powerwall_site_name(hass, "Some site")
with patch(
"homeassistant.components.powerwall.config_flow.Powerwall",
return_value=mock_powerwall,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo(
ip="1.1.1.1",
macaddress="AA:BB:CC:DD:EE:FF",
hostname="00GGX",
),
)
assert result["type"] == "form"
assert result["errors"] is None
with patch(
"homeassistant.components.powerwall.config_flow.Powerwall",
return_value=mock_powerwall,
), patch(
"homeassistant.components.powerwall.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{},
)
await hass.async_block_till_done()
assert result2["type"] == "create_entry"
assert result2["title"] == "Some site"
assert result2["data"] == {"ip_address": "1.1.1.1", "password": "00GGX"}
assert len(mock_setup_entry.mock_calls) == 1
async def test_dhcp_discovery_cannot_connect(hass):
"""Test we can process the discovery from dhcp and we cannot connect."""
mock_powerwall = _mock_powerwall_side_effect(site_info=PowerwallUnreachableError)
with patch(
"homeassistant.components.powerwall.config_flow.Powerwall",
return_value=mock_powerwall,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo(
ip="1.1.1.1",
macaddress="AA:BB:CC:DD:EE:FF",
hostname="00GGX",
),
)
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "cannot_connect"
async def test_form_reauth(hass): async def test_form_reauth(hass):
"""Test reauthenticate.""" """Test reauthenticate."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
data=VALID_CONFIG, data=VALID_CONFIG,
unique_id="1.2.3.4", unique_id=MOCK_GATEWAY_DIN,
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data=entry.data DOMAIN,
context={"source": config_entries.SOURCE_REAUTH, "entry_id": entry.entry_id},
data=entry.data,
) )
assert result["type"] == "form" assert result["type"] == "form"
assert result["errors"] == {} assert result["errors"] == {}
@ -237,7 +333,6 @@ async def test_form_reauth(hass):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
CONF_IP_ADDRESS: "1.2.3.4",
CONF_PASSWORD: "new-test-password", CONF_PASSWORD: "new-test-password",
}, },
) )
@ -246,3 +341,68 @@ async def test_form_reauth(hass):
assert result2["type"] == "abort" assert result2["type"] == "abort"
assert result2["reason"] == "reauth_successful" assert result2["reason"] == "reauth_successful"
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
async def test_dhcp_discovery_update_ip_address(hass):
"""Test we can update the ip address from dhcp."""
entry = MockConfigEntry(
domain=DOMAIN,
data=VALID_CONFIG,
unique_id=MOCK_GATEWAY_DIN,
)
entry.add_to_hass(hass)
mock_powerwall = await _mock_powerwall_site_name(hass, "Some site")
with patch(
"homeassistant.components.powerwall.config_flow.Powerwall",
return_value=mock_powerwall,
), patch(
"homeassistant.components.powerwall.async_setup_entry",
return_value=True,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo(
ip="1.1.1.1",
macaddress="AA:BB:CC:DD:EE:FF",
hostname=MOCK_GATEWAY_DIN.lower(),
),
)
await hass.async_block_till_done()
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
assert entry.data[CONF_IP_ADDRESS] == "1.1.1.1"
async def test_dhcp_discovery_updates_unique_id(hass):
"""Test we can update the unique id from dhcp."""
entry = MockConfigEntry(
domain=DOMAIN,
data=VALID_CONFIG,
unique_id="1.2.3.4",
)
entry.add_to_hass(hass)
mock_powerwall = await _mock_powerwall_site_name(hass, "Some site")
with patch(
"homeassistant.components.powerwall.config_flow.Powerwall",
return_value=mock_powerwall,
), patch(
"homeassistant.components.powerwall.async_setup_entry",
return_value=True,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo(
ip="1.2.3.4",
macaddress="AA:BB:CC:DD:EE:FF",
hostname=MOCK_GATEWAY_DIN.lower(),
),
)
await hass.async_block_till_done()
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
assert entry.data[CONF_IP_ADDRESS] == "1.2.3.4"
assert entry.unique_id == MOCK_GATEWAY_DIN