mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Implement PlatformNotReady on onewire integration (#42395)
This commit is contained in:
parent
935c2df7cb
commit
0de52c173c
@ -1,7 +1,12 @@
|
|||||||
"""The 1-Wire component."""
|
"""The 1-Wire component."""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from .const import SUPPORTED_PLATFORMS
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
|
from .const import DOMAIN, SUPPORTED_PLATFORMS
|
||||||
|
from .onewirehub import CannotConnect, OneWireHub
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
@ -9,8 +14,18 @@ async def async_setup(hass, config):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry):
|
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry):
|
||||||
"""Set up a 1-Wire proxy for a config entry."""
|
"""Set up a 1-Wire proxy for a config entry."""
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
|
||||||
|
onewirehub = OneWireHub(hass)
|
||||||
|
try:
|
||||||
|
await onewirehub.initialize(config_entry)
|
||||||
|
except CannotConnect as exc:
|
||||||
|
raise ConfigEntryNotReady() from exc
|
||||||
|
|
||||||
|
hass.data[DOMAIN][config_entry.unique_id] = onewirehub
|
||||||
|
|
||||||
for component in SUPPORTED_PLATFORMS:
|
for component in SUPPORTED_PLATFORMS:
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
hass.config_entries.async_forward_entry_setup(config_entry, component)
|
hass.config_entries.async_forward_entry_setup(config_entry, component)
|
||||||
@ -18,7 +33,7 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass, config_entry):
|
async def async_unload_entry(hass: HomeAssistantType, config_entry: ConfigEntry):
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
unload_ok = all(
|
unload_ok = all(
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
@ -28,4 +43,6 @@ async def async_unload_entry(hass, config_entry):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN].pop(config_entry.unique_id)
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
"""Config flow for 1-Wire component."""
|
"""Config flow for 1-Wire component."""
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import exceptions
|
|
||||||
from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow
|
from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow
|
||||||
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE
|
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
@ -16,7 +15,7 @@ from .const import ( # pylint: disable=unused-import
|
|||||||
DEFAULT_SYSBUS_MOUNT_DIR,
|
DEFAULT_SYSBUS_MOUNT_DIR,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
from .onewirehub import OneWireHub
|
from .onewirehub import CannotConnect, InvalidPath, OneWireHub
|
||||||
|
|
||||||
DATA_SCHEMA_USER = vol.Schema(
|
DATA_SCHEMA_USER = vol.Schema(
|
||||||
{vol.Required(CONF_TYPE): vol.In([CONF_TYPE_OWSERVER, CONF_TYPE_SYSBUS])}
|
{vol.Required(CONF_TYPE): vol.In([CONF_TYPE_OWSERVER, CONF_TYPE_SYSBUS])}
|
||||||
@ -44,8 +43,8 @@ async def validate_input_owserver(hass: HomeAssistantType, data):
|
|||||||
|
|
||||||
host = data[CONF_HOST]
|
host = data[CONF_HOST]
|
||||||
port = data[CONF_PORT]
|
port = data[CONF_PORT]
|
||||||
if not await hub.can_connect(host, port):
|
# Raises CannotConnect exception on failure
|
||||||
raise CannotConnect
|
await hub.connect(host, port)
|
||||||
|
|
||||||
# Return info that you want to store in the config entry.
|
# Return info that you want to store in the config entry.
|
||||||
return {"title": host}
|
return {"title": host}
|
||||||
@ -71,8 +70,9 @@ async def validate_input_mount_dir(hass: HomeAssistantType, data):
|
|||||||
hub = OneWireHub(hass)
|
hub = OneWireHub(hass)
|
||||||
|
|
||||||
mount_dir = data[CONF_MOUNT_DIR]
|
mount_dir = data[CONF_MOUNT_DIR]
|
||||||
if not await hub.is_valid_mount_dir(mount_dir):
|
|
||||||
raise InvalidPath
|
# Raises InvalidDir exception on failure
|
||||||
|
await hub.check_mount_dir(mount_dir)
|
||||||
|
|
||||||
# Return info that you want to store in the config entry.
|
# Return info that you want to store in the config entry.
|
||||||
return {"title": mount_dir}
|
return {"title": mount_dir}
|
||||||
@ -185,11 +185,3 @@ class OneWireFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
if CONF_MOUNT_DIR not in platform_config:
|
if CONF_MOUNT_DIR not in platform_config:
|
||||||
platform_config[CONF_MOUNT_DIR] = DEFAULT_SYSBUS_MOUNT_DIR
|
platform_config[CONF_MOUNT_DIR] = DEFAULT_SYSBUS_MOUNT_DIR
|
||||||
return await self.async_step_mount_dir(platform_config)
|
return await self.async_step_mount_dir(platform_config)
|
||||||
|
|
||||||
|
|
||||||
class CannotConnect(exceptions.HomeAssistantError):
|
|
||||||
"""Error to indicate we cannot connect."""
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidPath(exceptions.HomeAssistantError):
|
|
||||||
"""Error to indicate the path is invalid."""
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
"""Hub for communication with 1-Wire server or mount_dir."""
|
"""Hub for communication with 1-Wire server or mount_dir."""
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from pyownet import protocol
|
from pyownet import protocol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
from .const import CONF_MOUNT_DIR, CONF_TYPE_OWSERVER, CONF_TYPE_SYSBUS
|
||||||
|
|
||||||
|
|
||||||
class OneWireHub:
|
class OneWireHub:
|
||||||
@ -15,21 +17,35 @@ class OneWireHub:
|
|||||||
def __init__(self, hass: HomeAssistantType):
|
def __init__(self, hass: HomeAssistantType):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
|
self.owproxy: protocol._Proxy = None
|
||||||
|
|
||||||
async def can_connect(self, host, port) -> bool:
|
async def connect(self, host: str, port: int) -> None:
|
||||||
"""Test if we can authenticate with the host."""
|
"""Connect to the owserver host."""
|
||||||
try:
|
try:
|
||||||
await self.hass.async_add_executor_job(protocol.proxy, host, port)
|
self.owproxy = await self.hass.async_add_executor_job(
|
||||||
except (protocol.Error, protocol.ConnError) as exc:
|
protocol.proxy, host, port
|
||||||
_LOGGER.error(
|
|
||||||
"Cannot connect to owserver on %s:%d, got: %s", host, port, exc
|
|
||||||
)
|
)
|
||||||
return False
|
except protocol.ConnError as exc:
|
||||||
return True
|
raise CannotConnect from exc
|
||||||
|
|
||||||
async def is_valid_mount_dir(self, mount_dir) -> bool:
|
async def check_mount_dir(self, mount_dir: str) -> None:
|
||||||
"""Test that the mount_dir is a valid path."""
|
"""Test that the mount_dir is a valid path."""
|
||||||
if not await self.hass.async_add_executor_job(os.path.isdir, mount_dir):
|
if not await self.hass.async_add_executor_job(os.path.isdir, mount_dir):
|
||||||
_LOGGER.error("Cannot find directory %s", mount_dir)
|
raise InvalidPath
|
||||||
return False
|
|
||||||
return True
|
async def initialize(self, config_entry: ConfigEntry) -> None:
|
||||||
|
"""Initialize a config entry."""
|
||||||
|
if config_entry.data[CONF_TYPE] == CONF_TYPE_SYSBUS:
|
||||||
|
await self.check_mount_dir(config_entry.data[CONF_MOUNT_DIR])
|
||||||
|
elif config_entry.data[CONF_TYPE] == CONF_TYPE_OWSERVER:
|
||||||
|
host = config_entry.data[CONF_HOST]
|
||||||
|
port = config_entry.data[CONF_PORT]
|
||||||
|
await self.connect(host, port)
|
||||||
|
|
||||||
|
|
||||||
|
class CannotConnect(HomeAssistantError):
|
||||||
|
"""Error to indicate we cannot connect."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidPath(HomeAssistantError):
|
||||||
|
"""Error to indicate the path is invalid."""
|
||||||
|
@ -8,6 +8,7 @@ from pi1wire import InvalidCRCException, Pi1Wire, UnsupportResponseException
|
|||||||
from pyownet import protocol
|
from pyownet import protocol
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.onewire.onewirehub import OneWireHub
|
||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT
|
from homeassistant.config_entries import SOURCE_IMPORT
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -156,11 +157,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up 1-Wire platform."""
|
"""Set up 1-Wire platform."""
|
||||||
entities = await hass.async_add_executor_job(get_entities, config_entry.data)
|
onewirehub = hass.data[DOMAIN][config_entry.unique_id]
|
||||||
|
entities = await hass.async_add_executor_job(
|
||||||
|
get_entities, onewirehub, config_entry.data
|
||||||
|
)
|
||||||
async_add_entities(entities, True)
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
def get_entities(config):
|
def get_entities(onewirehub: OneWireHub, config):
|
||||||
"""Get a list of entities."""
|
"""Get a list of entities."""
|
||||||
entities = []
|
entities = []
|
||||||
device_names = {}
|
device_names = {}
|
||||||
@ -174,19 +178,17 @@ def get_entities(config):
|
|||||||
owhost = config[CONF_HOST]
|
owhost = config[CONF_HOST]
|
||||||
owport = config[CONF_PORT]
|
owport = config[CONF_PORT]
|
||||||
|
|
||||||
_LOGGER.debug("Initializing using %s:%s", owhost, owport)
|
|
||||||
try:
|
try:
|
||||||
owproxy = protocol.proxy(host=owhost, port=owport)
|
devices = onewirehub.owproxy.dir()
|
||||||
devices = owproxy.dir()
|
except protocol.OwnetError as exc:
|
||||||
except protocol.Error as exc:
|
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Cannot connect to owserver on %s:%d, got: %s", owhost, owport, exc
|
"Failed to list devices on %s:%d, got: %s", owhost, owport, exc
|
||||||
)
|
)
|
||||||
return entities
|
return entities
|
||||||
for device in devices:
|
for device in devices:
|
||||||
_LOGGER.debug("Found device: %s", device)
|
_LOGGER.debug("Found device: %s", device)
|
||||||
family = owproxy.read(f"{device}family").decode()
|
family = onewirehub.owproxy.read(f"{device}family").decode()
|
||||||
device_type = owproxy.read(f"{device}type").decode()
|
device_type = onewirehub.owproxy.read(f"{device}type").decode()
|
||||||
sensor_id = os.path.split(os.path.split(device)[0])[1]
|
sensor_id = os.path.split(os.path.split(device)[0])[1]
|
||||||
dev_type = "std"
|
dev_type = "std"
|
||||||
if "EF" in family:
|
if "EF" in family:
|
||||||
@ -210,7 +212,9 @@ def get_entities(config):
|
|||||||
if "moisture" in sensor_key:
|
if "moisture" in sensor_key:
|
||||||
s_id = sensor_key.split("_")[1]
|
s_id = sensor_key.split("_")[1]
|
||||||
is_leaf = int(
|
is_leaf = int(
|
||||||
owproxy.read(f"{device}moisture/is_leaf.{s_id}").decode()
|
onewirehub.owproxy.read(
|
||||||
|
f"{device}moisture/is_leaf.{s_id}"
|
||||||
|
).decode()
|
||||||
)
|
)
|
||||||
if is_leaf:
|
if is_leaf:
|
||||||
sensor_key = f"wetness_{s_id}"
|
sensor_key = f"wetness_{s_id}"
|
||||||
@ -221,7 +225,7 @@ def get_entities(config):
|
|||||||
device_file,
|
device_file,
|
||||||
sensor_key,
|
sensor_key,
|
||||||
device_info,
|
device_info,
|
||||||
owproxy,
|
onewirehub.owproxy,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
from asynctest.mock import patch
|
from asynctest.mock import patch
|
||||||
|
|
||||||
from homeassistant.components.onewire.const import (
|
from homeassistant.components.onewire.const import (
|
||||||
|
CONF_MOUNT_DIR,
|
||||||
CONF_TYPE_OWSERVER,
|
CONF_TYPE_OWSERVER,
|
||||||
CONF_TYPE_SYSBUS,
|
CONF_TYPE_SYSBUS,
|
||||||
DEFAULT_SYSBUS_MOUNT_DIR,
|
DEFAULT_SYSBUS_MOUNT_DIR,
|
||||||
@ -20,6 +21,7 @@ async def setup_onewire_sysbus_integration(hass):
|
|||||||
source="user",
|
source="user",
|
||||||
data={
|
data={
|
||||||
CONF_TYPE: CONF_TYPE_SYSBUS,
|
CONF_TYPE: CONF_TYPE_SYSBUS,
|
||||||
|
CONF_MOUNT_DIR: DEFAULT_SYSBUS_MOUNT_DIR,
|
||||||
},
|
},
|
||||||
unique_id=f"{CONF_TYPE_SYSBUS}:{DEFAULT_SYSBUS_MOUNT_DIR}",
|
unique_id=f"{CONF_TYPE_SYSBUS}:{DEFAULT_SYSBUS_MOUNT_DIR}",
|
||||||
connection_class=CONN_CLASS_LOCAL_POLL,
|
connection_class=CONN_CLASS_LOCAL_POLL,
|
||||||
@ -28,8 +30,11 @@ async def setup_onewire_sysbus_integration(hass):
|
|||||||
)
|
)
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
with patch(
|
||||||
await hass.async_block_till_done()
|
"homeassistant.components.onewire.onewirehub.os.path.isdir", return_value=True
|
||||||
|
):
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
return config_entry
|
return config_entry
|
||||||
|
|
||||||
@ -52,7 +57,7 @@ async def setup_onewire_owserver_integration(hass):
|
|||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.onewire.sensor.protocol.proxy",
|
"homeassistant.components.onewire.onewirehub.protocol.proxy",
|
||||||
):
|
):
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
@ -1,9 +1,74 @@
|
|||||||
"""Tests for 1-Wire config flow."""
|
"""Tests for 1-Wire config flow."""
|
||||||
from homeassistant.components.onewire.const import DOMAIN
|
from asynctest.mock import patch
|
||||||
from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED
|
from pyownet.protocol import ConnError, OwnetError
|
||||||
|
|
||||||
|
from homeassistant.components.onewire.const import CONF_TYPE_OWSERVER, DOMAIN
|
||||||
|
from homeassistant.config_entries import (
|
||||||
|
CONN_CLASS_LOCAL_POLL,
|
||||||
|
ENTRY_STATE_LOADED,
|
||||||
|
ENTRY_STATE_NOT_LOADED,
|
||||||
|
ENTRY_STATE_SETUP_RETRY,
|
||||||
|
)
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TYPE
|
||||||
|
|
||||||
from . import setup_onewire_owserver_integration, setup_onewire_sysbus_integration
|
from . import setup_onewire_owserver_integration, setup_onewire_sysbus_integration
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_owserver_platform_not_ready(hass):
|
||||||
|
"""Create the 1-Wire integration."""
|
||||||
|
config_entry_owserver = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
source="user",
|
||||||
|
data={
|
||||||
|
CONF_TYPE: CONF_TYPE_OWSERVER,
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_PORT: "1234",
|
||||||
|
},
|
||||||
|
unique_id=f"{CONF_TYPE_OWSERVER}:1.2.3.4:1234",
|
||||||
|
connection_class=CONN_CLASS_LOCAL_POLL,
|
||||||
|
options={},
|
||||||
|
entry_id="2",
|
||||||
|
)
|
||||||
|
config_entry_owserver.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.onewire.onewirehub.protocol.proxy",
|
||||||
|
side_effect=ConnError,
|
||||||
|
):
|
||||||
|
await hass.config_entries.async_setup(config_entry_owserver.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||||
|
assert config_entry_owserver.state == ENTRY_STATE_SETUP_RETRY
|
||||||
|
assert not hass.data.get(DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_failed_owserver_listing(hass):
|
||||||
|
"""Create the 1-Wire integration."""
|
||||||
|
config_entry_owserver = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
source="user",
|
||||||
|
data={
|
||||||
|
CONF_TYPE: CONF_TYPE_OWSERVER,
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_PORT: "1234",
|
||||||
|
},
|
||||||
|
unique_id=f"{CONF_TYPE_OWSERVER}:1.2.3.4:1234",
|
||||||
|
connection_class=CONN_CLASS_LOCAL_POLL,
|
||||||
|
options={},
|
||||||
|
entry_id="2",
|
||||||
|
)
|
||||||
|
config_entry_owserver.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch("homeassistant.components.onewire.onewirehub.protocol.proxy") as owproxy:
|
||||||
|
owproxy.return_value.dir.side_effect = OwnetError
|
||||||
|
await hass.config_entries.async_setup(config_entry_owserver.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
return config_entry_owserver
|
||||||
|
|
||||||
|
|
||||||
async def test_unload_entry(hass):
|
async def test_unload_entry(hass):
|
||||||
"""Test being able to unload an entry."""
|
"""Test being able to unload an entry."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user