Home Assistant Unofficial Reference 2024.12.1
camera.py
Go to the documentation of this file.
1 """Camera platform that has a Raspberry Pi camera."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import os
7 import shutil
8 import subprocess
9 from tempfile import NamedTemporaryFile
10 
11 from homeassistant.components.camera import Camera
12 from homeassistant.const import CONF_FILE_PATH, CONF_NAME, EVENT_HOMEASSISTANT_STOP
13 from homeassistant.core import HomeAssistant
14 from homeassistant.helpers.entity_platform import AddEntitiesCallback
15 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
16 
17 from .const import (
18  CONF_HORIZONTAL_FLIP,
19  CONF_IMAGE_HEIGHT,
20  CONF_IMAGE_QUALITY,
21  CONF_IMAGE_ROTATION,
22  CONF_IMAGE_WIDTH,
23  CONF_OVERLAY_METADATA,
24  CONF_OVERLAY_TIMESTAMP,
25  CONF_TIMELAPSE,
26  CONF_VERTICAL_FLIP,
27  DOMAIN,
28 )
29 
30 _LOGGER = logging.getLogger(__name__)
31 
32 
33 def kill_raspistill(*args):
34  """Kill any previously running raspistill process.."""
35  with subprocess.Popen(
36  ["killall", "raspistill"],
37  stdout=subprocess.DEVNULL,
38  stderr=subprocess.STDOUT,
39  close_fds=False, # required for posix_spawn
40  ):
41  pass
42 
43 
45  hass: HomeAssistant,
46  config: ConfigType,
47  add_entities: AddEntitiesCallback,
48  discovery_info: DiscoveryInfoType | None = None,
49 ) -> None:
50  """Set up the Raspberry Camera."""
51  # We only want this platform to be set up via discovery.
52  # prevent initializing by erroneous platform config section in yaml conf
53  if discovery_info is None:
54  return
55 
56  if shutil.which("raspistill") is None:
57  _LOGGER.error("'raspistill' was not found")
58  return
59 
60  hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, kill_raspistill)
61 
62  setup_config = hass.data[DOMAIN]
63  file_path = setup_config[CONF_FILE_PATH]
64 
65  def delete_temp_file(*args):
66  """Delete the temporary file to prevent saving multiple temp images.
67 
68  Only used when no path is defined
69  """
70  os.remove(file_path)
71 
72  # If no file path is defined, use a temporary file
73  if file_path is None:
74  with NamedTemporaryFile(suffix=".jpg", delete=False) as temp_file:
75  file_path = temp_file.name
76  setup_config[CONF_FILE_PATH] = file_path
77  hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, delete_temp_file)
78 
79  # Check whether the file path has been whitelisted
80  elif not hass.config.is_allowed_path(file_path):
81  _LOGGER.error("'%s' is not a whitelisted directory", file_path)
82  return
83 
84  add_entities([RaspberryCamera(setup_config)])
85 
86 
88  """Representation of a Raspberry Pi camera."""
89 
90  def __init__(self, device_info):
91  """Initialize Raspberry Pi camera component."""
92  super().__init__()
93 
94  self._name_name = device_info[CONF_NAME]
95  self._config_config = device_info
96 
97  # Kill if there's raspistill instance
99 
100  cmd_args = [
101  "raspistill",
102  "--nopreview",
103  "-o",
104  device_info[CONF_FILE_PATH],
105  "-t",
106  "0",
107  "-w",
108  str(device_info[CONF_IMAGE_WIDTH]),
109  "-h",
110  str(device_info[CONF_IMAGE_HEIGHT]),
111  "-tl",
112  str(device_info[CONF_TIMELAPSE]),
113  "-q",
114  str(device_info[CONF_IMAGE_QUALITY]),
115  "-rot",
116  str(device_info[CONF_IMAGE_ROTATION]),
117  ]
118  if device_info[CONF_HORIZONTAL_FLIP]:
119  cmd_args.append("-hf")
120 
121  if device_info[CONF_VERTICAL_FLIP]:
122  cmd_args.append("-vf")
123 
124  if device_info[CONF_OVERLAY_METADATA]:
125  cmd_args.append("-a")
126  cmd_args.append(str(device_info[CONF_OVERLAY_METADATA]))
127 
128  if device_info[CONF_OVERLAY_TIMESTAMP]:
129  cmd_args.append("-a")
130  cmd_args.append("4")
131  cmd_args.append("-a")
132  cmd_args.append(str(device_info[CONF_OVERLAY_TIMESTAMP]))
133 
134  # The raspistill process started below must run "forever" in
135  # the background until killed when Home Assistant is stopped.
136  # Therefore it must not be wrapped with "with", since that
137  # waits for the subprocess to exit before continuing.
138  subprocess.Popen( # pylint: disable=consider-using-with
139  cmd_args,
140  stdout=subprocess.DEVNULL,
141  stderr=subprocess.STDOUT,
142  close_fds=False, # required for posix_spawn
143  )
144 
146  self, width: int | None = None, height: int | None = None
147  ) -> bytes | None:
148  """Return raspistill image response."""
149  with open(self._config_config[CONF_FILE_PATH], "rb") as file:
150  return file.read()
151 
152  @property
153  def name(self):
154  """Return the name of this camera."""
155  return self._name_name
156 
157  @property
158  def frame_interval(self):
159  """Return the interval between frames of the stream."""
160  return self._config_config[CONF_TIMELAPSE] / 1000
bytes|None camera_image(self, int|None width=None, int|None height=None)
Definition: camera.py:147
None add_entities(HomeAssistant hass, FreeboxRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
Definition: camera.py:54
None open(self, **Any kwargs)
Definition: lock.py:86
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: camera.py:49