Custom Component: Unifi Protect

@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

Do not use snapshots. The thumbnail directly from Protect can be retrieved.

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: notify.mobile_app_pixel
    data:
      message: Motion - Cam 1
      title: Backyard
      data:
        priority: high
        ttl: 0
        image: "/api/unifiprotect/thumbnail/{{ config_entry_id(trigger.entity_id) }}/{{ state_attr(trigger.entity_id, 'event_id') }}"
mode: single

You can also add the entity for video if you are on iOS.

2 Likes

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.

Yeah. It sends out two notifications. One as soon as the event happens with just the thumbnail. And a second, which will replace the first, with the video after the event ends. If you clear the notification before it sends out the video one, it will not send out the video one.

1 Like

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"

I use the telegram service for notifications and want to send the snapshot/thumbnail as an attachment. This works using the snapshot approach, but the snapshot is low resolution and of course a bit delayed compared to the detection event. Is there a way to either get the snapshot at full resolution or retrieve the unifi thumbnail to a JPG file similar to the snapshot service?

My current automation:

alias: Person ved indgangen
description: ""
trigger:
  - platform: state
    entity_id:
      - binary_sensor.g4_indgang_person_detected
    to: "on"
condition: []
action:
  - service: camera.snapshot
    data:
      filename: /config/www/snapshots/snapshot_indgang.jpg
    target:
      entity_id: camera.g4_indgang_high
  - service: notify.jaybebot
    data:
      message: Person detected
      data:
        photo:
          - file: /config/www/snapshots/snapshot_indgang.jpg
            caption: "Person detected!"
mode: single

Yes, notification services are able to sign media URLs. What I’m looking for is how to pull the image bytes from the media URL in an automation.

@AngellusMortis stated: “There needs to be another generic way to expose media for an entity without access to the Python code for automations, add-ons (Node Red) etc. The recent work and optimization to the recorder has made that really easy: expose them as attributes and then exclude them from recorder. That allows us to combine the way the camera platform exposes entity_picture with the new async_sign_path to add media an attribute.”

I was hoping/expecting to be able to pull the signed path from the trigger entity, but I’m not seeing that implemented.

Got a very strange issue yesterday that maybe someone have an idea about?

I changed the DNS of my phone to my Home Assistant NUC where i have AdGuard running. Immediately when I did so one of my 5 Unifi cameras disconnected with the message “Lost power”. Changing back DNS solved the issue. All other cameras kept working fine.

Camera kept recording but was unaccessible from other devices too.

I just can’t see how this can be related?