mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add shorthand attribute support to Camera platform (#59837)
This commit is contained in:
parent
57fd632cd9
commit
6b9c2d8295
@ -412,7 +412,7 @@ class AmcrestCam(Camera):
|
|||||||
f"{serial_number}-{self._resolution}-{self._channel}"
|
f"{serial_number}-{self._resolution}-{self._channel}"
|
||||||
)
|
)
|
||||||
_LOGGER.debug("Assigned unique_id=%s", self._attr_unique_id)
|
_LOGGER.debug("Assigned unique_id=%s", self._attr_unique_id)
|
||||||
self.is_streaming = self._get_video()
|
self._attr_is_streaming = self._get_video()
|
||||||
self._is_recording = self._get_recording()
|
self._is_recording = self._get_recording()
|
||||||
self._motion_detection_enabled = self._get_motion_detection()
|
self._motion_detection_enabled = self._get_motion_detection()
|
||||||
self._audio_enabled = self._get_audio()
|
self._audio_enabled = self._get_audio()
|
||||||
|
@ -369,9 +369,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
class Camera(Entity):
|
class Camera(Entity):
|
||||||
"""The base class for camera entities."""
|
"""The base class for camera entities."""
|
||||||
|
|
||||||
|
# Entity Properties
|
||||||
|
_attr_brand: str | None = None
|
||||||
|
_attr_frame_interval: float = MIN_STREAM_INTERVAL
|
||||||
|
_attr_frontend_stream_type: str | None
|
||||||
|
_attr_is_on: bool = True
|
||||||
|
_attr_is_recording: bool = False
|
||||||
|
_attr_is_streaming: bool = False
|
||||||
|
_attr_model: str | None = None
|
||||||
|
_attr_motion_detection_enabled: bool = False
|
||||||
|
_attr_should_poll: bool = False # No need to poll cameras
|
||||||
|
_attr_state: None = None # State is determined by is_on
|
||||||
|
_attr_supported_features: int = 0
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialize a camera."""
|
"""Initialize a camera."""
|
||||||
self.is_streaming: bool = False
|
|
||||||
self.stream: Stream | None = None
|
self.stream: Stream | None = None
|
||||||
self.stream_options: dict[str, str] = {}
|
self.stream_options: dict[str, str] = {}
|
||||||
self.content_type: str = DEFAULT_CONTENT_TYPE
|
self.content_type: str = DEFAULT_CONTENT_TYPE
|
||||||
@ -379,45 +391,47 @@ class Camera(Entity):
|
|||||||
self._warned_old_signature = False
|
self._warned_old_signature = False
|
||||||
self.async_update_token()
|
self.async_update_token()
|
||||||
|
|
||||||
@property
|
|
||||||
def should_poll(self) -> bool:
|
|
||||||
"""No need to poll cameras."""
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entity_picture(self) -> str:
|
def entity_picture(self) -> str:
|
||||||
"""Return a link to the camera feed as entity picture."""
|
"""Return a link to the camera feed as entity picture."""
|
||||||
|
if self._attr_entity_picture is not None:
|
||||||
|
return self._attr_entity_picture
|
||||||
return ENTITY_IMAGE_URL.format(self.entity_id, self.access_tokens[-1])
|
return ENTITY_IMAGE_URL.format(self.entity_id, self.access_tokens[-1])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return 0
|
return self._attr_supported_features
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_recording(self) -> bool:
|
def is_recording(self) -> bool:
|
||||||
"""Return true if the device is recording."""
|
"""Return true if the device is recording."""
|
||||||
return False
|
return self._attr_is_recording
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_streaming(self) -> bool:
|
||||||
|
"""Return true if the device is streaming."""
|
||||||
|
return self._attr_is_streaming
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brand(self) -> str | None:
|
def brand(self) -> str | None:
|
||||||
"""Return the camera brand."""
|
"""Return the camera brand."""
|
||||||
return None
|
return self._attr_brand
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def motion_detection_enabled(self) -> bool:
|
def motion_detection_enabled(self) -> bool:
|
||||||
"""Return the camera motion detection status."""
|
"""Return the camera motion detection status."""
|
||||||
return False
|
return self._attr_motion_detection_enabled
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self) -> str | None:
|
def model(self) -> str | None:
|
||||||
"""Return the camera model."""
|
"""Return the camera model."""
|
||||||
return None
|
return self._attr_model
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def frame_interval(self) -> float:
|
def frame_interval(self) -> float:
|
||||||
"""Return the interval between frames of the mjpeg stream."""
|
"""Return the interval between frames of the mjpeg stream."""
|
||||||
return MIN_STREAM_INTERVAL
|
return self._attr_frame_interval
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def frontend_stream_type(self) -> str | None:
|
def frontend_stream_type(self) -> str | None:
|
||||||
@ -427,6 +441,8 @@ class Camera(Entity):
|
|||||||
frontend which camera attributes and player to use. The default type
|
frontend which camera attributes and player to use. The default type
|
||||||
is to use HLS, and components can override to change the type.
|
is to use HLS, and components can override to change the type.
|
||||||
"""
|
"""
|
||||||
|
if hasattr(self, "_attr_frontend_stream_type"):
|
||||||
|
return self._attr_frontend_stream_type
|
||||||
if not self.supported_features & SUPPORT_STREAM:
|
if not self.supported_features & SUPPORT_STREAM:
|
||||||
return None
|
return None
|
||||||
return STREAM_TYPE_HLS
|
return STREAM_TYPE_HLS
|
||||||
@ -508,6 +524,7 @@ class Camera(Entity):
|
|||||||
return await self.handle_async_still_stream(request, self.frame_interval)
|
return await self.handle_async_still_stream(request, self.frame_interval)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@final
|
||||||
def state(self) -> str:
|
def state(self) -> str:
|
||||||
"""Return the camera state."""
|
"""Return the camera state."""
|
||||||
if self.is_recording:
|
if self.is_recording:
|
||||||
@ -519,7 +536,7 @@ class Camera(Entity):
|
|||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return true if on."""
|
"""Return true if on."""
|
||||||
return True
|
return self._attr_is_on
|
||||||
|
|
||||||
def turn_off(self) -> None:
|
def turn_off(self) -> None:
|
||||||
"""Turn off camera."""
|
"""Turn off camera."""
|
||||||
|
@ -24,13 +24,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
class DemoCamera(Camera):
|
class DemoCamera(Camera):
|
||||||
"""The representation of a Demo camera."""
|
"""The representation of a Demo camera."""
|
||||||
|
|
||||||
|
_attr_is_streaming = True
|
||||||
|
_attr_motion_detection_enabled = False
|
||||||
|
_attr_supported_features = SUPPORT_ON_OFF
|
||||||
|
|
||||||
def __init__(self, name, content_type):
|
def __init__(self, name, content_type):
|
||||||
"""Initialize demo camera component."""
|
"""Initialize demo camera component."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._name = name
|
self._attr_name = name
|
||||||
self.content_type = content_type
|
self.content_type = content_type
|
||||||
self._motion_status = False
|
|
||||||
self.is_streaming = True
|
|
||||||
self._images_index = 0
|
self._images_index = 0
|
||||||
|
|
||||||
async def async_camera_image(
|
async def async_camera_image(
|
||||||
@ -43,42 +45,24 @@ class DemoCamera(Camera):
|
|||||||
|
|
||||||
return await self.hass.async_add_executor_job(image_path.read_bytes)
|
return await self.hass.async_add_executor_job(image_path.read_bytes)
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of this camera."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def supported_features(self):
|
|
||||||
"""Camera support turn on/off features."""
|
|
||||||
return SUPPORT_ON_OFF
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_on(self):
|
|
||||||
"""Whether camera is on (streaming)."""
|
|
||||||
return self.is_streaming
|
|
||||||
|
|
||||||
@property
|
|
||||||
def motion_detection_enabled(self):
|
|
||||||
"""Camera Motion Detection Status."""
|
|
||||||
return self._motion_status
|
|
||||||
|
|
||||||
async def async_enable_motion_detection(self):
|
async def async_enable_motion_detection(self):
|
||||||
"""Enable the Motion detection in base station (Arm)."""
|
"""Enable the Motion detection in base station (Arm)."""
|
||||||
self._motion_status = True
|
self._attr_motion_detection_enabled = True
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_disable_motion_detection(self):
|
async def async_disable_motion_detection(self):
|
||||||
"""Disable the motion detection in base station (Disarm)."""
|
"""Disable the motion detection in base station (Disarm)."""
|
||||||
self._motion_status = False
|
self._attr_motion_detection_enabled = False
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_turn_off(self):
|
async def async_turn_off(self):
|
||||||
"""Turn off camera."""
|
"""Turn off camera."""
|
||||||
self.is_streaming = False
|
self._attr_is_streaming = False
|
||||||
|
self._attr_is_on = False
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_turn_on(self):
|
async def async_turn_on(self):
|
||||||
"""Turn on camera."""
|
"""Turn on camera."""
|
||||||
self.is_streaming = True
|
self._attr_is_streaming = True
|
||||||
|
self._attr_is_on = True
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
@ -186,7 +186,7 @@ class HyperionCamera(Camera):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
self._image_stream_clients += 1
|
self._image_stream_clients += 1
|
||||||
self.is_streaming = True
|
self._attr_is_streaming = True
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -196,7 +196,7 @@ class HyperionCamera(Camera):
|
|||||||
|
|
||||||
if not self._image_stream_clients:
|
if not self._image_stream_clients:
|
||||||
await self._client.async_send_image_stream_stop()
|
await self._client.async_send_image_stream_stop()
|
||||||
self.is_streaming = False
|
self._attr_is_streaming = False
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
|
@ -159,7 +159,7 @@ class MotionEyeMjpegCamera(MotionEyeEntity, MjpegCamera):
|
|||||||
self._motion_detection_enabled: bool = camera.get(KEY_MOTION_DETECTION, False)
|
self._motion_detection_enabled: bool = camera.get(KEY_MOTION_DETECTION, False)
|
||||||
|
|
||||||
# motionEye cameras are always streaming or unavailable.
|
# motionEye cameras are always streaming or unavailable.
|
||||||
self.is_streaming = True
|
self._attr_is_streaming = True
|
||||||
|
|
||||||
MotionEyeEntity.__init__(
|
MotionEyeEntity.__init__(
|
||||||
self,
|
self,
|
||||||
|
@ -79,7 +79,7 @@ class NestCamera(Camera):
|
|||||||
self._event_id: str | None = None
|
self._event_id: str | None = None
|
||||||
self._event_image_bytes: bytes | None = None
|
self._event_image_bytes: bytes | None = None
|
||||||
self._event_image_cleanup_unsub: Callable[[], None] | None = None
|
self._event_image_cleanup_unsub: Callable[[], None] | None = None
|
||||||
self.is_streaming = CameraLiveStreamTrait.NAME in self._device.traits
|
self._attr_is_streaming = CameraLiveStreamTrait.NAME in self._device.traits
|
||||||
self._placeholder_image: bytes | None = None
|
self._placeholder_image: bytes | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -172,13 +172,13 @@ class NetatmoCamera(NetatmoBase, Camera):
|
|||||||
|
|
||||||
if data["home_id"] == self._home_id and data["camera_id"] == self._id:
|
if data["home_id"] == self._home_id and data["camera_id"] == self._id:
|
||||||
if data[WEBHOOK_PUSH_TYPE] in ("NACamera-off", "NACamera-disconnection"):
|
if data[WEBHOOK_PUSH_TYPE] in ("NACamera-off", "NACamera-disconnection"):
|
||||||
self.is_streaming = False
|
self._attr_is_streaming = False
|
||||||
self._status = "off"
|
self._status = "off"
|
||||||
elif data[WEBHOOK_PUSH_TYPE] in (
|
elif data[WEBHOOK_PUSH_TYPE] in (
|
||||||
"NACamera-on",
|
"NACamera-on",
|
||||||
WEBHOOK_NACAMERA_CONNECTION,
|
WEBHOOK_NACAMERA_CONNECTION,
|
||||||
):
|
):
|
||||||
self.is_streaming = True
|
self._attr_is_streaming = True
|
||||||
self._status = "on"
|
self._status = "on"
|
||||||
elif data[WEBHOOK_PUSH_TYPE] == WEBHOOK_LIGHT_MODE:
|
elif data[WEBHOOK_PUSH_TYPE] == WEBHOOK_LIGHT_MODE:
|
||||||
self._light_state = data["sub_type"]
|
self._light_state = data["sub_type"]
|
||||||
@ -273,7 +273,7 @@ class NetatmoCamera(NetatmoBase, Camera):
|
|||||||
self._sd_status = camera.get("sd_status")
|
self._sd_status = camera.get("sd_status")
|
||||||
self._alim_status = camera.get("alim_status")
|
self._alim_status = camera.get("alim_status")
|
||||||
self._is_local = camera.get("is_local")
|
self._is_local = camera.get("is_local")
|
||||||
self.is_streaming = bool(self._status == "on")
|
self._attr_is_streaming = bool(self._status == "on")
|
||||||
|
|
||||||
if self._model == "NACamera": # Smart Indoor Camera
|
if self._model == "NACamera": # Smart Indoor Camera
|
||||||
self.hass.data[DOMAIN][DATA_EVENTS][self._id] = self.process_events(
|
self.hass.data[DOMAIN][DATA_EVENTS][self._id] = self.process_events(
|
||||||
|
@ -86,7 +86,7 @@ class UnifiVideoCamera(Camera):
|
|||||||
self._uuid = uuid
|
self._uuid = uuid
|
||||||
self._name = name
|
self._name = name
|
||||||
self._password = password
|
self._password = password
|
||||||
self.is_streaming = False
|
self._attr_is_streaming = False
|
||||||
self._connect_addr = None
|
self._connect_addr = None
|
||||||
self._camera = None
|
self._camera = None
|
||||||
self._motion_status = False
|
self._motion_status = False
|
||||||
|
@ -75,7 +75,7 @@ async def test_setup_camera(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
entity_state = hass.states.get(TEST_CAMERA_ENTITY_ID)
|
entity_state = hass.states.get(TEST_CAMERA_ENTITY_ID)
|
||||||
assert entity_state
|
assert entity_state
|
||||||
assert entity_state.state == "idle"
|
assert entity_state.state == "streaming"
|
||||||
assert entity_state.attributes.get("friendly_name") == TEST_CAMERA_NAME
|
assert entity_state.attributes.get("friendly_name") == TEST_CAMERA_NAME
|
||||||
|
|
||||||
|
|
||||||
@ -192,7 +192,7 @@ async def test_setup_camera_new_data_without_streaming(hass: HomeAssistant) -> N
|
|||||||
await setup_mock_motioneye_config_entry(hass, client=client)
|
await setup_mock_motioneye_config_entry(hass, client=client)
|
||||||
entity_state = hass.states.get(TEST_CAMERA_ENTITY_ID)
|
entity_state = hass.states.get(TEST_CAMERA_ENTITY_ID)
|
||||||
assert entity_state
|
assert entity_state
|
||||||
assert entity_state.state == "idle"
|
assert entity_state.state == "streaming"
|
||||||
|
|
||||||
cameras = copy.deepcopy(TEST_CAMERAS)
|
cameras = copy.deepcopy(TEST_CAMERAS)
|
||||||
cameras[KEY_CAMERAS][0][KEY_VIDEO_STREAMING] = False
|
cameras[KEY_CAMERAS][0][KEY_VIDEO_STREAMING] = False
|
||||||
|
Loading…
x
Reference in New Issue
Block a user