mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Use own CoAP lib and support for multicast updates (#42718)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
e5dee965f1
commit
f45075eeb5
@ -387,7 +387,7 @@ homeassistant/components/seven_segments/* @fabaff
|
||||
homeassistant/components/seventeentrack/* @bachya
|
||||
homeassistant/components/sharkiq/* @ajmarks
|
||||
homeassistant/components/shell_command/* @home-assistant/core
|
||||
homeassistant/components/shelly/* @balloob @bieniu
|
||||
homeassistant/components/shelly/* @balloob @bieniu @thecode
|
||||
homeassistant/components/shiftr/* @fabaff
|
||||
homeassistant/components/shodan/* @fabaff
|
||||
homeassistant/components/sighthound/* @robmarkcole
|
||||
|
@ -2,8 +2,8 @@
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from socket import gethostbyname
|
||||
|
||||
import aiocoap
|
||||
import aioshelly
|
||||
import async_timeout
|
||||
|
||||
@ -14,44 +14,59 @@ from homeassistant.const import (
|
||||
CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client, device_registry, update_coordinator
|
||||
from homeassistant.helpers import (
|
||||
aiohttp_client,
|
||||
device_registry,
|
||||
singleton,
|
||||
update_coordinator,
|
||||
)
|
||||
|
||||
from .const import COAP_CONTEXT, DATA_CONFIG_ENTRY, DOMAIN
|
||||
from .const import DATA_CONFIG_ENTRY, DOMAIN
|
||||
|
||||
PLATFORMS = ["binary_sensor", "cover", "light", "sensor", "switch"]
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
"""Set up the Shelly component."""
|
||||
hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}}
|
||||
hass.data[DOMAIN][COAP_CONTEXT] = await aiocoap.Context.create_client_context()
|
||||
@singleton.singleton("shelly_coap")
|
||||
async def get_coap_context(hass):
|
||||
"""Get CoAP context to be used in all Shelly devices."""
|
||||
context = aioshelly.COAP()
|
||||
await context.initialize()
|
||||
|
||||
async def shutdown_listener(*_):
|
||||
"""Home Assistant shutdown listener."""
|
||||
await hass.data[DOMAIN][COAP_CONTEXT].shutdown()
|
||||
@callback
|
||||
def shutdown_listener(ev):
|
||||
context.close()
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown_listener)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
"""Set up the Shelly component."""
|
||||
hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}}
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Set up Shelly from a config entry."""
|
||||
temperature_unit = "C" if hass.config.units.is_metric else "F"
|
||||
|
||||
ip_address = await hass.async_add_executor_job(gethostbyname, entry.data[CONF_HOST])
|
||||
|
||||
options = aioshelly.ConnectionOptions(
|
||||
entry.data[CONF_HOST],
|
||||
ip_address,
|
||||
entry.data.get(CONF_USERNAME),
|
||||
entry.data.get(CONF_PASSWORD),
|
||||
temperature_unit,
|
||||
)
|
||||
|
||||
coap_context = hass.data[DOMAIN][COAP_CONTEXT]
|
||||
coap_context = await get_coap_context(hass)
|
||||
|
||||
try:
|
||||
async with async_timeout.timeout(10):
|
||||
async with async_timeout.timeout(5):
|
||||
device = await aioshelly.Device.create(
|
||||
aiohttp_client.async_get_clientsession(hass),
|
||||
coap_context,
|
||||
@ -78,23 +93,35 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
||||
|
||||
def __init__(self, hass, entry, device: aioshelly.Device):
|
||||
"""Initialize the Shelly device wrapper."""
|
||||
sleep_mode = device.settings.get("sleep_mode")
|
||||
|
||||
if sleep_mode:
|
||||
sleep_period = sleep_mode["period"]
|
||||
if sleep_mode["unit"] == "h":
|
||||
sleep_period *= 60 # hours to minutes
|
||||
|
||||
update_interval = 1.2 * sleep_period * 60 # minutes to seconds
|
||||
else:
|
||||
update_interval = 2 * device.settings["coiot"]["update_period"]
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=device.settings["name"] or device.settings["device"]["hostname"],
|
||||
update_interval=timedelta(seconds=5),
|
||||
update_interval=timedelta(seconds=update_interval),
|
||||
)
|
||||
self.hass = hass
|
||||
self.entry = entry
|
||||
self.device = device
|
||||
|
||||
self.device.subscribe_updates(self.async_set_updated_data)
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""Fetch data."""
|
||||
|
||||
try:
|
||||
async with async_timeout.timeout(5):
|
||||
return await self.device.update()
|
||||
except (aiocoap.error.Error, OSError) as err:
|
||||
except OSError as err:
|
||||
raise update_coordinator.UpdateFailed("Error fetching data") from err
|
||||
|
||||
@property
|
||||
@ -122,6 +149,10 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
||||
sw_version=self.device.settings["fw"],
|
||||
)
|
||||
|
||||
def shutdown(self):
|
||||
"""Shutdown the wrapper."""
|
||||
self.device.shutdown()
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Unload a config entry."""
|
||||
@ -134,6 +165,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
)
|
||||
)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY].pop(entry.entry_id)
|
||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY].pop(entry.entry_id).shutdown()
|
||||
|
||||
return unload_ok
|
||||
|
@ -1,8 +1,8 @@
|
||||
"""Config flow for Shelly integration."""
|
||||
import asyncio
|
||||
import logging
|
||||
from socket import gethostbyname
|
||||
|
||||
import aiocoap
|
||||
import aiohttp
|
||||
import aioshelly
|
||||
import async_timeout
|
||||
@ -17,6 +17,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
from .__init__ import get_coap_context
|
||||
from .const import DOMAIN # pylint:disable=unused-import
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -37,10 +38,13 @@ async def validate_input(hass: core.HomeAssistant, host, data):
|
||||
|
||||
Data has the keys from DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
ip_address = await hass.async_add_executor_job(gethostbyname, host)
|
||||
|
||||
options = aioshelly.ConnectionOptions(
|
||||
host, data.get(CONF_USERNAME), data.get(CONF_PASSWORD)
|
||||
ip_address, data.get(CONF_USERNAME), data.get(CONF_PASSWORD)
|
||||
)
|
||||
coap_context = await aiocoap.Context.create_client_context()
|
||||
coap_context = await get_coap_context(hass)
|
||||
|
||||
async with async_timeout.timeout(5):
|
||||
device = await aioshelly.Device.create(
|
||||
aiohttp_client.async_get_clientsession(hass),
|
||||
@ -48,7 +52,7 @@ async def validate_input(hass: core.HomeAssistant, host, data):
|
||||
options,
|
||||
)
|
||||
|
||||
await coap_context.shutdown()
|
||||
device.shutdown()
|
||||
|
||||
# Return info that you want to store in the config entry.
|
||||
return {
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""Constants for the Shelly integration."""
|
||||
|
||||
COAP_CONTEXT = "coap_context"
|
||||
DATA_CONFIG_ENTRY = "config_entry"
|
||||
DOMAIN = "shelly"
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Shelly",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/shelly",
|
||||
"requirements": ["aioshelly==0.4.0"],
|
||||
"requirements": ["aioshelly==0.5.0"],
|
||||
"zeroconf": [{ "type": "_http._tcp.local.", "name": "shelly*" }],
|
||||
"codeowners": ["@balloob", "@bieniu"]
|
||||
"codeowners": ["@balloob", "@bieniu", "@thecode"]
|
||||
}
|
||||
|
@ -221,7 +221,7 @@ aiopvpc==2.0.2
|
||||
aiopylgtv==0.3.3
|
||||
|
||||
# homeassistant.components.shelly
|
||||
aioshelly==0.4.0
|
||||
aioshelly==0.5.0
|
||||
|
||||
# homeassistant.components.switcher_kis
|
||||
aioswitcher==1.2.1
|
||||
|
@ -137,7 +137,7 @@ aiopvpc==2.0.2
|
||||
aiopylgtv==0.3.3
|
||||
|
||||
# homeassistant.components.shelly
|
||||
aioshelly==0.4.0
|
||||
aioshelly==0.5.0
|
||||
|
||||
# homeassistant.components.switcher_kis
|
||||
aioswitcher==1.2.1
|
||||
|
@ -43,7 +43,6 @@ async def test_form(hass):
|
||||
"aioshelly.Device.create",
|
||||
new=AsyncMock(
|
||||
return_value=Mock(
|
||||
shutdown=AsyncMock(),
|
||||
settings=MOCK_SETTINGS,
|
||||
)
|
||||
),
|
||||
@ -88,7 +87,6 @@ async def test_title_without_name_and_prefix(hass):
|
||||
"aioshelly.Device.create",
|
||||
new=AsyncMock(
|
||||
return_value=Mock(
|
||||
shutdown=AsyncMock(),
|
||||
settings=settings,
|
||||
)
|
||||
),
|
||||
@ -137,7 +135,6 @@ async def test_form_auth(hass):
|
||||
"aioshelly.Device.create",
|
||||
new=AsyncMock(
|
||||
return_value=Mock(
|
||||
shutdown=AsyncMock(),
|
||||
settings=MOCK_SETTINGS,
|
||||
)
|
||||
),
|
||||
@ -309,7 +306,6 @@ async def test_zeroconf(hass):
|
||||
"aioshelly.Device.create",
|
||||
new=AsyncMock(
|
||||
return_value=Mock(
|
||||
shutdown=AsyncMock(),
|
||||
settings=MOCK_SETTINGS,
|
||||
)
|
||||
),
|
||||
@ -466,7 +462,6 @@ async def test_zeroconf_require_auth(hass):
|
||||
"aioshelly.Device.create",
|
||||
new=AsyncMock(
|
||||
return_value=Mock(
|
||||
shutdown=AsyncMock(),
|
||||
settings=MOCK_SETTINGS,
|
||||
)
|
||||
),
|
||||
|
Loading…
x
Reference in New Issue
Block a user