diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index cba98b81e30..4e724c804bd 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -94,13 +94,13 @@ class AxisBinarySensor(AxisEventEntity, BinarySensorEntity): self.cancel_scheduled_update() self.cancel_scheduled_update = None - if self.is_on or self.hub.option_trigger_time == 0: + if self.is_on or self.hub.config.trigger_time == 0: self.async_write_ha_state() return self.cancel_scheduled_update = async_call_later( self.hass, - timedelta(seconds=self.hub.option_trigger_time), + timedelta(seconds=self.hub.config.trigger_time), scheduled_update, ) diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index 79543b738da..769be676a78 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -51,8 +51,8 @@ class AxisCamera(AxisEntity, MjpegCamera): MjpegCamera.__init__( self, - username=hub.username, - password=hub.password, + username=hub.config.username, + password=hub.config.password, mjpeg_url=self.mjpeg_source, still_image_url=self.image_source, authentication=HTTP_DIGEST_AUTHENTICATION, @@ -76,27 +76,27 @@ class AxisCamera(AxisEntity, MjpegCamera): """ image_options = self.generate_options(skip_stream_profile=True) self._still_image_url = ( - f"http://{self.hub.host}:{self.hub.port}/axis-cgi" + f"http://{self.hub.config.host}:{self.hub.config.port}/axis-cgi" f"/jpg/image.cgi{image_options}" ) mjpeg_options = self.generate_options() self._mjpeg_url = ( - f"http://{self.hub.host}:{self.hub.port}/axis-cgi" + f"http://{self.hub.config.host}:{self.hub.config.port}/axis-cgi" f"/mjpg/video.cgi{mjpeg_options}" ) stream_options = self.generate_options(add_video_codec_h264=True) self._stream_source = ( - f"rtsp://{self.hub.username}:{self.hub.password}" - f"@{self.hub.host}/axis-media/media.amp{stream_options}" + f"rtsp://{self.hub.config.username}:{self.hub.config.password}" + f"@{self.hub.config.host}/axis-media/media.amp{stream_options}" ) self.hub.additional_diagnostics["camera_sources"] = { "Image": self._still_image_url, "MJPEG": self._mjpeg_url, "Stream": ( - f"rtsp://user:pass@{self.hub.host}/axis-media" + f"rtsp://user:pass@{self.hub.config.host}/axis-media" f"/media.amp{stream_options}" ), } @@ -126,12 +126,12 @@ class AxisCamera(AxisEntity, MjpegCamera): if ( not skip_stream_profile - and self.hub.option_stream_profile != DEFAULT_STREAM_PROFILE + and self.hub.config.stream_profile != DEFAULT_STREAM_PROFILE ): - options_dict["streamprofile"] = self.hub.option_stream_profile + options_dict["streamprofile"] = self.hub.config.stream_profile - if self.hub.option_video_source != DEFAULT_VIDEO_SOURCE: - options_dict["camera"] = self.hub.option_video_source + if self.hub.config.video_source != DEFAULT_VIDEO_SOURCE: + options_dict["camera"] = self.hub.config.video_source if not options_dict: return "" diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index 4d6d4e12b0a..66e338f0fdf 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -272,7 +272,7 @@ class AxisOptionsFlowHandler(OptionsFlowWithConfigEntry): schema[ vol.Optional( - CONF_STREAM_PROFILE, default=self.hub.option_stream_profile + CONF_STREAM_PROFILE, default=self.hub.config.stream_profile ) ] = vol.In(stream_profiles) @@ -291,7 +291,7 @@ class AxisOptionsFlowHandler(OptionsFlowWithConfigEntry): video_sources[int(idx) + 1] = video_source.name schema[ - vol.Optional(CONF_VIDEO_SOURCE, default=self.hub.option_video_source) + vol.Optional(CONF_VIDEO_SOURCE, default=self.hub.config.video_source) ] = vol.In(video_sources) return self.async_show_form( diff --git a/homeassistant/components/axis/hub/config.py b/homeassistant/components/axis/hub/config.py new file mode 100644 index 00000000000..e6d8378b45c --- /dev/null +++ b/homeassistant/components/axis/hub/config.py @@ -0,0 +1,66 @@ +"""Axis network device abstraction.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Self + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_HOST, + CONF_MODEL, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_TRIGGER_TIME, + CONF_USERNAME, +) + +from ..const import ( + CONF_STREAM_PROFILE, + CONF_VIDEO_SOURCE, + DEFAULT_STREAM_PROFILE, + DEFAULT_TRIGGER_TIME, + DEFAULT_VIDEO_SOURCE, +) + + +@dataclass +class AxisConfig: + """Represent a Axis config entry.""" + + entry: ConfigEntry + + host: str + port: int + username: str + password: str + model: str + name: str + + # Options + + stream_profile: str + """Option defining what stream profile camera platform should use.""" + trigger_time: int + """Option defining minimum number of seconds to keep trigger high.""" + video_source: str + """Option defining what video source camera platform should use.""" + + @classmethod + def from_config_entry(cls, config_entry: ConfigEntry) -> Self: + """Create object from config entry.""" + config = config_entry.data + options = config_entry.options + return cls( + entry=config_entry, + host=config[CONF_HOST], + username=config[CONF_USERNAME], + password=config[CONF_PASSWORD], + port=config[CONF_PORT], + model=config[CONF_MODEL], + name=config[CONF_NAME], + stream_profile=options.get(CONF_STREAM_PROFILE, DEFAULT_STREAM_PROFILE), + trigger_time=options.get(CONF_TRIGGER_TIME, DEFAULT_TRIGGER_TIME), + video_source=options.get(CONF_VIDEO_SOURCE, DEFAULT_VIDEO_SOURCE), + ) diff --git a/homeassistant/components/axis/hub/hub.py b/homeassistant/components/axis/hub/hub.py index 5a0f45a1682..b352a514021 100644 --- a/homeassistant/components/axis/hub/hub.py +++ b/homeassistant/components/axis/hub/hub.py @@ -14,30 +14,14 @@ from homeassistant.components import mqtt from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN from homeassistant.components.mqtt.models import ReceiveMessage from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - CONF_HOST, - CONF_MODEL, - CONF_NAME, - CONF_PASSWORD, - CONF_PORT, - CONF_TRIGGER_TIME, - CONF_USERNAME, -) from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.setup import async_when_setup -from ..const import ( - ATTR_MANUFACTURER, - CONF_STREAM_PROFILE, - CONF_VIDEO_SOURCE, - DEFAULT_STREAM_PROFILE, - DEFAULT_TRIGGER_TIME, - DEFAULT_VIDEO_SOURCE, - DOMAIN as AXIS_DOMAIN, -) +from ..const import ATTR_MANUFACTURER, DOMAIN as AXIS_DOMAIN +from .config import AxisConfig class AxisHub: @@ -48,7 +32,7 @@ class AxisHub: ) -> None: """Initialize the device.""" self.hass = hass - self.config_entry = config_entry + self.config = AxisConfig.from_config_entry(config_entry) self.api = api self.available = True @@ -64,65 +48,10 @@ class AxisHub: hub: AxisHub = hass.data[AXIS_DOMAIN][config_entry.entry_id] return hub - @property - def host(self) -> str: - """Return the host address of this device.""" - host: str = self.config_entry.data[CONF_HOST] - return host - - @property - def port(self) -> int: - """Return the HTTP port of this device.""" - port: int = self.config_entry.data[CONF_PORT] - return port - - @property - def username(self) -> str: - """Return the username of this device.""" - username: str = self.config_entry.data[CONF_USERNAME] - return username - - @property - def password(self) -> str: - """Return the password of this device.""" - password: str = self.config_entry.data[CONF_PASSWORD] - return password - - @property - def model(self) -> str: - """Return the model of this device.""" - model: str = self.config_entry.data[CONF_MODEL] - return model - - @property - def name(self) -> str: - """Return the name of this device.""" - name: str = self.config_entry.data[CONF_NAME] - return name - @property def unique_id(self) -> str | None: """Return the unique ID (serial number) of this device.""" - return self.config_entry.unique_id - - # Options - - @property - def option_stream_profile(self) -> str: - """Config entry option defining what stream profile camera platform should use.""" - return self.config_entry.options.get( - CONF_STREAM_PROFILE, DEFAULT_STREAM_PROFILE - ) - - @property - def option_trigger_time(self) -> int: - """Config entry option defining minimum number of seconds to keep trigger high.""" - return self.config_entry.options.get(CONF_TRIGGER_TIME, DEFAULT_TRIGGER_TIME) - - @property - def option_video_source(self) -> str: - """Config entry option defining what video source camera platform should use.""" - return self.config_entry.options.get(CONF_VIDEO_SOURCE, DEFAULT_VIDEO_SOURCE) + return self.config.entry.unique_id # Signals @@ -161,20 +90,21 @@ class AxisHub: cannot be used with weak references. """ hub = AxisHub.get_hub(hass, config_entry) - hub.api.config.host = hub.host + hub.config = AxisConfig.from_config_entry(config_entry) + hub.api.config.host = hub.config.host async_dispatcher_send(hass, hub.signal_new_address) async def async_update_device_registry(self) -> None: """Update device registry.""" device_registry = dr.async_get(self.hass) device_registry.async_get_or_create( - config_entry_id=self.config_entry.entry_id, + config_entry_id=self.config.entry.entry_id, configuration_url=self.api.config.url, connections={(CONNECTION_NETWORK_MAC, self.unique_id)}, # type: ignore[arg-type] identifiers={(AXIS_DOMAIN, self.unique_id)}, # type: ignore[arg-type] manufacturer=ATTR_MANUFACTURER, - model=f"{self.model} {self.product_type}", - name=self.name, + model=f"{self.config.model} {self.product_type}", + name=self.config.name, sw_version=self.fw_version, ) @@ -186,7 +116,7 @@ class AxisHub: # This means the user has too low privileges return if status.status.state == ClientState.ACTIVE: - self.config_entry.async_on_unload( + self.config.entry.async_on_unload( await mqtt.async_subscribe( hass, f"{self.api.vapix.serial_number}/#", self.mqtt_message ) diff --git a/tests/components/axis/test_hub.py b/tests/components/axis/test_hub.py index 1d36e0bbda0..f3d0dd3a225 100644 --- a/tests/components/axis/test_hub.py +++ b/tests/components/axis/test_hub.py @@ -63,9 +63,9 @@ async def test_device_setup( assert forward_entry_setup.mock_calls[2][1][1] == "light" assert forward_entry_setup.mock_calls[3][1][1] == "switch" - assert hub.host == config[CONF_HOST] - assert hub.model == config[CONF_MODEL] - assert hub.name == config[CONF_NAME] + assert hub.config.host == config[CONF_HOST] + assert hub.config.model == config[CONF_MODEL] + assert hub.config.name == config[CONF_NAME] assert hub.unique_id == FORMATTED_MAC device_entry = device_registry.async_get_device(