Skip to content
This repository has been archived by the owner on Aug 26, 2022. It is now read-only.

Commit

Permalink
Merge pull request #13 from briis/V0.0.10
Browse files Browse the repository at this point in the history
V0.0.10
  • Loading branch information
briis committed Jan 8, 2020
2 parents 6b91a2b + 97c7f2d commit 04b5177
Show file tree
Hide file tree
Showing 7 changed files with 396 additions and 196 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Version 0.0.10
* `New Items`:
* Added new Icon to the Sensor, and now the Icon is changing based on Recording Mode state, so it is clear to see if motion recording is on or off
* `Fixes`:
* Code clean-up and now all code conforms to PEP standards

## Version 0.0.9
* `camera`:
* **BREAKING CHANGE** The service `camera.unifiprotect_save_thumbnail` has been removed, and has been replaced by a new Service you can read more about below. The implementation was not done according to Home Assistant standards, so I decided to rewrite it. If you use this Service in any automation, please replace it with the new Service.
Expand Down
51 changes: 35 additions & 16 deletions custom_components/unifiprotect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@
import requests
from . import protectnvr as nvr

from homeassistant.const import ATTR_ATTRIBUTION, CONF_HOST, CONF_PORT, CONF_SSL, CONF_USERNAME, CONF_PASSWORD, CONF_FILENAME, ATTR_ENTITY_ID
from homeassistant.const import (
ATTR_ATTRIBUTION,
CONF_HOST,
CONF_PORT,
CONF_SSL,
CONF_USERNAME,
CONF_PASSWORD,
CONF_FILENAME,
ATTR_ENTITY_ID,
)
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import Entity

__version__ = '0.0.9'
__version__ = "0.0.10"

_LOGGER = logging.getLogger(__name__)

Expand All @@ -22,7 +31,6 @@
SERVICE_SAVE_THUMBNAIL = "save_thumbnail_image"

DOMAIN = "unifiprotect"
DEFAULT_ENTITY_NAMESPACE = "unifprotect"
DEFAULT_PASSWORD = "ubnt"
DEFAULT_PORT = 7443
DEFAULT_SSL = False
Expand All @@ -43,12 +51,14 @@
)

SAVE_THUMBNAIL_SCHEMA = vol.Schema(
{vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(CONF_FILENAME): cv.string,
vol.Optional(CONF_IMAGE_WIDTH, default=DEFAULT_THUMB_WIDTH): cv.string
}
{
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(CONF_FILENAME): cv.string,
vol.Optional(CONF_IMAGE_WIDTH, default=DEFAULT_THUMB_WIDTH): cv.string,
}
)


def setup(hass, config):
"""Set up the Unifi Protect component."""
conf = config[DOMAIN]
Expand All @@ -59,10 +69,10 @@ def setup(hass, config):
use_ssl = conf.get(CONF_SSL)

try:
nvrobject = nvr.protectRemote(host,port,username,password,use_ssl)
nvrobject = nvr.ProtectServer(host, port, username, password, use_ssl)
hass.data[DATA_UFP] = nvrobject
_LOGGER.debug("Connected to Unifi Protect Platform")

except nvr.NotAuthorized:
_LOGGER.error("Authorization failure while connecting to NVR")
return False
Expand All @@ -78,17 +88,21 @@ async def async_save_thumbnail(call):
await async_handle_save_thumbnail_service(hass, call)

hass.services.register(
DOMAIN, SERVICE_SAVE_THUMBNAIL, async_save_thumbnail, schema=SAVE_THUMBNAIL_SCHEMA
DOMAIN,
SERVICE_SAVE_THUMBNAIL,
async_save_thumbnail,
schema=SAVE_THUMBNAIL_SCHEMA,
)

return True


async def async_handle_save_thumbnail_service(hass, call):
"""Handle save thumbnail service calls."""
# Get the Camera ID from Entity_id
entity_id = call.data[ATTR_ENTITY_ID]
entity_state = hass.states.get(entity_id[0])
camera_uuid = entity_state.attributes['uuid']
camera_uuid = entity_state.attributes["uuid"]
if camera_uuid is None:
_LOGGER.error("Unable to get Camera ID for selected Camera")
return
Expand All @@ -99,21 +113,26 @@ async def async_handle_save_thumbnail_service(hass, call):

if not hass.config.is_allowed_path(filename):
_LOGGER.error("Can't write %s, no access to path!", filename)
return
return

