Add save clips to Blink services (#84149)

This commit is contained in:
Brent Perdue 2023-06-01 14:06:53 -04:00 committed by GitHub
parent a1a055f618
commit 23ca26ae56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 90 additions and 9 deletions

View file

@ -8,7 +8,13 @@ import voluptuous as vol
from homeassistant.components import persistent_notification from homeassistant.components import persistent_notification
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
from homeassistant.const import CONF_FILENAME, CONF_NAME, CONF_PIN, CONF_SCAN_INTERVAL from homeassistant.const import (
CONF_FILE_PATH,
CONF_FILENAME,
CONF_NAME,
CONF_PIN,
CONF_SCAN_INTERVAL,
)
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
@ -18,6 +24,7 @@ from .const import (
DOMAIN, DOMAIN,
PLATFORMS, PLATFORMS,
SERVICE_REFRESH, SERVICE_REFRESH,
SERVICE_SAVE_RECENT_CLIPS,
SERVICE_SAVE_VIDEO, SERVICE_SAVE_VIDEO,
SERVICE_SEND_PIN, SERVICE_SEND_PIN,
) )
@ -28,6 +35,9 @@ SERVICE_SAVE_VIDEO_SCHEMA = vol.Schema(
{vol.Required(CONF_NAME): cv.string, vol.Required(CONF_FILENAME): cv.string} {vol.Required(CONF_NAME): cv.string, vol.Required(CONF_FILENAME): cv.string}
) )
SERVICE_SEND_PIN_SCHEMA = vol.Schema({vol.Optional(CONF_PIN): cv.string}) SERVICE_SEND_PIN_SCHEMA = vol.Schema({vol.Optional(CONF_PIN): cv.string})
SERVICE_SAVE_RECENT_CLIPS_SCHEMA = vol.Schema(
{vol.Required(CONF_NAME): cv.string, vol.Required(CONF_FILE_PATH): cv.string}
)
def _blink_startup_wrapper(hass: HomeAssistant, entry: ConfigEntry) -> Blink: def _blink_startup_wrapper(hass: HomeAssistant, entry: ConfigEntry) -> Blink:
@ -100,6 +110,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Call save video service handler.""" """Call save video service handler."""
await async_handle_save_video_service(hass, entry, call) await async_handle_save_video_service(hass, entry, call)
async def async_save_recent_clips(call):
"""Call save recent clips service handler."""
await async_handle_save_recent_clips_service(hass, entry, call)
def send_pin(call): def send_pin(call):
"""Call blink to send new pin.""" """Call blink to send new pin."""
pin = call.data[CONF_PIN] pin = call.data[CONF_PIN]
@ -112,6 +126,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SAVE_VIDEO, async_save_video, schema=SERVICE_SAVE_VIDEO_SCHEMA DOMAIN, SERVICE_SAVE_VIDEO, async_save_video, schema=SERVICE_SAVE_VIDEO_SCHEMA
) )
hass.services.async_register(
DOMAIN,
SERVICE_SAVE_RECENT_CLIPS,
async_save_recent_clips,
schema=SERVICE_SAVE_RECENT_CLIPS_SCHEMA,
)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SEND_PIN, send_pin, schema=SERVICE_SEND_PIN_SCHEMA DOMAIN, SERVICE_SEND_PIN, send_pin, schema=SERVICE_SEND_PIN_SCHEMA
) )
@ -164,13 +184,33 @@ async def async_handle_save_video_service(hass, entry, call):
_LOGGER.error("Can't write %s, no access to path!", video_path) _LOGGER.error("Can't write %s, no access to path!", video_path)
return return
def _write_video(camera_name, video_path): def _write_video(name, file_path):
"""Call video write.""" """Call video write."""
all_cameras = hass.data[DOMAIN][entry.entry_id].cameras all_cameras = hass.data[DOMAIN][entry.entry_id].cameras
if camera_name in all_cameras: if name in all_cameras:
all_cameras[camera_name].video_to_file(video_path) all_cameras[name].video_to_file(file_path)
try: try:
await hass.async_add_executor_job(_write_video, camera_name, video_path) await hass.async_add_executor_job(_write_video, camera_name, video_path)
except OSError as err: except OSError as err:
_LOGGER.error("Can't write image to file: %s", err) _LOGGER.error("Can't write image to file: %s", err)
async def async_handle_save_recent_clips_service(hass, entry, call):
"""Save multiple recent clips to output directory."""
camera_name = call.data[CONF_NAME]
clips_dir = call.data[CONF_FILE_PATH]
if not hass.config.is_allowed_path(clips_dir):
_LOGGER.error("Can't write to directory %s, no access to path!", clips_dir)
return
def _save_recent_clips(name, output_dir):
"""Call save recent clips."""
all_cameras = hass.data[DOMAIN][entry.entry_id].cameras
if name in all_cameras:
all_cameras[name].save_recent_clips(output_dir=output_dir)
try:
await hass.async_add_executor_job(_save_recent_clips, camera_name, clips_dir)
except OSError as err:
_LOGGER.error("Can't write recent clips to directory: %s", err)

View file

@ -55,8 +55,15 @@ class BlinkSyncModule(AlarmControlPanelEntity):
def update(self) -> None: def update(self) -> None:
"""Update the state of the device.""" """Update the state of the device."""
_LOGGER.debug("Updating Blink Alarm Control Panel %s", self._name) if self.data.check_if_ok_to_update():
self.data.refresh() _LOGGER.debug(
"Initiating a blink.refresh() from BlinkSyncModule('%s') (%s)",
self._name,
self.data,
)
self.data.refresh()
_LOGGER.info("Updating State of Blink Alarm Control Panel '%s'", self._name)
self._attr_state = ( self._attr_state = (
STATE_ALARM_ARMED_AWAY if self.sync.arm else STATE_ALARM_DISARMED STATE_ALARM_ARMED_AWAY if self.sync.arm else STATE_ALARM_DISARMED
) )

View file

@ -1,6 +1,8 @@
"""Support for Blink system camera control.""" """Support for Blink system camera control."""
from __future__ import annotations from __future__ import annotations
import logging
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass, BinarySensorDeviceClass,
BinarySensorEntity, BinarySensorEntity,
@ -20,6 +22,8 @@ from .const import (
TYPE_MOTION_DETECTED, TYPE_MOTION_DETECTED,
) )
_LOGGER = logging.getLogger(__name__)
BINARY_SENSORS_TYPES: tuple[BinarySensorEntityDescription, ...] = ( BINARY_SENSORS_TYPES: tuple[BinarySensorEntityDescription, ...] = (
BinarySensorEntityDescription( BinarySensorEntityDescription(
key=TYPE_BATTERY, key=TYPE_BATTERY,
@ -74,8 +78,13 @@ class BlinkBinarySensor(BinarySensorEntity):
def update(self) -> None: def update(self) -> None:
"""Update sensor state.""" """Update sensor state."""
self.data.refresh()
state = self._camera.attributes[self.entity_description.key] state = self._camera.attributes[self.entity_description.key]
_LOGGER.debug(
"'%s' %s = %s",
self._camera.attributes["name"],
self.entity_description.key,
state,
)
if self.entity_description.key == TYPE_BATTERY: if self.entity_description.key == TYPE_BATTERY:
state = state != "ok" state = state != "ok"
self._attr_is_on = state self._attr_is_on = state

View file

@ -23,6 +23,7 @@ TYPE_WIFI_STRENGTH = "wifi_strength"
SERVICE_REFRESH = "blink_update" SERVICE_REFRESH = "blink_update"
SERVICE_TRIGGER = "trigger_camera" SERVICE_TRIGGER = "trigger_camera"
SERVICE_SAVE_VIDEO = "save_video" SERVICE_SAVE_VIDEO = "save_video"
SERVICE_SAVE_RECENT_CLIPS = "save_recent_clips"
SERVICE_SEND_PIN = "send_pin" SERVICE_SEND_PIN = "send_pin"
PLATFORMS = [ PLATFORMS = [

View file

@ -78,9 +78,14 @@ class BlinkSensor(SensorEntity):
def update(self) -> None: def update(self) -> None:
"""Retrieve sensor data from the camera.""" """Retrieve sensor data from the camera."""
self.data.refresh()
try: try:
self._attr_native_value = self._camera.attributes[self._sensor_key] self._attr_native_value = self._camera.attributes[self._sensor_key]
_LOGGER.debug(
"'%s' %s = %s",
self._camera.attributes["name"],
self._sensor_key,
self._attr_native_value,
)
except KeyError: except KeyError:
self._attr_native_value = None self._attr_native_value = None
_LOGGER.error( _LOGGER.error(

View file

@ -25,12 +25,31 @@ save_video:
text: text:
filename: filename:
name: File name name: File name
description: Filename to writable path (directory may need to be included in whitelist_dirs in config) description: Filename to writable path (directory may need to be included in allowlist_external_dirs in config)
required: true required: true
example: "/tmp/video.mp4" example: "/tmp/video.mp4"
selector: selector:
text: text:
save_recent_clips:
name: Save recent clips
description: 'Save all recent video clips to local directory with file pattern "%Y%m%d_%H%M%S_{name}.mp4"'
fields:
name:
name: Name
description: Name of camera to grab recent clips from.
required: true
example: "Living Room"
selector:
text:
file_path:
name: Output directory
description: Directory name of writable path (directory may need to be included in allowlist_external_dirs in config)
required: true
example: "/tmp"
selector:
text:
send_pin: send_pin:
name: Send pin name: Send pin
description: Send a new PIN to blink for 2FA. description: Send a new PIN to blink for 2FA.