Add shorthand attribute support to Camera platform (#59837)

This commit is contained in:
Franck Nijhof 2021-11-25 16:03:53 +01:00 committed by GitHub
parent 57fd632cd9
commit 6b9c2d8295
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 52 additions and 51 deletions

View file

@ -412,7 +412,7 @@ class AmcrestCam(Camera):
f"{serial_number}-{self._resolution}-{self._channel}"
)
_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._motion_detection_enabled = self._get_motion_detection()
self._audio_enabled = self._get_audio()

View file

@ -369,9 +369,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
class Camera(Entity):
"""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:
"""Initialize a camera."""
self.is_streaming: bool = False
self.stream: Stream | None = None
self.stream_options: dict[str, str] = {}
self.content_type: str = DEFAULT_CONTENT_TYPE
@ -379,45 +391,47 @@ class Camera(Entity):
self._warned_old_signature = False
self.async_update_token()
@property
def should_poll(self) -> bool:
"""No need to poll cameras."""
return False
@property
def entity_picture(self) -> str:
"""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])
@property
def supported_features(self) -> int:
"""Flag supported features."""
return 0
return self._attr_supported_features
@property
def is_recording(self) -> bool:
"""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
def brand(self) -> str | None:
"""Return the camera brand."""
return None
return self._attr_brand
@property
def motion_detection_enabled(self) -> bool:
"""Return the camera motion detection status."""
return False
return self._attr_motion_detection_enabled
@property
def model(self) -> str | None:
"""Return the camera model."""
return None
return self._attr_model
@property
def frame_interval(self) -> float:
"""Return the interval between frames of the mjpeg stream."""
return MIN_STREAM_INTERVAL
return self._attr_frame_interval
@property
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
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:
return None
return STREAM_TYPE_HLS
@ -508,6 +524,7 @@ class Camera(Entity):
return await self.handle_async_still_stream(request, self.frame_interval)
@property
@final
def state(self) -> str:
"""Return the camera state."""
if self.is_recording:
@ -519,7 +536,7 @@ class Camera(Entity):
@property
def is_on(self) -> bool:
"""Return true if on."""
return True
return self._attr_is_on
def turn_off(self) -> None:
"""Turn off camera."""

View file

@ -24,13 +24,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class DemoCamera(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):
"""Initialize demo camera component."""
super().__init__()
self._name = name
self._attr_name = name
self.content_type = content_type
self._motion_status = False
self.is_streaming = True
self._images_index = 0
async def async_camera_image(
@ -43,42 +45,24 @@ class DemoCamera(Camera):
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):
"""Enable the Motion detection in base station (Arm)."""
self._motion_status = True
self._attr_motion_detection_enabled = True
self.async_write_ha_state()
async def async_disable_motion_detection(self):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False
self._attr_motion_detection_enabled = False
self.async_write_ha_state()
async def async_turn_off(self):
"""Turn off camera."""
self.is_streaming = False
self._attr_is_streaming = False
self._attr_is_on = False
self.async_write_ha_state()
async def async_turn_on(self):
"""Turn on camera."""
self.is_streaming = True
self._attr_is_streaming = True
self._attr_is_on = True
self.async_write_ha_state()

View file

@ -186,7 +186,7 @@ class HyperionCamera(Camera):
return False
self._image_stream_clients += 1
self.is_streaming = True
self._attr_is_streaming = True
self.async_write_ha_state()
return True
@ -196,7 +196,7 @@ class HyperionCamera(Camera):
if not self._image_stream_clients:
await self._client.async_send_image_stream_stop()
self.is_streaming = False
self._attr_is_streaming = False
self.async_write_ha_state()
@asynccontextmanager

View file

@ -159,7 +159,7 @@ class MotionEyeMjpegCamera(MotionEyeEntity, MjpegCamera):
self._motion_detection_enabled: bool = camera.get(KEY_MOTION_DETECTION, False)
# motionEye cameras are always streaming or unavailable.
self.is_streaming = True
self._attr_is_streaming = True
MotionEyeEntity.__init__(
self,

View file

@ -79,7 +79,7 @@ class NestCamera(Camera):
self._event_id: str | None = None
self._event_image_bytes: bytes | 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
@property

View file

@ -172,13 +172,13 @@ class NetatmoCamera(NetatmoBase, Camera):
if data["home_id"] == self._home_id and data["camera_id"] == self._id:
if data[WEBHOOK_PUSH_TYPE] in ("NACamera-off", "NACamera-disconnection"):
self.is_streaming = False
self._attr_is_streaming = False
self._status = "off"
elif data[WEBHOOK_PUSH_TYPE] in (
"NACamera-on",
WEBHOOK_NACAMERA_CONNECTION,
):
self.is_streaming = True
self._attr_is_streaming = True
self._status = "on"
elif data[WEBHOOK_PUSH_TYPE] == WEBHOOK_LIGHT_MODE:
self._light_state = data["sub_type"]
@ -273,7 +273,7 @@ class NetatmoCamera(NetatmoBase, Camera):
self._sd_status = camera.get("sd_status")
self._alim_status = camera.get("alim_status")
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
self.hass.data[DOMAIN][DATA_EVENTS][self._id] = self.process_events(

View file

@ -86,7 +86,7 @@ class UnifiVideoCamera(Camera):
self._uuid = uuid
self._name = name
self._password = password
self.is_streaming = False
self._attr_is_streaming = False
self._connect_addr = None
self._camera = None
self._motion_status = False

View file

@ -75,7 +75,7 @@ async def test_setup_camera(hass: HomeAssistant) -> None:
entity_state = hass.states.get(TEST_CAMERA_ENTITY_ID)
assert entity_state
assert entity_state.state == "idle"
assert entity_state.state == "streaming"
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)
entity_state = hass.states.get(TEST_CAMERA_ENTITY_ID)
assert entity_state
assert entity_state.state == "idle"
assert entity_state.state == "streaming"
cameras = copy.deepcopy(TEST_CAMERAS)
cameras[KEY_CAMERAS][0][KEY_VIDEO_STREAMING] = False