def _write_thumbnail(camera_id, filename, image_width):
"""Call thumbnail write."""
image_data = hass.data[DATA_UFP].get_thumbnail(camera_id, image_width)
if image_data is None:
_LOGGER.warning("Last recording not found for Camera %s", entity_state.attributes['friendly_name'])
_LOGGER.warning(
"Last recording not found for Camera %s",
entity_state.attributes["friendly_name"],
)
return False
# We got an image, now write the image to disk
with open(filename, 'wb') as img_file:
with open(filename, "wb") as img_file:
img_file.write(image_data)
_LOGGER.debug("Thumbnail Image written to %s", filename)

try:
await hass.async_add_executor_job(_write_thumbnail, camera_uuid, filename, image_width)
await hass.async_add_executor_job(
_write_thumbnail, camera_uuid, filename, image_width
)
except OSError as err:
_LOGGER.error("Can't write image to file: %s", err)

48 changes: 24 additions & 24 deletions custom_components/unifiprotect/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,28 @@
from datetime import timedelta

import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (BinarySensorDevice)
from homeassistant.const import (ATTR_ATTRIBUTION,
CONF_MONITORED_CONDITIONS)
from homeassistant.core import callback
from homeassistant.helpers.config_validation import (PLATFORM_SCHEMA)
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from . import ATTRIBUTION, DATA_UFP, DEFAULT_BRAND

_LOGGER = logging.getLogger(__name__)

DEPENDENCIES = ['unifiprotect']
DEPENDENCIES = ["unifiprotect"]

SCAN_INTERVAL = timedelta(seconds=3)

# sensor_type [ description, unit, icon ]
SENSOR_TYPES = {
'motion': ['Motion', 'motion', 'motionDetected']
}
SENSOR_TYPES = {"motion": ["Motion", "motion", "motionDetected"]}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All(
cv.ensure_list, [vol.In(SENSOR_TYPES)]
),
}
)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})

