Custom Component: Unifi Protect

I think it’s been there a while (a year perhaps), I looked quite some time ago and saw they were supported and hence got one for my front door.

Trying to understand how to pull smart detection thumbnails from media source into notify service. Does anyone have an example of code or just a pointer to where to get event_id in media source?

Is there any hope on the horizon about the delay in video stream when using HomeAssistant dashboards? Over time, my delay grows to be minutes. Completely unworkable. I was previously using Node Red Dashboards, and it’s flawless with the video stream. HA dash is more flexible though.

2 Likes

If you are asking to capture an image from Protect and then send a notification to the HA app on your phone, you need capture the image then send it as an image.
Here is an example I use for when everyone is away. Its simple, but you can get the idea. Its works on Android. Have not tried on iPhone as I do not own one.

alias: Camera Person Backyard 1 (All Away)
description: ""
trigger:
  - platform: state
    entity_id:
      - binary_sensor.backyard_1_person_detected
    to: "on"
condition:
  - condition: state
    entity_id: group.track_all_persons
    state: not_home
action:
  - service: camera.snapshot
    data:
      filename: /config/www/snapshots/backyard_camera-1_person.png
    target:
      device_id: 5556471c03cfb4db15xxxxxxxxxxxxxx
  - service: notify.mobile_app_pixel
    data:
      message: Motion - Cam 1
      title: Backyard
      data:
        priority: high
        ttl: 0
        image: /local/snapshots/backyard_camera-1_person.png
mode: single

Does any one know why the snapshots are now taken in low resolution of 480x360 instead of the higher quality as before?

@WedHumpDay
am trying to find where do event_id come from in this part of the component docs? Is there an event that fires on smart_notifiaction?

They are in the trigger data you get when an event is fired - in the to_state (for triggers when detection started) or from_state (for triggers when detection ended). Set up an automation to trigger from those events, walk in front of the camera, and then look at the traces from the automation and the changed variables, you’ll see something like this (in this case trigger.from_state.attributes.event_id):

trigger:
  id: Person
  idx: '0'
  alias: null
  platform: device
  entity_id: binary_sensor.house_front_person_detected
  from_state:
    entity_id: binary_sensor.house_front_person_detected
    state: 'on'
    attributes:
      event_id: 63da750401c10b03870172d7-61364b1601e45f0387000fc6
      event_score: 83
      attribution: Powered by UniFi Protect Server
      icon: mdi:walk
      friendly_name: House-Front Person Detected
    last_changed: '2023-02-01T14:19:48.475959+00:00'
    last_updated: '2023-02-01T14:19:48.475959+00:00'
    context:
      id: 01GR6NJ6BVK1Y9AMAR0M8HKCMF
      parent_id: null
      user_id: null
  to_state:
    entity_id: binary_sensor.house_front_person_detected
    state: 'off'
    attributes:
      attribution: Powered by UniFi Protect Server
      icon: mdi:walk
      friendly_name: House-Front Person Detected
    last_changed: '2023-02-01T14:20:00.352970+00:00'
    last_updated: '2023-02-01T14:20:00.352970+00:00'
    context:
      id: 01GR6NJHZ0CNDGA42R8QQZHQHW
      parent_id: null
      user_id: null

Thanks - exactly what i was looking for. Do you have a code snippet for retrieving an image from protect media source - say to pass to notify service or otherwise

I use this in the automation to grab the relevant variables from the trigger data (in this case from), working (right now) on getting the video start and end dates right for the api call:

variables:
  entity_id: >-
    {{ (device_entities(device_id(trigger.entity_id)) | select('match','camera')
    | list) | last }}
  nvr_id: "{{ config_entry_id(entity_id) }}"
  event_id: "{{ trigger.from_state.attributes.event_id }}"
  friendly_name: "{{ state_attr(entity_id, 'friendly_name') }}"

the video start and end I want to use something like this:

  video_start: "{{ (trigger.from_state.last_changed | string).isoformat() }}"
  video_end: "{{ (trigger.to_state.last_changed | string).isoformat() }}"

but it needs some mangling of types to get it working - on it right now :wink:

No - thats the holy grail :slight_smile: I wish I could :slight_smile:
I cant get the media_source to work - but am playing - I’m trying to get the notifications data in the right place so the video and image clips work on the notify.xxx calls - the last bit of that jigsaw is mangling those video start and end dates - they just dont seem to want to work for me reliably at the moment (types wrong and IoS doesnt seem to want to play nicely) - and its a pain to test - keep having to get up and walk in front of the camera :slight_smile:

I’ll post here an updated variables extraction if I get it working

Yes been digging into those blueprints - very very helpful - struggling with the logic around the video start and end times - in the blueprints you are snapping states[trigger.entity_id].last_changed.isoformat() for the start, and later on the same thing for the end parameter in the video_url - which doesn’t seem to work when I throw it at the IoS device - or am I missing something?

