# UniFi Protect Binding

This binding integrates Ubiquiti UniFi Protect into openHAB. It connects to your Protect NVR/CloudKey/UNVR and provides live events and configurable settings for Cameras, Floodlights, and Sensors.

It uses the official Protect Integration API over HTTPS and WebSocket with a Bearer Token.

# Features

  • Supports multiple Protect devices (Cameras, Doorbells, Floodlights, Sensors)
  • Uses the official Protect Integration API locally to a UniFi Protect NVR/CloudKey/UNVR
  • Has granular triggers and channels for realtime motion events including AI object detection, audio, and line crossing events.
  • Uses websockets for realtime updates without polling
  • Supports WebRTC streaming for cameras with very low server CPU overhead
  • Supports 2-way audio for cameras that support it
  • Uses STUN for external access to cameras when outside your local network (e.g. when using the openHAB cloud service)
  • Provides general purpose image snapshot API endpoints for cameras

# Native Binaries

  • Uses go2rtc (opens new window) and FFmpeg (opens new window) for WebRTC playback and publishing.
  • The binding will automatically download and extract the binaries if they are not present on linux, mac, windows and freeBSD.
  • By default the binding will first try and find the binaries on the system PATH before downloading them.
  • If your platform is not supported, or downloading the binaries is not possible, install the binaries manually and ensure they are on the system PATH.

See Binding Configuration to enable/disable downloading the binaries.

# Supported Things

  • nvr (Bridge): The Protect NVR/CloudKey/UNVR. Required to discover and manage child devices.
  • camera: A Protect camera. Channels are added dynamically based on device capabilities (mic, HDR, smart detection, PTZ, etc.).
  • light: A Protect Floodlight.
  • sensor: A Protect environmental/contact sensor.

# Discovery

  • Add the nvr bridge by entering its Hostname/IP and a Bearer Token.
  • Once the NVR is ONLINE, Cameras, Floodlights, and Sensors are discovered automatically and appear in the Inbox.
  • Approve discovered things to add them to your system. Manual creation is also possible using deviceId.

# Binding Configuration

Name Type Description Required
downloadBinaries boolean Download binaries if they are not on the system PATH. This setting controls whether the binding should download the native binaries if they are not found. By default, the binding will download the binaries if they are not on the system PATH for supported platforms yes
useStun boolean Use STUN for external IP discovery. This will allow camera streams to work behind NATs when outside your local network (e.g. when using the openHAB cloud service) and is enabled by default. yes

Note: Enabling STUN will incur an approximately 5-second delay delivering the stream to clients as it discovers your external IP and pins a port on your router for streams. If you are not using the openHAB cloud service with cameras, disabling STUN will allow for near-instant stream starts (video will start within a second of loading) on your local network or over a VPN.

# Thing Configuration

# NVR (Bridge) nvr

Name Type Description Default Required Advanced
hostname text Hostname or IP address of the NVR N/A yes no
token text Bearer token used for API/WebSocket authentication N/A yes no

How to get the Token:

  • In the UniFi Protect UI, go to Settings → Control Plane → Integrations and create an API token.
  • Copy the token and paste it into the NVR bridge configuration in openHAB.

Protect API key creation

# camera Configuration

Name Type Description Required
deviceId text Unique device identifier of the camera yes
enableWebRTC boolean Enable WebRTC streaming yes

When WebRTC is enabled, the camera will be able to stream through openHAB using WebRTC. You can disable WebRTC by setting enableWebRTC to false.

# light Configuration

Name Type Description Required
deviceId text Unique device identifier of the floodlight yes

# sensor Configuration

Name Type Description Required
deviceId text Unique device identifier of the sensor yes

# Channels

Below are the channels exposed by each thing type. Some camera channels are created dynamically depending on device capabilities.

# nvr Bridge Channels

No channels.

# camera Channels

  • The following are dynamically created depending on the supported features.
  • Advanced channels are hidden by default in the MainUI, select "Show Advanced" to see them.
