diff --git a/changelog b/changelog index b971356a..2bbe38b4 100644 --- a/changelog +++ b/changelog @@ -1,4 +1,7 @@ aarlo +0.8.1a10 + Add import notice + Add siren support. 0.8.1a9 Fix a location issue (again) 0.8.1a8 diff --git a/custom_components/aarlo/__init__.py b/custom_components/aarlo/__init__.py index 5ceabeef..9cd4b9ca 100644 --- a/custom_components/aarlo/__init__.py +++ b/custom_components/aarlo/__init__.py @@ -25,9 +25,17 @@ CONF_USERNAME, Platform ) -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import ( + DOMAIN as HOMEASSISTANT_DOMAIN, + HomeAssistant, + callback +) from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.issue_registry import ( + async_create_issue, + IssueSeverity +) from homeassistant.helpers.typing import ConfigType import homeassistant.helpers.device_registry as dr @@ -45,7 +53,7 @@ from .cfg import BlendedCfg, PyaarloCfg -__version__ = "0.8.1a9" +__version__ = "0.8.1a10" _LOGGER = logging.getLogger(__name__) @@ -173,6 +181,7 @@ Platform.LIGHT, Platform.MEDIA_PLAYER, Platform.SENSOR, + Platform.SIREN, Platform.SWITCH, ] @@ -194,6 +203,21 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: data=config ) ) + + async_create_issue( + hass, + HOMEASSISTANT_DOMAIN, + f"deprecated_yaml_{COMPONENT_DOMAIN}", + is_fixable=False, + issue_domain=COMPONENT_DOMAIN, + severity=IssueSeverity.WARNING, + translation_key="deprecated_yaml", + translation_placeholders={ + "domain": COMPONENT_DOMAIN, + "integration_title": "Aarlo Cameras", + }, + ) + return True _LOGGER.debug('ignoring a YAML setup') diff --git a/custom_components/aarlo/camera.py b/custom_components/aarlo/camera.py index 7c0b7451..68523602 100644 --- a/custom_components/aarlo/camera.py +++ b/custom_components/aarlo/camera.py @@ -45,6 +45,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType +import pyaarlo from pyaarlo.constant import ( ACTIVITY_STATE_KEY, CHARGER_KEY, @@ -295,6 +296,7 @@ async def async_service_callback(call): class ArloCam(Camera): """An implementation of a Netgear Arlo IP camera.""" + _camera: pyaarlo.ArloCamera | None = None _state: str | None = None _recent: bool = False _last_image_source: str | None = None @@ -432,7 +434,7 @@ async def handle_async_mjpeg_stream(self, request): _LOGGER.error(error_msg) return - stream = CameraMjpeg(self._ffmpeg.binary) + stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) await stream.open_camera(video.video_url, extra_cmd=self._ffmpeg_arguments) try: @@ -446,8 +448,8 @@ async def handle_async_mjpeg_stream(self, request): finally: try: await stream.close() - except: - _LOGGER.debug(f"problem with stream close for {self._attr_name}") + except Exception as e: + _LOGGER.debug(f"problem with stream close for {self._attr_name} {str(e)}") def clear_stream(self): """Clear out inactive stream. diff --git a/custom_components/aarlo/manifest.json b/custom_components/aarlo/manifest.json index e29febf5..a1f79fe2 100644 --- a/custom_components/aarlo/manifest.json +++ b/custom_components/aarlo/manifest.json @@ -15,5 +15,5 @@ "unidecode", "pyaarlo>=0.8.0.2" ], - "version": "0.8.1a9" + "version": "0.8.1a10" } diff --git a/custom_components/aarlo/siren.py b/custom_components/aarlo/siren.py new file mode 100644 index 00000000..0e472e5d --- /dev/null +++ b/custom_components/aarlo/siren.py @@ -0,0 +1,156 @@ +""" +Support for Arlo Sirens. + +For more details about this platform, please refer to the documentation at +https://github.com/twrecked/hass-aarlo/blob/master/README.md +https://www.home-assistant.io/integrations/light +""" + +import logging +from collections.abc import Callable + +from homeassistant.components.siren import ( + DOMAIN as SIREN_DOMAIN, + SirenEntity +) +from homeassistant.components.siren.const import ( + SirenEntityFeature +) +from homeassistant.core import callback +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.util import slugify + +from pyaarlo.constant import ( + SIREN_STATE_KEY +) + +from .const import * +from .utils import to_bool + + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = [COMPONENT_DOMAIN] + + +async def async_setup_entry( + hass: HomeAssistantType, + _entry: ConfigEntry, + async_add_entities: Callable[[list], None], +) -> None: + """Set up an Arlo IP Siren.""" + + arlo = hass.data.get(COMPONENT_DATA) + if not arlo: + return + + # See what devices have sirens. + all_devices = [] + for base in arlo.base_stations: + if base.has_capability(SIREN_STATE_KEY): + all_devices.append(base) + for camera in arlo.cameras: + if camera.has_capability(SIREN_STATE_KEY): + all_devices.append(camera) + for doorbell in arlo.doorbells: + if doorbell.has_capability(SIREN_STATE_KEY): + all_devices.append(doorbell) + + # We have at least one. + if all_devices: + sirens = [] + for device in all_devices: + sirens.append(AarloSiren(device)) + + sirens.append(AarloAllSirens(arlo, all_devices)) + + async_add_entities(sirens) + + +class AarloSirenBase(SirenEntity): + """Representation of an Aarlo Siren.""" + + def __init__(self, sirens, name, unique_id, device_id, ): + + self._sirens = sirens + + self._attr_name = name + self._attr_unique_id = unique_id + self._attr_is_on = False + self._attr_should_poll = False + self._attr_supported_features = SirenEntityFeature( + SirenEntityFeature.DURATION | + SirenEntityFeature.TURN_ON | + SirenEntityFeature.TURN_OFF | + SirenEntityFeature.VOLUME_SET + ) + self._attr_device_info = DeviceInfo( + identifiers={(COMPONENT_DOMAIN, device_id)}, + manufacturer=COMPONENT_BRAND, + ) + + self.entity_id = f"{SIREN_DOMAIN}.{COMPONENT_DOMAIN}_{slugify(self._attr_name)}" + _LOGGER.info(f"ArloSirenBase: {self._attr_name} created") + + async def async_added_to_hass(self): + """Register callbacks.""" + + @callback + def update_state(_device, attr, value): + _LOGGER.debug(f"siren-callback:{self._attr_name}:{attr}:{str(value)[:80]}") + self._attr_is_on = any(to_bool(siren.siren_state) for siren in self._sirens) + self.async_schedule_update_ha_state() + + for siren in self._sirens: + _LOGGER.debug(f"register siren callbacks for {siren.name}") + siren.add_attr_callback(SIREN_STATE_KEY, update_state) + + def turn_on(self, **kwargs): + """Turn the sirens on.""" + volume = int(kwargs.get("volume_level", 1.0) * 8) + duration = kwargs.get("duration", 10) + for siren in self._sirens: + _LOGGER.debug(f"{self._attr_name} {siren.name} vol={volume}, dur={duration}") + siren.siren_on(duration=duration, volume=volume) + + # Flip us on. update_state should confirm this. + self._attr_is_on = True + self.async_schedule_update_ha_state() + + def turn_off(self, **kwargs): + """Turn the sirens off.""" + for siren in self._sirens: + _LOGGER.debug(f"{self._attr_name} {siren.name} off") + siren.siren_off() + + +class AarloSiren(AarloSirenBase): + """Representation of an Aarlo Siren.""" + + def __init__(self, device): + + super().__init__( + [device], + f"{device.name} Siren", + f"{COMPONENT_DOMAIN}_siren_{device.device_id}", + device.device_id + ) + + _LOGGER.info(f"ArloSiren: {self._attr_name} created") + + +class AarloAllSirens(AarloSirenBase): + """Representation of an Aarlo Siren.""" + + def __init__(self, arlo, devices): + + super().__init__( + devices, + "All Arlo Sirens", + f"{COMPONENT_DOMAIN}_all_sirens", + arlo.device_id + ) + + _LOGGER.info(f"ArloAllSirens: {self._attr_name} created") diff --git a/custom_components/aarlo/switch.py b/custom_components/aarlo/switch.py index 8a19e6a4..241ee415 100644 --- a/custom_components/aarlo/switch.py +++ b/custom_components/aarlo/switch.py @@ -26,13 +26,13 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.util import slugify from pyaarlo.constant import ( ACTIVITY_STATE_KEY, SILENT_MODE_KEY, SIREN_STATE_KEY ) -from homeassistant.util import slugify from .const import * from .utils import to_bool