2019-04-03 15:40:03 +00:00
|
|
|
"""Camera platform that has a Raspberry Pi camera."""
|
2016-03-27 18:49:04 +00:00
|
|
|
import os
|
|
|
|
import subprocess
|
|
|
|
import logging
|
|
|
|
import shutil
|
2018-02-14 07:07:50 +00:00
|
|
|
from tempfile import NamedTemporaryFile
|
2016-03-27 18:49:04 +00:00
|
|
|
|
2016-08-27 20:44:22 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
|
|
|
from homeassistant.const import CONF_NAME, CONF_FILE_PATH, EVENT_HOMEASSISTANT_STOP
|
2016-08-27 20:44:22 +00:00
|
|
|
from homeassistant.helpers import config_validation as cv
|
2016-03-27 18:49:04 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
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"
|
2016-08-27 20:44:22 +00:00
|
|
|
|
|
|
|
DEFAULT_HORIZONTAL_FLIP = 0
|
|
|
|
DEFAULT_IMAGE_HEIGHT = 480
|
2018-01-29 22:37:19 +00:00
|
|
|
DEFAULT_IMAGE_QUALITY = 7
|
2016-08-27 20:44:22 +00:00
|
|
|
DEFAULT_IMAGE_ROTATION = 0
|
|
|
|
DEFAULT_IMAGE_WIDTH = 640
|
2019-07-31 19:25:30 +00:00
|
|
|
DEFAULT_NAME = "Raspberry Pi Camera"
|
2016-08-27 20:44:22 +00:00
|
|
|
DEFAULT_TIMELAPSE = 1000
|
|
|
|
DEFAULT_VERTICAL_FLIP = 0
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
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)
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
2016-08-27 20:44:22 +00:00
|
|
|
|
2016-03-27 18:49:04 +00:00
|
|
|
|
2016-10-27 06:51:13 +00:00
|
|
|
def kill_raspistill(*args):
|
|
|
|
"""Kill any previously running raspistill process.."""
|
2019-07-31 19:25:30 +00:00
|
|
|
subprocess.Popen(
|
|
|
|
["killall", "raspistill"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
|
|
|
|
)
|
2016-10-27 06:51:13 +00:00
|
|
|
|
|
|
|
|
2018-08-24 14:37:30 +00:00
|
|
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Set up the Raspberry Camera."""
|
2016-03-27 18:49:04 +00:00
|
|
|
if shutil.which("raspistill") is None:
|
2016-08-27 20:44:22 +00:00
|
|
|
_LOGGER.error("'raspistill' was not found")
|
2016-03-27 18:49:04 +00:00
|
|
|
return False
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
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),
|
|
|
|
}
|
2016-03-27 18:49:04 +00:00
|
|
|
|
2016-10-27 06:51:13 +00:00
|
|
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, kill_raspistill)
|
|
|
|
|
2018-02-14 07:07:50 +00:00
|
|
|
file_path = setup_config[CONF_FILE_PATH]
|
2016-10-27 06:51:13 +00:00
|
|
|
|
2018-02-14 07:07:50 +00:00
|
|
|
def delete_temp_file(*args):
|
|
|
|
"""Delete the temporary file to prevent saving multiple temp images.
|
|
|
|
|
|
|
|
Only used when no path is defined
|
|
|
|
"""
|
|
|
|
os.remove(file_path)
|
|
|
|
|
|
|
|
# If no file path is defined, use a temporary file
|
|
|
|
if file_path is None:
|
2019-07-31 19:25:30 +00:00
|
|
|
temp_file = NamedTemporaryFile(suffix=".jpg", delete=False)
|
2018-02-14 07:07:50 +00:00
|
|
|
temp_file.close()
|
|
|
|
file_path = temp_file.name
|
|
|
|
setup_config[CONF_FILE_PATH] = file_path
|
|
|
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, delete_temp_file)
|
|
|
|
|
|
|
|
# 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)
|
2016-10-27 06:51:13 +00:00
|
|
|
return False
|
2016-03-27 18:49:04 +00:00
|
|
|
|
2018-08-24 14:37:30 +00:00
|
|
|
add_entities([RaspberryCamera(setup_config)])
|
2018-03-06 19:44:37 +00:00
|
|
|
|
2016-03-27 18:49:04 +00:00
|
|
|
|
|
|
|
class RaspberryCamera(Camera):
|
2016-06-30 08:33:34 +00:00
|
|
|
"""Representation of a Raspberry Pi camera."""
|
2016-03-27 18:49:04 +00:00
|
|
|
|
|
|
|
def __init__(self, device_info):
|
|
|
|
"""Initialize Raspberry Pi camera component."""
|
|
|
|
super().__init__()
|
|
|
|
|
2016-08-27 20:44:22 +00:00
|
|
|
self._name = device_info[CONF_NAME]
|
2016-03-27 18:49:04 +00:00
|
|
|
self._config = device_info
|
|
|
|
|
2016-08-27 20:44:22 +00:00
|
|
|
# Kill if there's raspistill instance
|
2016-10-27 06:51:13 +00:00
|
|
|
kill_raspistill()
|
2016-03-27 18:49:04 +00:00
|
|
|
|
|
|
|
cmd_args = [
|
2019-07-31 19:25:30 +00:00
|
|
|
"raspistill",
|
|
|
|
"--nopreview",
|
|
|
|
"-o",
|
|
|
|
device_info[CONF_FILE_PATH],
|
|
|
|
"-t",
|
|
|
|
"0",
|
|
|
|
"-w",
|
|
|
|
str(device_info[CONF_IMAGE_WIDTH]),
|
|
|
|
"-h",
|
|
|
|
str(device_info[CONF_IMAGE_HEIGHT]),
|
|
|
|
"-tl",
|
|
|
|
str(device_info[CONF_TIMELAPSE]),
|
|
|
|
"-q",
|
|
|
|
str(device_info[CONF_IMAGE_QUALITY]),
|
|
|
|
"-rot",
|
|
|
|
str(device_info[CONF_IMAGE_ROTATION]),
|
2016-03-27 18:49:04 +00:00
|
|
|
]
|
2016-08-27 20:44:22 +00:00
|
|
|
if device_info[CONF_HORIZONTAL_FLIP]:
|
2016-03-27 18:49:04 +00:00
|
|
|
cmd_args.append("-hf")
|
|
|
|
|
2016-08-27 20:44:22 +00:00
|
|
|
if device_info[CONF_VERTICAL_FLIP]:
|
2016-03-27 18:49:04 +00:00
|
|
|
cmd_args.append("-vf")
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
subprocess.Popen(cmd_args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
2016-03-27 18:49:04 +00:00
|
|
|
|
|
|
|
def camera_image(self):
|
2018-01-29 22:37:19 +00:00
|
|
|
"""Return raspistill image response."""
|
2019-07-31 19:25:30 +00:00
|
|
|
with open(self._config[CONF_FILE_PATH], "rb") as file:
|
2016-03-27 18:49:04 +00:00
|
|
|
return file.read()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of this camera."""
|
|
|
|
return self._name
|