forked from home-assistant/core
Add wattpilot goecharger and goecontroller integration with autodiscovery
This commit is contained in:
@@ -517,6 +517,10 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/goalzero/ @tkdrob
|
||||
/homeassistant/components/gogogate2/ @vangorra
|
||||
/tests/components/gogogate2/ @vangorra
|
||||
/homeassistant/components/goecharger/ @0xFEEDC0DE64
|
||||
/tests/components/goecharger/ @0xFEEDC0DE64
|
||||
/homeassistant/components/goecontroller/ @0xFEEDC0DE64
|
||||
/tests/components/goecontroller/ @0xFEEDC0DE64
|
||||
/homeassistant/components/goodwe/ @mletenay @starkillerOG
|
||||
/tests/components/goodwe/ @mletenay @starkillerOG
|
||||
/homeassistant/components/google/ @allenporter
|
||||
@@ -1547,6 +1551,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/water_heater/ @home-assistant/core
|
||||
/tests/components/water_heater/ @home-assistant/core
|
||||
/homeassistant/components/watson_tts/ @rutkai
|
||||
/homeassistant/components/wattpilot/ @0xFEEDC0DE64
|
||||
/tests/components/wattpilot/ @0xFEEDC0DE64
|
||||
/homeassistant/components/watttime/ @bachya
|
||||
/tests/components/watttime/ @bachya
|
||||
/homeassistant/components/waze_travel_time/ @eifinger
|
||||
|
49
homeassistant/components/goecharger/__init__.py
Normal file
49
homeassistant/components/goecharger/__init__.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""The go-eCharger integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# TODO List the platforms that you want to support.
|
||||
# For your initial PR, limit it to 1 platform.
|
||||
PLATFORMS: list[Platform] = [Platform.LIGHT]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up the go-eCharger integration."""
|
||||
|
||||
# zeroconf_instance = await zeroconf.async_get_instance(hass)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up go-eCharger from a config entry."""
|
||||
|
||||
# zeroconf_instance = await zeroconf.async_get_instance(hass)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
# TODO 1. Create API instance
|
||||
# TODO 2. Validate the API connection (and authentication)
|
||||
# TODO 3. Store an API object for your platforms to access
|
||||
# hass.data[DOMAIN][entry.entry_id] = MyApi(...)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
123
homeassistant/components/goecharger/config_flow.py
Normal file
123
homeassistant/components/goecharger/config_flow.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""Config flow for go-eCharger integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# TODO adjust the data schema to the data that you need
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("host"): str,
|
||||
vol.Required("username"): str,
|
||||
vol.Required("password"): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class PlaceholderHub:
|
||||
"""Placeholder class to make tests pass.
|
||||
|
||||
TODO Remove this placeholder class and replace with things from your PyPI package.
|
||||
"""
|
||||
|
||||
def __init__(self, host: str) -> None:
|
||||
"""Initialize."""
|
||||
self.host = host
|
||||
|
||||
async def authenticate(self, username: str, password: str) -> bool:
|
||||
"""Test if we can authenticate with the host."""
|
||||
return True
|
||||
|
||||
|
||||
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Validate the user input allows us to connect.
|
||||
|
||||
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
# TODO validate the data can be used to set up a connection.
|
||||
|
||||
# If your PyPI package is not built with async, pass your methods
|
||||
# to the executor:
|
||||
# await hass.async_add_executor_job(
|
||||
# your_validate_func, data["username"], data["password"]
|
||||
# )
|
||||
|
||||
hub = PlaceholderHub(data["host"])
|
||||
|
||||
if not await hub.authenticate(data["username"], data["password"]):
|
||||
raise InvalidAuth
|
||||
|
||||
# If you cannot connect:
|
||||
# throw CannotConnect
|
||||
# If the authentication is wrong:
|
||||
# InvalidAuth
|
||||
|
||||
# Return info that you want to store in the config entry.
|
||||
return {"title": "Name of the device"}
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for go-eCharger."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
try:
|
||||
info = await validate_input(self.hass, user_input)
|
||||
except CannotConnect:
|
||||
errors["base"] = "cannot_connect"
|
||||
except InvalidAuth:
|
||||
errors["base"] = "invalid_auth"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
return self.async_create_entry(title=info["title"], data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_zeroconf(
|
||||
self, discovery_info: zeroconf.ZeroconfServiceInfo
|
||||
) -> FlowResult:
|
||||
"""Handle zeroconf discovery."""
|
||||
|
||||
serial = discovery_info.properties.get("serial")
|
||||
if serial is None:
|
||||
return self.async_abort(reason="mdns_missing_serial")
|
||||
|
||||
self.context["title_placeholders"] = {"name": serial}
|
||||
|
||||
# Check if already configured
|
||||
await self.async_set_unique_id(serial)
|
||||
self._abort_if_unique_id_configured(updates={})
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", description_placeholders={"serial": serial}
|
||||
)
|
||||
|
||||
|
||||
class CannotConnect(HomeAssistantError):
|
||||
"""Error to indicate we cannot connect."""
|
||||
|
||||
|
||||
class InvalidAuth(HomeAssistantError):
|
||||
"""Error to indicate there is invalid auth."""
|
3
homeassistant/components/goecharger/const.py
Normal file
3
homeassistant/components/goecharger/const.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""Constants for the go-eCharger integration."""
|
||||
|
||||
DOMAIN = "goecharger"
|
16
homeassistant/components/goecharger/manifest.json
Normal file
16
homeassistant/components/goecharger/manifest.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"domain": "goecharger",
|
||||
"name": "go-eCharger",
|
||||
"after_dependencies": ["zeroconf"],
|
||||
"codeowners": ["@0xFEEDC0DE64"],
|
||||
"config_flow": true,
|
||||
"dependencies": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/goecharger",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": [],
|
||||
"zeroconf": [
|
||||
"_go-e_go-eCharger_V4._tcp.local.",
|
||||
"_go-e_go-eCharger._tcp.local."
|
||||
]
|
||||
}
|
23
homeassistant/components/goecharger/strings.json
Normal file
23
homeassistant/components/goecharger/strings.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"flow_title": "{serial}",
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"mdns_missing_serial": "Missing serial number in MDNS properties."
|
||||
}
|
||||
}
|
||||
}
|
49
homeassistant/components/goecontroller/__init__.py
Normal file
49
homeassistant/components/goecontroller/__init__.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""The go-e Controller integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# TODO List the platforms that you want to support.
|
||||
# For your initial PR, limit it to 1 platform.
|
||||
PLATFORMS: list[Platform] = [Platform.LIGHT]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up the go-eCharger integration."""
|
||||
|
||||
# zeroconf_instance = await zeroconf.async_get_instance(hass)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up go-e Controller from a config entry."""
|
||||
|
||||
# zeroconf_instance = await zeroconf.async_get_instance(hass)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
# TODO 1. Create API instance
|
||||
# TODO 2. Validate the API connection (and authentication)
|
||||
# TODO 3. Store an API object for your platforms to access
|
||||
# hass.data[DOMAIN][entry.entry_id] = MyApi(...)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
123
homeassistant/components/goecontroller/config_flow.py
Normal file
123
homeassistant/components/goecontroller/config_flow.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""Config flow for go-e Controller integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# TODO adjust the data schema to the data that you need
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("host"): str,
|
||||
vol.Required("username"): str,
|
||||
vol.Required("password"): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class PlaceholderHub:
|
||||
"""Placeholder class to make tests pass.
|
||||
|
||||
TODO Remove this placeholder class and replace with things from your PyPI package.
|
||||
"""
|
||||
|
||||
def __init__(self, host: str) -> None:
|
||||
"""Initialize."""
|
||||
self.host = host
|
||||
|
||||
async def authenticate(self, username: str, password: str) -> bool:
|
||||
"""Test if we can authenticate with the host."""
|
||||
return True
|
||||
|
||||
|
||||
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Validate the user input allows us to connect.
|
||||
|
||||
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
# TODO validate the data can be used to set up a connection.
|
||||
|
||||
# If your PyPI package is not built with async, pass your methods
|
||||
# to the executor:
|
||||
# await hass.async_add_executor_job(
|
||||
# your_validate_func, data["username"], data["password"]
|
||||
# )
|
||||
|
||||
hub = PlaceholderHub(data["host"])
|
||||
|
||||
if not await hub.authenticate(data["username"], data["password"]):
|
||||
raise InvalidAuth
|
||||
|
||||
# If you cannot connect:
|
||||
# throw CannotConnect
|
||||
# If the authentication is wrong:
|
||||
# InvalidAuth
|
||||
|
||||
# Return info that you want to store in the config entry.
|
||||
return {"title": "Name of the device"}
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for go-e Controller."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
try:
|
||||
info = await validate_input(self.hass, user_input)
|
||||
except CannotConnect:
|
||||
errors["base"] = "cannot_connect"
|
||||
except InvalidAuth:
|
||||
errors["base"] = "invalid_auth"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
return self.async_create_entry(title=info["title"], data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_zeroconf(
|
||||
self, discovery_info: zeroconf.ZeroconfServiceInfo
|
||||
) -> FlowResult:
|
||||
"""Handle zeroconf discovery."""
|
||||
|
||||
serial = discovery_info.properties.get("serial")
|
||||
if serial is None:
|
||||
return self.async_abort(reason="mdns_missing_serial")
|
||||
|
||||
self.context["title_placeholders"] = {"name": serial}
|
||||
|
||||
# Check if already configured
|
||||
await self.async_set_unique_id(serial)
|
||||
self._abort_if_unique_id_configured(updates={})
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", description_placeholders={"serial": serial}
|
||||
)
|
||||
|
||||
|
||||
class CannotConnect(HomeAssistantError):
|
||||
"""Error to indicate we cannot connect."""
|
||||
|
||||
|
||||
class InvalidAuth(HomeAssistantError):
|
||||
"""Error to indicate there is invalid auth."""
|
3
homeassistant/components/goecontroller/const.py
Normal file
3
homeassistant/components/goecontroller/const.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""Constants for the go-e Controller integration."""
|
||||
|
||||
DOMAIN = "goecontroller"
|
16
homeassistant/components/goecontroller/manifest.json
Normal file
16
homeassistant/components/goecontroller/manifest.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"domain": "goecontroller",
|
||||
"name": "go-e Controller",
|
||||
"after_dependencies": ["zeroconf"],
|
||||
"codeowners": ["@0xFEEDC0DE64"],
|
||||
"config_flow": true,
|
||||
"dependencies": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/goecontroller",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": [],
|
||||
"zeroconf": [
|
||||
"_go-e_controller._tcp.local.",
|
||||
"_go-e_controller-lite._tcp._tcp.local."
|
||||
]
|
||||
}
|
23
homeassistant/components/goecontroller/strings.json
Normal file
23
homeassistant/components/goecontroller/strings.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"flow_title": "{serial}",
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"mdns_missing_serial": "Missing serial number in MDNS properties."
|
||||
}
|
||||
}
|
||||
}
|
48
homeassistant/components/wattpilot/__init__.py
Normal file
48
homeassistant/components/wattpilot/__init__.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""The Fronius Wattpilot integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# TODO List the platforms that you want to support.
|
||||
# For your initial PR, limit it to 1 platform.
|
||||
PLATFORMS: list[Platform] = [Platform.LIGHT]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up the go-eCharger integration."""
|
||||
|
||||
# zeroconf_instance = await zeroconf.async_get_instance(hass)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Fronius Wattpilot from a config entry."""
|
||||
|
||||
# zeroconf_instance = await zeroconf.async_get_instance(hass)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
# TODO 1. Create API instance
|
||||
# TODO 2. Validate the API connection (and authentication)
|
||||
# TODO 3. Store an API object for your platforms to access
|
||||
# hass.data[DOMAIN][entry.entry_id] = MyApi(...)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
123
homeassistant/components/wattpilot/config_flow.py
Normal file
123
homeassistant/components/wattpilot/config_flow.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""Config flow for Fronius Wattpilot integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# TODO adjust the data schema to the data that you need
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("host"): str,
|
||||
vol.Required("username"): str,
|
||||
vol.Required("password"): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class PlaceholderHub:
|
||||
"""Placeholder class to make tests pass.
|
||||
|
||||
TODO Remove this placeholder class and replace with things from your PyPI package.
|
||||
"""
|
||||
|
||||
def __init__(self, host: str) -> None:
|
||||
"""Initialize."""
|
||||
self.host = host
|
||||
|
||||
async def authenticate(self, username: str, password: str) -> bool:
|
||||
"""Test if we can authenticate with the host."""
|
||||
return True
|
||||
|
||||
|
||||
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Validate the user input allows us to connect.
|
||||
|
||||
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
# TODO validate the data can be used to set up a connection.
|
||||
|
||||
# If your PyPI package is not built with async, pass your methods
|
||||
# to the executor:
|
||||
# await hass.async_add_executor_job(
|
||||
# your_validate_func, data["username"], data["password"]
|
||||
# )
|
||||
|
||||
hub = PlaceholderHub(data["host"])
|
||||
|
||||
if not await hub.authenticate(data["username"], data["password"]):
|
||||
raise InvalidAuth
|
||||
|
||||
# If you cannot connect:
|
||||
# throw CannotConnect
|
||||
# If the authentication is wrong:
|
||||
# InvalidAuth
|
||||
|
||||
# Return info that you want to store in the config entry.
|
||||
return {"title": "Name of the device"}
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Fronius Wattpilot."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
try:
|
||||
info = await validate_input(self.hass, user_input)
|
||||
except CannotConnect:
|
||||
errors["base"] = "cannot_connect"
|
||||
except InvalidAuth:
|
||||
errors["base"] = "invalid_auth"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
return self.async_create_entry(title=info["title"], data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_zeroconf(
|
||||
self, discovery_info: zeroconf.ZeroconfServiceInfo
|
||||
) -> FlowResult:
|
||||
"""Handle zeroconf discovery."""
|
||||
|
||||
serial = discovery_info.properties.get("serial")
|
||||
if serial is None:
|
||||
return self.async_abort(reason="mdns_missing_serial")
|
||||
|
||||
self.context["title_placeholders"] = {"name": serial}
|
||||
|
||||
# Check if already configured
|
||||
await self.async_set_unique_id(serial)
|
||||
self._abort_if_unique_id_configured(updates={})
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", description_placeholders={"serial": serial}
|
||||
)
|
||||
|
||||
|
||||
class CannotConnect(HomeAssistantError):
|
||||
"""Error to indicate we cannot connect."""
|
||||
|
||||
|
||||
class InvalidAuth(HomeAssistantError):
|
||||
"""Error to indicate there is invalid auth."""
|
3
homeassistant/components/wattpilot/const.py
Normal file
3
homeassistant/components/wattpilot/const.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""Constants for the Fronius Wattpilot integration."""
|
||||
|
||||
DOMAIN = "wattpilot"
|
16
homeassistant/components/wattpilot/manifest.json
Normal file
16
homeassistant/components/wattpilot/manifest.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"domain": "wattpilot",
|
||||
"name": "Fronius Wattpilot",
|
||||
"after_dependencies": ["zeroconf"],
|
||||
"codeowners": ["@0xFEEDC0DE64"],
|
||||
"config_flow": true,
|
||||
"dependencies": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/wattpilot",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": [],
|
||||
"zeroconf": [
|
||||
"_fronius_wattpilot._tcp.local.",
|
||||
"_fronius_wattpilot_V2._tcp.local."
|
||||
]
|
||||
}
|
23
homeassistant/components/wattpilot/strings.json
Normal file
23
homeassistant/components/wattpilot/strings.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"flow_title": "{serial}",
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"mdns_missing_serial": "Missing serial number in MDNS properties."
|
||||
}
|
||||
}
|
||||
}
|
@@ -201,6 +201,8 @@ FLOWS = {
|
||||
"github",
|
||||
"glances",
|
||||
"goalzero",
|
||||
"goecharger",
|
||||
"goecontroller",
|
||||
"gogogate2",
|
||||
"goodwe",
|
||||
"google",
|
||||
@@ -599,6 +601,7 @@ FLOWS = {
|
||||
"vulcan",
|
||||
"wallbox",
|
||||
"waqi",
|
||||
"wattpilot",
|
||||
"watttime",
|
||||
"waze_travel_time",
|
||||
"weatherflow",
|
||||
|
@@ -2222,6 +2222,18 @@
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling"
|
||||
},
|
||||
"goecharger": {
|
||||
"name": "go-eCharger",
|
||||
"integration_type": "device",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"goecontroller": {
|
||||
"name": "go-e Controller",
|
||||
"integration_type": "device",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"gogogate2": {
|
||||
"name": "Gogogate2 and ismartgate",
|
||||
"integration_type": "hub",
|
||||
@@ -6704,6 +6716,12 @@
|
||||
"config_flow": false,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"wattpilot": {
|
||||
"name": "Fronius Wattpilot",
|
||||
"integration_type": "device",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"watttime": {
|
||||
"name": "WattTime",
|
||||
"integration_type": "service",
|
||||
|
@@ -465,6 +465,36 @@ ZEROCONF = {
|
||||
"domain": "freebox",
|
||||
},
|
||||
],
|
||||
"_fronius_wattpilot._tcp.local.": [
|
||||
{
|
||||
"domain": "wattpilot",
|
||||
},
|
||||
],
|
||||
"_fronius_wattpilot_V2._tcp.local.": [
|
||||
{
|
||||
"domain": "wattpilot",
|
||||
},
|
||||
],
|
||||
"_go-e_controller-lite._tcp._tcp.local.": [
|
||||
{
|
||||
"domain": "goecontroller",
|
||||
},
|
||||
],
|
||||
"_go-e_controller._tcp.local.": [
|
||||
{
|
||||
"domain": "goecontroller",
|
||||
},
|
||||
],
|
||||
"_go-e_go-eCharger._tcp.local.": [
|
||||
{
|
||||
"domain": "goecharger",
|
||||
},
|
||||
],
|
||||
"_go-e_go-eCharger_V4._tcp.local.": [
|
||||
{
|
||||
"domain": "goecharger",
|
||||
},
|
||||
],
|
||||
"_googlecast._tcp.local.": [
|
||||
{
|
||||
"domain": "cast",
|
||||
|
1
tests/components/goecharger/__init__.py
Normal file
1
tests/components/goecharger/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Tests for the go-eCharger integration."""
|
14
tests/components/goecharger/conftest.py
Normal file
14
tests/components/goecharger/conftest.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Common fixtures for the go-eCharger tests."""
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.goecharger.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
90
tests/components/goecharger/test_config_flow.py
Normal file
90
tests/components/goecharger/test_config_flow.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""Test the go-eCharger config flow."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.goecharger.config_flow import CannotConnect, InvalidAuth
|
||||
from homeassistant.components.goecharger.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
|
||||
|
||||
|
||||
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
||||
"""Test we get the form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.goecharger.config_flow.PlaceholderHub.authenticate",
|
||||
return_value=True,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result2["title"] == "Name of the device"
|
||||
assert result2["data"] == {
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
||||
"""Test we handle invalid auth."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.goecharger.config_flow.PlaceholderHub.authenticate",
|
||||
side_effect=InvalidAuth,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
|
||||
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
||||
"""Test we handle cannot connect error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.goecharger.config_flow.PlaceholderHub.authenticate",
|
||||
side_effect=CannotConnect,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["errors"] == {"base": "cannot_connect"}
|
1
tests/components/goecontroller/__init__.py
Normal file
1
tests/components/goecontroller/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Tests for the go-e Controller integration."""
|
14
tests/components/goecontroller/conftest.py
Normal file
14
tests/components/goecontroller/conftest.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Common fixtures for the go-e Controller tests."""
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.goecontroller.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
93
tests/components/goecontroller/test_config_flow.py
Normal file
93
tests/components/goecontroller/test_config_flow.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""Test the go-e Controller config flow."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.goecontroller.config_flow import (
|
||||
CannotConnect,
|
||||
InvalidAuth,
|
||||
)
|
||||
from homeassistant.components.goecontroller.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
|
||||
|
||||
|
||||
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
||||
"""Test we get the form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.goecontroller.config_flow.PlaceholderHub.authenticate",
|
||||
return_value=True,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result2["title"] == "Name of the device"
|
||||
assert result2["data"] == {
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
||||
"""Test we handle invalid auth."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.goecontroller.config_flow.PlaceholderHub.authenticate",
|
||||
side_effect=InvalidAuth,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
|
||||
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
||||
"""Test we handle cannot connect error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.goecontroller.config_flow.PlaceholderHub.authenticate",
|
||||
side_effect=CannotConnect,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["errors"] == {"base": "cannot_connect"}
|
1
tests/components/wattpilot/__init__.py
Normal file
1
tests/components/wattpilot/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Tests for the Fronius Wattpilot integration."""
|
14
tests/components/wattpilot/conftest.py
Normal file
14
tests/components/wattpilot/conftest.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Common fixtures for the Fronius Wattpilot tests."""
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.wattpilot.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
90
tests/components/wattpilot/test_config_flow.py
Normal file
90
tests/components/wattpilot/test_config_flow.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""Test the Fronius Wattpilot config flow."""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.wattpilot.config_flow import CannotConnect, InvalidAuth
|
||||
from homeassistant.components.wattpilot.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
|
||||
|
||||
|
||||
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
||||
"""Test we get the form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.wattpilot.config_flow.PlaceholderHub.authenticate",
|
||||
return_value=True,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result2["title"] == "Name of the device"
|
||||
assert result2["data"] == {
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
||||
"""Test we handle invalid auth."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.wattpilot.config_flow.PlaceholderHub.authenticate",
|
||||
side_effect=InvalidAuth,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
|
||||
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
||||
"""Test we handle cannot connect error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.wattpilot.config_flow.PlaceholderHub.authenticate",
|
||||
side_effect=CannotConnect,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"host": "1.1.1.1",
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == FlowResultType.FORM
|
||||
assert result2["errors"] == {"base": "cannot_connect"}
|
Reference in New Issue
Block a user