From 55bf5514ad6a6cf5945a398001671ac46169ac06 Mon Sep 17 00:00:00 2001 From: alxrdn Date: Thu, 30 Apr 2020 15:30:37 +0200 Subject: [PATCH] Add overlay options wrapper to rpi_camera (#34461) * add overlay options wrapper to rpi_camera * Refactor to set config yaml section under the top level integration domain key * Remove return values that are not checked Co-Authored-By: Martin Hjelmare * Remove superfluous debug log messages * Return if not set up via discovery * Add convenience reference to hass.data[DOMAIN] * Black formatting * Isort * Exclude all rpi_camera modules Co-authored-by: Martin Hjelmare --- .coveragerc | 2 +- .../components/rpi_camera/__init__.py | 88 +++++++++++++++++ homeassistant/components/rpi_camera/camera.py | 94 ++++++++----------- homeassistant/components/rpi_camera/const.py | 22 +++++ 4 files changed, 148 insertions(+), 58 deletions(-) create mode 100644 homeassistant/components/rpi_camera/const.py diff --git a/.coveragerc b/.coveragerc index cf9fa59397d..e2aa4243a46 100644 --- a/.coveragerc +++ b/.coveragerc @@ -611,7 +611,7 @@ omit = homeassistant/components/roomba/vacuum.py homeassistant/components/route53/* homeassistant/components/rova/sensor.py - homeassistant/components/rpi_camera/camera.py + homeassistant/components/rpi_camera/* homeassistant/components/rpi_gpio/* homeassistant/components/rpi_gpio/cover.py homeassistant/components/rpi_gpio_pwm/light.py diff --git a/homeassistant/components/rpi_camera/__init__.py b/homeassistant/components/rpi_camera/__init__.py index 04638e463a1..2f962872d8c 100644 --- a/homeassistant/components/rpi_camera/__init__.py +++ b/homeassistant/components/rpi_camera/__init__.py @@ -1 +1,89 @@ """The rpi_camera component.""" +import logging + +import voluptuous as vol + +from homeassistant.const import CONF_FILE_PATH, CONF_NAME +from homeassistant.helpers import config_validation as cv, discovery + +from .const import ( + CONF_HORIZONTAL_FLIP, + CONF_IMAGE_HEIGHT, + CONF_IMAGE_QUALITY, + CONF_IMAGE_ROTATION, + CONF_IMAGE_WIDTH, + CONF_OVERLAY_METADATA, + CONF_OVERLAY_TIMESTAMP, + CONF_TIMELAPSE, + CONF_VERTICAL_FLIP, + DEFAULT_HORIZONTAL_FLIP, + DEFAULT_IMAGE_HEIGHT, + DEFAULT_IMAGE_QUALITY, + DEFAULT_IMAGE_ROTATION, + DEFAULT_IMAGE_WIDTH, + DEFAULT_NAME, + DEFAULT_TIMELAPSE, + DEFAULT_VERTICAL_FLIP, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_FILE_PATH): cv.isfile, + vol.Optional( + CONF_HORIZONTAL_FLIP, default=DEFAULT_HORIZONTAL_FLIP + ): vol.All(vol.Coerce(int), vol.Range(min=0, max=1)), + vol.Optional( + CONF_IMAGE_HEIGHT, default=DEFAULT_IMAGE_HEIGHT + ): vol.Coerce(int), + vol.Optional( + CONF_IMAGE_QUALITY, default=DEFAULT_IMAGE_QUALITY + ): vol.All(vol.Coerce(int), vol.Range(min=0, max=100)), + vol.Optional( + CONF_IMAGE_ROTATION, default=DEFAULT_IMAGE_ROTATION + ): vol.All(vol.Coerce(int), vol.Range(min=0, max=359)), + vol.Optional(CONF_IMAGE_WIDTH, default=DEFAULT_IMAGE_WIDTH): vol.Coerce( + int + ), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OVERLAY_METADATA): vol.All( + vol.Coerce(int), vol.Range(min=4, max=2056) + ), + vol.Optional(CONF_OVERLAY_TIMESTAMP): cv.string, + vol.Optional(CONF_TIMELAPSE, default=DEFAULT_TIMELAPSE): vol.Coerce( + int + ), + vol.Optional( + CONF_VERTICAL_FLIP, default=DEFAULT_VERTICAL_FLIP + ): vol.All(vol.Coerce(int), vol.Range(min=0, max=1)), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + + +def setup(hass, config): + """Set up the rpi_camera integration.""" + config_domain = config[DOMAIN] + hass.data[DOMAIN] = { + CONF_FILE_PATH: config_domain.get(CONF_FILE_PATH), + CONF_HORIZONTAL_FLIP: config_domain.get(CONF_HORIZONTAL_FLIP), + CONF_IMAGE_WIDTH: config_domain.get(CONF_IMAGE_WIDTH), + CONF_IMAGE_HEIGHT: config_domain.get(CONF_IMAGE_HEIGHT), + CONF_IMAGE_QUALITY: config_domain.get(CONF_IMAGE_QUALITY), + CONF_IMAGE_ROTATION: config_domain.get(CONF_IMAGE_ROTATION), + CONF_NAME: config_domain.get(CONF_NAME), + CONF_OVERLAY_METADATA: config_domain.get(CONF_OVERLAY_METADATA), + CONF_OVERLAY_TIMESTAMP: config_domain.get(CONF_OVERLAY_TIMESTAMP), + CONF_TIMELAPSE: config_domain.get(CONF_TIMELAPSE), + CONF_VERTICAL_FLIP: config_domain.get(CONF_VERTICAL_FLIP), + } + + discovery.load_platform(hass, "camera", DOMAIN, {}, config) + + return True diff --git a/homeassistant/components/rpi_camera/camera.py b/homeassistant/components/rpi_camera/camera.py index bf04e0ef492..47ce87c4a8d 100644 --- a/homeassistant/components/rpi_camera/camera.py +++ b/homeassistant/components/rpi_camera/camera.py @@ -5,53 +5,24 @@ import shutil import subprocess from tempfile import NamedTemporaryFile -import voluptuous as vol - -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera +from homeassistant.components.camera import Camera from homeassistant.const import CONF_FILE_PATH, CONF_NAME, EVENT_HOMEASSISTANT_STOP -from homeassistant.helpers import config_validation as cv + +from .const import ( + CONF_HORIZONTAL_FLIP, + CONF_IMAGE_HEIGHT, + CONF_IMAGE_QUALITY, + CONF_IMAGE_ROTATION, + CONF_IMAGE_WIDTH, + CONF_OVERLAY_METADATA, + CONF_OVERLAY_TIMESTAMP, + CONF_TIMELAPSE, + CONF_VERTICAL_FLIP, + DOMAIN, +) _LOGGER = logging.getLogger(__name__) -CONF_HORIZONTAL_FLIP = "horizontal_flip" -CONF_IMAGE_HEIGHT = "image_height" -CONF_IMAGE_QUALITY = "image_quality" -CONF_IMAGE_ROTATION = "image_rotation" -CONF_IMAGE_WIDTH = "image_width" -CONF_TIMELAPSE = "timelapse" -CONF_VERTICAL_FLIP = "vertical_flip" - -DEFAULT_HORIZONTAL_FLIP = 0 -DEFAULT_IMAGE_HEIGHT = 480 -DEFAULT_IMAGE_QUALITY = 7 -DEFAULT_IMAGE_ROTATION = 0 -DEFAULT_IMAGE_WIDTH = 640 -DEFAULT_NAME = "Raspberry Pi Camera" -DEFAULT_TIMELAPSE = 1000 -DEFAULT_VERTICAL_FLIP = 0 - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_FILE_PATH): cv.isfile, - vol.Optional(CONF_HORIZONTAL_FLIP, default=DEFAULT_HORIZONTAL_FLIP): vol.All( - vol.Coerce(int), vol.Range(min=0, max=1) - ), - vol.Optional(CONF_IMAGE_HEIGHT, default=DEFAULT_IMAGE_HEIGHT): vol.Coerce(int), - vol.Optional(CONF_IMAGE_QUALITY, default=DEFAULT_IMAGE_QUALITY): vol.All( - vol.Coerce(int), vol.Range(min=0, max=100) - ), - vol.Optional(CONF_IMAGE_ROTATION, default=DEFAULT_IMAGE_ROTATION): vol.All( - vol.Coerce(int), vol.Range(min=0, max=359) - ), - vol.Optional(CONF_IMAGE_WIDTH, default=DEFAULT_IMAGE_WIDTH): vol.Coerce(int), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_TIMELAPSE, default=1000): vol.Coerce(int), - vol.Optional(CONF_VERTICAL_FLIP, default=DEFAULT_VERTICAL_FLIP): vol.All( - vol.Coerce(int), vol.Range(min=0, max=1) - ), - } -) - def kill_raspistill(*args): """Kill any previously running raspistill process..""" @@ -62,24 +33,18 @@ def kill_raspistill(*args): def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Raspberry Camera.""" + # We only want this platform to be set up via discovery. + # prevent initializing by erroneous platform config section in yaml conf + if discovery_info is None: + return + if shutil.which("raspistill") is None: _LOGGER.error("'raspistill' was not found") - return False - - setup_config = { - CONF_NAME: config.get(CONF_NAME), - CONF_IMAGE_WIDTH: config.get(CONF_IMAGE_WIDTH), - CONF_IMAGE_HEIGHT: config.get(CONF_IMAGE_HEIGHT), - CONF_IMAGE_QUALITY: config.get(CONF_IMAGE_QUALITY), - CONF_IMAGE_ROTATION: config.get(CONF_IMAGE_ROTATION), - CONF_TIMELAPSE: config.get(CONF_TIMELAPSE), - CONF_HORIZONTAL_FLIP: config.get(CONF_HORIZONTAL_FLIP), - CONF_VERTICAL_FLIP: config.get(CONF_VERTICAL_FLIP), - CONF_FILE_PATH: config.get(CONF_FILE_PATH), - } + return hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, kill_raspistill) + setup_config = hass.data[DOMAIN] file_path = setup_config[CONF_FILE_PATH] def delete_temp_file(*args): @@ -100,7 +65,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # Check whether the file path has been whitelisted elif not hass.config.is_allowed_path(file_path): _LOGGER.error("'%s' is not a whitelisted directory", file_path) - return False + return add_entities([RaspberryCamera(setup_config)]) @@ -142,6 +107,16 @@ class RaspberryCamera(Camera): if device_info[CONF_VERTICAL_FLIP]: cmd_args.append("-vf") + if device_info[CONF_OVERLAY_METADATA]: + cmd_args.append("-a") + cmd_args.append(str(device_info[CONF_OVERLAY_METADATA])) + + if device_info[CONF_OVERLAY_TIMESTAMP]: + cmd_args.append("-a") + cmd_args.append("4") + cmd_args.append("-a") + cmd_args.append(str(device_info[CONF_OVERLAY_TIMESTAMP])) + subprocess.Popen(cmd_args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) def camera_image(self): @@ -153,3 +128,8 @@ class RaspberryCamera(Camera): def name(self): """Return the name of this camera.""" return self._name + + @property + def frame_interval(self): + """Return the interval between frames of the stream.""" + return self._config[CONF_TIMELAPSE] / 1000 diff --git a/homeassistant/components/rpi_camera/const.py b/homeassistant/components/rpi_camera/const.py new file mode 100644 index 00000000000..19da9e2a286 --- /dev/null +++ b/homeassistant/components/rpi_camera/const.py @@ -0,0 +1,22 @@ +"""Consts used by rpi_camera.""" + +DOMAIN = "rpi_camera" + +CONF_HORIZONTAL_FLIP = "horizontal_flip" +CONF_IMAGE_HEIGHT = "image_height" +CONF_IMAGE_QUALITY = "image_quality" +CONF_IMAGE_ROTATION = "image_rotation" +CONF_IMAGE_WIDTH = "image_width" +CONF_OVERLAY_METADATA = "overlay_metadata" +CONF_OVERLAY_TIMESTAMP = "overlay_timestamp" +CONF_TIMELAPSE = "timelapse" +CONF_VERTICAL_FLIP = "vertical_flip" + +DEFAULT_HORIZONTAL_FLIP = 0 +DEFAULT_IMAGE_HEIGHT = 480 +DEFAULT_IMAGE_QUALITY = 7 +DEFAULT_IMAGE_ROTATION = 0 +DEFAULT_IMAGE_WIDTH = 640 +DEFAULT_NAME = "Raspberry Pi Camera" +DEFAULT_TIMELAPSE = 1000 +DEFAULT_VERTICAL_FLIP = 0