forked from platypush/platypush
[#398] Replaced camera
response objects with schemas.
This commit is contained in:
parent
579faf63bc
commit
13bde4adba
8 changed files with 521 additions and 296 deletions
|
@ -1,5 +0,0 @@
|
||||||
``camera.android``
|
|
||||||
=============================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.message.response.camera.android
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
``camera``
|
|
||||||
=====================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.message.response.camera
|
|
||||||
:members:
|
|
|
@ -6,8 +6,6 @@ Responses
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
:caption: Responses:
|
:caption: Responses:
|
||||||
|
|
||||||
platypush/responses/camera.rst
|
|
||||||
platypush/responses/camera.android.rst
|
|
||||||
platypush/responses/google.drive.rst
|
platypush/responses/google.drive.rst
|
||||||
platypush/responses/pihole.rst
|
platypush/responses/pihole.rst
|
||||||
platypush/responses/printer.cups.rst
|
platypush/responses/printer.cups.rst
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
from platypush.message.response import Response
|
|
||||||
|
|
||||||
|
|
||||||
class CameraResponse(Response):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
|
|
@ -1,191 +0,0 @@
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from platypush.message.response.camera import CameraResponse
|
|
||||||
|
|
||||||
|
|
||||||
class AndroidCameraStatusResponse(CameraResponse):
|
|
||||||
"""
|
|
||||||
Example response:
|
|
||||||
|
|
||||||
.. code-block:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"stream_url": "https://192.168.1.30:8080/video",
|
|
||||||
"image_url": "https://192.168.1.30:8080/photo.jpg",
|
|
||||||
"audio_url": "https://192.168.1.30:8080/audio.wav",
|
|
||||||
"orientation": "landscape",
|
|
||||||
"idle": "off",
|
|
||||||
"audio_only": "off",
|
|
||||||
"overlay": "off",
|
|
||||||
"quality": "49",
|
|
||||||
"focus_homing": "off",
|
|
||||||
"ip_address": "192.168.1.30",
|
|
||||||
"motion_limit": "250",
|
|
||||||
"adet_limit": "200",
|
|
||||||
"night_vision": "off",
|
|
||||||
"night_vision_average": "2",
|
|
||||||
"night_vision_gain": "1.0",
|
|
||||||
"motion_detect": "off",
|
|
||||||
"motion_display": "off",
|
|
||||||
"video_chunk_len": "60",
|
|
||||||
"gps_active": "off",
|
|
||||||
"video_size": "1920x1080",
|
|
||||||
"mirror_flip": "none",
|
|
||||||
"ffc": "off",
|
|
||||||
"rtsp_video_formats": "",
|
|
||||||
"rtsp_audio_formats": "",
|
|
||||||
"video_connections": "0",
|
|
||||||
"audio_connections": "0",
|
|
||||||
"ivideon_streaming": "off",
|
|
||||||
"zoom": "100",
|
|
||||||
"crop_x": "50",
|
|
||||||
"crop_y": "50",
|
|
||||||
"coloreffect": "none",
|
|
||||||
"scenemode": "auto",
|
|
||||||
"focusmode": "continuous-video",
|
|
||||||
"whitebalance": "auto",
|
|
||||||
"flashmode": "off",
|
|
||||||
"antibanding": "off",
|
|
||||||
"torch": "off",
|
|
||||||
"focus_distance": "0.0",
|
|
||||||
"focal_length": "4.25",
|
|
||||||
"aperture": "1.7",
|
|
||||||
"filter_density": "0.0",
|
|
||||||
"exposure_ns": "9384",
|
|
||||||
"frame_duration": "33333333",
|
|
||||||
"iso": "100",
|
|
||||||
"manual_sensor": "off",
|
|
||||||
"photo_size": "1920x1080"
|
|
||||||
}
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
attrs = {
|
|
||||||
'orientation', 'idle', 'audio_only', 'overlay', 'quality', 'focus_homing',
|
|
||||||
'ip_address', 'motion_limit', 'adet_limit', 'night_vision',
|
|
||||||
'night_vision_average', 'night_vision_gain', 'motion_detect',
|
|
||||||
'motion_display', 'video_chunk_len', 'gps_active', 'video_size',
|
|
||||||
'mirror_flip', 'ffc', 'rtsp_video_formats', 'rtsp_audio_formats',
|
|
||||||
'video_connections', 'audio_connections', 'ivideon_streaming', 'zoom',
|
|
||||||
'crop_x', 'crop_y', 'coloreffect', 'scenemode', 'focusmode',
|
|
||||||
'whitebalance', 'flashmode', 'antibanding', 'torch', 'focus_distance',
|
|
||||||
'focal_length', 'aperture', 'filter_density', 'exposure_ns',
|
|
||||||
'frame_duration', 'iso', 'manual_sensor', 'photo_size',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args,
|
|
||||||
name: str = None,
|
|
||||||
stream_url: str = None,
|
|
||||||
image_url: str = None,
|
|
||||||
audio_url: str = None,
|
|
||||||
orientation: str = None,
|
|
||||||
idle: str = None,
|
|
||||||
audio_only: str = None,
|
|
||||||
overlay: str = None,
|
|
||||||
quality: str = None,
|
|
||||||
focus_homing: str = None,
|
|
||||||
ip_address: str = None,
|
|
||||||
motion_limit: str = None,
|
|
||||||
adet_limit: str = None,
|
|
||||||
night_vision: str = None,
|
|
||||||
night_vision_average: str = None,
|
|
||||||
night_vision_gain: str = None,
|
|
||||||
motion_detect: str = None,
|
|
||||||
motion_display: str = None,
|
|
||||||
video_chunk_len: str = None,
|
|
||||||
gps_active: str = None,
|
|
||||||
video_size: str = None,
|
|
||||||
mirror_flip: str = None,
|
|
||||||
ffc: str = None,
|
|
||||||
rtsp_video_formats: str = None,
|
|
||||||
rtsp_audio_formats: str = None,
|
|
||||||
video_connections: str = None,
|
|
||||||
audio_connections: str = None,
|
|
||||||
ivideon_streaming: str = None,
|
|
||||||
zoom: str = None,
|
|
||||||
crop_x: str = None,
|
|
||||||
crop_y: str = None,
|
|
||||||
coloreffect: str = None,
|
|
||||||
scenemode: str = None,
|
|
||||||
focusmode: str = None,
|
|
||||||
whitebalance: str = None,
|
|
||||||
flashmode: str = None,
|
|
||||||
antibanding: str = None,
|
|
||||||
torch: str = None,
|
|
||||||
focus_distance: str = None,
|
|
||||||
focal_length: str = None,
|
|
||||||
aperture: str = None,
|
|
||||||
filter_density: str = None,
|
|
||||||
exposure_ns: str = None,
|
|
||||||
frame_duration: str = None,
|
|
||||||
iso: str = None,
|
|
||||||
manual_sensor: str = None,
|
|
||||||
photo_size: str = None,
|
|
||||||
**kwargs):
|
|
||||||
|
|
||||||
self.status = {
|
|
||||||
"name": name,
|
|
||||||
"stream_url": stream_url,
|
|
||||||
"image_url": image_url,
|
|
||||||
"audio_url": audio_url,
|
|
||||||
"orientation": orientation,
|
|
||||||
"idle": True if idle == "on" else False,
|
|
||||||
"audio_only": True if audio_only == "on" else False,
|
|
||||||
"overlay": True if overlay == "on" else False,
|
|
||||||
"quality": int(quality or 0),
|
|
||||||
"focus_homing": True if focus_homing == "on" else False,
|
|
||||||
"ip_address": ip_address,
|
|
||||||
"motion_limit": int(motion_limit or 0),
|
|
||||||
"adet_limit": int(adet_limit or 0),
|
|
||||||
"night_vision": True if night_vision == "on" else False,
|
|
||||||
"night_vision_average": float(night_vision_average or 0),
|
|
||||||
"night_vision_gain": float(night_vision_gain or 0),
|
|
||||||
"motion_detect": True if motion_detect == "on" else False,
|
|
||||||
"motion_display": True if motion_display == "on" else False,
|
|
||||||
"video_chunk_len": int(video_chunk_len or 0),
|
|
||||||
"gps_active": True if gps_active == "on" else False,
|
|
||||||
"video_size": video_size,
|
|
||||||
"mirror_flip": mirror_flip,
|
|
||||||
"ffc": True if ffc == "on" else False,
|
|
||||||
"rtsp_video_formats": rtsp_video_formats,
|
|
||||||
"rtsp_audio_formats": rtsp_audio_formats,
|
|
||||||
"video_connections": int(video_connections or 0),
|
|
||||||
"audio_connections": int(audio_connections or 0),
|
|
||||||
"ivideon_streaming": True if ivideon_streaming == "on" else False,
|
|
||||||
"zoom": int(zoom or 0),
|
|
||||||
"crop_x": int(crop_x or 0),
|
|
||||||
"crop_y": int(crop_y or 0),
|
|
||||||
"coloreffect": coloreffect,
|
|
||||||
"scenemode": scenemode,
|
|
||||||
"focusmode": focusmode,
|
|
||||||
"whitebalance": whitebalance,
|
|
||||||
"flashmode": True if flashmode == "on" else False,
|
|
||||||
"antibanding": True if antibanding == "on" else False,
|
|
||||||
"torch": True if torch == "on" else False,
|
|
||||||
"focus_distance": float(focus_distance or 0),
|
|
||||||
"focal_length": float(focal_length or 0),
|
|
||||||
"aperture": float(aperture or 0),
|
|
||||||
"filter_density": float(filter_density or 0),
|
|
||||||
"exposure_ns": int(exposure_ns or 0),
|
|
||||||
"frame_duration": int(frame_duration or 0),
|
|
||||||
"iso": int(iso or 0),
|
|
||||||
"manual_sensor": True if manual_sensor == "on" else False,
|
|
||||||
"photo_size": photo_size,
|
|
||||||
}
|
|
||||||
|
|
||||||
super().__init__(*args, output=self.status, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class AndroidCameraStatusListResponse(CameraResponse):
|
|
||||||
def __init__(self, status: List[AndroidCameraStatusResponse], **kwargs):
|
|
||||||
self.status = [s.status for s in status]
|
|
||||||
super().__init__(output=self.status, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class AndroidCameraPictureResponse(CameraResponse):
|
|
||||||
def __init__(self, image_file: str, *args, **kwargs):
|
|
||||||
self.image_file = image_file
|
|
||||||
super().__init__(*args, output={'image_file': image_file}, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
|
|
@ -3,18 +3,29 @@ import os
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
from typing import Optional, Union, Dict, List, Any
|
from typing import Optional, Sequence, Union, Dict, List, Any
|
||||||
|
|
||||||
from platypush.message.response.camera.android import AndroidCameraStatusResponse, AndroidCameraStatusListResponse, \
|
|
||||||
AndroidCameraPictureResponse
|
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import Plugin, action
|
||||||
|
from platypush.schemas.camera.android.ipcam import CameraStatusSchema
|
||||||
|
|
||||||
|
|
||||||
class AndroidIpcam:
|
class AndroidIpcam:
|
||||||
|
"""
|
||||||
|
IPCam camera configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
args = {}
|
args = {}
|
||||||
|
|
||||||
def __init__(self, name: str, host: str, port: int = 8080, username: Optional[str] = None,
|
def __init__(
|
||||||
password: Optional[str] = None, timeout: int = 10, ssl: bool = True):
|
self,
|
||||||
|
name: str,
|
||||||
|
host: str,
|
||||||
|
port: int = 8080,
|
||||||
|
username: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
timeout: int = 10,
|
||||||
|
ssl: bool = True,
|
||||||
|
):
|
||||||
self.args = {
|
self.args = {
|
||||||
'name': name,
|
'name': name,
|
||||||
'host': host,
|
'host': host,
|
||||||
|
@ -41,12 +52,13 @@ class AndroidIpcam:
|
||||||
self.args[key] = value
|
self.args[key] = value
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return json.dumps(getattr(self, 'args') or {})
|
return json.dumps(self.args or {})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base_url(self) -> str:
|
def base_url(self) -> str:
|
||||||
return 'http{ssl}://{host}:{port}/'.format(
|
return 'http{ssl}://{host}:{port}/'.format(
|
||||||
ssl=('s' if self.ssl else ''), host=self.host, port=self.port)
|
ssl=('s' if self.ssl else ''), host=self.host, port=self.port
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stream_url(self) -> str:
|
def stream_url(self) -> str:
|
||||||
|
@ -67,16 +79,20 @@ class CameraAndroidIpcamPlugin(Plugin):
|
||||||
`IPCam <https://play.google.com/store/apps/details?id=com.pas.webcam>`_.
|
`IPCam <https://play.google.com/store/apps/details?id=com.pas.webcam>`_.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: Optional[str] = None,
|
||||||
host: Optional[str] = None,
|
host: Optional[str] = None,
|
||||||
port: Optional[int] = 8080,
|
port: int = 8080,
|
||||||
username: Optional[str] = None,
|
username: Optional[str] = None,
|
||||||
password: Optional[str] = None,
|
password: Optional[str] = None,
|
||||||
timeout: int = 10,
|
timeout: int = 10,
|
||||||
ssl: bool = True,
|
ssl: bool = True,
|
||||||
cameras: Optional[Dict[str, Dict[str, Any]]] = None,
|
cameras: Optional[Sequence[dict]] = None,
|
||||||
**kwargs):
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
|
:param name: Custom name for the default camera (default: IP/hostname)
|
||||||
:param host: Camera host name or address
|
:param host: Camera host name or address
|
||||||
:param port: Camera port
|
:param port: Camera port
|
||||||
:param username: Camera username, if set
|
:param username: Camera username, if set
|
||||||
|
@ -84,28 +100,44 @@ class CameraAndroidIpcamPlugin(Plugin):
|
||||||
:param timeout: Connection timeout
|
:param timeout: Connection timeout
|
||||||
:param ssl: Use HTTPS instead of HTTP
|
:param ssl: Use HTTPS instead of HTTP
|
||||||
:param cameras: Alternatively, you can specify a list of IPCam cameras as a
|
:param cameras: Alternatively, you can specify a list of IPCam cameras as a
|
||||||
name->dict mapping. The keys will be unique names used to identify your
|
list of objects with ``name``, ``host``, ``port``, ``username``,
|
||||||
cameras, the values will contain dictionaries containing `host, `port`,
|
``password``, ``timeout`` and ``ssl`` attributes.
|
||||||
`username`, `password`, `timeout` and `ssl` attributes for each camera.
|
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.cameras: List[AndroidIpcam] = []
|
self.cameras: List[AndroidIpcam] = []
|
||||||
self._camera_name_to_idx: Dict[str, int] = {}
|
self._camera_name_to_idx: Dict[str, int] = {}
|
||||||
|
|
||||||
if not cameras:
|
if not cameras:
|
||||||
camera = AndroidIpcam(name=host, host=host, port=port, username=username,
|
assert host, 'You need to specify at least one camera'
|
||||||
password=password, timeout=timeout, ssl=ssl)
|
name = name or host
|
||||||
|
camera = AndroidIpcam(
|
||||||
|
name=name,
|
||||||
|
host=host,
|
||||||
|
port=port,
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
timeout=timeout,
|
||||||
|
ssl=ssl,
|
||||||
|
)
|
||||||
self.cameras.append(camera)
|
self.cameras.append(camera)
|
||||||
self._camera_name_to_idx[host] = 0
|
self._camera_name_to_idx[name] = 0
|
||||||
else:
|
else:
|
||||||
for name, camera in cameras.items():
|
for camera in cameras:
|
||||||
camera = AndroidIpcam(name=name, host=camera['host'], port=camera.get('port', port),
|
assert 'host' in camera, 'You need to specify the host for each camera'
|
||||||
username=camera.get('username'), password=camera.get('password'),
|
name = camera.get('name', camera['host'])
|
||||||
timeout=camera.get('timeout', timeout), ssl=camera.get('ssl', ssl))
|
camera = AndroidIpcam(
|
||||||
|
name=name,
|
||||||
|
host=camera['host'],
|
||||||
|
port=camera.get('port', port),
|
||||||
|
username=camera.get('username'),
|
||||||
|
password=camera.get('password'),
|
||||||
|
timeout=camera.get('timeout', timeout),
|
||||||
|
ssl=camera.get('ssl', ssl),
|
||||||
|
)
|
||||||
self._camera_name_to_idx[name] = len(self.cameras)
|
self._camera_name_to_idx[name] = len(self.cameras)
|
||||||
self.cameras.append(camera)
|
self.cameras.append(camera)
|
||||||
|
|
||||||
def _get_camera(self, camera: Union[int, str] = None) -> AndroidIpcam:
|
def _get_camera(self, camera: Optional[Union[int, str]] = None) -> AndroidIpcam:
|
||||||
if not camera:
|
if not camera:
|
||||||
camera = 0
|
camera = 0
|
||||||
|
|
||||||
|
@ -114,10 +146,14 @@ class CameraAndroidIpcamPlugin(Plugin):
|
||||||
|
|
||||||
return self.cameras[self._camera_name_to_idx[camera]]
|
return self.cameras[self._camera_name_to_idx[camera]]
|
||||||
|
|
||||||
def _exec(self, url: str, camera: Union[int, str] = None, *args, **kwargs) -> Union[Dict[str, Any], bool]:
|
def _exec(
|
||||||
|
self, url: str, *args, camera: Optional[Union[int, str]] = None, **kwargs
|
||||||
|
) -> Union[Dict[str, Any], bool]:
|
||||||
cam = self._get_camera(camera)
|
cam = self._get_camera(camera)
|
||||||
url = cam.base_url + url
|
url = cam.base_url + url
|
||||||
response = requests.get(url, auth=cam.auth, timeout=cam.timeout, verify=False, *args, **kwargs)
|
response = requests.get(
|
||||||
|
url, auth=cam.auth, timeout=cam.timeout, verify=False, *args, **kwargs
|
||||||
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
if response.headers.get('content-type') == 'application/json':
|
if response.headers.get('content-type') == 'application/json':
|
||||||
|
@ -125,87 +161,138 @@ class CameraAndroidIpcamPlugin(Plugin):
|
||||||
|
|
||||||
return response.text.find('Ok') != -1
|
return response.text.find('Ok') != -1
|
||||||
|
|
||||||
@action
|
def _change_setting(
|
||||||
def change_setting(self, key: str, value: Union[str, int, bool], camera: Union[int, str] = None) -> bool:
|
self,
|
||||||
"""
|
key: str,
|
||||||
Change a setting.
|
value: Union[str, int, bool],
|
||||||
:param key: Setting name
|
camera: Optional[Union[int, str]] = None,
|
||||||
:param value: Setting value
|
) -> bool:
|
||||||
:param camera: Camera index or configured name
|
|
||||||
:return: True on success, False otherwise
|
|
||||||
"""
|
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
payload = "on" if value else "off"
|
payload = "on" if value else "off"
|
||||||
else:
|
else:
|
||||||
payload = value
|
payload = value
|
||||||
|
|
||||||
return self._exec("settings/{key}?set={payload}".format(key=key, payload=payload), camera=camera)
|
return bool(
|
||||||
|
self._exec(
|
||||||
|
"settings/{key}?set={payload}".format(key=key, payload=payload),
|
||||||
|
camera=camera,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def status(self, camera: Union[int, str] = None) -> AndroidCameraStatusListResponse:
|
def change_setting(
|
||||||
|
self,
|
||||||
|
key: str,
|
||||||
|
value: Union[str, int, bool],
|
||||||
|
camera: Optional[Union[int, str]] = None,
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Change a setting.
|
||||||
|
|
||||||
|
:param key: Setting name
|
||||||
|
:param value: Setting value
|
||||||
|
:param camera: Camera index or configured name
|
||||||
|
:return: True on success, False otherwise
|
||||||
|
"""
|
||||||
|
return self._change_setting(key, value, camera=camera)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def status(self, camera: Optional[Union[int, str]] = None) -> List[dict]:
|
||||||
"""
|
"""
|
||||||
:param camera: Camera index or name (default: status of all the cameras)
|
:param camera: Camera index or name (default: status of all the cameras)
|
||||||
:return: True if the camera is available, False otherwise
|
:return: .. schema:: camera.android.ipcam.CameraStatusSchema(many=True)
|
||||||
"""
|
"""
|
||||||
cameras = self._camera_name_to_idx.keys() if camera is None else [camera]
|
cameras = self._camera_name_to_idx.keys() if camera is None else [camera]
|
||||||
statuses = []
|
statuses = []
|
||||||
|
|
||||||
for c in cameras:
|
for c in cameras:
|
||||||
try:
|
try:
|
||||||
if isinstance(camera, int):
|
print('****** HERE ******')
|
||||||
|
print(self._camera_name_to_idx)
|
||||||
|
if isinstance(c, int):
|
||||||
cam = self.cameras[c]
|
cam = self.cameras[c]
|
||||||
else:
|
else:
|
||||||
cam = self.cameras[self._camera_name_to_idx[c]]
|
cam = self.cameras[self._camera_name_to_idx[c]]
|
||||||
|
|
||||||
status_data = self._exec('status.json', params={'show_avail': 1}, camera=cam.name).get('curvals', {})
|
response = self._exec(
|
||||||
status = AndroidCameraStatusResponse(
|
'status.json', params={'show_avail': 1}, camera=cam.name
|
||||||
name=cam.name,
|
)
|
||||||
stream_url=cam.stream_url,
|
assert isinstance(response, dict), f'Invalid response: {response}'
|
||||||
image_url=cam.image_url,
|
|
||||||
audio_url=cam.audio_url,
|
status_data = response.get('curvals', {})
|
||||||
**{k: v for k, v in status_data.items()
|
status = CameraStatusSchema().dump(
|
||||||
if k in AndroidCameraStatusResponse.attrs})
|
{
|
||||||
|
'name': cam.name,
|
||||||
|
'stream_url': cam.stream_url,
|
||||||
|
'image_url': cam.image_url,
|
||||||
|
'audio_url': cam.audio_url,
|
||||||
|
**status_data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
statuses.append(status)
|
statuses.append(status)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning('Could not get the status of {}: {}'.format(c, str(e)))
|
self.logger.warning(
|
||||||
|
'Could not get the status of %s: %s: %s', c, type(e), e
|
||||||
|
)
|
||||||
|
|
||||||
return AndroidCameraStatusListResponse(statuses)
|
return statuses
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_front_facing_camera(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_front_facing_camera(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable the front-facing camera."""
|
"""Enable/disable the front-facing camera."""
|
||||||
return self.change_setting('ffc', activate, camera=camera)
|
return self._change_setting('ffc', activate, camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_torch(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_torch(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable the torch."""
|
"""Enable/disable the torch."""
|
||||||
url = 'enabletorch' if activate else 'disabletorch'
|
url = 'enabletorch' if activate else 'disabletorch'
|
||||||
return self._exec(url, camera=camera)
|
return bool(self._exec(url, camera=camera))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_focus(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_focus(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable the focus."""
|
"""Enable/disable the focus."""
|
||||||
url = 'focus' if activate else 'nofocus'
|
url = 'focus' if activate else 'nofocus'
|
||||||
return self._exec(url, camera=camera)
|
return bool(self._exec(url, camera=camera))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def start_recording(self, tag: Optional[str] = None, camera: Union[int, str] = None) -> bool:
|
def start_recording(
|
||||||
|
self, tag: Optional[str] = None, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Start recording."""
|
"""Start recording."""
|
||||||
params = {'force': 1}
|
params = {
|
||||||
if tag:
|
'force': 1,
|
||||||
params['tag'] = tag
|
**({'tag': tag} if tag else {}),
|
||||||
|
}
|
||||||
return self._exec('startvideo', params=params, camera=camera)
|
return bool(self._exec('startvideo', params=params, camera=camera))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def stop_recording(self, camera: Union[int, str] = None) -> bool:
|
def stop_recording(self, camera: Optional[Union[int, str]] = None) -> bool:
|
||||||
"""Stop recording."""
|
"""Stop recording."""
|
||||||
return self._exec('stopvideo', params={'force': 1}, camera=camera)
|
return bool(self._exec('stopvideo', params={'force': 1}, camera=camera))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def take_picture(self, image_file: str, camera: Union[int, str] = None) -> AndroidCameraPictureResponse:
|
def take_picture(
|
||||||
"""Take a picture and save it on the local device."""
|
self, image_file: str, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Take a picture and save it on the local device.
|
||||||
|
|
||||||
|
:return: dict
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"image_file": "/path/to/image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
cam = self._get_camera(camera)
|
cam = self._get_camera(camera)
|
||||||
image_file = os.path.abspath(os.path.expanduser(image_file))
|
image_file = os.path.abspath(os.path.expanduser(image_file))
|
||||||
os.makedirs(os.path.dirname(image_file), exist_ok=True)
|
os.makedirs(os.path.dirname(image_file), exist_ok=True)
|
||||||
|
@ -214,47 +301,64 @@ class CameraAndroidIpcamPlugin(Plugin):
|
||||||
|
|
||||||
with open(image_file, 'wb') as f:
|
with open(image_file, 'wb') as f:
|
||||||
f.write(response.content)
|
f.write(response.content)
|
||||||
return AndroidCameraPictureResponse(image_file=image_file)
|
|
||||||
|
return {'image_file': image_file}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_night_vision(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_night_vision(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable night vision."""
|
"""Enable/disable night vision."""
|
||||||
return self.change_setting('night_vision', activate, camera=camera)
|
return self._change_setting('night_vision', activate, camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_overlay(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_overlay(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable video overlay."""
|
"""Enable/disable video overlay."""
|
||||||
return self.change_setting('overlay', activate, camera=camera)
|
return self._change_setting('overlay', activate, camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_gps(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_gps(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable GPS."""
|
"""Enable/disable GPS."""
|
||||||
return self.change_setting('gps_active', activate, camera=camera)
|
return self._change_setting('gps_active', activate, camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_quality(self, quality: int = 100, camera: Union[int, str] = None) -> bool:
|
def set_quality(
|
||||||
|
self, quality: int = 100, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Set video quality."""
|
"""Set video quality."""
|
||||||
return self.change_setting('quality', int(quality), camera=camera)
|
return self._change_setting('quality', int(quality), camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_motion_detect(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_motion_detect(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable motion detect."""
|
"""Enable/disable motion detect."""
|
||||||
return self.change_setting('motion_detect', activate, camera=camera)
|
return self._change_setting('motion_detect', activate, camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_orientation(self, orientation: str = 'landscape', camera: Union[int, str] = None) -> bool:
|
def set_orientation(
|
||||||
|
self, orientation: str = 'landscape', camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Set video orientation."""
|
"""Set video orientation."""
|
||||||
return self.change_setting('orientation', orientation, camera=camera)
|
return self._change_setting('orientation', orientation, camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_zoom(self, zoom: float, camera: Union[int, str] = None) -> bool:
|
def set_zoom(self, zoom: float, camera: Optional[Union[int, str]] = None) -> bool:
|
||||||
"""Set the zoom level."""
|
"""Set the zoom level."""
|
||||||
return self._exec('settings/ptz', params={'zoom': float(zoom)}, camera=camera)
|
return bool(
|
||||||
|
self._exec('settings/ptz', params={'zoom': float(zoom)}, camera=camera)
|
||||||
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_scenemode(self, scenemode: str = 'auto', camera: Union[int, str] = None) -> bool:
|
def set_scenemode(
|
||||||
|
self, scenemode: str = 'auto', camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Set video orientation."""
|
"""Set video orientation."""
|
||||||
return self.change_setting('scenemode', scenemode, camera=camera)
|
return self._change_setting('scenemode', scenemode, camera=camera)
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
0
platypush/schemas/camera/android/__init__.py
Normal file
0
platypush/schemas/camera/android/__init__.py
Normal file
332
platypush/schemas/camera/android/ipcam.py
Normal file
332
platypush/schemas/camera/android/ipcam.py
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
from marshmallow import EXCLUDE, fields, pre_dump
|
||||||
|
from marshmallow.schema import Schema
|
||||||
|
from marshmallow.validate import OneOf
|
||||||
|
|
||||||
|
from platypush.schemas import StrippedString
|
||||||
|
|
||||||
|
|
||||||
|
class CameraStatusSchema(Schema):
|
||||||
|
"""
|
||||||
|
Schema for the camera status.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta: # type: ignore
|
||||||
|
"""
|
||||||
|
Exclude unknown fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
unknown = EXCLUDE
|
||||||
|
|
||||||
|
name = StrippedString(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Name or IP of the camera',
|
||||||
|
'example': 'Front Door',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
stream_url = fields.Url(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'URL to the video stream',
|
||||||
|
'example': 'http://192.168.1.10:8080/video',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
image_url = fields.Url(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'URL to get a snapshot from the camera',
|
||||||
|
'example': 'http://192.168.1.10:8080/photo.jpg',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
audio_url = fields.Url(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'URL to get audio from the camera',
|
||||||
|
'example': 'http://192.168.1.10:8080/audio.wav',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
orientation = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Orientation of the camera',
|
||||||
|
'example': 'landscape',
|
||||||
|
'validate': OneOf(['landscape', 'portrait']),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
idle = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Idle status of the camera',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
audio_only = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether the camera is in audio-only mode',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
overlay = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether the camera is in overlay mode',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
quality = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Quality of the video stream, in percent',
|
||||||
|
'example': 49,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
night_vision = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether night vision is enabled',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
night_vision_average = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Average brightness for night vision',
|
||||||
|
'example': 2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
night_vision_gain = fields.Float(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Brightness gain for night vision',
|
||||||
|
'example': 1.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ip_address = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'IP address of the camera',
|
||||||
|
'example': '192.168.1.10',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
motion_limit = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Motion limit',
|
||||||
|
'example': 250,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
motion_detect = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether motion detection is enabled',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
motion_display = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether motion display is enabled',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
gps_active = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether GPS is active',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
video_size = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Size of the video stream',
|
||||||
|
'example': '1920x1080',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
photo_size = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Size of the photo',
|
||||||
|
'example': '1920x1080',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
mirror_flip = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Mirror/flip mode',
|
||||||
|
'example': 'none',
|
||||||
|
'validate': OneOf(['none', 'horizontal', 'vertical', 'both']),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
video_connections = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Number of active video connections',
|
||||||
|
'example': 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
audio_connections = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Number of active audio connections',
|
||||||
|
'example': 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
zoom = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Zoom level, as a percentage',
|
||||||
|
'example': 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
crop_x = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Crop X, as a percentage',
|
||||||
|
'example': 50,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
crop_y = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Crop Y, as a percentage',
|
||||||
|
'example': 50,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
coloreffect = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Color effect',
|
||||||
|
'example': 'none',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
scenemode = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Scene mode',
|
||||||
|
'example': 'auto',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
focusmode = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Focus mode',
|
||||||
|
'example': 'continuous-video',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
whitebalance = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'White balance',
|
||||||
|
'example': 'auto',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
flashmode = fields.Str(
|
||||||
|
required=True,
|
||||||
|
validate=OneOf(['off', 'on', 'auto']),
|
||||||
|
metadata={
|
||||||
|
'description': 'Flash mode',
|
||||||
|
'example': 'off',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
torch = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether the torch is enabled',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
focus_distance = fields.Float(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Focus distance',
|
||||||
|
'example': 0.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
focal_length = fields.Float(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Focal length',
|
||||||
|
'example': 4.25,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
aperture = fields.Float(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Aperture',
|
||||||
|
'example': 1.7,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
filter_density = fields.Float(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Filter density',
|
||||||
|
'example': 0.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
exposure_ns = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Exposure time in nanoseconds',
|
||||||
|
'example': 9384,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
iso = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'ISO',
|
||||||
|
'example': 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
manual_sensor = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether the sensor is in manual mode',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@pre_dump
|
||||||
|
def normalize_bools(self, data, **_):
|
||||||
|
for k, v in data.items():
|
||||||
|
if k != 'flashmode' and isinstance(v, str) and v.lower() in ['on', 'off']:
|
||||||
|
data[k] = v.lower() == 'on'
|
||||||
|
return data
|
Loading…
Reference in a new issue