async def async_setup_platform(hass, config, async_add_entities, _discovery_info=None):
"""Set up an Unifi Protect binary sensor."""
Expand All @@ -40,21 +40,22 @@ async def async_setup_platform(hass, config, async_add_entities, _discovery_info

async_add_entities(sensors, True)


class UfpBinarySensor(BinarySensorDevice):
"""A Unifi Protect Binary Sensor."""

def __init__(self, device, sensor_type, nvrdata):
"""Initialize an Arlo sensor."""
self._name = '{0} {1}'.format(SENSOR_TYPES[sensor_type][0], device['name'])
self._unique_id = self._name.lower().replace(' ', '_')
self._name = "{0} {1}".format(SENSOR_TYPES[sensor_type][0], device["name"])
self._unique_id = self._name.lower().replace(" ", "_")
self._device = device
self._sensor_type = sensor_type
self._nvrdata = nvrdata
self._state = False
self._class = SENSOR_TYPES.get(self._sensor_type)[1]
self._attr = SENSOR_TYPES.get(self._sensor_type)[2]
self.remove_timer = None
_LOGGER.debug('UfpBinarySensor: %s created', self._name)
_LOGGER.debug("UfpBinarySensor: %s created", self._name)

@property
def unique_id(self):
Expand All @@ -72,8 +73,8 @@ def device_state_attributes(self):
attrs = {}

attrs[ATTR_ATTRIBUTION] = ATTRIBUTION
attrs['brand'] = DEFAULT_BRAND
attrs['friendly_name'] = self._name
attrs["brand"] = DEFAULT_BRAND
attrs["friendly_name"] = self._name

return attrs

Expand All @@ -85,19 +86,18 @@ def is_on(self):
def update(self):
""" Updates Motions State."""

event_list_sorted = sorted(self._nvrdata.motion_events, key=lambda k: k['start'], reverse=True)
event_list_sorted = sorted(
self._nvrdata.motion_events, key=lambda k: k["start"], reverse=True
)
is_motion = None

for event in event_list_sorted:
if (self._device['id'] == event['camera']):
if (event['end'] is None):
if self._device["id"] == event["camera"]:
if event["end"] is None:
is_motion = True
else:
is_motion = False

break
self._state = is_motion




85 changes: 52 additions & 33 deletions custom_components/unifiprotect/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,18 @@
from datetime import timedelta

import requests
import voluptuous as vol

from homeassistant.components.camera import DOMAIN, SUPPORT_STREAM, Camera
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL, CONF_USERNAME, CONF_PASSWORD, CONF_NAME
from homeassistant.components.camera import SUPPORT_STREAM, Camera
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from . import ATTRIBUTION, DATA_UFP, DEFAULT_BRAND, protectnvr as nvr
from . import DATA_UFP, DEFAULT_BRAND, protectnvr as nvr


_LOGGER = logging.getLogger(__name__)

SCAN_INTERVAL = timedelta(seconds=30)

DEPENDENCIES = ['unifiprotect']
DEPENDENCIES = ["unifiprotect"]


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Discover cameras on a Unifi Protect NVR."""
Expand All @@ -27,10 +25,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
nvrobject = hass.data.get(DATA_UFP)
cameras = nvrobject.cameras

cameras = [
camera
for camera in cameras
]
cameras = [camera for camera in cameras]
except nvr.NotAuthorized:
_LOGGER.error("Authorization failure while connecting to NVR")
return False
Expand All @@ -43,17 +38,41 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=

async_add_entities(
[
UnifiVideoCamera(hass, nvrobject, camera['id'], camera["name"], camera["rtsp"], camera["recording_mode"], camera["type"], camera['up_since'], camera['last_motion'], camera['online'])
UnifiVideoCamera(
hass,
nvrobject,
camera["id"],
camera["name"],
camera["rtsp"],
camera["recording_mode"],
camera["type"],
camera["up_since"],
camera["last_motion"],
camera["online"],
)
for camera in cameras
]
)

return True


class UnifiVideoCamera(Camera):
"""A Ubiquiti Unifi Video Camera."""

def __init__(self, hass, camera, uuid, name, stream_source, recording_mode, model, up_since, last_motion, online):
def __init__(
self,
hass,
camera,
uuid,
name,
stream_source,
recording_mode,
model,
up_since,
last_motion,
online,
):
"""Initialize an Unifi camera."""
super().__init__()
self.hass = hass
Expand All @@ -71,9 +90,9 @@ def __init__(self, hass, camera, uuid, name, stream_source, recording_mode, mode
self._last_image = None
self._supported_features = SUPPORT_STREAM if self._stream_source else 0

if (recording_mode != 'never' and self._online):
if recording_mode != "never" and self._online:
self._isrecording = True

_LOGGER.debug("Camera %s added to Home Assistant", self._name)

@property
Expand Down Expand Up @@ -120,48 +139,48 @@ def is_recording(self):
def device_state_attributes(self):
"""Add additional Attributes to Camera."""
attrs = {}
attrs['up_since'] = self._up_since
attrs['last_motion'] = self._last_motion
attrs['online'] = self._online
attrs['uuid'] = self._uuid
attrs["up_since"] = self._up_since
attrs["last_motion"] = self._last_motion
attrs["online"] = self._online
attrs["uuid"] = self._uuid

return attrs

def update(self):
""" Updates Attribute States."""

caminfo = self._nvr.cameras
for camera in caminfo:
if (self._uuid == camera['id']):
self._online = camera['online']
self._up_since = camera['up_since']
self._last_motion = camera['last_motion']
self._motion_status = camera['recording_mode']
if (self._motion_status != 'never' and self._online):
if self._uuid == camera["id"]:
self._online = camera["online"]
self._up_since = camera["up_since"]
self._last_motion = camera["last_motion"]
self._motion_status = camera["recording_mode"]
if self._motion_status != "never" and self._online:
self._isrecording = True
else:
self._isrecording = False
break

def enable_motion_detection(self):
"""Enable motion detection in camera."""
ret = self._nvr.set_camera_recording(self._uuid,'motion')
ret = self._nvr.set_camera_recording(self._uuid, "motion")
if not ret:
return
self._motion_status = 'motion'

self._motion_status = "motion"
self._isrecording = True
_LOGGER.debug("Motion Detection Enabled for Camera: " + self._name)
_LOGGER.debug("Motion Detection Enabled for Camera: %s", self._name)

def disable_motion_detection(self):
"""Disable motion detection in camera."""
ret = self._nvr.set_camera_recording(self._uuid,'never')
ret = self._nvr.set_camera_recording(self._uuid, "never")
if not ret:
return
self._motion_status = 'never'

self._motion_status = "never"
self._isrecording = False
_LOGGER.debug("Motion Detection Disabled for Camera: " + self._name)
_LOGGER.debug("Motion Detection Disabled for Camera: %s", self._name)

def camera_image(self):
"""Return bytes of camera image."""
Expand Down
Loading

0 comments on commit 04b5177

Please sign in to comment.