Channel ID Item Type RW Description Advanced
mic-volume Dimmer RW Microphone volume (0-100) false
video-mode String RW Camera video mode (e.g., default, highFps, sport, slowShutter, lprReflex, lprNoneReflex) false
hdr-type String RW HDR mode (auto, on, off) false
osd-name Switch RW Show name on OSD false
osd-date Switch RW Show date on OSD false
osd-logo Switch RW Show logo on OSD false
led-enabled Switch RW Enable/disable camera status LED false
active-patrol-slot Number RW Active PTZ patrol slot (set 0 to stop) false
webrtc-url-high String R WebRTC stream URL for high quality true
webrtc-url-medium String R WebRTC stream URL for medium quality true
webrtc-url-low String R WebRTC stream URL for low quality true
webrtc-url-package String R WebRTC stream URL for package quality true
rtsp-url-high String R RTSP stream URL for high quality true
rtsp-url-medium String R RTSP stream URL for medium quality true
rtsp-url-low String R RTSP stream URL for low quality true
rtsp-url-package String R RTSP stream URL for package quality true
snapshot Image R Snapshot image. Send a REFRESH command to update. false
snapshot-url String R Snapshot image URL true
motion-contact Contact R Motion state (OPEN = motion detected) false
motion-snapshot Image R Snapshot captured around motion event false
smart-detect-audio-contact Contact R Smart audio detection active state false
smart-detect-audio-snapshot Image R Snapshot captured around smart audio detection false
smart-detect-zone-contact Contact R Smart zone detection active state false
smart-detect-zone-snapshot Image R Snapshot captured around smart zone detection false
smart-detect-line-contact Contact R Smart line detection active state false
smart-detect-line-snapshot Image R Snapshot captured around smart line detection false
smart-detect-loiter-contact Contact R Smart loiter detection active state false
smart-detect-loiter-snapshot Image R Snapshot captured around smart loiter detection false
doorbell-default-message String RW Default doorbell LCD message text configured on the NVR false
doorbell-default-message-reset-timeout Number:Time RW Default doorbell LCD message reset timeout false

Trigger channels (for rules):

  • Triggers Channels are hidden by default in the MainUI, select "Show Advanced" to see them.
Trigger Channel ID Payload (if any) Description
motion-start none Motion started
motion-update none Motion updated (debounced update event)
smart-audio-detect-start alrmSmoke, alrmCmonx, alrmSiren, alrmBabyCry, alrmSpeak, alrmBark, alrmBurglar, alrmCarHorn, alrmGlassBreak, none Smart audio detection started
smart-audio-detect-update alrmSmoke, alrmCmonx, alrmSiren, alrmBabyCry, alrmSpeak, alrmBark, alrmBurglar, alrmCarHorn, alrmGlassBreak, none Smart audio detection updated
smart-detect-zone-start person, vehicle, package, licensePlate, face, animal, none Zone smart detection started
smart-detect-zone-update person, vehicle, package, licensePlate, face, animal, none Zone smart detection updated
smart-detect-line-start person, vehicle, package, licensePlate, face, animal, none Line smart detection started
smart-detect-line-update person, vehicle, package, licensePlate, face, animal, none Line smart detection updated
smart-detect-loiter-start person, vehicle, package, licensePlate, face, animal, none Loiter smart detection started
smart-detect-loiter-update person, vehicle, package, licensePlate, face, animal, none Loiter smart detection updated

# Snapshot Channels

Snapshot channels can be configured to take a snapshot before or after the trigger event or item state change. By default, a snapshot is taken before the trigger event fires or item state updates so its immediately available for use in rules. This however can cause a slight delay in the rule execution if the snapshot itself is delayed. The order in which the snapshot is taken can be configured via the snapshot channel configuration through textual files or the MainUI. To take a snapshot after the event trigger fires or item state updates, you can set the sequence to "after". If you do not want to take a snapshot on a trigger event or item state update, you can set the sequence to "none".

# Motion Contact Channels

Motion contact channels are configured to latch/close after a defined delay after a motion event. By default, the contact is considered latched/closed after 5 seconds of no motion events. This delay can be configured via the contact channel configuration through textual files or the MainUI.

# Floodlight

Channel ID Item Type RW Description Advanced
light Switch RW Main floodlight on/off (forces light) false
is-dark Switch R Scene is currently dark false
pir-motion Trigger - PIR motion event false
last-motion DateTime R Timestamp of last motion false
light-mode String RW Light mode (always, motion, off) false
enable-at String RW When mode is relevant (fulltime, dark) false
indicator-enabled Switch RW Status LED indicator on floodlight false
pir-duration Number:Time RW How long the light stays on after motion false
pir-sensitivity Number:Dimensionless RW PIR motion sensitivity (0-100) false
led-level Number RW LED brightness level (1-6) false

# Sensor

Channel ID Item Type RW Description Advanced
battery Number R Battery charge level (%) false
contact Contact R Contact state (OPEN/CLOSED) false
temperature Number:Temperature R Ambient temperature false
humidity Number R Ambient humidity false
illuminance Number:Illuminance R Ambient light (Lux) false
alarm-contact Contact R Smoke/CO alarm contact (OPEN = alarming) false
water-leak-contact Contact R Water leak contact (OPEN = leak) false
tamper-contact Contact R Tamper contact (OPEN = tampering) false

