-
-
Notifications
You must be signed in to change notification settings - Fork 33.8k
Add turn_on/off service to camera #15051
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5a4431b
edacc48
513cd8d
a8e7608
f84a4f2
436ef9c
f79fedd
f2f0488
c09fb82
adecece
9592bf8
29bd9d3
953ef4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,8 @@ | |
import voluptuous as vol | ||
|
||
from homeassistant.core import callback | ||
from homeassistant.const import ATTR_ENTITY_ID | ||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \ | ||
SERVICE_TURN_ON | ||
from homeassistant.exceptions import HomeAssistantError | ||
from homeassistant.loader import bind_hass | ||
from homeassistant.helpers.entity import Entity | ||
|
@@ -47,6 +48,9 @@ | |
STATE_STREAMING = 'streaming' | ||
STATE_IDLE = 'idle' | ||
|
||
# Bitfield of features supported by the camera entity | ||
SUPPORT_ON_OFF = 1 | ||
|
||
DEFAULT_CONTENT_TYPE = 'image/jpeg' | ||
ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}' | ||
|
||
|
@@ -79,6 +83,35 @@ class Image: | |
content = attr.ib(type=bytes) | ||
|
||
|
||
@bind_hass | ||
def turn_off(hass, entity_id=None): | ||
"""Turn off camera.""" | ||
hass.add_job(async_turn_off, hass, entity_id) | ||
|
||
|
||
@bind_hass | ||
async def async_turn_off(hass, entity_id=None): | ||
"""Turn off camera.""" | ||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} | ||
await hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data) | ||
|
||
|
||
@bind_hass | ||
def turn_on(hass, entity_id=None): | ||
"""Turn on camera.""" | ||
hass.add_job(async_turn_on, hass, entity_id) | ||
|
||
|
||
@bind_hass | ||
async def async_turn_on(hass, entity_id=None): | ||
"""Turn on camera, and set operation mode.""" | ||
data = {} | ||
if entity_id is not None: | ||
data[ATTR_ENTITY_ID] = entity_id | ||
|
||
await hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data) | ||
|
||
|
||
@bind_hass | ||
def enable_motion_detection(hass, entity_id=None): | ||
"""Enable Motion Detection.""" | ||
|
@@ -119,6 +152,9 @@ async def async_get_image(hass, entity_id, timeout=10): | |
if camera is None: | ||
raise HomeAssistantError('Camera not found') | ||
|
||
if not camera.is_on: | ||
raise HomeAssistantError('Camera is off') | ||
|
||
with suppress(asyncio.CancelledError, asyncio.TimeoutError): | ||
with async_timeout.timeout(timeout, loop=hass.loop): | ||
image = await camera.async_camera_image() | ||
|
@@ -163,6 +199,12 @@ async def async_handle_camera_service(service): | |
await camera.async_enable_motion_detection() | ||
elif service.service == SERVICE_DISABLE_MOTION: | ||
await camera.async_disable_motion_detection() | ||
elif service.service == SERVICE_TURN_OFF and \ | ||
camera.supported_features & SUPPORT_ON_OFF: | ||
await camera.async_turn_off() | ||
elif service.service == SERVICE_TURN_ON and \ | ||
camera.supported_features & SUPPORT_ON_OFF: | ||
await camera.async_turn_on() | ||
|
||
if not camera.should_poll: | ||
continue | ||
|
@@ -200,6 +242,12 @@ def _write_image(to_file, image_data): | |
except OSError as err: | ||
_LOGGER.error("Can't write image to file: %s", err) | ||
|
||
hass.services.async_register( | ||
DOMAIN, SERVICE_TURN_OFF, async_handle_camera_service, | ||
schema=CAMERA_SERVICE_SCHEMA) | ||
hass.services.async_register( | ||
DOMAIN, SERVICE_TURN_ON, async_handle_camera_service, | ||
schema=CAMERA_SERVICE_SCHEMA) | ||
hass.services.async_register( | ||
DOMAIN, SERVICE_ENABLE_MOTION, async_handle_camera_service, | ||
schema=CAMERA_SERVICE_SCHEMA) | ||
|
@@ -243,6 +291,11 @@ def entity_picture(self): | |
"""Return a link to the camera feed as entity picture.""" | ||
return ENTITY_IMAGE_URL.format(self.entity_id, self.access_tokens[-1]) | ||
|
||
@property | ||
def supported_features(self): | ||
"""Flag supported features.""" | ||
return 0 | ||
|
||
@property | ||
def is_recording(self): | ||
"""Return true if the device is recording.""" | ||
|
@@ -337,10 +390,34 @@ def state(self): | |
return STATE_STREAMING | ||
return STATE_IDLE | ||
|
||
@property | ||
def is_on(self): | ||
"""Return true if on.""" | ||
return True | ||
|
||
def turn_off(self): | ||
"""Turn off camera.""" | ||
raise NotImplementedError() | ||
|
||
@callback | ||
def async_turn_off(self): | ||
"""Turn off camera.""" | ||
return self.hass.async_add_job(self.turn_off) | ||
|
||
def turn_on(self): | ||
"""Turn off camera.""" | ||
raise NotImplementedError() | ||
|
||
@callback | ||
def async_turn_on(self): | ||
"""Turn off camera.""" | ||
return self.hass.async_add_job(self.turn_on) | ||
|
||
def enable_motion_detection(self): | ||
"""Enable motion detection in the camera.""" | ||
raise NotImplementedError() | ||
|
||
@callback | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is wrong. This function returns a task that has to be awaited. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean I should not add callback, but should change the method signature to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function used to return a task that needs to be awaited. By labelling it callback, you tell people not to await it, meaning the function is never executed. If you change it to async, it's more clear, but then you also need to add an await. |
||
def async_enable_motion_detection(self): | ||
"""Call the job and enable motion detection.""" | ||
return self.hass.async_add_job(self.enable_motion_detection) | ||
|
@@ -349,6 +426,7 @@ def disable_motion_detection(self): | |
"""Disable motion detection in camera.""" | ||
raise NotImplementedError() | ||
|
||
@callback | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is wrong. This function returns a task that has to be awaited. |
||
def async_disable_motion_detection(self): | ||
"""Call the job and disable motion detection.""" | ||
return self.hass.async_add_job(self.disable_motion_detection) | ||
|
@@ -393,15 +471,18 @@ async def get(self, request, entity_id): | |
camera = self.component.get_entity(entity_id) | ||
|
||
if camera is None: | ||
status = 404 if request[KEY_AUTHENTICATED] else 401 | ||
return web.Response(status=status) | ||
raise web.HTTPNotFound() | ||
|
||
authenticated = (request[KEY_AUTHENTICATED] or | ||
request.query.get('token') in camera.access_tokens) | ||
|
||
if not authenticated: | ||
raise web.HTTPUnauthorized() | ||
|
||
if not camera.is_on: | ||
AE8F | _LOGGER.debug('Camera is off.') | |
raise web.HTTPServiceUnavailable() | ||
|
||
return await self.handle(request, camera) | ||
|
||
async def handle(self, request, camera): | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This differs from how we set
data
on line 108 - 110. I think we should go with one of the ways, not have different logic in turn on vs turn off.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will use turn_off version