Narcoleptic's Patch Mangler

    unifi-protect
    TypeScript icon, indicating that this package has built-in type declarations

    3.0.4 • Public • Published

    unifi-protect: UniFi Protect API

    UniFi Protect API

    Downloads Version

    A complete UniFi Protect API implementation.

    unifi-protect is a library that enabled you to connect to and communicate with the Ubiquiti UniFi Protect API and ecosystem. UniFi Protect is Ubiquiti's next-generation video security platform, with rich camera, doorbell, and NVR controller hardware options for you to choose from, as well as an app which you can use to view, configure and manage your video camera and doorbells.

    Why use this library for UniFi Protect support?

    In short - because I use it every day to support a very popular Homebridge plugin named homebridge-unifi-protect that I maintain. I have been occasionally asked if I would consider packaging the core API library separately from the plugin so that other open source projects can take advantage of the work that's been done here to understand and decode the UniFi Protect API.

    In addition, this implementation is unique: it's the first complete open source implementation of the realtime UniFi Protect update API, enabling instantaneous updates to Protect-related events.

    Finally - the most significant reason that you should use this library: it's very well-tested, it is modern, and most importantly, it just works. It's quite easy to add support for UniFi Protect in your project using this library, and you can rely on the fact that the code is used by a significant population of users out there who ensure its continued robustness.

    How you can contribute and make this library even better

    This implementation is largely feature complete. I strive to add support for meaningful features to a broad groups of people in order to avoid any unnecessary cruft and technical debt that may accrue over time.

    The UniFi Protect API is undocumented and implementing a library like this one is the result of many hours of trial and error as well as community support.

    Features

    • Full access to the UniFi Protect NVR JSON.
    • The ability to retrieve the JSON details, including status, of any supported UniFi Protect device.
    • The ability to modify the Protect NVR JSON or Protect devices.

    Changelog

    • Changelog: changes and release history of this library.

    Installation

    To use this library in Node, install it from the command line:

    npm install unifi-protect

    Documentation

    If you'd like to see all this in action in a well-documented, real-world example, please take a good look at my homebridge-unifi-protect project. It relies heavily on this library for the core functionality it provides.

    ProtectApi(nvrAddress: string, username: string, password: string [, log: protectLogging])

    Initialize the UniFi Protect API using the UniFi Protect account information contained in username and password. log is an optional parameter that enables you to customize the type of logging that can be generated, including debug logging. If log isn't specified, the Protect API will default to logging to the console.

    refreshDevices()

    This is where the magic happens. This function:

    • Logs into the Protect API, if we don't already have an access token.
    • If we do have an access token and it's nearly time to refresh it, it will do so.
    • It then requests a refresh of the UniFi Protect NVR state which includes device information for all the Protect devices associated with that NVR. There are failsafes in place to ensure the Protect NVR doesn't get slammed with API requests causing potential performance issues with the NVR.

    Note: refreshDevices() must be called at least once after instantiating the API in order to populate the list of UniFi Protect devices associated with an account.

    Returns: true if successful, false otherwise.

    protectApi.cameras[]

    The cameras property maintains the list of all known Protect camera devices. It is an array of ProtectCameraConfig objects, and you can look through protect-types.ts for a sense of what's contained in a ProtectCameraConfig object.

    This property is refreshed each time refreshDevices() is called.

    protectApi.lights[]

    The lights property maintains the list of all known Protect light devices. It is an array of ProtectLightConfig objects, and you can look through protect-types.ts for a sense of what's contained in a ProtectLightConfig object.

    This property is refreshed each time refreshDevices() is called.

    protectApi.sensors[]

    The sensors property maintains the list of all known Protect sensor devices. It is an array of ProtectSensorConfig objects, and you can look through protect-types.ts for a sense of what's contained in a ProtectSensorConfig object.

    This property is refreshed each time refreshDevices() is called.

    protectApi.viewers[]

    The viewers property maintains the list of all known Protect viewer devices. It is an array of ProtectViewerConfig objects, and you can look through protect-types.ts for a sense of what's contained in a ProtectViewerConfig object.

    This property is refreshed each time refreshDevices() is called.

    More to come...

    UniFi Protect Realtime Updates API

    So...how does UniFi Protect provide realtime updates? On UniFi OS-based controllers, it uses a websocket called updates. This connection provides a realtime stream of health, status, and events that the cameras encounter - including motion events and doorbell ring events.

    Reverse engineering the realtime updates API is a bit more difficult than the system events API because it's based on a binary protocol. The Protect system events API is a steady stream of JSONs published on all UniFi OS controllers over the system websocket. It's used by more than just UniFi Protect, which makes it interesting for future exploration.

    The Protect realtime updates API, however, is a binary protocol published over the updates websocket, and until now has been undocumented. I spent time analyzing what's happening in the Protect browser webUI as well as observing the controller and various Protect versions themselves to reverse engineer what's going on. Pouring through obfuscated code is like solving a puzzle with all the pieces in front of you - you know it's all there, you're just not always sure how it fits together.

    For the impatient, you can take a look at the code for how to decode and read the binary protocol here in protect-updates-api.ts and the interface information located in protect-types.ts as well.

    I welcome any additions or corrections to the protocol for the benefit of the community. I hope this helps others launch their own exploration and create new and interesting Protect-enabled capabilities.

    Non-Ubiquiti Apps Using the Protect API

    This list represents all known apps that are using the realtime updates API for UniFi Protect. If you're using the information you discovered on this page for your own UniFi Protect-based solution, please open an issue and I'm happy to add a link to it below. I hope this can serve as a repository of sorts for UniFi Protect-based apps and solutions in the community.

    • homebridge-unifi-protect: Seamless integration of UniFi Protect into HomeKit with support for cameras, doorbells, and more.

    Connecting

    • Login to the UniFi Protect controller and obtain the bootstrap JSON. The URL is: https://protect-nvr-ip/proxy/protect/api/bootstrap. You can look through protect-api.ts for a better understanding of the Protect login process and how to obtain the bootstrap JSON.
    • Open the websocket to the updates URL. The URL is: wss://protect-nvr-ip/proxy/protect/ws/updates?lastUpdateId?lastUpdateId=X. You can grab lastUpdateId from the bootstrap JSON in the prior step. You can see an example in protect-api.ts.
    • Then you're ready to listen to messages. You can see an example of this in protect-nvr.ts.

    Those are the basics and gets us up and running. Now, to explain how the updates API works...

    Updates Websocket Binary Format

    UniFi OS update data packets are used to provide a realtime stream of updates to Protect. It differs from the system events API in that the system events API appears to be shared across other applications (Network, Access, etc.) while the updates events API appears to only be utilized by Protect and not shared by other applications, although the protocol is shared.

    The updates websocket is used by the UniFi Protect webUI and native applications to provide realtime updates back to the controller. UniFi cameras and doorbells also use a websocket to provide those same updates to the Protect controller. The updates websocket uses a binary protocol to encode data largely to minimize bandwidth requirements, and provide access to more than JSON data, if needed.

    So how does it all work? Cameras continuously stream updates to the UniFi Protect controller containing things like camera health, statistics, and - crucially for us - events such as motion and doorbell ring. A complete update packet is composed of four frames:

     Header Frame (8 bytes)
     ----------------------
     Action Frame
     ----------------------
     Header Frame (8 bytes)
     ----------------------
     Data Frame

    Let's look at each of these.

    Header Frame

    The header frame is required overhead since websockets provide only a transport medium. It's purpose is to tell us what's coming in the frame that follows.

    A packet header is composed of 8 bytes in this order:

    Byte Offset Description Bits Values
    0 Packet Type 8 1 - action frame, 2 - payload frame.
    1 Payload Format 8 1 - JSON object, 2 - UTF8-encoded string, 3 - Node Buffer.
    2 Deflated 8 0 - uncompressed, 1 - deflated / compressed (zlib-based).
    3 Unknown 8 Always 0. Possibly reserved for future use by Ubiquiti?
    4-7 Payload Size 32 Size of payload in network-byte order (big endian).

    If the header has marked the payload as deflated (compressed), you'll need to inflate (uncompress) the payload before you can use it.

    Action Frame

    The action frame identifies what the action and category that the update contains:

    Property Description
    action What action is being taken. Known actions are add and update.
    id The identifier for the device we're updating.
    modelKey The device model category that we're updating.
    newUpdateId A new UUID generated on a per-update basis. This can be safely ignored it seems.
    Data Frame

    The final part of the update packet is the data frame. The data frame can be three different types of data - although in practice, JSONs are all that come across, I've found. Those types are:

    Payload Type Description
    1 JSON. If the action frame's action property is set to update and the modelKey property is not set to event (e.g. camera), this will always a subset of the configuration bootstrap JSON.
    2 A UTF8-encoded string.
    3 Node Buffer.

    Tips

    • update actions are always tied to any valid modelKey that exists in the bootstrap JSON. The exception is events which is tied to the Protect events history list that it maintains. The supported modelKeys from the bootstrap JSON are: bridge, camera, group, light, liveview, nvr, sensor, user, and viewer.
    • add actions are always tied to the event modelKey and indicate the beginning of an event item in the Protect events list. A subsequent update action is sent signaling the end of the event capture, and it's confidence score for motion detection.
    • This is not the same thing as motion detection. If you want to detect motion, you should watch the update action for camera modelKeys, and look for a JSON that updates lastMotion. For doorbell rings, lastRing. The Protect events list is useful for the Protect app, but it's of limited utility to HomeKit, and it's slow relative to just looking for the lastMotion JSON that tends to be much more timely in its delivery. If you want true realtime updates, you want to look at the update action.
    • JSONs are only payload type that seems to be sent, although the protocol is designed to accept all three.
    • With the exception of update actions with a modelKey of event, JSONs are always a subset of the bootstrap JSON, indexed off of modelKey. So for a modelKey of camera, the data payload is always a subset of ProtectCameraConfigInterface (see protect-types.ts).

    Library Development Dashboard

    This is mostly of interest to the true developer nerds amongst us.

    License Build Status Dependencies GitHub commits since latest release (by SemVer)

    Install

    npm i unifi-protect

    DownloadsWeekly Downloads

    427

    Version

    3.0.4

    License

    ISC

    Unpacked Size

    140 kB

    Total Files

    24

    Last publish

    Collaborators

    • hjdhjd