Trigger channels (for rules):

Trigger Channel ID Payload (if any) Description
opened door, window, garage, leak, none Sensor opened
closed door, window, garage, leak, none Sensor closed
alarm smoke, CO (optional) Smoke/CO alarm event
water-leak door, window, garage, leak, none Water leak detected
tamper none Tampering detected

# Real-time Media

If enabled in the binding configuration, openHAB will proxy live media using WebRTC which is compatible with the MainUI video widget.

# Stream URLs

The URL for WebRTC streams can be found in 2 different ways

  1. As a property on the Camera Thing (webrtc-url-high, webrtc-url-medium, webrtc-url-low, webrtc-url-package)
  2. As an Item linked to a channel on the Camera Thing (webrtc-url-high, webrtc-url-medium, webrtc-url-low, webrtc-url-package)

All of the above URLs are relative to the openHAB instance.

The playback URLs can be used in the MainUI video widget by enabling WebRTC in the advanced settings in the video widget and using an above URL or Item as the source.

An example WebRTC stream URL would be:

/unifiprotect/media/play/unifiprotect:camera:home:1234567890:high

Where unifiprotect:camera:home:1234567890 is the camera's Thing UID and high is the quality (high, medium, low, package) if supported by the camera.

You can either use the String URL or select the Item linked to the channel in the MainUI video widget.

video widget settings

It's also highly recommended to use the camera's Snapshot URL property or the Item linked to the snapshot-url channel to get the live snapshot image URL which can be used for the poster image option in the MainUI video widget.

An example snapshot image URL would be:

/unifiprotect/media/image/unifiprotect:camera:home:1234567890

Where unifiprotect:camera:home:1234567890 is the camera's Thing UID. You can append ?quality=high to the URL to get the a higher quality snapshot image if supported by the camera, but can fail if not supported. The default quality level is suitable for most use cases, and supported by all cameras.

# Talkback Support (2-way audio)

Some UniFi Protect cameras support "Talkback", which allows you to publish audio back to the camera in a push to talk manner. If supported, you can enable "Two Way Audio" in the MainUI video widget (along with selecting WebRTC under advanced settings in the video widget) which will display a microphone icon for push to talk functionality. This is automatically supported by the binding and will be enabled if supported by the camera.

# Full Examples (Textual Configuration)

Replace the IDs with your own thing and item names.

# Things (.things)

Bridge unifiprotect:nvr:myNvr "UniFi Protect NVR" [ hostname="192.168.1.10", token="YOUR_LONG_TOKEN" ] {
    Thing camera:frontdoor [ deviceId="60546f80e4b0abcd12345678" ]
    Thing light:driveway [ deviceId="60a1b2c3d4e5f67890123456" ]
    Thing sensor:garagedoor [ deviceId="60112233445566778899aabb" ]
}

# Items (.items)

// Camera
Dimmer  Cam_Front_MicVolume        "Mic Volume [%d %%]"                 { channel="unifiprotect:camera:myNvr:frontdoor:mic-volume" }
String  Cam_Front_VideoMode        "Video Mode [%s]"                    { channel="unifiprotect:camera:myNvr:frontdoor:video-mode" }
String  Cam_Front_HDR              "HDR [%s]"                           { channel="unifiprotect:camera:myNvr:frontdoor:hdr-type" }
Switch  Cam_Front_OSD_Name         "OSD Name"                           { channel="unifiprotect:camera:myNvr:frontdoor:osd-name" }
Switch  Cam_Front_OSD_Date         "OSD Date"                           { channel="unifiprotect:camera:myNvr:frontdoor:osd-date" }
Switch  Cam_Front_OSD_Logo         "OSD Logo"                           { channel="unifiprotect:camera:myNvr:frontdoor:osd-logo" }
Switch  Cam_Front_LED              "Status LED"                         { channel="unifiprotect:camera:myNvr:frontdoor:led-enabled" }
Number  Cam_Front_PatrolSlot       "PTZ Patrol Slot [%d]"               { channel="unifiprotect:camera:myNvr:frontdoor:active-patrol-slot" }
String  Cam_Front_WebRTC_High      "WebRTC High [%s]"                   { channel="unifiprotect:camera:myNvr:frontdoor:webrtc-url-high" }
String  Cam_Front_WebRTC_Medium    "WebRTC Medium [%s]"                 { channel="unifiprotect:camera:myNvr:frontdoor:webrtc-url-medium" }
String  Cam_Front_WebRTC_Low       "WebRTC Low [%s]"                    { channel="unifiprotect:camera:myNvr:frontdoor:webrtc-url-low" }
String  Cam_Front_WebRTC_Package   "WebRTC Package [%s]"                { channel="unifiprotect:camera:myNvr:frontdoor:webrtc-url-package" }
Contact Cam_Front_Motion           "Motion [%s]"                        { channel="unifiprotect:camera:myNvr:frontdoor:motion-contact" }
Image   Cam_Front_MotionSnapshot   "Motion Snapshot"                    { channel="unifiprotect:camera:myNvr:frontdoor:motion-snapshot" }

