mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
commit
ac2310e7f9
@ -21,6 +21,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||||||
from .axis_base import AxisEntityBase
|
from .axis_base import AxisEntityBase
|
||||||
from .const import DOMAIN as AXIS_DOMAIN
|
from .const import DOMAIN as AXIS_DOMAIN
|
||||||
|
|
||||||
|
AXIS_IMAGE = "http://{host}:{port}/axis-cgi/jpg/image.cgi"
|
||||||
|
AXIS_VIDEO = "http://{host}:{port}/axis-cgi/mjpg/video.cgi"
|
||||||
|
AXIS_STREAM = "rtsp://{user}:{password}@{host}/axis-media/media.amp?videocodec=h264"
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the Axis camera video stream."""
|
"""Set up the Axis camera video stream."""
|
||||||
@ -32,13 +36,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
CONF_NAME: config_entry.data[CONF_NAME],
|
CONF_NAME: config_entry.data[CONF_NAME],
|
||||||
CONF_USERNAME: config_entry.data[CONF_USERNAME],
|
CONF_USERNAME: config_entry.data[CONF_USERNAME],
|
||||||
CONF_PASSWORD: config_entry.data[CONF_PASSWORD],
|
CONF_PASSWORD: config_entry.data[CONF_PASSWORD],
|
||||||
CONF_MJPEG_URL: (
|
CONF_MJPEG_URL: AXIS_VIDEO.format(
|
||||||
f"http://{config_entry.data[CONF_HOST]}"
|
host=config_entry.data[CONF_HOST], port=config_entry.data[CONF_PORT],
|
||||||
f":{config_entry.data[CONF_PORT]}/axis-cgi/mjpg/video.cgi"
|
|
||||||
),
|
),
|
||||||
CONF_STILL_IMAGE_URL: (
|
CONF_STILL_IMAGE_URL: AXIS_IMAGE.format(
|
||||||
f"http://{config_entry.data[CONF_HOST]}"
|
host=config_entry.data[CONF_HOST], port=config_entry.data[CONF_PORT],
|
||||||
f":{config_entry.data[CONF_PORT]}/axis-cgi/jpg/image.cgi"
|
|
||||||
),
|
),
|
||||||
CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION,
|
CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION,
|
||||||
}
|
}
|
||||||
@ -70,19 +72,17 @@ class AxisCamera(AxisEntityBase, MjpegCamera):
|
|||||||
|
|
||||||
async def stream_source(self):
|
async def stream_source(self):
|
||||||
"""Return the stream source."""
|
"""Return the stream source."""
|
||||||
return (
|
return AXIS_STREAM.format(
|
||||||
f"rtsp://{self.device.config_entry.data[CONF_USERNAME]}´"
|
user=self.device.config_entry.data[CONF_USERNAME],
|
||||||
f":{self.device.config_entry.data[CONF_PASSWORD]}"
|
password=self.device.config_entry.data[CONF_PASSWORD],
|
||||||
f"@{self.device.host}/axis-media/media.amp?videocodec=h264"
|
host=self.device.host,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _new_address(self):
|
def _new_address(self):
|
||||||
"""Set new device address for video stream."""
|
"""Set new device address for video stream."""
|
||||||
port = self.device.config_entry.data[CONF_PORT]
|
port = self.device.config_entry.data[CONF_PORT]
|
||||||
self._mjpeg_url = (f"http://{self.device.host}:{port}/axis-cgi/mjpg/video.cgi",)
|
self._mjpeg_url = AXIS_VIDEO.format(host=self.device.host, port=port)
|
||||||
self._still_image_url = (
|
self._still_image_url = AXIS_IMAGE.format(host=self.device.host, port=port)
|
||||||
f"http://{self.device.host}:{port}/axis-cgi/jpg/image.cgi"
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
|
@ -141,7 +141,7 @@ async def async_request_stream(hass, entity_id, fmt):
|
|||||||
source,
|
source,
|
||||||
fmt=fmt,
|
fmt=fmt,
|
||||||
keepalive=camera_prefs.preload_stream,
|
keepalive=camera_prefs.preload_stream,
|
||||||
options=camera.options,
|
options=camera.stream_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "HomeKit Controller",
|
"name": "HomeKit Controller",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||||
"requirements": ["aiohomekit[IP]==0.2.29.1"],
|
"requirements": ["aiohomekit[IP]==0.2.29.2"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"zeroconf": ["_hap._tcp.local."],
|
"zeroconf": ["_hap._tcp.local."],
|
||||||
"codeowners": ["@Jc2k"]
|
"codeowners": ["@Jc2k"]
|
||||||
|
@ -25,6 +25,7 @@ from homeassistant.helpers.event import async_track_time_interval
|
|||||||
from homeassistant.helpers.service import verify_domain_control
|
from homeassistant.helpers.service import verify_domain_control
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
CONF_ZONE_RUN_TIME,
|
||||||
DATA_CLIENT,
|
DATA_CLIENT,
|
||||||
DATA_PROGRAMS,
|
DATA_PROGRAMS,
|
||||||
DATA_PROVISION_SETTINGS,
|
DATA_PROVISION_SETTINGS,
|
||||||
@ -33,6 +34,8 @@ from .const import (
|
|||||||
DATA_ZONES,
|
DATA_ZONES,
|
||||||
DATA_ZONES_DETAILS,
|
DATA_ZONES_DETAILS,
|
||||||
DEFAULT_PORT,
|
DEFAULT_PORT,
|
||||||
|
DEFAULT_SCAN_INTERVAL,
|
||||||
|
DEFAULT_ZONE_RUN,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
PROGRAM_UPDATE_TOPIC,
|
PROGRAM_UPDATE_TOPIC,
|
||||||
SENSOR_UPDATE_TOPIC,
|
SENSOR_UPDATE_TOPIC,
|
||||||
@ -41,19 +44,14 @@ from .const import (
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DATA_LISTENER = "listener"
|
|
||||||
|
|
||||||
CONF_CONTROLLERS = "controllers"
|
CONF_CONTROLLERS = "controllers"
|
||||||
CONF_PROGRAM_ID = "program_id"
|
CONF_PROGRAM_ID = "program_id"
|
||||||
CONF_SECONDS = "seconds"
|
CONF_SECONDS = "seconds"
|
||||||
CONF_ZONE_ID = "zone_id"
|
CONF_ZONE_ID = "zone_id"
|
||||||
CONF_ZONE_RUN_TIME = "zone_run_time"
|
|
||||||
|
|
||||||
DEFAULT_ATTRIBUTION = "Data provided by Green Electronics LLC"
|
DEFAULT_ATTRIBUTION = "Data provided by Green Electronics LLC"
|
||||||
DEFAULT_ICON = "mdi:water"
|
DEFAULT_ICON = "mdi:water"
|
||||||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
|
|
||||||
DEFAULT_SSL = True
|
DEFAULT_SSL = True
|
||||||
DEFAULT_ZONE_RUN = 60 * 10
|
|
||||||
|
|
||||||
SERVICE_ALTER_PROGRAM = vol.Schema({vol.Required(CONF_PROGRAM_ID): cv.positive_int})
|
SERVICE_ALTER_PROGRAM = vol.Schema({vol.Required(CONF_PROGRAM_ID): cv.positive_int})
|
||||||
|
|
||||||
@ -109,7 +107,6 @@ async def async_setup(hass, config):
|
|||||||
"""Set up the RainMachine component."""
|
"""Set up the RainMachine component."""
|
||||||
hass.data[DOMAIN] = {}
|
hass.data[DOMAIN] = {}
|
||||||
hass.data[DOMAIN][DATA_CLIENT] = {}
|
hass.data[DOMAIN][DATA_CLIENT] = {}
|
||||||
hass.data[DOMAIN][DATA_LISTENER] = {}
|
|
||||||
|
|
||||||
if DOMAIN not in config:
|
if DOMAIN not in config:
|
||||||
return True
|
return True
|
||||||
@ -143,7 +140,7 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
config_entry.data[CONF_IP_ADDRESS],
|
config_entry.data[CONF_IP_ADDRESS],
|
||||||
config_entry.data[CONF_PASSWORD],
|
config_entry.data[CONF_PASSWORD],
|
||||||
port=config_entry.data[CONF_PORT],
|
port=config_entry.data[CONF_PORT],
|
||||||
ssl=config_entry.data[CONF_SSL],
|
ssl=config_entry.data.get(CONF_SSL, DEFAULT_SSL),
|
||||||
)
|
)
|
||||||
except RainMachineError as err:
|
except RainMachineError as err:
|
||||||
_LOGGER.error("An error occurred: %s", err)
|
_LOGGER.error("An error occurred: %s", err)
|
||||||
@ -156,8 +153,10 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
rainmachine = RainMachine(
|
rainmachine = RainMachine(
|
||||||
hass,
|
hass,
|
||||||
controller,
|
controller,
|
||||||
config_entry.data[CONF_ZONE_RUN_TIME],
|
config_entry.data.get(CONF_ZONE_RUN_TIME, DEFAULT_ZONE_RUN),
|
||||||
config_entry.data[CONF_SCAN_INTERVAL],
|
config_entry.data.get(
|
||||||
|
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL.total_seconds()
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update the data object, which at this point (prior to any sensors registering
|
# Update the data object, which at this point (prior to any sensors registering
|
||||||
@ -260,9 +259,6 @@ async def async_unload_entry(hass, config_entry):
|
|||||||
"""Unload an OpenUV config entry."""
|
"""Unload an OpenUV config entry."""
|
||||||
hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
|
hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
|
||||||
|
|
||||||
remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id)
|
|
||||||
remove_listener()
|
|
||||||
|
|
||||||
tasks = [
|
tasks = [
|
||||||
hass.config_entries.async_forward_entry_unload(config_entry, component)
|
hass.config_entries.async_forward_entry_unload(config_entry, component)
|
||||||
for component in ("binary_sensor", "sensor", "switch")
|
for component in ("binary_sensor", "sensor", "switch")
|
||||||
|
@ -4,10 +4,22 @@ from regenmaschine.errors import RainMachineError
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT
|
from homeassistant.const import (
|
||||||
|
CONF_IP_ADDRESS,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_SCAN_INTERVAL,
|
||||||
|
CONF_SSL,
|
||||||
|
)
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
from .const import DEFAULT_PORT, DOMAIN # pylint: disable=unused-import
|
from .const import ( # pylint: disable=unused-import
|
||||||
|
CONF_ZONE_RUN_TIME,
|
||||||
|
DEFAULT_PORT,
|
||||||
|
DEFAULT_SCAN_INTERVAL,
|
||||||
|
DEFAULT_ZONE_RUN,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
@ -53,8 +65,8 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
user_input[CONF_IP_ADDRESS],
|
user_input[CONF_IP_ADDRESS],
|
||||||
user_input[CONF_PASSWORD],
|
user_input[CONF_PASSWORD],
|
||||||
websession,
|
websession,
|
||||||
port=user_input.get(CONF_PORT, DEFAULT_PORT),
|
port=user_input[CONF_PORT],
|
||||||
ssl=True,
|
ssl=user_input.get(CONF_SSL, True),
|
||||||
)
|
)
|
||||||
except RainMachineError:
|
except RainMachineError:
|
||||||
return await self._show_form({CONF_PASSWORD: "invalid_credentials"})
|
return await self._show_form({CONF_PASSWORD: "invalid_credentials"})
|
||||||
@ -63,5 +75,17 @@ class RainMachineFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
# access token without using the IP address and password, so we have to
|
# access token without using the IP address and password, so we have to
|
||||||
# store it:
|
# store it:
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=user_input[CONF_IP_ADDRESS], data=user_input
|
title=user_input[CONF_IP_ADDRESS],
|
||||||
|
data={
|
||||||
|
CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS],
|
||||||
|
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||||
|
CONF_PORT: user_input[CONF_PORT],
|
||||||
|
CONF_SSL: user_input.get(CONF_SSL, True),
|
||||||
|
CONF_SCAN_INTERVAL: user_input.get(
|
||||||
|
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL.total_seconds()
|
||||||
|
),
|
||||||
|
CONF_ZONE_RUN_TIME: user_input.get(
|
||||||
|
CONF_ZONE_RUN_TIME, DEFAULT_ZONE_RUN
|
||||||
|
),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
"""Define constants for the SimpliSafe component."""
|
"""Define constants for the SimpliSafe component."""
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
DOMAIN = "rainmachine"
|
DOMAIN = "rainmachine"
|
||||||
|
|
||||||
|
CONF_ZONE_RUN_TIME = "zone_run_time"
|
||||||
|
|
||||||
DATA_CLIENT = "client"
|
DATA_CLIENT = "client"
|
||||||
DATA_PROGRAMS = "programs"
|
DATA_PROGRAMS = "programs"
|
||||||
DATA_PROVISION_SETTINGS = "provision.settings"
|
DATA_PROVISION_SETTINGS = "provision.settings"
|
||||||
@ -10,6 +14,8 @@ DATA_ZONES = "zones"
|
|||||||
DATA_ZONES_DETAILS = "zones_details"
|
DATA_ZONES_DETAILS = "zones_details"
|
||||||
|
|
||||||
DEFAULT_PORT = 8080
|
DEFAULT_PORT = 8080
|
||||||
|
DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
DEFAULT_ZONE_RUN = 60 * 10
|
||||||
|
|
||||||
PROGRAM_UPDATE_TOPIC = f"{DOMAIN}_program_update"
|
PROGRAM_UPDATE_TOPIC = f"{DOMAIN}_program_update"
|
||||||
SENSOR_UPDATE_TOPIC = f"{DOMAIN}_data_update"
|
SENSOR_UPDATE_TOPIC = f"{DOMAIN}_data_update"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
"name": "Sighthound",
|
"name": "Sighthound",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/sighthound",
|
"documentation": "https://www.home-assistant.io/integrations/sighthound",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
|
"pillow==7.0.0",
|
||||||
"simplehound==0.3"
|
"simplehound==0.3"
|
||||||
],
|
],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "SimpliSafe",
|
"name": "SimpliSafe",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/simplisafe",
|
"documentation": "https://www.home-assistant.io/integrations/simplisafe",
|
||||||
"requirements": ["simplisafe-python==9.0.2"],
|
"requirements": ["simplisafe-python==9.0.3"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@bachya"]
|
"codeowners": ["@bachya"]
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ DOMAIN = "somfy"
|
|||||||
|
|
||||||
CONF_CLIENT_ID = "client_id"
|
CONF_CLIENT_ID = "client_id"
|
||||||
CONF_CLIENT_SECRET = "client_secret"
|
CONF_CLIENT_SECRET = "client_secret"
|
||||||
CONF_OPTIMISTIC = "optimisitic"
|
CONF_OPTIMISTIC = "optimistic"
|
||||||
|
|
||||||
SOMFY_AUTH_CALLBACK_PATH = "/auth/somfy/callback"
|
SOMFY_AUTH_CALLBACK_PATH = "/auth/somfy/callback"
|
||||||
SOMFY_AUTH_START = "/auth/somfy"
|
SOMFY_AUTH_START = "/auth/somfy"
|
||||||
@ -36,8 +36,8 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
{
|
{
|
||||||
DOMAIN: vol.Schema(
|
DOMAIN: vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_CLIENT_ID): cv.string,
|
vol.Inclusive(CONF_CLIENT_ID, "oauth"): cv.string,
|
||||||
vol.Required(CONF_CLIENT_SECRET): cv.string,
|
vol.Inclusive(CONF_CLIENT_SECRET, "oauth"): cv.string,
|
||||||
vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
|
vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -51,23 +51,21 @@ SOMFY_COMPONENTS = ["cover", "switch"]
|
|||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Set up the Somfy component."""
|
"""Set up the Somfy component."""
|
||||||
hass.data[DOMAIN] = {}
|
hass.data[DOMAIN] = {}
|
||||||
|
domain_config = config.get(DOMAIN, {})
|
||||||
|
hass.data[DOMAIN][CONF_OPTIMISTIC] = domain_config.get(CONF_OPTIMISTIC, False)
|
||||||
|
|
||||||
if DOMAIN not in config:
|
if CONF_CLIENT_ID in domain_config:
|
||||||
return True
|
config_flow.SomfyFlowHandler.async_register_implementation(
|
||||||
|
|
||||||
hass.data[DOMAIN][CONF_OPTIMISTIC] = config[DOMAIN][CONF_OPTIMISTIC]
|
|
||||||
|
|
||||||
config_flow.SomfyFlowHandler.async_register_implementation(
|
|
||||||
hass,
|
|
||||||
config_entry_oauth2_flow.LocalOAuth2Implementation(
|
|
||||||
hass,
|
hass,
|
||||||
DOMAIN,
|
config_entry_oauth2_flow.LocalOAuth2Implementation(
|
||||||
config[DOMAIN][CONF_CLIENT_ID],
|
hass,
|
||||||
config[DOMAIN][CONF_CLIENT_SECRET],
|
DOMAIN,
|
||||||
"https://accounts.somfy.com/oauth/oauth/v2/auth",
|
config[DOMAIN][CONF_CLIENT_ID],
|
||||||
"https://accounts.somfy.com/oauth/oauth/v2/token",
|
config[DOMAIN][CONF_CLIENT_SECRET],
|
||||||
),
|
"https://accounts.somfy.com/oauth/oauth/v2/auth",
|
||||||
)
|
"https://accounts.somfy.com/oauth/oauth/v2/token",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "velbus",
|
"domain": "velbus",
|
||||||
"name": "Velbus",
|
"name": "Velbus",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/velbus",
|
"documentation": "https://www.home-assistant.io/integrations/velbus",
|
||||||
"requirements": ["python-velbus==2.0.42"],
|
"requirements": ["python-velbus==2.0.43"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@Cereal2nd", "@brefra"]
|
"codeowners": ["@Cereal2nd", "@brefra"]
|
||||||
|
@ -89,9 +89,19 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
Will automatically load components to support devices found on the network.
|
Will automatically load components to support devices found on the network.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
hass.data[DATA_ZHA] = hass.data.get(DATA_ZHA, {})
|
zha_data = hass.data.setdefault(DATA_ZHA, {})
|
||||||
hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS] = []
|
config = zha_data.get(DATA_ZHA_CONFIG, {})
|
||||||
hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED] = asyncio.Event()
|
|
||||||
|
if config.get(CONF_ENABLE_QUIRKS, True):
|
||||||
|
# needs to be done here so that the ZHA module is finished loading
|
||||||
|
# before zhaquirks is imported
|
||||||
|
import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel, import-error
|
||||||
|
|
||||||
|
zha_gateway = ZHAGateway(hass, config, config_entry)
|
||||||
|
await zha_gateway.async_initialize()
|
||||||
|
|
||||||
|
zha_data[DATA_ZHA_DISPATCHERS] = []
|
||||||
|
zha_data[DATA_ZHA_PLATFORM_LOADED] = asyncio.Event()
|
||||||
platforms = []
|
platforms = []
|
||||||
for component in COMPONENTS:
|
for component in COMPONENTS:
|
||||||
platforms.append(
|
platforms.append(
|
||||||
@ -102,20 +112,10 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
|
|
||||||
async def _platforms_loaded():
|
async def _platforms_loaded():
|
||||||
await asyncio.gather(*platforms)
|
await asyncio.gather(*platforms)
|
||||||
hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED].set()
|
zha_data[DATA_ZHA_PLATFORM_LOADED].set()
|
||||||
|
|
||||||
hass.async_create_task(_platforms_loaded())
|
hass.async_create_task(_platforms_loaded())
|
||||||
|
|
||||||
config = hass.data[DATA_ZHA].get(DATA_ZHA_CONFIG, {})
|
|
||||||
|
|
||||||
if config.get(CONF_ENABLE_QUIRKS, True):
|
|
||||||
# needs to be done here so that the ZHA module is finished loading
|
|
||||||
# before zhaquirks is imported
|
|
||||||
import zhaquirks # noqa: F401 pylint: disable=unused-import, import-outside-toplevel, import-error
|
|
||||||
|
|
||||||
zha_gateway = ZHAGateway(hass, config, config_entry)
|
|
||||||
await zha_gateway.async_initialize()
|
|
||||||
|
|
||||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
device_registry = await hass.helpers.device_registry.async_get_registry()
|
||||||
device_registry.async_get_or_create(
|
device_registry.async_get_or_create(
|
||||||
config_entry_id=config_entry.entry_id,
|
config_entry_id=config_entry.entry_id,
|
||||||
@ -130,8 +130,8 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
|
|
||||||
async def async_zha_shutdown(event):
|
async def async_zha_shutdown(event):
|
||||||
"""Handle shutdown tasks."""
|
"""Handle shutdown tasks."""
|
||||||
await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].shutdown()
|
await zha_data[DATA_ZHA_GATEWAY].shutdown()
|
||||||
await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].async_update_device_storage()
|
await zha_data[DATA_ZHA_GATEWAY].async_update_device_storage()
|
||||||
|
|
||||||
hass.bus.async_listen_once(ha_const.EVENT_HOMEASSISTANT_STOP, async_zha_shutdown)
|
hass.bus.async_listen_once(ha_const.EVENT_HOMEASSISTANT_STOP, async_zha_shutdown)
|
||||||
hass.async_create_task(zha_gateway.async_load_devices())
|
hass.async_create_task(zha_gateway.async_load_devices())
|
||||||
|
@ -85,11 +85,11 @@ class ZigbeeChannel(LogMixin):
|
|||||||
self, cluster: zha_typing.ZigpyClusterType, ch_pool: zha_typing.ChannelPoolType
|
self, cluster: zha_typing.ZigpyClusterType, ch_pool: zha_typing.ChannelPoolType
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize ZigbeeChannel."""
|
"""Initialize ZigbeeChannel."""
|
||||||
self._channel_name = cluster.ep_attribute
|
self._generic_id = f"channel_0x{cluster.cluster_id:04x}"
|
||||||
|
self._channel_name = getattr(cluster, "ep_attribute", self._generic_id)
|
||||||
if self.CHANNEL_NAME:
|
if self.CHANNEL_NAME:
|
||||||
self._channel_name = self.CHANNEL_NAME
|
self._channel_name = self.CHANNEL_NAME
|
||||||
self._ch_pool = ch_pool
|
self._ch_pool = ch_pool
|
||||||
self._generic_id = f"channel_0x{cluster.cluster_id:04x}"
|
|
||||||
self._cluster = cluster
|
self._cluster = cluster
|
||||||
self._id = f"{ch_pool.id}:0x{cluster.cluster_id:04x}"
|
self._id = f"{ch_pool.id}:0x{cluster.cluster_id:04x}"
|
||||||
unique_id = ch_pool.unique_id.replace("-", ":")
|
unique_id = ch_pool.unique_id.replace("-", ":")
|
||||||
|
@ -7,10 +7,12 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
from serial import SerialException
|
||||||
import zigpy.device as zigpy_dev
|
import zigpy.device as zigpy_dev
|
||||||
|
|
||||||
from homeassistant.components.system_log import LogEntry, _figure_out_source
|
from homeassistant.components.system_log import LogEntry, _figure_out_source
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers.device_registry import (
|
from homeassistant.helpers.device_registry import (
|
||||||
CONNECTION_ZIGBEE,
|
CONNECTION_ZIGBEE,
|
||||||
async_get_registry as get_dev_reg,
|
async_get_registry as get_dev_reg,
|
||||||
@ -98,7 +100,6 @@ class ZHAGateway:
|
|||||||
self.ha_entity_registry = None
|
self.ha_entity_registry = None
|
||||||
self.application_controller = None
|
self.application_controller = None
|
||||||
self.radio_description = None
|
self.radio_description = None
|
||||||
hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self
|
|
||||||
self._log_levels = {
|
self._log_levels = {
|
||||||
DEBUG_LEVEL_ORIGINAL: async_capture_log_levels(),
|
DEBUG_LEVEL_ORIGINAL: async_capture_log_levels(),
|
||||||
DEBUG_LEVEL_CURRENT: async_capture_log_levels(),
|
DEBUG_LEVEL_CURRENT: async_capture_log_levels(),
|
||||||
@ -122,7 +123,11 @@ class ZHAGateway:
|
|||||||
radio_details = RADIO_TYPES[radio_type]
|
radio_details = RADIO_TYPES[radio_type]
|
||||||
radio = radio_details[ZHA_GW_RADIO]()
|
radio = radio_details[ZHA_GW_RADIO]()
|
||||||
self.radio_description = radio_details[ZHA_GW_RADIO_DESCRIPTION]
|
self.radio_description = radio_details[ZHA_GW_RADIO_DESCRIPTION]
|
||||||
await radio.connect(usb_path, baudrate)
|
try:
|
||||||
|
await radio.connect(usb_path, baudrate)
|
||||||
|
except (SerialException, OSError) as exception:
|
||||||
|
_LOGGER.error("Couldn't open serial port for ZHA: %s", str(exception))
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
if CONF_DATABASE in self._config:
|
if CONF_DATABASE in self._config:
|
||||||
database = self._config[CONF_DATABASE]
|
database = self._config[CONF_DATABASE]
|
||||||
@ -133,7 +138,22 @@ class ZHAGateway:
|
|||||||
apply_application_controller_patch(self)
|
apply_application_controller_patch(self)
|
||||||
self.application_controller.add_listener(self)
|
self.application_controller.add_listener(self)
|
||||||
self.application_controller.groups.add_listener(self)
|
self.application_controller.groups.add_listener(self)
|
||||||
await self.application_controller.startup(auto_form=True)
|
|
||||||
|
try:
|
||||||
|
res = await self.application_controller.startup(auto_form=True)
|
||||||
|
if res is False:
|
||||||
|
await self.application_controller.shutdown()
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
except asyncio.TimeoutError as exception:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Couldn't start %s coordinator",
|
||||||
|
radio_details[ZHA_GW_RADIO_DESCRIPTION],
|
||||||
|
exc_info=exception,
|
||||||
|
)
|
||||||
|
radio.close()
|
||||||
|
raise ConfigEntryNotReady from exception
|
||||||
|
|
||||||
|
self._hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self
|
||||||
self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(
|
self._hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID] = str(
|
||||||
self.application_controller.ieee
|
self.application_controller.ieee
|
||||||
)
|
)
|
||||||
|
@ -565,9 +565,23 @@ def _log_pkg_error(package: str, component: str, config: Dict, message: str) ->
|
|||||||
def _identify_config_schema(module: ModuleType) -> Tuple[Optional[str], Optional[Dict]]:
|
def _identify_config_schema(module: ModuleType) -> Tuple[Optional[str], Optional[Dict]]:
|
||||||
"""Extract the schema and identify list or dict based."""
|
"""Extract the schema and identify list or dict based."""
|
||||||
try:
|
try:
|
||||||
schema = module.CONFIG_SCHEMA.schema[module.DOMAIN] # type: ignore
|
key = next(k for k in module.CONFIG_SCHEMA.schema if k == module.DOMAIN) # type: ignore
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, StopIteration):
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
schema = module.CONFIG_SCHEMA.schema[key] # type: ignore
|
||||||
|
|
||||||
|
if hasattr(key, "default"):
|
||||||
|
default_value = schema(key.default())
|
||||||
|
|
||||||
|
if isinstance(default_value, dict):
|
||||||
|
return "dict", schema
|
||||||
|
|
||||||
|
if isinstance(default_value, list):
|
||||||
|
return "list", schema
|
||||||
|
|
||||||
|
return None, None
|
||||||
|
|
||||||
t_schema = str(schema)
|
t_schema = str(schema)
|
||||||
if t_schema.startswith("{") or "schema_with_slug_keys" in t_schema:
|
if t_schema.startswith("{") or "schema_with_slug_keys" in t_schema:
|
||||||
return ("dict", schema)
|
return ("dict", schema)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 107
|
MINOR_VERSION = 107
|
||||||
PATCH_VERSION = "1"
|
PATCH_VERSION = "2"
|
||||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER = (3, 7, 0)
|
REQUIRED_PYTHON_VER = (3, 7, 0)
|
||||||
|
@ -163,7 +163,7 @@ aioftp==0.12.0
|
|||||||
aioharmony==0.1.13
|
aioharmony==0.1.13
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit[IP]==0.2.29.1
|
aiohomekit[IP]==0.2.29.2
|
||||||
|
|
||||||
# homeassistant.components.emulated_hue
|
# homeassistant.components.emulated_hue
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
@ -1017,6 +1017,7 @@ pilight==0.1.1
|
|||||||
# homeassistant.components.proxy
|
# homeassistant.components.proxy
|
||||||
# homeassistant.components.qrcode
|
# homeassistant.components.qrcode
|
||||||
# homeassistant.components.seven_segments
|
# homeassistant.components.seven_segments
|
||||||
|
# homeassistant.components.sighthound
|
||||||
# homeassistant.components.tensorflow
|
# homeassistant.components.tensorflow
|
||||||
pillow==7.0.0
|
pillow==7.0.0
|
||||||
|
|
||||||
@ -1665,7 +1666,7 @@ python-telnet-vlc==1.0.4
|
|||||||
python-twitch-client==0.6.0
|
python-twitch-client==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.velbus
|
# homeassistant.components.velbus
|
||||||
python-velbus==2.0.42
|
python-velbus==2.0.43
|
||||||
|
|
||||||
# homeassistant.components.vlc
|
# homeassistant.components.vlc
|
||||||
python-vlc==1.1.2
|
python-vlc==1.1.2
|
||||||
@ -1855,7 +1856,7 @@ simplehound==0.3
|
|||||||
simplepush==1.1.4
|
simplepush==1.1.4
|
||||||
|
|
||||||
# homeassistant.components.simplisafe
|
# homeassistant.components.simplisafe
|
||||||
simplisafe-python==9.0.2
|
simplisafe-python==9.0.3
|
||||||
|
|
||||||
# homeassistant.components.sisyphus
|
# homeassistant.components.sisyphus
|
||||||
sisyphus-control==2.2.1
|
sisyphus-control==2.2.1
|
||||||
|
@ -62,7 +62,7 @@ aiobotocore==0.11.1
|
|||||||
aioesphomeapi==2.6.1
|
aioesphomeapi==2.6.1
|
||||||
|
|
||||||
# homeassistant.components.homekit_controller
|
# homeassistant.components.homekit_controller
|
||||||
aiohomekit[IP]==0.2.29.1
|
aiohomekit[IP]==0.2.29.2
|
||||||
|
|
||||||
# homeassistant.components.emulated_hue
|
# homeassistant.components.emulated_hue
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
@ -365,6 +365,14 @@ pexpect==4.6.0
|
|||||||
# homeassistant.components.pilight
|
# homeassistant.components.pilight
|
||||||
pilight==0.1.1
|
pilight==0.1.1
|
||||||
|
|
||||||
|
# homeassistant.components.doods
|
||||||
|
# homeassistant.components.proxy
|
||||||
|
# homeassistant.components.qrcode
|
||||||
|
# homeassistant.components.seven_segments
|
||||||
|
# homeassistant.components.sighthound
|
||||||
|
# homeassistant.components.tensorflow
|
||||||
|
pillow==7.0.0
|
||||||
|
|
||||||
# homeassistant.components.plex
|
# homeassistant.components.plex
|
||||||
plexapi==3.3.0
|
plexapi==3.3.0
|
||||||
|
|
||||||
@ -587,7 +595,7 @@ python-nest==4.1.0
|
|||||||
python-twitch-client==0.6.0
|
python-twitch-client==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.velbus
|
# homeassistant.components.velbus
|
||||||
python-velbus==2.0.42
|
python-velbus==2.0.43
|
||||||
|
|
||||||
# homeassistant.components.awair
|
# homeassistant.components.awair
|
||||||
python_awair==0.0.4
|
python_awair==0.0.4
|
||||||
@ -641,7 +649,7 @@ sentry-sdk==0.13.5
|
|||||||
simplehound==0.3
|
simplehound==0.3
|
||||||
|
|
||||||
# homeassistant.components.simplisafe
|
# homeassistant.components.simplisafe
|
||||||
simplisafe-python==9.0.2
|
simplisafe-python==9.0.3
|
||||||
|
|
||||||
# homeassistant.components.sleepiq
|
# homeassistant.components.sleepiq
|
||||||
sleepyq==0.7
|
sleepyq==0.7
|
||||||
|
@ -4,7 +4,7 @@ from unittest.mock import patch
|
|||||||
from regenmaschine.errors import RainMachineError
|
from regenmaschine.errors import RainMachineError
|
||||||
|
|
||||||
from homeassistant import data_entry_flow
|
from homeassistant import data_entry_flow
|
||||||
from homeassistant.components.rainmachine import DOMAIN, config_flow
|
from homeassistant.components.rainmachine import CONF_ZONE_RUN_TIME, DOMAIN, config_flow
|
||||||
from homeassistant.config_entries import SOURCE_USER
|
from homeassistant.config_entries import SOURCE_USER
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_IP_ADDRESS,
|
CONF_IP_ADDRESS,
|
||||||
@ -98,6 +98,7 @@ async def test_step_import(hass):
|
|||||||
CONF_PORT: 8080,
|
CONF_PORT: 8080,
|
||||||
CONF_SSL: True,
|
CONF_SSL: True,
|
||||||
CONF_SCAN_INTERVAL: 60,
|
CONF_SCAN_INTERVAL: 60,
|
||||||
|
CONF_ZONE_RUN_TIME: 600,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -129,4 +130,5 @@ async def test_step_user(hass):
|
|||||||
CONF_PORT: 8080,
|
CONF_PORT: 8080,
|
||||||
CONF_SSL: True,
|
CONF_SSL: True,
|
||||||
CONF_SCAN_INTERVAL: 60,
|
CONF_SCAN_INTERVAL: 60,
|
||||||
|
CONF_ZONE_RUN_TIME: 600,
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ async def test_tracked_devices(hass):
|
|||||||
devices_response=[DEVICE_1, DEVICE_2],
|
devices_response=[DEVICE_1, DEVICE_2],
|
||||||
known_wireless_clients=(CLIENT_4["mac"],),
|
known_wireless_clients=(CLIENT_4["mac"],),
|
||||||
)
|
)
|
||||||
assert len(hass.states.async_entity_ids("device_tracker")) == 5
|
assert len(hass.states.async_entity_ids("device_tracker")) == 6
|
||||||
|
|
||||||
client_1 = hass.states.get("device_tracker.client_1")
|
client_1 = hass.states.get("device_tracker.client_1")
|
||||||
assert client_1 is not None
|
assert client_1 is not None
|
||||||
@ -349,7 +349,7 @@ async def test_option_ssid_filter(hass):
|
|||||||
controller = await setup_unifi_integration(
|
controller = await setup_unifi_integration(
|
||||||
hass, options={CONF_SSID_FILTER: ["ssid"]}, clients_response=[CLIENT_3],
|
hass, options={CONF_SSID_FILTER: ["ssid"]}, clients_response=[CLIENT_3],
|
||||||
)
|
)
|
||||||
assert len(hass.states.async_entity_ids("device_tracker")) == 0
|
assert len(hass.states.async_entity_ids("device_tracker")) == 1
|
||||||
|
|
||||||
# SSID filter active
|
# SSID filter active
|
||||||
client_3 = hass.states.get("device_tracker.client_3")
|
client_3 = hass.states.get("device_tracker.client_3")
|
||||||
|
@ -10,6 +10,7 @@ from unittest.mock import Mock
|
|||||||
import asynctest
|
import asynctest
|
||||||
from asynctest import CoroutineMock, patch
|
from asynctest import CoroutineMock, patch
|
||||||
import pytest
|
import pytest
|
||||||
|
import voluptuous as vol
|
||||||
from voluptuous import Invalid, MultipleInvalid
|
from voluptuous import Invalid, MultipleInvalid
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@ -989,3 +990,20 @@ async def test_component_config_exceptions(hass, caplog):
|
|||||||
"Unknown error validating config for test_platform platform for test_domain component with PLATFORM_SCHEMA"
|
"Unknown error validating config for test_platform platform for test_domain component with PLATFORM_SCHEMA"
|
||||||
in caplog.text
|
in caplog.text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"domain, schema, expected",
|
||||||
|
[
|
||||||
|
("zone", vol.Schema({vol.Optional("zone", default=[]): list}), "list"),
|
||||||
|
("zone", vol.Schema({vol.Optional("zone", default=dict): dict}), "dict"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_identify_config_schema(domain, schema, expected):
|
||||||
|
"""Test identify config schema."""
|
||||||
|
assert (
|
||||||
|
config_util._identify_config_schema(Mock(DOMAIN=domain, CONFIG_SCHEMA=schema))[
|
||||||
|
0
|
||||||
|
]
|
||||||
|
== expected
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user