ok - got it - you wait until the sensor clears, then grab the new updated state time.

so I use a script to send notifications out via MQTT (that way they go to a whole pile of other platforms and logs and things, and a platform that sends out iOS notifications back via HA - but ignore that as it’s sort of irrelevant).

And I didn’t really want the interaction on the iOS notifications in the blueprint. So I have distilled the logic down to this - you can replace the script call to MQTT with a notify script easily enough:

alias: Security Camera Triggered
description: Send notifications when a security camera detects something
trigger:
  - type: turned_on
    platform: device
    device_id: ffcac901c50a0b33c66b18eb8c6957ce
    entity_id: binary_sensor.garagecam_front_person_detected
    domain: binary_sensor
    id: Person
  - type: turned_on
    platform: device
    device_id: ffcac901c50a0b33c66b18eb8c6957ce
    entity_id: binary_sensor.garagecam_front_vehicle_detected
    domain: binary_sensor
    id: Vehicle
condition: []
action:
  - variables:
      parent_entity_id: >-
        {{ (device_entities(device_id(trigger.entity_id)) |
        select('match','camera') | list) | last }}
      event_id: "{{ state_attr(trigger.entity_id, 'event_id') }}"
      image_url: >-
        /api/unifiprotect/thumbnail/{{ config_entry_id(trigger.entity_id) }}/{{
        event_id }}
      video_start: "{{ states[trigger.entity_id].last_changed.isoformat() }}"
      friendly_name: "{{ state_attr(parent_entity_id, 'friendly_name') }}"
  - service: script.send_mqtt_announcement
    data:
      message: "{{ friendly_name }} camera has recorded an image, tap and hold to view."
      title: Automation
      subtitle: >-
        {% if trigger.id == "Person" %}Person{% elif trigger.id == "Motion"
        %}Motion{% else %}Vehicle{% endif %} detected
      channels: HA_NOTIFY
      image: "{{ image_url }}"
      view: /default-mobile/cameras
      thread: security
      level: info
      tag: "{{ event_id }}"
  - wait_template: "{{ states(trigger.entity_id) == 'off' }}"
    continue_on_timeout: false
  - variables:
      video_url: >-
        /api/unifiprotect/video/{{ config_entry_id(trigger.entity_id) }}/{{
        trigger.entity_id }}/{{ video_start }}/{{
        states[trigger.entity_id].last_changed.isoformat() }}
  - service: script.send_mqtt_announcement
    data:
      message: >-
        {{ friendly_name }} camera has recorded some video, tap and hold to
        view.
      title: Automation
      subtitle: >-
        {% if trigger.id == "Person" %}Person{% elif trigger.id == "Motion"
        %}Motion{% else %}Vehicle{% endif %} detected
      channels: HA_NOTIFY
      video: "{{ video_url }}"
      view: /default-mobile/cameras
      thread: security
      level: info
      tag: "{{ event_id }}"
mode: restart

I tried reading all 985 posts, but my mind kept wandering. Is there an “easy” way to get the door bell video to display on an Echo Show and a TV when door bell is pushed? I have it announcing to certain dots, but I would like the video to display on an Echo show and a Roku TV (I can change it to be an Apple TV if it is easier). It seems like people are using scrypted or monocle cam with some luck.

UNVR
G4 Doorbell

My doorbell cam shows on my Apple TV whenever there’s a person detected and doorbell press.
(Homekit YAML)
Haven’t seen if this is configurable via UI yet.

entity_config:
  camera.front_door_camera_video:
    linked_doorbell_sensor: binary_sensor.doorbell_front_door
    linked_motion_sensor: binary_sensor.front_door_person_detected 

Edit: Updated for the new person detected entity. I used to have my own from a template.

2 Likes

Hey all, I had to reinstall the Unifi Protect component, after adding correct server, username and password, the component has the status “Failed to set up” and looking at the log I see the final error as “ValueError: ‘homekit’ is not a valid VideoMode

I cannot find any one else with this issue so I’d appreciate the help!

