Add reconnect logic and proper reporting to MotionMount integration (#125670)

* Add reconnect logic and proper reporting

* Use snake_case

* Log on warning, not on info

* Reduce line length

* Refactor non-raising code out of try blocks

* Remove `_ensure_connected()` from action functions
This commit is contained in:
RJPoelstra 2024-09-16 11:56:13 +02:00 committed by GitHub
parent 18e2c2f6dd
commit e6b86b662a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 59 additions and 9 deletions

View File

@ -1,5 +1,7 @@
"""Support for MotionMount sensors.""" """Support for MotionMount sensors."""
import logging
import socket
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import motionmount import motionmount
@ -12,6 +14,8 @@ from homeassistant.helpers.entity import Entity
from .const import DOMAIN, EMPTY_MAC from .const import DOMAIN, EMPTY_MAC
_LOGGER = logging.getLogger(__name__)
class MotionMountEntity(Entity): class MotionMountEntity(Entity):
"""Representation of a MotionMount entity.""" """Representation of a MotionMount entity."""
@ -70,3 +74,23 @@ class MotionMountEntity(Entity):
self.mm.remove_listener(self.async_write_ha_state) self.mm.remove_listener(self.async_write_ha_state)
self.mm.remove_listener(self.update_name) self.mm.remove_listener(self.update_name)
await super().async_will_remove_from_hass() await super().async_will_remove_from_hass()
async def _ensure_connected(self) -> bool:
"""Make sure there is a connection with the MotionMount.
Returns false if the connection failed to be ensured.
"""
if self.mm.is_connected:
return True
try:
await self.mm.connect()
except (ConnectionError, TimeoutError, socket.gaierror):
# We're not interested in exceptions here. In case of a failed connection
# the try/except from the caller will report it.
# The purpose of `_ensure_connected()` is only to make sure we try to
# reconnect, where failures should not be logged each time
return False
else:
_LOGGER.warning("Successfully reconnected to MotionMount")
return True

View File

@ -1,11 +1,14 @@
"""Support for MotionMount numeric control.""" """Support for MotionMount numeric control."""
import socket
import motionmount import motionmount
from homeassistant.components.number import NumberEntity from homeassistant.components.number import NumberEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
@ -46,7 +49,10 @@ class MotionMountExtension(MotionMountEntity, NumberEntity):
async def async_set_native_value(self, value: float) -> None: async def async_set_native_value(self, value: float) -> None:
"""Set the new value for extension.""" """Set the new value for extension."""
try:
await self.mm.set_extension(int(value)) await self.mm.set_extension(int(value))
except (TimeoutError, socket.gaierror) as ex:
raise HomeAssistantError("Failed to communicate with MotionMount") from ex
class MotionMountTurn(MotionMountEntity, NumberEntity): class MotionMountTurn(MotionMountEntity, NumberEntity):
@ -69,4 +75,7 @@ class MotionMountTurn(MotionMountEntity, NumberEntity):
async def async_set_native_value(self, value: float) -> None: async def async_set_native_value(self, value: float) -> None:
"""Set the new value for turn.""" """Set the new value for turn."""
try:
await self.mm.set_turn(int(value * -1)) await self.mm.set_turn(int(value * -1))
except (TimeoutError, socket.gaierror) as ex:
raise HomeAssistantError("Failed to communicate with MotionMount") from ex

View File

@ -1,15 +1,23 @@
"""Support for MotionMount numeric control.""" """Support for MotionMount numeric control."""
from datetime import timedelta
import logging
import socket
import motionmount import motionmount
from homeassistant.components.select import SelectEntity from homeassistant.components.select import SelectEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, WALL_PRESET_NAME from .const import DOMAIN, WALL_PRESET_NAME
from .entity import MotionMountEntity from .entity import MotionMountEntity
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=60)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
@ -23,6 +31,7 @@ async def async_setup_entry(
class MotionMountPresets(MotionMountEntity, SelectEntity): class MotionMountPresets(MotionMountEntity, SelectEntity):
"""The presets of a MotionMount.""" """The presets of a MotionMount."""
_attr_should_poll = True
_attr_translation_key = "motionmount_preset" _attr_translation_key = "motionmount_preset"
def __init__( def __init__(
@ -44,7 +53,14 @@ class MotionMountPresets(MotionMountEntity, SelectEntity):
async def async_update(self) -> None: async def async_update(self) -> None:
"""Get latest state from MotionMount.""" """Get latest state from MotionMount."""
if not await self._ensure_connected():
return
try:
self._presets = await self.mm.get_presets() self._presets = await self.mm.get_presets()
except (TimeoutError, socket.gaierror) as ex:
_LOGGER.warning("Failed to communicate with MotionMount: %s", ex)
else:
self._update_options(self._presets) self._update_options(self._presets)
@property @property
@ -72,8 +88,9 @@ class MotionMountPresets(MotionMountEntity, SelectEntity):
async def async_select_option(self, option: str) -> None: async def async_select_option(self, option: str) -> None:
"""Set the new option.""" """Set the new option."""
index = int(option[:1]) index = int(option[:1])
try:
await self.mm.go_to_preset(index) await self.mm.go_to_preset(index)
except (TimeoutError, socket.gaierror) as ex:
raise HomeAssistantError("Failed to communicate with MotionMount") from ex
else:
self._attr_current_option = option self._attr_current_option = option
# Perform an update so we detect changes to the presets (changes are not pushed)
self.async_schedule_update_ha_state(True)