mirror of
https://github.com/home-assistant/core.git
synced 2025-08-06 22:25:13 +02:00
Merge pull request #67588 from home-assistant/rc
This commit is contained in:
@@ -33,6 +33,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
except FRITZ_EXCEPTIONS as ex:
|
except FRITZ_EXCEPTIONS as ex:
|
||||||
raise ConfigEntryNotReady from ex
|
raise ConfigEntryNotReady from ex
|
||||||
|
|
||||||
|
if (
|
||||||
|
"X_AVM-DE_UPnP1" in avm_wrapper.connection.services
|
||||||
|
and not (await avm_wrapper.async_get_upnp_configuration())["NewEnable"]
|
||||||
|
):
|
||||||
|
raise ConfigEntryAuthFailed("Missing UPnP configuration")
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
hass.data[DOMAIN][entry.entry_id] = avm_wrapper
|
hass.data[DOMAIN][entry.entry_id] = avm_wrapper
|
||||||
|
|
||||||
|
@@ -630,6 +630,11 @@ class AvmWrapper(FritzBoxTools):
|
|||||||
)
|
)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
async def async_get_upnp_configuration(self) -> dict[str, Any]:
|
||||||
|
"""Call X_AVM-DE_UPnP service."""
|
||||||
|
|
||||||
|
return await self.hass.async_add_executor_job(self.get_upnp_configuration)
|
||||||
|
|
||||||
async def async_get_wan_link_properties(self) -> dict[str, Any]:
|
async def async_get_wan_link_properties(self) -> dict[str, Any]:
|
||||||
"""Call WANCommonInterfaceConfig service."""
|
"""Call WANCommonInterfaceConfig service."""
|
||||||
|
|
||||||
@@ -698,6 +703,11 @@ class AvmWrapper(FritzBoxTools):
|
|||||||
partial(self.set_allow_wan_access, ip_address, turn_on)
|
partial(self.set_allow_wan_access, ip_address, turn_on)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_upnp_configuration(self) -> dict[str, Any]:
|
||||||
|
"""Call X_AVM-DE_UPnP service."""
|
||||||
|
|
||||||
|
return self._service_call_action("X_AVM-DE_UPnP", "1", "GetInfo")
|
||||||
|
|
||||||
def get_ontel_num_deflections(self) -> dict[str, Any]:
|
def get_ontel_num_deflections(self) -> dict[str, Any]:
|
||||||
"""Call GetNumberOfDeflections action from X_AVM-DE_OnTel service."""
|
"""Call GetNumberOfDeflections action from X_AVM-DE_OnTel service."""
|
||||||
|
|
||||||
|
@@ -29,6 +29,7 @@ from .const import (
|
|||||||
ERROR_AUTH_INVALID,
|
ERROR_AUTH_INVALID,
|
||||||
ERROR_CANNOT_CONNECT,
|
ERROR_CANNOT_CONNECT,
|
||||||
ERROR_UNKNOWN,
|
ERROR_UNKNOWN,
|
||||||
|
ERROR_UPNP_NOT_CONFIGURED,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -79,6 +80,12 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
_LOGGER.exception("Unexpected exception")
|
_LOGGER.exception("Unexpected exception")
|
||||||
return ERROR_UNKNOWN
|
return ERROR_UNKNOWN
|
||||||
|
|
||||||
|
if (
|
||||||
|
"X_AVM-DE_UPnP1" in self.avm_wrapper.connection.services
|
||||||
|
and not (await self.avm_wrapper.async_get_upnp_configuration())["NewEnable"]
|
||||||
|
):
|
||||||
|
return ERROR_UPNP_NOT_CONFIGURED
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def async_check_configured_entry(self) -> ConfigEntry | None:
|
async def async_check_configured_entry(self) -> ConfigEntry | None:
|
||||||
|
@@ -46,6 +46,7 @@ DEFAULT_USERNAME = ""
|
|||||||
|
|
||||||
ERROR_AUTH_INVALID = "invalid_auth"
|
ERROR_AUTH_INVALID = "invalid_auth"
|
||||||
ERROR_CANNOT_CONNECT = "cannot_connect"
|
ERROR_CANNOT_CONNECT = "cannot_connect"
|
||||||
|
ERROR_UPNP_NOT_CONFIGURED = "upnp_not_configured"
|
||||||
ERROR_UNKNOWN = "unknown_error"
|
ERROR_UNKNOWN = "unknown_error"
|
||||||
|
|
||||||
FRITZ_SERVICES = "fritz_services"
|
FRITZ_SERVICES = "fritz_services"
|
||||||
|
@@ -36,6 +36,7 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"upnp_not_configured": "Missing UPnP settings on device.",
|
||||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
||||||
|
@@ -9,7 +9,8 @@
|
|||||||
"already_configured": "Device is already configured",
|
"already_configured": "Device is already configured",
|
||||||
"already_in_progress": "Configuration flow is already in progress",
|
"already_in_progress": "Configuration flow is already in progress",
|
||||||
"cannot_connect": "Failed to connect",
|
"cannot_connect": "Failed to connect",
|
||||||
"invalid_auth": "Invalid authentication"
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"upnp_not_configured": "Missing UPnP settings on device."
|
||||||
},
|
},
|
||||||
"flow_title": "{name}",
|
"flow_title": "{name}",
|
||||||
"step": {
|
"step": {
|
||||||
|
@@ -221,12 +221,9 @@ class GrowattData:
|
|||||||
# Create datetime from the latest entry
|
# Create datetime from the latest entry
|
||||||
date_now = dt.now().date()
|
date_now = dt.now().date()
|
||||||
last_updated_time = dt.parse_time(str(sorted_keys[-1]))
|
last_updated_time = dt.parse_time(str(sorted_keys[-1]))
|
||||||
combined_timestamp = datetime.datetime.combine(
|
mix_detail["lastdataupdate"] = datetime.datetime.combine(
|
||||||
date_now, last_updated_time
|
date_now, last_updated_time
|
||||||
)
|
)
|
||||||
# Convert datetime to UTC
|
|
||||||
combined_timestamp_utc = dt.as_utc(combined_timestamp)
|
|
||||||
mix_detail["lastdataupdate"] = combined_timestamp_utc.isoformat()
|
|
||||||
|
|
||||||
# Dashboard data is largely inaccurate for mix system but it is the only call with the ability to return the combined
|
# Dashboard data is largely inaccurate for mix system but it is the only call with the ability to return the combined
|
||||||
# imported from grid value that is the combination of charging AND load consumption
|
# imported from grid value that is the combination of charging AND load consumption
|
||||||
|
@@ -274,7 +274,7 @@ class HomeAccessory(Accessory):
|
|||||||
if self.config.get(ATTR_SW_VERSION) is not None:
|
if self.config.get(ATTR_SW_VERSION) is not None:
|
||||||
sw_version = format_version(self.config[ATTR_SW_VERSION])
|
sw_version = format_version(self.config[ATTR_SW_VERSION])
|
||||||
if sw_version is None:
|
if sw_version is None:
|
||||||
sw_version = __version__
|
sw_version = format_version(__version__)
|
||||||
hw_version = None
|
hw_version = None
|
||||||
if self.config.get(ATTR_HW_VERSION) is not None:
|
if self.config.get(ATTR_HW_VERSION) is not None:
|
||||||
hw_version = format_version(self.config[ATTR_HW_VERSION])
|
hw_version = format_version(self.config[ATTR_HW_VERSION])
|
||||||
@@ -289,7 +289,9 @@ class HomeAccessory(Accessory):
|
|||||||
serv_info = self.get_service(SERV_ACCESSORY_INFO)
|
serv_info = self.get_service(SERV_ACCESSORY_INFO)
|
||||||
char = self.driver.loader.get_char(CHAR_HARDWARE_REVISION)
|
char = self.driver.loader.get_char(CHAR_HARDWARE_REVISION)
|
||||||
serv_info.add_characteristic(char)
|
serv_info.add_characteristic(char)
|
||||||
serv_info.configure_char(CHAR_HARDWARE_REVISION, value=hw_version)
|
serv_info.configure_char(
|
||||||
|
CHAR_HARDWARE_REVISION, value=hw_version[:MAX_VERSION_LENGTH]
|
||||||
|
)
|
||||||
self.iid_manager.assign(char)
|
self.iid_manager.assign(char)
|
||||||
char.broker = self
|
char.broker = self
|
||||||
|
|
||||||
@@ -532,7 +534,7 @@ class HomeBridge(Bridge):
|
|||||||
"""Initialize a Bridge object."""
|
"""Initialize a Bridge object."""
|
||||||
super().__init__(driver, name)
|
super().__init__(driver, name)
|
||||||
self.set_info_service(
|
self.set_info_service(
|
||||||
firmware_revision=__version__,
|
firmware_revision=format_version(__version__),
|
||||||
manufacturer=MANUFACTURER,
|
manufacturer=MANUFACTURER,
|
||||||
model=BRIDGE_MODEL,
|
model=BRIDGE_MODEL,
|
||||||
serial_number=BRIDGE_SERIAL_NUMBER,
|
serial_number=BRIDGE_SERIAL_NUMBER,
|
||||||
|
@@ -100,6 +100,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
NUMBERS_ONLY_RE = re.compile(r"[^\d.]+")
|
NUMBERS_ONLY_RE = re.compile(r"[^\d.]+")
|
||||||
VERSION_RE = re.compile(r"([0-9]+)(\.[0-9]+)?(\.[0-9]+)?")
|
VERSION_RE = re.compile(r"([0-9]+)(\.[0-9]+)?(\.[0-9]+)?")
|
||||||
|
MAX_VERSION_PART = 2**32 - 1
|
||||||
|
|
||||||
|
|
||||||
MAX_PORT = 65535
|
MAX_PORT = 65535
|
||||||
@@ -363,7 +364,15 @@ def convert_to_float(state):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def cleanup_name_for_homekit(name: str | None) -> str | None:
|
def coerce_int(state: str) -> int:
|
||||||
|
"""Return int."""
|
||||||
|
try:
|
||||||
|
return int(state)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_name_for_homekit(name: str | None) -> str:
|
||||||
"""Ensure the name of the device will not crash homekit."""
|
"""Ensure the name of the device will not crash homekit."""
|
||||||
#
|
#
|
||||||
# This is not a security measure.
|
# This is not a security measure.
|
||||||
@@ -371,7 +380,7 @@ def cleanup_name_for_homekit(name: str | None) -> str | None:
|
|||||||
# UNICODE_EMOJI is also not allowed but that
|
# UNICODE_EMOJI is also not allowed but that
|
||||||
# likely isn't a problem
|
# likely isn't a problem
|
||||||
if name is None:
|
if name is None:
|
||||||
return None
|
return "None" # None crashes apple watches
|
||||||
return name.translate(HOMEKIT_CHAR_TRANSLATIONS)[:MAX_NAME_LENGTH]
|
return name.translate(HOMEKIT_CHAR_TRANSLATIONS)[:MAX_NAME_LENGTH]
|
||||||
|
|
||||||
|
|
||||||
@@ -420,13 +429,23 @@ def get_aid_storage_fullpath_for_entry_id(hass: HomeAssistant, entry_id: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _format_version_part(version_part: str) -> str:
|
||||||
|
return str(max(0, min(MAX_VERSION_PART, coerce_int(version_part))))
|
||||||
|
|
||||||
|
|
||||||
def format_version(version):
|
def format_version(version):
|
||||||
"""Extract the version string in a format homekit can consume."""
|
"""Extract the version string in a format homekit can consume."""
|
||||||
split_ver = str(version).replace("-", ".")
|
split_ver = str(version).replace("-", ".").replace(" ", ".")
|
||||||
num_only = NUMBERS_ONLY_RE.sub("", split_ver)
|
num_only = NUMBERS_ONLY_RE.sub("", split_ver)
|
||||||
if match := VERSION_RE.search(num_only):
|
if (match := VERSION_RE.search(num_only)) is None:
|
||||||
return match.group(0)
|
|
||||||
return None
|
return None
|
||||||
|
value = ".".join(map(_format_version_part, match.group(0).split(".")))
|
||||||
|
return None if _is_zero_but_true(value) else value
|
||||||
|
|
||||||
|
|
||||||
|
def _is_zero_but_true(value):
|
||||||
|
"""Zero but true values can crash apple watches."""
|
||||||
|
return convert_to_float(value) == 0
|
||||||
|
|
||||||
|
|
||||||
def remove_state_files_for_entry_id(hass: HomeAssistant, entry_id: str):
|
def remove_state_files_for_entry_id(hass: HomeAssistant, entry_id: str):
|
||||||
|
@@ -75,11 +75,16 @@ from .const import (
|
|||||||
ATTR_TOPIC,
|
ATTR_TOPIC,
|
||||||
CONF_BIRTH_MESSAGE,
|
CONF_BIRTH_MESSAGE,
|
||||||
CONF_BROKER,
|
CONF_BROKER,
|
||||||
|
CONF_CERTIFICATE,
|
||||||
|
CONF_CLIENT_CERT,
|
||||||
|
CONF_CLIENT_KEY,
|
||||||
CONF_COMMAND_TOPIC,
|
CONF_COMMAND_TOPIC,
|
||||||
CONF_ENCODING,
|
CONF_ENCODING,
|
||||||
CONF_QOS,
|
CONF_QOS,
|
||||||
CONF_RETAIN,
|
CONF_RETAIN,
|
||||||
CONF_STATE_TOPIC,
|
CONF_STATE_TOPIC,
|
||||||
|
CONF_TLS_INSECURE,
|
||||||
|
CONF_TLS_VERSION,
|
||||||
CONF_TOPIC,
|
CONF_TOPIC,
|
||||||
CONF_WILL_MESSAGE,
|
CONF_WILL_MESSAGE,
|
||||||
DATA_MQTT_CONFIG,
|
DATA_MQTT_CONFIG,
|
||||||
@@ -94,6 +99,7 @@ from .const import (
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
MQTT_CONNECTED,
|
MQTT_CONNECTED,
|
||||||
MQTT_DISCONNECTED,
|
MQTT_DISCONNECTED,
|
||||||
|
PROTOCOL_31,
|
||||||
PROTOCOL_311,
|
PROTOCOL_311,
|
||||||
)
|
)
|
||||||
from .discovery import LAST_DISCOVERY
|
from .discovery import LAST_DISCOVERY
|
||||||
@@ -118,13 +124,6 @@ SERVICE_DUMP = "dump"
|
|||||||
|
|
||||||
CONF_DISCOVERY_PREFIX = "discovery_prefix"
|
CONF_DISCOVERY_PREFIX = "discovery_prefix"
|
||||||
CONF_KEEPALIVE = "keepalive"
|
CONF_KEEPALIVE = "keepalive"
|
||||||
CONF_CERTIFICATE = "certificate"
|
|
||||||
CONF_CLIENT_KEY = "client_key"
|
|
||||||
CONF_CLIENT_CERT = "client_cert"
|
|
||||||
CONF_TLS_INSECURE = "tls_insecure"
|
|
||||||
CONF_TLS_VERSION = "tls_version"
|
|
||||||
|
|
||||||
PROTOCOL_31 = "3.1"
|
|
||||||
|
|
||||||
DEFAULT_PORT = 1883
|
DEFAULT_PORT = 1883
|
||||||
DEFAULT_KEEPALIVE = 60
|
DEFAULT_KEEPALIVE = 60
|
||||||
@@ -757,6 +756,58 @@ class Subscription:
|
|||||||
encoding: str | None = attr.ib(default="utf-8")
|
encoding: str | None = attr.ib(default="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
class MqttClientSetup:
|
||||||
|
"""Helper class to setup the paho mqtt client from config."""
|
||||||
|
|
||||||
|
# We don't import on the top because some integrations
|
||||||
|
# should be able to optionally rely on MQTT.
|
||||||
|
import paho.mqtt.client as mqtt # pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
|
def __init__(self, config: ConfigType) -> None:
|
||||||
|
"""Initialize the MQTT client setup helper."""
|
||||||
|
|
||||||
|
if config[CONF_PROTOCOL] == PROTOCOL_31:
|
||||||
|
proto = self.mqtt.MQTTv31
|
||||||
|
else:
|
||||||
|
proto = self.mqtt.MQTTv311
|
||||||
|
|
||||||
|
if (client_id := config.get(CONF_CLIENT_ID)) is None:
|
||||||
|
# PAHO MQTT relies on the MQTT server to generate random client IDs.
|
||||||
|
# However, that feature is not mandatory so we generate our own.
|
||||||
|
client_id = self.mqtt.base62(uuid.uuid4().int, padding=22)
|
||||||
|
self._client = self.mqtt.Client(client_id, protocol=proto)
|
||||||
|
|
||||||
|
# Enable logging
|
||||||
|
self._client.enable_logger()
|
||||||
|
|
||||||
|
username = config.get(CONF_USERNAME)
|
||||||
|
password = config.get(CONF_PASSWORD)
|
||||||
|
if username is not None:
|
||||||
|
self._client.username_pw_set(username, password)
|
||||||
|
|
||||||
|
if (certificate := config.get(CONF_CERTIFICATE)) == "auto":
|
||||||
|
certificate = certifi.where()
|
||||||
|
|
||||||
|
client_key = config.get(CONF_CLIENT_KEY)
|
||||||
|
client_cert = config.get(CONF_CLIENT_CERT)
|
||||||
|
tls_insecure = config.get(CONF_TLS_INSECURE)
|
||||||
|
if certificate is not None:
|
||||||
|
self._client.tls_set(
|
||||||
|
certificate,
|
||||||
|
certfile=client_cert,
|
||||||
|
keyfile=client_key,
|
||||||
|
tls_version=ssl.PROTOCOL_TLS,
|
||||||
|
)
|
||||||
|
|
||||||
|
if tls_insecure is not None:
|
||||||
|
self._client.tls_insecure_set(tls_insecure)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self) -> mqtt.Client:
|
||||||
|
"""Return the paho MQTT client."""
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
|
||||||
class MQTT:
|
class MQTT:
|
||||||
"""Home Assistant MQTT client."""
|
"""Home Assistant MQTT client."""
|
||||||
|
|
||||||
@@ -821,46 +872,7 @@ class MQTT:
|
|||||||
|
|
||||||
def init_client(self):
|
def init_client(self):
|
||||||
"""Initialize paho client."""
|
"""Initialize paho client."""
|
||||||
# We don't import on the top because some integrations
|
self._mqttc = MqttClientSetup(self.conf).client
|
||||||
# should be able to optionally rely on MQTT.
|
|
||||||
import paho.mqtt.client as mqtt # pylint: disable=import-outside-toplevel
|
|
||||||
|
|
||||||
if self.conf[CONF_PROTOCOL] == PROTOCOL_31:
|
|
||||||
proto: int = mqtt.MQTTv31
|
|
||||||
else:
|
|
||||||
proto = mqtt.MQTTv311
|
|
||||||
|
|
||||||
if (client_id := self.conf.get(CONF_CLIENT_ID)) is None:
|
|
||||||
# PAHO MQTT relies on the MQTT server to generate random client IDs.
|
|
||||||
# However, that feature is not mandatory so we generate our own.
|
|
||||||
client_id = mqtt.base62(uuid.uuid4().int, padding=22)
|
|
||||||
self._mqttc = mqtt.Client(client_id, protocol=proto)
|
|
||||||
|
|
||||||
# Enable logging
|
|
||||||
self._mqttc.enable_logger()
|
|
||||||
|
|
||||||
username = self.conf.get(CONF_USERNAME)
|
|
||||||
password = self.conf.get(CONF_PASSWORD)
|
|
||||||
if username is not None:
|
|
||||||
self._mqttc.username_pw_set(username, password)
|
|
||||||
|
|
||||||
if (certificate := self.conf.get(CONF_CERTIFICATE)) == "auto":
|
|
||||||
certificate = certifi.where()
|
|
||||||
|
|
||||||
client_key = self.conf.get(CONF_CLIENT_KEY)
|
|
||||||
client_cert = self.conf.get(CONF_CLIENT_CERT)
|
|
||||||
tls_insecure = self.conf.get(CONF_TLS_INSECURE)
|
|
||||||
if certificate is not None:
|
|
||||||
self._mqttc.tls_set(
|
|
||||||
certificate,
|
|
||||||
certfile=client_cert,
|
|
||||||
keyfile=client_key,
|
|
||||||
tls_version=ssl.PROTOCOL_TLS,
|
|
||||||
)
|
|
||||||
|
|
||||||
if tls_insecure is not None:
|
|
||||||
self._mqttc.tls_insecure_set(tls_insecure)
|
|
||||||
|
|
||||||
self._mqttc.on_connect = self._mqtt_on_connect
|
self._mqttc.on_connect = self._mqtt_on_connect
|
||||||
self._mqttc.on_disconnect = self._mqtt_on_disconnect
|
self._mqttc.on_disconnect = self._mqtt_on_disconnect
|
||||||
self._mqttc.on_message = self._mqtt_on_message
|
self._mqttc.on_message = self._mqtt_on_message
|
||||||
|
@@ -17,6 +17,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
|
||||||
|
from . import MqttClientSetup
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_PAYLOAD,
|
ATTR_PAYLOAD,
|
||||||
ATTR_QOS,
|
ATTR_QOS,
|
||||||
@@ -62,6 +63,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
can_connect = await self.hass.async_add_executor_job(
|
can_connect = await self.hass.async_add_executor_job(
|
||||||
try_connection,
|
try_connection,
|
||||||
|
self.hass,
|
||||||
user_input[CONF_BROKER],
|
user_input[CONF_BROKER],
|
||||||
user_input[CONF_PORT],
|
user_input[CONF_PORT],
|
||||||
user_input.get(CONF_USERNAME),
|
user_input.get(CONF_USERNAME),
|
||||||
@@ -102,6 +104,7 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
data = self._hassio_discovery
|
data = self._hassio_discovery
|
||||||
can_connect = await self.hass.async_add_executor_job(
|
can_connect = await self.hass.async_add_executor_job(
|
||||||
try_connection,
|
try_connection,
|
||||||
|
self.hass,
|
||||||
data[CONF_HOST],
|
data[CONF_HOST],
|
||||||
data[CONF_PORT],
|
data[CONF_PORT],
|
||||||
data.get(CONF_USERNAME),
|
data.get(CONF_USERNAME),
|
||||||
@@ -152,6 +155,7 @@ class MQTTOptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
can_connect = await self.hass.async_add_executor_job(
|
can_connect = await self.hass.async_add_executor_job(
|
||||||
try_connection,
|
try_connection,
|
||||||
|
self.hass,
|
||||||
user_input[CONF_BROKER],
|
user_input[CONF_BROKER],
|
||||||
user_input[CONF_PORT],
|
user_input[CONF_PORT],
|
||||||
user_input.get(CONF_USERNAME),
|
user_input.get(CONF_USERNAME),
|
||||||
@@ -313,25 +317,24 @@ class MQTTOptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def try_connection(broker, port, username, password, protocol="3.1"):
|
def try_connection(hass, broker, port, username, password, protocol="3.1"):
|
||||||
"""Test if we can connect to an MQTT broker."""
|
"""Test if we can connect to an MQTT broker."""
|
||||||
# pylint: disable-next=import-outside-toplevel
|
# Get the config from configuration.yaml
|
||||||
import paho.mqtt.client as mqtt
|
yaml_config = hass.data.get(DATA_MQTT_CONFIG, {})
|
||||||
|
entry_config = {
|
||||||
if protocol == "3.1":
|
CONF_BROKER: broker,
|
||||||
proto = mqtt.MQTTv31
|
CONF_PORT: port,
|
||||||
else:
|
CONF_USERNAME: username,
|
||||||
proto = mqtt.MQTTv311
|
CONF_PASSWORD: password,
|
||||||
|
CONF_PROTOCOL: protocol,
|
||||||
client = mqtt.Client(protocol=proto)
|
}
|
||||||
if username and password:
|
client = MqttClientSetup({**yaml_config, **entry_config}).client
|
||||||
client.username_pw_set(username, password)
|
|
||||||
|
|
||||||
result = queue.Queue(maxsize=1)
|
result = queue.Queue(maxsize=1)
|
||||||
|
|
||||||
def on_connect(client_, userdata, flags, result_code):
|
def on_connect(client_, userdata, flags, result_code):
|
||||||
"""Handle connection result."""
|
"""Handle connection result."""
|
||||||
result.put(result_code == mqtt.CONNACK_ACCEPTED)
|
result.put(result_code == MqttClientSetup.mqtt.CONNACK_ACCEPTED)
|
||||||
|
|
||||||
client.on_connect = on_connect
|
client.on_connect = on_connect
|
||||||
|
|
||||||
|
@@ -22,6 +22,12 @@ CONF_STATE_VALUE_TEMPLATE = "state_value_template"
|
|||||||
CONF_TOPIC = "topic"
|
CONF_TOPIC = "topic"
|
||||||
CONF_WILL_MESSAGE = "will_message"
|
CONF_WILL_MESSAGE = "will_message"
|
||||||
|
|
||||||
|
CONF_CERTIFICATE = "certificate"
|
||||||
|
CONF_CLIENT_KEY = "client_key"
|
||||||
|
CONF_CLIENT_CERT = "client_cert"
|
||||||
|
CONF_TLS_INSECURE = "tls_insecure"
|
||||||
|
CONF_TLS_VERSION = "tls_version"
|
||||||
|
|
||||||
DATA_MQTT_CONFIG = "mqtt_config"
|
DATA_MQTT_CONFIG = "mqtt_config"
|
||||||
DATA_MQTT_RELOAD_NEEDED = "mqtt_reload_needed"
|
DATA_MQTT_RELOAD_NEEDED = "mqtt_reload_needed"
|
||||||
|
|
||||||
@@ -56,4 +62,5 @@ MQTT_DISCONNECTED = "mqtt_disconnected"
|
|||||||
PAYLOAD_EMPTY_JSON = "{}"
|
PAYLOAD_EMPTY_JSON = "{}"
|
||||||
PAYLOAD_NONE = "None"
|
PAYLOAD_NONE = "None"
|
||||||
|
|
||||||
|
PROTOCOL_31 = "3.1"
|
||||||
PROTOCOL_311 = "3.1.1"
|
PROTOCOL_311 = "3.1.1"
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"domain": "obihai",
|
"domain": "obihai",
|
||||||
"name": "Obihai",
|
"name": "Obihai",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/obihai",
|
"documentation": "https://www.home-assistant.io/integrations/obihai",
|
||||||
"requirements": ["pyobihai==1.3.1"],
|
"requirements": ["pyobihai==1.3.2"],
|
||||||
"codeowners": ["@dshokouhi"],
|
"codeowners": ["@dshokouhi"],
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pyobihai"]
|
"loggers": ["pyobihai"]
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"name": "Sonos",
|
"name": "Sonos",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/sonos",
|
"documentation": "https://www.home-assistant.io/integrations/sonos",
|
||||||
"requirements": ["soco==0.26.3"],
|
"requirements": ["soco==0.26.4"],
|
||||||
"dependencies": ["ssdp"],
|
"dependencies": ["ssdp"],
|
||||||
"after_dependencies": ["plex", "spotify", "zeroconf", "media_source"],
|
"after_dependencies": ["plex", "spotify", "zeroconf", "media_source"],
|
||||||
"zeroconf": ["_sonos._tcp.local."],
|
"zeroconf": ["_sonos._tcp.local."],
|
||||||
|
@@ -7,7 +7,7 @@ from .backports.enum import StrEnum
|
|||||||
|
|
||||||
MAJOR_VERSION: Final = 2022
|
MAJOR_VERSION: Final = 2022
|
||||||
MINOR_VERSION: Final = 3
|
MINOR_VERSION: Final = 3
|
||||||
PATCH_VERSION: Final = "0"
|
PATCH_VERSION: Final = "1"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
||||||
|
@@ -149,10 +149,17 @@ async def _async_setup_component(
|
|||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
"""
|
"""
|
||||||
|
integration: loader.Integration | None = None
|
||||||
|
|
||||||
def log_error(msg: str, link: str | None = None) -> None:
|
def log_error(msg: str) -> None:
|
||||||
"""Log helper."""
|
"""Log helper."""
|
||||||
_LOGGER.error("Setup failed for %s: %s", domain, msg)
|
if integration is None:
|
||||||
|
custom = ""
|
||||||
|
link = None
|
||||||
|
else:
|
||||||
|
custom = "" if integration.is_built_in else "custom integration "
|
||||||
|
link = integration.documentation
|
||||||
|
_LOGGER.error("Setup failed for %s%s: %s", custom, domain, msg)
|
||||||
async_notify_setup_error(hass, domain, link)
|
async_notify_setup_error(hass, domain, link)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -174,7 +181,7 @@ async def _async_setup_component(
|
|||||||
try:
|
try:
|
||||||
await async_process_deps_reqs(hass, config, integration)
|
await async_process_deps_reqs(hass, config, integration)
|
||||||
except HomeAssistantError as err:
|
except HomeAssistantError as err:
|
||||||
log_error(str(err), integration.documentation)
|
log_error(str(err))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Some integrations fail on import because they call functions incorrectly.
|
# Some integrations fail on import because they call functions incorrectly.
|
||||||
@@ -182,7 +189,7 @@ async def _async_setup_component(
|
|||||||
try:
|
try:
|
||||||
component = integration.get_component()
|
component = integration.get_component()
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
log_error(f"Unable to import component: {err}", integration.documentation)
|
log_error(f"Unable to import component: {err}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
processed_config = await conf_util.async_process_component_config(
|
processed_config = await conf_util.async_process_component_config(
|
||||||
@@ -190,7 +197,7 @@ async def _async_setup_component(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if processed_config is None:
|
if processed_config is None:
|
||||||
log_error("Invalid config.", integration.documentation)
|
log_error("Invalid config.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
start = timer()
|
start = timer()
|
||||||
@@ -287,6 +294,7 @@ async def async_prepare_setup_platform(
|
|||||||
|
|
||||||
def log_error(msg: str) -> None:
|
def log_error(msg: str) -> None:
|
||||||
"""Log helper."""
|
"""Log helper."""
|
||||||
|
|
||||||
_LOGGER.error("Unable to prepare setup for platform %s: %s", platform_path, msg)
|
_LOGGER.error("Unable to prepare setup for platform %s: %s", platform_path, msg)
|
||||||
async_notify_setup_error(hass, platform_path)
|
async_notify_setup_error(hass, platform_path)
|
||||||
|
|
||||||
|
@@ -1727,7 +1727,7 @@ pynx584==0.5
|
|||||||
pynzbgetapi==0.2.0
|
pynzbgetapi==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.obihai
|
# homeassistant.components.obihai
|
||||||
pyobihai==1.3.1
|
pyobihai==1.3.2
|
||||||
|
|
||||||
# homeassistant.components.octoprint
|
# homeassistant.components.octoprint
|
||||||
pyoctoprintapi==0.1.7
|
pyoctoprintapi==0.1.7
|
||||||
@@ -2235,7 +2235,7 @@ smhi-pkg==1.0.15
|
|||||||
snapcast==2.1.3
|
snapcast==2.1.3
|
||||||
|
|
||||||
# homeassistant.components.sonos
|
# homeassistant.components.sonos
|
||||||
soco==0.26.3
|
soco==0.26.4
|
||||||
|
|
||||||
# homeassistant.components.solaredge_local
|
# homeassistant.components.solaredge_local
|
||||||
solaredge-local==0.2.0
|
solaredge-local==0.2.0
|
||||||
|
@@ -1371,7 +1371,7 @@ smarthab==0.21
|
|||||||
smhi-pkg==1.0.15
|
smhi-pkg==1.0.15
|
||||||
|
|
||||||
# homeassistant.components.sonos
|
# homeassistant.components.sonos
|
||||||
soco==0.26.3
|
soco==0.26.4
|
||||||
|
|
||||||
# homeassistant.components.solaredge
|
# homeassistant.components.solaredge
|
||||||
solaredge==0.0.2
|
solaredge==0.0.2
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = homeassistant
|
name = homeassistant
|
||||||
version = 2022.3.0
|
version = 2022.3.1
|
||||||
author = The Home Assistant Authors
|
author = The Home Assistant Authors
|
||||||
author_email = hello@home-assistant.io
|
author_email = hello@home-assistant.io
|
||||||
license = Apache-2.0
|
license = Apache-2.0
|
||||||
|
@@ -42,7 +42,6 @@ from homeassistant.const import (
|
|||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
__version__,
|
|
||||||
__version__ as hass_version,
|
__version__ as hass_version,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.event import TRACK_STATE_CHANGE_CALLBACKS
|
from homeassistant.helpers.event import TRACK_STATE_CHANGE_CALLBACKS
|
||||||
@@ -166,7 +165,9 @@ async def test_home_accessory(hass, hk_driver):
|
|||||||
serv.get_characteristic(CHAR_SERIAL_NUMBER).value
|
serv.get_characteristic(CHAR_SERIAL_NUMBER).value
|
||||||
== "light.accessory_that_exceeds_the_maximum_maximum_maximum_maximum"
|
== "light.accessory_that_exceeds_the_maximum_maximum_maximum_maximum"
|
||||||
)
|
)
|
||||||
assert serv.get_characteristic(CHAR_FIRMWARE_REVISION).value == hass_version
|
assert hass_version.startswith(
|
||||||
|
serv.get_characteristic(CHAR_FIRMWARE_REVISION).value
|
||||||
|
)
|
||||||
|
|
||||||
hass.states.async_set(entity_id, "on")
|
hass.states.async_set(entity_id, "on")
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@@ -216,7 +217,9 @@ async def test_accessory_with_missing_basic_service_info(hass, hk_driver):
|
|||||||
assert serv.get_characteristic(CHAR_MANUFACTURER).value == "Home Assistant Sensor"
|
assert serv.get_characteristic(CHAR_MANUFACTURER).value == "Home Assistant Sensor"
|
||||||
assert serv.get_characteristic(CHAR_MODEL).value == "Sensor"
|
assert serv.get_characteristic(CHAR_MODEL).value == "Sensor"
|
||||||
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == entity_id
|
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == entity_id
|
||||||
assert serv.get_characteristic(CHAR_FIRMWARE_REVISION).value == hass_version
|
assert hass_version.startswith(
|
||||||
|
serv.get_characteristic(CHAR_FIRMWARE_REVISION).value
|
||||||
|
)
|
||||||
assert isinstance(acc.to_HAP(), dict)
|
assert isinstance(acc.to_HAP(), dict)
|
||||||
|
|
||||||
|
|
||||||
@@ -244,7 +247,9 @@ async def test_accessory_with_hardware_revision(hass, hk_driver):
|
|||||||
assert serv.get_characteristic(CHAR_MANUFACTURER).value == "Home Assistant Sensor"
|
assert serv.get_characteristic(CHAR_MANUFACTURER).value == "Home Assistant Sensor"
|
||||||
assert serv.get_characteristic(CHAR_MODEL).value == "Sensor"
|
assert serv.get_characteristic(CHAR_MODEL).value == "Sensor"
|
||||||
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == entity_id
|
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == entity_id
|
||||||
assert serv.get_characteristic(CHAR_FIRMWARE_REVISION).value == hass_version
|
assert hass_version.startswith(
|
||||||
|
serv.get_characteristic(CHAR_FIRMWARE_REVISION).value
|
||||||
|
)
|
||||||
assert serv.get_characteristic(CHAR_HARDWARE_REVISION).value == "1.2.3"
|
assert serv.get_characteristic(CHAR_HARDWARE_REVISION).value == "1.2.3"
|
||||||
assert isinstance(acc.to_HAP(), dict)
|
assert isinstance(acc.to_HAP(), dict)
|
||||||
|
|
||||||
@@ -687,7 +692,9 @@ def test_home_bridge(hk_driver):
|
|||||||
serv = bridge.services[0] # SERV_ACCESSORY_INFO
|
serv = bridge.services[0] # SERV_ACCESSORY_INFO
|
||||||
assert serv.display_name == SERV_ACCESSORY_INFO
|
assert serv.display_name == SERV_ACCESSORY_INFO
|
||||||
assert serv.get_characteristic(CHAR_NAME).value == BRIDGE_NAME
|
assert serv.get_characteristic(CHAR_NAME).value == BRIDGE_NAME
|
||||||
assert serv.get_characteristic(CHAR_FIRMWARE_REVISION).value == __version__
|
assert hass_version.startswith(
|
||||||
|
serv.get_characteristic(CHAR_FIRMWARE_REVISION).value
|
||||||
|
)
|
||||||
assert serv.get_characteristic(CHAR_MANUFACTURER).value == MANUFACTURER
|
assert serv.get_characteristic(CHAR_MANUFACTURER).value == MANUFACTURER
|
||||||
assert serv.get_characteristic(CHAR_MODEL).value == BRIDGE_MODEL
|
assert serv.get_characteristic(CHAR_MODEL).value == BRIDGE_MODEL
|
||||||
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == BRIDGE_SERIAL_NUMBER
|
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == BRIDGE_SERIAL_NUMBER
|
||||||
|
@@ -399,4 +399,4 @@ async def test_empty_name(hass, hk_driver):
|
|||||||
assert acc.category == 10 # Sensor
|
assert acc.category == 10 # Sensor
|
||||||
|
|
||||||
assert acc.char_humidity.value == 20
|
assert acc.char_humidity.value == 20
|
||||||
assert acc.display_name is None
|
assert acc.display_name == "None"
|
||||||
|
@@ -30,6 +30,7 @@ from homeassistant.components.homekit.util import (
|
|||||||
async_port_is_available,
|
async_port_is_available,
|
||||||
async_show_setup_message,
|
async_show_setup_message,
|
||||||
cleanup_name_for_homekit,
|
cleanup_name_for_homekit,
|
||||||
|
coerce_int,
|
||||||
convert_to_float,
|
convert_to_float,
|
||||||
density_to_air_quality,
|
density_to_air_quality,
|
||||||
format_version,
|
format_version,
|
||||||
@@ -349,13 +350,23 @@ async def test_format_version():
|
|||||||
assert format_version("undefined-undefined-1.6.8") == "1.6.8"
|
assert format_version("undefined-undefined-1.6.8") == "1.6.8"
|
||||||
assert format_version("56.0-76060") == "56.0.76060"
|
assert format_version("56.0-76060") == "56.0.76060"
|
||||||
assert format_version(3.6) == "3.6"
|
assert format_version(3.6) == "3.6"
|
||||||
assert format_version("AK001-ZJ100") == "001.100"
|
assert format_version("AK001-ZJ100") == "1.100"
|
||||||
assert format_version("HF-LPB100-") == "100"
|
assert format_version("HF-LPB100-") == "100"
|
||||||
assert format_version("AK001-ZJ2149") == "001.2149"
|
assert format_version("AK001-ZJ2149") == "1.2149"
|
||||||
|
assert format_version("13216407885") == "4294967295" # max value
|
||||||
|
assert format_version("000132 16407885") == "132.16407885"
|
||||||
assert format_version("0.1") == "0.1"
|
assert format_version("0.1") == "0.1"
|
||||||
|
assert format_version("0") is None
|
||||||
assert format_version("unknown") is None
|
assert format_version("unknown") is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_coerce_int():
|
||||||
|
"""Test coerce_int method."""
|
||||||
|
assert coerce_int("1") == 1
|
||||||
|
assert coerce_int("") == 0
|
||||||
|
assert coerce_int(0) == 0
|
||||||
|
|
||||||
|
|
||||||
async def test_accessory_friendly_name():
|
async def test_accessory_friendly_name():
|
||||||
"""Test we provide a helpful friendly name."""
|
"""Test we provide a helpful friendly name."""
|
||||||
|
|
||||||
|
@@ -3,8 +3,9 @@ from unittest.mock import patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
import yaml
|
||||||
|
|
||||||
from homeassistant import config_entries, data_entry_flow
|
from homeassistant import config as hass_config, config_entries, data_entry_flow
|
||||||
from homeassistant.components import mqtt
|
from homeassistant.components import mqtt
|
||||||
from homeassistant.components.hassio import HassioServiceInfo
|
from homeassistant.components.hassio import HassioServiceInfo
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@@ -151,7 +152,7 @@ async def test_manual_config_set(
|
|||||||
"discovery": True,
|
"discovery": True,
|
||||||
}
|
}
|
||||||
# Check we tried the connection, with precedence for config entry settings
|
# Check we tried the connection, with precedence for config entry settings
|
||||||
mock_try_connection.assert_called_once_with("127.0.0.1", 1883, None, None)
|
mock_try_connection.assert_called_once_with(hass, "127.0.0.1", 1883, None, None)
|
||||||
# Check config entry got setup
|
# Check config entry got setup
|
||||||
assert len(mock_finish_setup.mock_calls) == 1
|
assert len(mock_finish_setup.mock_calls) == 1
|
||||||
|
|
||||||
@@ -642,3 +643,95 @@ async def test_options_bad_will_message_fails(hass, mock_try_connection):
|
|||||||
mqtt.CONF_BROKER: "test-broker",
|
mqtt.CONF_BROKER: "test-broker",
|
||||||
mqtt.CONF_PORT: 1234,
|
mqtt.CONF_PORT: 1234,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_try_connection_with_advanced_parameters(
|
||||||
|
hass, mock_try_connection_success, tmp_path
|
||||||
|
):
|
||||||
|
"""Test config flow with advanced parameters from config."""
|
||||||
|
# Mock certificate files
|
||||||
|
certfile = tmp_path / "cert.pem"
|
||||||
|
certfile.write_text("## mock certificate file ##")
|
||||||
|
keyfile = tmp_path / "key.pem"
|
||||||
|
keyfile.write_text("## mock key file ##")
|
||||||
|
config = {
|
||||||
|
"certificate": "auto",
|
||||||
|
"tls_insecure": True,
|
||||||
|
"client_cert": certfile,
|
||||||
|
"client_key": keyfile,
|
||||||
|
}
|
||||||
|
new_yaml_config_file = tmp_path / "configuration.yaml"
|
||||||
|
new_yaml_config = yaml.dump({mqtt.DOMAIN: config})
|
||||||
|
new_yaml_config_file.write_text(new_yaml_config)
|
||||||
|
assert new_yaml_config_file.read_text() == new_yaml_config
|
||||||
|
|
||||||
|
with patch.object(hass_config, "YAML_CONFIG_FILE", new_yaml_config_file):
|
||||||
|
await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
config_entry = MockConfigEntry(domain=mqtt.DOMAIN)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
config_entry.data = {
|
||||||
|
mqtt.CONF_BROKER: "test-broker",
|
||||||
|
mqtt.CONF_PORT: 1234,
|
||||||
|
mqtt.CONF_USERNAME: "user",
|
||||||
|
mqtt.CONF_PASSWORD: "pass",
|
||||||
|
mqtt.CONF_DISCOVERY: True,
|
||||||
|
mqtt.CONF_BIRTH_MESSAGE: {
|
||||||
|
mqtt.ATTR_TOPIC: "ha_state/online",
|
||||||
|
mqtt.ATTR_PAYLOAD: "online",
|
||||||
|
mqtt.ATTR_QOS: 1,
|
||||||
|
mqtt.ATTR_RETAIN: True,
|
||||||
|
},
|
||||||
|
mqtt.CONF_WILL_MESSAGE: {
|
||||||
|
mqtt.ATTR_TOPIC: "ha_state/offline",
|
||||||
|
mqtt.ATTR_PAYLOAD: "offline",
|
||||||
|
mqtt.ATTR_QOS: 2,
|
||||||
|
mqtt.ATTR_RETAIN: False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test default/suggested values from config
|
||||||
|
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "broker"
|
||||||
|
defaults = {
|
||||||
|
mqtt.CONF_BROKER: "test-broker",
|
||||||
|
mqtt.CONF_PORT: 1234,
|
||||||
|
}
|
||||||
|
suggested = {
|
||||||
|
mqtt.CONF_USERNAME: "user",
|
||||||
|
mqtt.CONF_PASSWORD: "pass",
|
||||||
|
}
|
||||||
|
for k, v in defaults.items():
|
||||||
|
assert get_default(result["data_schema"].schema, k) == v
|
||||||
|
for k, v in suggested.items():
|
||||||
|
assert get_suggested(result["data_schema"].schema, k) == v
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
mqtt.CONF_BROKER: "another-broker",
|
||||||
|
mqtt.CONF_PORT: 2345,
|
||||||
|
mqtt.CONF_USERNAME: "us3r",
|
||||||
|
mqtt.CONF_PASSWORD: "p4ss",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "options"
|
||||||
|
|
||||||
|
# check if the username and password was set from config flow and not from configuration.yaml
|
||||||
|
assert mock_try_connection_success.username_pw_set.mock_calls[0][1] == (
|
||||||
|
"us3r",
|
||||||
|
"p4ss",
|
||||||
|
)
|
||||||
|
|
||||||
|
# check if tls_insecure_set is called
|
||||||
|
assert mock_try_connection_success.tls_insecure_set.mock_calls[0][1] == (True,)
|
||||||
|
|
||||||
|
# check if the certificate settings were set from configuration.yaml
|
||||||
|
assert mock_try_connection_success.tls_set.mock_calls[0].kwargs[
|
||||||
|
"certfile"
|
||||||
|
] == str(certfile)
|
||||||
|
assert mock_try_connection_success.tls_set.mock_calls[0].kwargs[
|
||||||
|
"keyfile"
|
||||||
|
] == str(keyfile)
|
||||||
|
@@ -11,6 +11,7 @@ import voluptuous as vol
|
|||||||
from homeassistant import config_entries, setup
|
from homeassistant import config_entries, setup
|
||||||
from homeassistant.const import EVENT_COMPONENT_LOADED, EVENT_HOMEASSISTANT_START
|
from homeassistant.const import EVENT_COMPONENT_LOADED, EVENT_HOMEASSISTANT_START
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import discovery
|
from homeassistant.helpers import discovery
|
||||||
from homeassistant.helpers.config_validation import (
|
from homeassistant.helpers.config_validation import (
|
||||||
PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA,
|
||||||
@@ -621,6 +622,22 @@ async def test_integration_disabled(hass, caplog):
|
|||||||
assert disabled_reason in caplog.text
|
assert disabled_reason in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_integration_logs_is_custom(hass, caplog):
|
||||||
|
"""Test we highlight it's a custom component when errors happen."""
|
||||||
|
mock_integration(
|
||||||
|
hass,
|
||||||
|
MockModule("test_component1"),
|
||||||
|
built_in=False,
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.setup.async_process_deps_reqs",
|
||||||
|
side_effect=HomeAssistantError("Boom"),
|
||||||
|
):
|
||||||
|
result = await setup.async_setup_component(hass, "test_component1", {})
|
||||||
|
assert not result
|
||||||
|
assert "Setup failed for custom integration test_component1: Boom" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_async_get_loaded_integrations(hass):
|
async def test_async_get_loaded_integrations(hass):
|
||||||
"""Test we can enumerate loaded integations."""
|
"""Test we can enumerate loaded integations."""
|
||||||
hass.config.components.add("notbase")
|
hass.config.components.add("notbase")
|
||||||
|
Reference in New Issue
Block a user