Logger: homeassistant.config_entries
Source: custom_components/unifiprotect/data.py:93
Integration: UniFi Protect (documentation, issues)
First occurred: 18:21:50 (5 occurrences)
Last logged: 18:45:57
Error setting up entry UNVR_PKSN for unifiprotect

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 382, in async_setup
    result = await component.async_setup_entry(hass, self)
  File "/config/custom_components/unifiprotect/__init__.py", line 193, in async_setup_entry
    await data_service.async_setup()
  File "/config/custom_components/unifiprotect/data.py", line 73, in async_setup
    await self.async_refresh()
  File "/config/custom_components/unifiprotect/data.py", line 93, in async_refresh
    updates = await self.api.update(force=force)
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/api.py", line 506, in update
    self._bootstrap = await self.get_bootstrap()
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/api.py", line 675, in get_bootstrap
    return Bootstrap.from_unifi_dict(**data, api=self)
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/data/base.py", line 104, in from_unifi_dict
    data = cls.unifi_dict_to_dict(data)
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/data/bootstrap.py", line 152, in unifi_dict_to_dict
    return super().unifi_dict_to_dict(data)
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/data/base.py", line 336, in unifi_dict_to_dict
    data[key] = cls._clean_protect_obj_dict(data[key], unifi_dicts[key], api)
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/data/base.py", line 281, in _clean_protect_obj_dict
    items[key] = cls._clean_protect_obj(value, klass, api)
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/data/base.py", line 265, in _clean_protect_obj
    return klass.unifi_dict_to_dict(data=data)
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/data/devices.py", line 697, in unifi_dict_to_dict
    return super().unifi_dict_to_dict(data)
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/data/base.py", line 607, in unifi_dict_to_dict
    return super().unifi_dict_to_dict(data)
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/data/base.py", line 326, in unifi_dict_to_dict
    data[key] = cls._clean_protect_obj(data[key], unifi_objs[key], api)
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/data/base.py", line 265, in _clean_protect_obj
    return klass.unifi_dict_to_dict(data=data)
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/data/base.py", line 319, in unifi_dict_to_dict
    data[key] = convert_unifi_data(data[key], cls.__fields__[key])
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/utils.py", line 160, in convert_unifi_data
    value = [convert_unifi_data(v, field) for v in value]
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/utils.py", line 160, in <listcomp>
    value = [convert_unifi_data(v, field) for v in value]
  File "/usr/local/lib/python3.10/site-packages/pyunifiprotect/utils.py", line 176, in convert_unifi_data
    value = field.type_(value)
  File "/usr/local/lib/python3.10/enum.py", line 385, in __call__
    return cls.__new__(cls, value)
  File "/usr/local/lib/python3.10/enum.py", line 710, in __new__
    raise ve_exc
ValueError: 'homekit' is not a valid VideoMode


I think this is a feature request - or at least a design question.

Why does setting privacy mode change the recording state? The way it used to work was perfect - set the mask, or remove the mask. Now turning privacy mode on sets recording mode to ‘never’ and turning privacy mode back off does not restore the previous state. Seems like turning privacy on then off shouldn’t change any other state.

Great integration and support, thank you!

Thank you Sir! I have updated my Camera rules to pull the image directly from Protect and everything is working great.

Just tried the blueprint for sending notifications on motion. Discovered that the notification contains a short video clip of the motion instead of a static image (shown in the notification). Didn’t know that was possible on Android.

Since I don’t understand how I can customize the blueprint and I want to use my current automation for sending notifications I wonder if I can get access to that clip in my own automation? Where do I find it?

Second question is if/how I can access the type of movement (person/vehicle) so that I can print this out in my notification?

alias: KAMERA - Ny test
description: ""
trigger:
  - platform: state
    entity_id:
      - binary_sensor.framsidan_person_detected
      - binary_sensor.framsidan_vehicle_detected
      - binary_sensor.garageuppfarten_person_detected
      - binary_sensor.garageuppfarten_vehicle_detected
    to: "on"
action:
  - service: notify.ls_mobil
    data:
      message: >-
        Test 
      title: Rörelse på framsidan
      data:
        priority: high
        clickAction: app://com.ubnt.unifi.protect
        ttl: 0
        group: framsidan
        image: >-
          /api/unifiprotect/thumbnail/{{ config_entry_id(trigger.entity_id)
          }}/{{ state_attr(trigger.entity_id, 'event_id') }}
mode: queued
max: 10

I see the new “signed paths” way of accessing images works “magically” with the notify component, but I can’t seem to figure out if there’s a way to access the images directly via URL, either locally from a script or remotely.

Developer docs don’t show a way to sign a path from a script/automation:

Any way to get something as simple as a local download to work?

automation:

- id: unifi_protect_event
  alias: "UniFi Protect: Motion Event"
  mode: parallel
  
  trigger:
  - platform: state
	entity_id: binary_sensor.back_yard_motion
	to: "on"
	
  variables:
	camera_name: "{{ device_attr(trigger.entity_id, 'name') }}"
	nvr_id: "{{ config_entry_id(trigger.entity_id) }}"
	event_id: "{{ state_attr(trigger.entity_id, 'event_id') }}"
#         base_url: "http://127.0.0.1:8123"
#         base_url: "http://homeassistant.local:8123"
	base_url: "https://<redacted>.ui.nabu.casa"
	image_path: "/api/unifiprotect/thumbnail/{{ nvr_id }}/{{ event_id }}"

  action:		
	- service: downloader.download_file
	  data:
		url: "{{ base_url }}{{ image_path }}"
		filename: "{{ camera_name }}/{{ now().strftime('%Y-%m-%d_%H.%M.%S') }}.jpg"
		subdir: "media/events"