// Floodlight
Switch  Light_Driveway_OnOff       "Driveway Light"                     { channel="unifiprotect:light:myNvr:driveway:light" }
Switch  Light_Driveway_IsDark      "Is Dark"                            { channel="unifiprotect:light:myNvr:driveway:is-dark" }
DateTime Light_Driveway_LastMotion "Last Motion [%1$ta %1$tR]"          { channel="unifiprotect:light:myNvr:driveway:last-motion" }
String  Light_Driveway_Mode        "Mode [%s]"                          { channel="unifiprotect:light:myNvr:driveway:light-mode" }
String  Light_Driveway_EnableAt    "Enable At [%s]"                     { channel="unifiprotect:light:myNvr:driveway:enable-at" }
Switch  Light_Driveway_Indicator   "Indicator LED"                      { channel="unifiprotect:light:myNvr:driveway:indicator-enabled" }
Number  Light_Driveway_PIR_Dur     "PIR Duration [%.0f ms]"             { channel="unifiprotect:light:myNvr:driveway:pir-duration" }
Number  Light_Driveway_PIR_Sens    "PIR Sensitivity [%.0f]"             { channel="unifiprotect:light:myNvr:driveway:pir-sensitivity" }
Number  Light_Driveway_LED_Level   "LED Level [%.0f]"                   { channel="unifiprotect:light:myNvr:driveway:led-level" }

// Sensor
Number  Sensor_Garage_Battery      "Battery [%.0f %%]"                  { channel="unifiprotect:sensor:myNvr:garagedoor:battery" }
Contact Sensor_Garage_Contact      "Contact [%s]"                       { channel="unifiprotect:sensor:myNvr:garagedoor:contact" }
Number:Temperature Sensor_Garage_T "Temperature [%.1f %unit%]"          { channel="unifiprotect:sensor:myNvr:garagedoor:temperature" }
Number  Sensor_Garage_Humidity     "Humidity [%.0f %%]"                 { channel="unifiprotect:sensor:myNvr:garagedoor:humidity" }
Number:Illuminance Sensor_Garage_L "Illuminance [%.0f lx]"              { channel="unifiprotect:sensor:myNvr:garagedoor:illuminance" }
Contact Sensor_Garage_Alarm        "Alarm [%s]"                         { channel="unifiprotect:sensor:myNvr:garagedoor:alarm-contact" }
Contact Sensor_Garage_Leak         "Leak [%s]"                          { channel="unifiprotect:sensor:myNvr:garagedoor:water-leak-contact" }
Contact Sensor_Garage_Tamper       "Tamper [%s]"                        { channel="unifiprotect:sensor:myNvr:garagedoor:tamper-contact" }

# Sitemap (.sitemap)

sitemap home label="Home" {
    Frame label="Front Door Camera" {
        Text item=Cam_Front_Motion
        Image item=Cam_Front_MotionSnapshot
    }
    Frame label="Driveway Floodlight" {
        Switch item=Light_Driveway_OnOff
        Text item=Light_Driveway_IsDark
        Text item=Light_Driveway_LastMotion
        Selection item=Light_Driveway_Mode mappings=[always="Always", motion="Motion", off="Off"]
        Selection item=Light_Driveway_EnableAt mappings=[fulltime="Full time", dark="Dark"]
        Setpoint item=Light_Driveway_PIR_Sens minValue=0 maxValue=100 step=1
        Setpoint item=Light_Driveway_LED_Level minValue=1 maxValue=6 step=1
    }
    Frame label="Garage Sensor" {
        Text item=Sensor_Garage_Contact
        Text item=Sensor_Garage_T
        Text item=Sensor_Garage_Humidity
        Text item=Sensor_Garage_L
        Text item=Sensor_Garage_Battery
    }
}

# Rules

Examples showing trigger channels.

// Camera motion start/update
rule "Front door motion alert"
when
    Channel "unifiprotect:camera:myNvr:frontdoor:motion-start" triggered
then
    logInfo("protect", "Front door motion started")
end

rule "Front door motion update"
when
    Channel "unifiprotect:camera:myNvr:frontdoor:motion-update" triggered
then
    logInfo("protect", "Front door motion update")
end

