Skip to content
84 changes: 84 additions & 0 deletions homeassistant/components/rpi_camera/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,85 @@
"""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,
DOMAIN,
DEFAULT_HORIZONTAL_FLIP,
DEFAULT_IMAGE_HEIGHT,
DEFAULT_IMAGE_QUALITY,
DEFAULT_IMAGE_ROTATION,
DEFAULT_IMAGE_WIDTH,
DEFAULT_NAME,
DEFAULT_TIMELAPSE,
DEFAULT_VERTICAL_FLIP,
)

_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."""
_LOGGER.debug("setup")
Comment thread
MartinHjelmare marked this conversation as resolved.
Outdated
_LOGGER.debug("config[%s] = %s", DOMAIN, config[DOMAIN])

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),
}

_LOGGER.debug("hass.data[%s] = %s", DOMAIN, hass.data[DOMAIN])

discovery.load_platform(hass, "camera", DOMAIN, {}, config)

return True
99 changes: 42 additions & 57 deletions homeassistant/components/rpi_camera/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,82 +5,44 @@
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

_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)
),
}
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__)

def kill_raspistill(*args):
"""Kill any previously running raspistill process.."""
_LOGGER.debug("killall raspistill")
subprocess.Popen(
["killall", "raspistill"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
)


def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Raspberry Camera."""
_LOGGER.debug("setup_platform")
Comment thread
alxrdn marked this conversation as resolved.
Outdated
if shutil.which("raspistill") is None:
_LOGGER.error("'raspistill' was not found")
return False
Comment thread
alxrdn marked this conversation as resolved.
Outdated

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),
}
_LOGGER.debug("hass.data[%s] = %s", DOMAIN, hass.data[DOMAIN])
Comment thread
alxrdn marked this conversation as resolved.
Outdated

hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, kill_raspistill)

file_path = setup_config[CONF_FILE_PATH]
file_path = hass.data[DOMAIN][CONF_FILE_PATH]
Comment thread
alxrdn marked this conversation as resolved.
Outdated

def delete_temp_file(*args):
"""Delete the temporary file to prevent saving multiple temp images.
Expand All @@ -94,15 +56,16 @@ def delete_temp_file(*args):
temp_file = NamedTemporaryFile(suffix=".jpg", delete=False)
temp_file.close()
file_path = temp_file.name
setup_config[CONF_FILE_PATH] = file_path
hass.data[DOMAIN][CONF_FILE_PATH] = file_path
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, delete_temp_file)
_LOGGER.debug("hass.data[%s][%s] = %s", DOMAIN, CONF_FILE_PATH, hass.data[DOMAIN][CONF_FILE_PATH])

# 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
Comment thread
alxrdn marked this conversation as resolved.
Outdated

add_entities([RaspberryCamera(setup_config)])
add_entities([RaspberryCamera(hass.data[DOMAIN])])


class RaspberryCamera(Camera):
Expand All @@ -111,7 +74,9 @@ class RaspberryCamera(Camera):
def __init__(self, device_info):
"""Initialize Raspberry Pi camera component."""
super().__init__()
_LOGGER.debug("RaspberryCamera::init")

_LOGGER.debug("device_info = %s", device_info)
self._name = device_info[CONF_NAME]
self._config = device_info

Expand Down Expand Up @@ -142,14 +107,34 @@ def __init__(self, device_info):
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]))

_LOGGER.debug("cmd_args = %s", cmd_args)

subprocess.Popen(cmd_args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)

def camera_image(self):
"""Return raspistill image response."""
_LOGGER.debug("returning image from %s", self._config[CONF_FILE_PATH])

with open(self._config[CONF_FILE_PATH], "rb") as file:
return file.read()

@property
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

22 changes: 22 additions & 0 deletions homeassistant/components/rpi_camera/const.py
Original file line number Diff line number Diff line change
@@ -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