mirror of
https://github.com/home-assistant/core.git
synced 2025-07-10 06:47:09 +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/seventeentrack/* @bachya
|
||||||
homeassistant/components/sharkiq/* @ajmarks
|
homeassistant/components/sharkiq/* @ajmarks
|
||||||
homeassistant/components/shell_command/* @home-assistant/core
|
homeassistant/components/shell_command/* @home-assistant/core
|
||||||
homeassistant/components/shelly/* @balloob @bieniu
|
homeassistant/components/shelly/* @balloob @bieniu @thecode
|
||||||
homeassistant/components/shiftr/* @fabaff
|
homeassistant/components/shiftr/* @fabaff
|
||||||
homeassistant/components/shodan/* @fabaff
|
homeassistant/components/shodan/* @fabaff
|
||||||
homeassistant/components/sighthound/* @robmarkcole
|
homeassistant/components/sighthound/* @robmarkcole
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
from socket import gethostbyname
|
||||||
|
|
||||||
import aiocoap
|
|
||||||
import aioshelly
|
import aioshelly
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
|
||||||
@ -14,44 +14,59 @@ from homeassistant.const import (
|
|||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
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"]
|
PLATFORMS = ["binary_sensor", "cover", "light", "sensor", "switch"]
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: dict):
|
@singleton.singleton("shelly_coap")
|
||||||
"""Set up the Shelly component."""
|
async def get_coap_context(hass):
|
||||||
hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}}
|
"""Get CoAP context to be used in all Shelly devices."""
|
||||||
hass.data[DOMAIN][COAP_CONTEXT] = await aiocoap.Context.create_client_context()
|
context = aioshelly.COAP()
|
||||||
|
await context.initialize()
|
||||||
|
|
||||||
async def shutdown_listener(*_):
|
@callback
|
||||||
"""Home Assistant shutdown listener."""
|
def shutdown_listener(ev):
|
||||||
await hass.data[DOMAIN][COAP_CONTEXT].shutdown()
|
context.close()
|
||||||
|
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown_listener)
|
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
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
"""Set up Shelly from a config entry."""
|
"""Set up Shelly from a config entry."""
|
||||||
temperature_unit = "C" if hass.config.units.is_metric else "F"
|
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(
|
options = aioshelly.ConnectionOptions(
|
||||||
entry.data[CONF_HOST],
|
ip_address,
|
||||||
entry.data.get(CONF_USERNAME),
|
entry.data.get(CONF_USERNAME),
|
||||||
entry.data.get(CONF_PASSWORD),
|
entry.data.get(CONF_PASSWORD),
|
||||||
temperature_unit,
|
temperature_unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
coap_context = hass.data[DOMAIN][COAP_CONTEXT]
|
coap_context = await get_coap_context(hass)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(10):
|
async with async_timeout.timeout(5):
|
||||||
device = await aioshelly.Device.create(
|
device = await aioshelly.Device.create(
|
||||||
aiohttp_client.async_get_clientsession(hass),
|
aiohttp_client.async_get_clientsession(hass),
|
||||||
coap_context,
|
coap_context,
|
||||||
@ -78,23 +93,35 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
|||||||
|
|
||||||
def __init__(self, hass, entry, device: aioshelly.Device):
|
def __init__(self, hass, entry, device: aioshelly.Device):
|
||||||
"""Initialize the Shelly device wrapper."""
|
"""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__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
name=device.settings["name"] or device.settings["device"]["hostname"],
|
name=device.settings["name"] or device.settings["device"]["hostname"],
|
||||||
update_interval=timedelta(seconds=5),
|
update_interval=timedelta(seconds=update_interval),
|
||||||
)
|
)
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.entry = entry
|
self.entry = entry
|
||||||
self.device = device
|
self.device = device
|
||||||
|
|
||||||
|
self.device.subscribe_updates(self.async_set_updated_data)
|
||||||
|
|
||||||
async def _async_update_data(self):
|
async def _async_update_data(self):
|
||||||
"""Fetch data."""
|
"""Fetch data."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with async_timeout.timeout(5):
|
async with async_timeout.timeout(5):
|
||||||
return await self.device.update()
|
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
|
raise update_coordinator.UpdateFailed("Error fetching data") from err
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -122,6 +149,10 @@ class ShellyDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
|||||||
sw_version=self.device.settings["fw"],
|
sw_version=self.device.settings["fw"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
"""Shutdown the wrapper."""
|
||||||
|
self.device.shutdown()
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
@ -134,6 +165,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
if unload_ok:
|
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
|
return unload_ok
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""Config flow for Shelly integration."""
|
"""Config flow for Shelly integration."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from socket import gethostbyname
|
||||||
|
|
||||||
import aiocoap
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import aioshelly
|
import aioshelly
|
||||||
import async_timeout
|
import async_timeout
|
||||||
@ -17,6 +17,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
|
from .__init__ import get_coap_context
|
||||||
from .const import DOMAIN # pylint:disable=unused-import
|
from .const import DOMAIN # pylint:disable=unused-import
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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.
|
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(
|
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):
|
async with async_timeout.timeout(5):
|
||||||
device = await aioshelly.Device.create(
|
device = await aioshelly.Device.create(
|
||||||
aiohttp_client.async_get_clientsession(hass),
|
aiohttp_client.async_get_clientsession(hass),
|
||||||
@ -48,7 +52,7 @@ async def validate_input(hass: core.HomeAssistant, host, data):
|
|||||||
options,
|
options,
|
||||||
)
|
)
|
||||||
|
|
||||||
await coap_context.shutdown()
|
device.shutdown()
|
||||||
|
|
||||||
# Return info that you want to store in the config entry.
|
# Return info that you want to store in the config entry.
|
||||||
return {
|
return {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""Constants for the Shelly integration."""
|
"""Constants for the Shelly integration."""
|
||||||
|
|
||||||
COAP_CONTEXT = "coap_context"
|
|
||||||
DATA_CONFIG_ENTRY = "config_entry"
|
DATA_CONFIG_ENTRY = "config_entry"
|
||||||
DOMAIN = "shelly"
|
DOMAIN = "shelly"
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Shelly",
|
"name": "Shelly",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/shelly",
|
"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*" }],
|
"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
|
aiopylgtv==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.shelly
|
# homeassistant.components.shelly
|
||||||
aioshelly==0.4.0
|
aioshelly==0.5.0
|
||||||
|
|
||||||
# homeassistant.components.switcher_kis
|
# homeassistant.components.switcher_kis
|
||||||
aioswitcher==1.2.1
|
aioswitcher==1.2.1
|
||||||
|
@ -137,7 +137,7 @@ aiopvpc==2.0.2
|
|||||||
aiopylgtv==0.3.3
|
aiopylgtv==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.shelly
|
# homeassistant.components.shelly
|
||||||
aioshelly==0.4.0
|
aioshelly==0.5.0
|
||||||
|
|
||||||
# homeassistant.components.switcher_kis
|
# homeassistant.components.switcher_kis
|
||||||
aioswitcher==1.2.1
|
aioswitcher==1.2.1
|
||||||
|
@ -43,7 +43,6 @@ async def test_form(hass):
|
|||||||
"aioshelly.Device.create",
|
"aioshelly.Device.create",
|
||||||
new=AsyncMock(
|
new=AsyncMock(
|
||||||
return_value=Mock(
|
return_value=Mock(
|
||||||
shutdown=AsyncMock(),
|
|
||||||
settings=MOCK_SETTINGS,
|
settings=MOCK_SETTINGS,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -88,7 +87,6 @@ async def test_title_without_name_and_prefix(hass):
|
|||||||
"aioshelly.Device.create",
|
"aioshelly.Device.create",
|
||||||
new=AsyncMock(
|
new=AsyncMock(
|
||||||
return_value=Mock(
|
return_value=Mock(
|
||||||
shutdown=AsyncMock(),
|
|
||||||
settings=settings,
|
settings=settings,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -137,7 +135,6 @@ async def test_form_auth(hass):
|
|||||||
"aioshelly.Device.create",
|
"aioshelly.Device.create",
|
||||||
new=AsyncMock(
|
new=AsyncMock(
|
||||||
return_value=Mock(
|
return_value=Mock(
|
||||||
shutdown=AsyncMock(),
|
|
||||||
settings=MOCK_SETTINGS,
|
settings=MOCK_SETTINGS,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -309,7 +306,6 @@ async def test_zeroconf(hass):
|
|||||||
"aioshelly.Device.create",
|
"aioshelly.Device.create",
|
||||||
new=AsyncMock(
|
new=AsyncMock(
|
||||||
return_value=Mock(
|
return_value=Mock(
|
||||||
shutdown=AsyncMock(),
|
|
||||||
settings=MOCK_SETTINGS,
|
settings=MOCK_SETTINGS,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -466,7 +462,6 @@ async def test_zeroconf_require_auth(hass):
|
|||||||
"aioshelly.Device.create",
|
"aioshelly.Device.create",
|
||||||
new=AsyncMock(
|
new=AsyncMock(
|
||||||
return_value=Mock(
|
return_value=Mock(
|
||||||
shutdown=AsyncMock(),
|
|
||||||
settings=MOCK_SETTINGS,
|
settings=MOCK_SETTINGS,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user