1 """Camera platform that receives images through HTTP POST."""
3 from __future__
import annotations
6 from collections
import deque
7 from datetime
import timedelta
9 from typing
import cast
11 from aiohttp
import web
12 import voluptuous
as vol
16 DOMAIN
as CAMERA_DOMAIN,
17 PLATFORM_SCHEMA
as CAMERA_PLATFORM_SCHEMA,
29 _LOGGER = logging.getLogger(__name__)
31 CONF_BUFFER_SIZE =
"buffer"
32 CONF_IMAGE_FIELD =
"field"
34 DEFAULT_NAME =
"Push Camera"
36 ATTR_FILENAME =
"filename"
37 ATTR_LAST_TRIP =
"last_trip"
39 PUSH_CAMERA_DATA =
"push_camera"
41 PLATFORM_SCHEMA = CAMERA_PLATFORM_SCHEMA.extend(
43 vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
44 vol.Optional(CONF_BUFFER_SIZE, default=1): cv.positive_int,
45 vol.Optional(CONF_TIMEOUT, default=
timedelta(seconds=5)): vol.All(
46 cv.time_period, cv.positive_timedelta
48 vol.Optional(CONF_IMAGE_FIELD, default=
"image"): cv.string,
49 vol.Required(CONF_WEBHOOK_ID): cv.string,
57 async_add_entities: AddEntitiesCallback,
58 discovery_info: DiscoveryInfoType |
None =
None,
60 """Set up the Push Camera platform."""
61 if PUSH_CAMERA_DATA
not in hass.data:
62 hass.data[PUSH_CAMERA_DATA] = {}
64 webhook_id = config.get(CONF_WEBHOOK_ID)
70 config[CONF_BUFFER_SIZE],
72 config[CONF_IMAGE_FIELD],
81 hass: HomeAssistant, webhook_id: str, request: web.Request
83 """Handle incoming webhook POST with image files."""
85 async
with asyncio.timeout(5):
86 data =
dict(await request.post())
87 except (TimeoutError, web.HTTPException)
as error:
88 _LOGGER.error(
"Could not get information from POST <%s>", error)
91 camera = hass.data[PUSH_CAMERA_DATA][webhook_id]
93 if camera.image_field
not in data:
94 _LOGGER.warning(
"Webhook call without POST parameter <%s>", camera.image_field)
97 image_data = cast(web.FileField, data[camera.image_field])
98 await camera.update_image(image_data.file.read(), image_data.filename)
102 """The representation of a Push camera."""
104 def __init__(self, hass, name, buffer_size, timeout, image_field, webhook_id):
105 """Initialize push camera component."""
112 self.
queuequeue = deque([], buffer_size)
116 self.
webhook_urlwebhook_url = webhook.async_generate_url(hass, webhook_id)
119 """Call when entity is added to hass."""
120 self.
hasshass.data[PUSH_CAMERA_DATA][self.
webhook_idwebhook_id] = self
123 webhook.async_register(
133 """HTTP field containing the image file."""
137 """Update the camera image."""
141 self.
queuequeue.clear()
144 self.
queuequeue.appendleft(image)
147 def reset_state(now):
148 """Set state to idle after no new images for a period of time."""
151 _LOGGER.debug(
"Reset state")
158 self.
hasshass, reset_state, dt_util.utcnow() + self.
_timeout_timeout
164 self, width: int |
None =
None, height: int |
None =
None
166 """Return a still image response."""
169 self.
queuequeue.rotate(1)
176 """Return the name of this camera."""
177 return self.
_name_name
181 """Camera Motion Detection Status."""
186 """Return the state attributes."""
191 (ATTR_FILENAME, self.
_filename_filename),
None async_write_ha_state(self)
def __init__(self, hass, name, buffer_size, timeout, image_field, webhook_id)
None async_added_to_hass(self)
bytes|None async_camera_image(self, int|None width=None, int|None height=None)
def motion_detection_enabled(self)
def update_image(self, image, filename)
def extra_state_attributes(self)
None async_write_ha_state(self)
str|UndefinedType|None name(self)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
None handle_webhook(HomeAssistant hass, str webhook_id, web.Request request)
CALLBACK_TYPE async_track_point_in_utc_time(HomeAssistant hass, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action, datetime point_in_time)