// Camera smart detection with payload
rule "Front door smart zone detect"
when
    Channel "unifiprotect:camera:myNvr:frontdoor:smart-detect-zone-start" triggered
then
    // Access payload from the trigger channel event (person, vehicle, package, licensePlate, face, animal, none)
    val String payload = receivedEvent.getEvent()
    logInfo("protect", "Smart zone detection started: {}", payload)
end

rule "Front door smart zone update"
when
    Channel "unifiprotect:camera:myNvr:frontdoor:smart-detect-zone-update" triggered
then
    val String payload = receivedEvent.getEvent()
    logInfo("protect", "Smart zone detection updated: {}", payload)
end

// Camera smart audio detect with payload
rule "Front door smart audio detect"
when
    Channel "unifiprotect:camera:myNvr:frontdoor:smart-audio-detect-start" triggered
then
    val String payload = receivedEvent.getEvent() // alrmSmoke, alrmCmonx, alrmSiren, alrmBabyCry, alrmSpeak, alrmBark, alrmBurglar, alrmCarHorn, alrmGlassBreak, none
    logInfo("protect", "Smart audio detected: {}", payload)
end

rule "Front door smart audio update"
when
    Channel "unifiprotect:camera:myNvr:frontdoor:smart-audio-detect-update" triggered
then
    val String payload = receivedEvent.getEvent()
    logInfo("protect", "Smart audio detection updated: {}", payload)
end

// Camera doorbell ring with payload filtering
rule "Front doorbell pressed"
when
    Channel "unifiprotect:camera:myNvr:frontdoor:ring" triggered PRESSED
then
    logInfo("protect", "Doorbell pressed")
end

// Or handle any ring payload generically
rule "Front doorbell ring generic"
when
    Channel "unifiprotect:camera:myNvr:frontdoor:ring" triggered
then
    val String payload = receivedEvent.getEvent() // PRESSED, RELEASED
    logInfo("protect", "Doorbell ring event: {}", payload)
end

// Floodlight PIR motion trigger
rule "Driveway PIR motion"
when
    Channel "unifiprotect:light:myNvr:driveway:pir-motion" triggered
then
    logInfo("protect", "Driveway PIR motion")
    // Optionally turn on the light for a bit
    sendCommand(Light_Driveway_OnOff, ON)
    createTimer(now.plusSeconds(30), [ | sendCommand(Light_Driveway_OnOff, OFF) ])
end

// Sensor opened/closed with payload
rule "Garage sensor opened"
when
    Channel "unifiprotect:sensor:myNvr:garagedoor:opened" triggered
then
    val String payload = receivedEvent.getEvent() // door, window, garage, leak, none
    logInfo("protect", "Garage sensor opened: {}", payload)
end

rule "Garage sensor closed"
when
    Channel "unifiprotect:sensor:myNvr:garagedoor:closed" triggered
then
    val String payload = receivedEvent.getEvent() // door, window, garage, leak, none
    logInfo("protect", "Garage sensor closed: {}", payload)
end

// Sensor water leak
rule "Garage water leak"
when
    Channel "unifiprotect:sensor:myNvr:garagedoor:water-leak" triggered
then
    val String payload = receivedEvent.getEvent() // door, window, garage, leak, none
    logWarn("protect", "Water leak detected by garage sensor: {}", payload)
end

# Tips and Tricks

# Main UI Widgets

It can be helpful to display a live preview of multiple cameras in the MainUI using low quality streams, much like the UniFi Protect app, where clicking on a camera preview opens a higher quality version in a popup. The following widget creates a preview card for a camera, muting the audio and will open another widget or page when clicked anywhere on the card. Use the "low" quality stream for this preview widget, and the "high" quality stream for the popup, such as a dedicated page with a single video widget for the camera. A modest server can support dozens of simultaneous streams for MainUI clients with many camera views in a single page.

uid: unifi-webrtc-video-preview
tags: []
props:
  parameters:
    - description: Thing UID
      label: Thing UID
      name: thingUid
      required: true
      type: TEXT
  parameterGroups:
    - name: clickAction
      context: action
      label: Click Action
component: f7-card
config: {}
slots:
  default:
    - component: oh-video
      config:
        hideControls: true
        playerType: webrtc
        posterURL: ='/unifiprotect/media/image/' + props.thingUid + '?quality=low'
        startMuted: true
        style:
          position: absolute
        url: ='/unifiprotect/media/play/' + props.thingUid + ':low'
    - component: oh-button
      config:
        actionPropsParameterGroup: clickAction
        style:
          height: 100%
          margin: 0px
          opacity: 100%
          position: absolute
          top: 0px
          width: 100%