How to Integrate UniFi Protect with Fabman for Easy Video Access

Hello everyone,

I’m Roland from Happylab in Vienna. We use Fabman to manage our makerspace and UniFi Protect for our video surveillance needs. To streamline our operations and make it easier to monitor machine usage, we developed an integration between Fabman and UniFi Protect. I thought this solution could be beneficial for other makerspaces as well, so I’ve put together this guide to share how you can set it up in your own space.

By following the steps below, you can easily link your Fabman Activity Log entries to the relevant video sequences in UniFi Protect, making it much simpler to review footage and manage your makerspace effectively. This integration allows lab managers to quickly access relevant video footage directly from the Fabman Activity Log, saving time and effort in identifying and resolving issues like unreported defects or improper machine use.

Prerequisites

  • A UniFi Protect installation
  • A web server with PHP and HTTPS to host the webhook script provided in the attachment
  • A Fabman API Key with permissions to edit Activity Log entries

Finding Your UniFi Account and Camera IDs

UniFi Protect assigns unique IDs to your account and each camera. These can be easily located in the URL when accessing UniFi Protect through a browser.

Steps to Integrate UniFi Protect with Fabman

  1. Copy the PHP Script to Your Web Server

    • Copy the provided unifi.php script to your web server and ensure it is accessible via an HTTPS URL.
  2. Configure the PHP Script

    • Open the script and set the following variables at the beginning of the source code:
      • secret: A personal string of your choice to ensure that only authorized requests can trigger the webhook.
      • unifi_id: The ID of your UniFi account (as noted above).
  3. Set Up the Webhook in Fabman

    • Navigate to the Webhooks section in Fabman and add a new webhook with the following details:
      • URL: https://<yourserver>/unifi.php?active=1&secret=<yoursecret> (set active=0 to deactivate the webhook)
      • Descriptive Label: e.g., “Add UniFi-Link to Notes in Activity Log”
      • Event Type: Select “activity log”
  4. Configure UniFi Integration in Fabman Metadata

    • For each machine (referred to as “Equipment” in Fabman) you wish to monitor, add the camera ID(s) to the machine’s metadata. This can be done as follows:
    {
        "unifiCam": "65e04bf43674x503e4000504"
    }
    
    • If multiple cameras cover the area, list them as an array:
    {
        "unifiCam": [
            {
                "id": "65e04bf43674x503e4000504",
                "name": "Camera 1"
            },
            {
                "id": "65x04bf30074r503e4132422",
                "name": "Camera 2"
            }
        ]
    }
    

Result

Once the setup is complete, every time a machine is used, a link (or multiple links, if multiple cameras are configured) will be automatically added to the corresponding Activity Log entry in Fabman. This link will direct you to the exact video sequence from the camera specified in the equipment configuration, specifically to the moment when the activity starts, such as when the chip card is held up to the Fabman Bridge.

By implementing this integration, you can quickly and easily review video footage related to specific machine activities, enhancing your ability to maintain a well-managed and secure makerspace.

Attached: Source Code “unifi.php”

<?php

    // Configuration
    $secret = 'YOUR_SECRET';
    $unifi_id = 'YOUR_UNIFI_ACCOUNT_ID';
    $api_key = 'YOUR_FABMAN_API_KEY';
    $api_url_base = 'https://fabman.io/api/v1/';

    // Check secret token
    if ($_GET['secret'] != $secret) {
        echo "Webhook not executed due to wrong token (url?secret=token)";
        exit;
    }

    // Exit if URL Parameter active is not true
    if (isset($_GET['active']) && ($_GET['active'] == "0" || strtolower($_GET['active']) == "false")) {
        echo "Webhook is disabled.";
        exit;
    }

    // Get webhook payload
    $obj = json_decode(file_get_contents("php://input"));        

    if ($obj->type == "resourceLog_created") { // On Activity Start
        if (isset($obj->details->resource->metadata->unifiCam)) { // Check if UniFi Cam is configured
            if (!is_array($obj->details->resource->metadata->unifiCam)) {
                echo "NOT AN ARRAY\n";
                $unifiCams = array(
                    (object) array(
                        "name" => "UniFi Protect Cam",
                        "id" => $obj->details->resource->metadata->unifiCam
                    )
                );                
            } else {
                $unifiCams = $obj->details->resource->metadata->unifiCam;
            }

            $activityId = $obj->details->log->id;
            $activityCreatedAt = $obj->details->log->createdAt;

            // Convert the timestamp to a Unix epoch timestamp (ms)
            $videoTimestamp = strtotime($activityCreatedAt) * 1000;

            // Prepare note string
            $notes = "View camera recordings:<ul>";
            $urlBase = "https://unifi.ui.com/consoles/'.$unifi_id.'/protect/timelapse/";            
            foreach ($unifiCams as $unifiCam) {
                $unifiUrl = $urlBase . $unifiCam->id . "?start=" . $videoTimestamp;
                echo "unifiUrl: $unifiUrl\n";
                // Prepare notes
                $notes .= '<li><a target="_blank" href="' . $unifiUrl . '">' . $unifiCam->name . '</a></li>';            
            }
            $notes .= "</ul>";

            // Write link to video in activity note
            echo "Write url to Notes of Activity $activityId.\n";
            setResourceLogNote($api_url_base, $api_key, $activityId, $notes);

        } else {
            echo "No UniFi ID configured in the resource metadata (JSON parameter \"unifiCam\").\n";
        }
    } else {
        echo "Link is only set for resource type \"resourceLog_created\".\n";
    }

    // Append complete payload to Fabman webhook log
    echo "\n\nPAYLOAD:\n";
    var_dump($obj);



    // Function to get the current resource log details
    function getResourceLog($api_url, $api_key) {
        $ch = curl_init($api_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $api_key
        ]);

        $response = curl_exec($ch);

        if ($response === false) {
            echo 'Curl error: ' . curl_error($ch);
            curl_close($ch);
            return false;
        }

        curl_close($ch);
        return json_decode($response, true);
    }

    // Function to update the resource log
    function updateResourceLog($api_url, $api_key, $data) {
        $ch = curl_init($api_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $api_key
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

        $response = curl_exec($ch);

        if ($response === false) {
            echo 'Curl error: ' . curl_error($ch);
            curl_close($ch);
            return false;
        }

        curl_close($ch);
        return json_decode($response, true);
    }

    // Function to set the notes field of a resource log entry with retries
    function setResourceLogNote($api_url_base, $api_key, $resource_log_id, $new_notes, $max_retries = 5) {
        $api_url = $api_url_base . 'resource-logs/' . $resource_log_id;
        $retry_count = 0;
        $success = false;

        while ($retry_count < $max_retries && !$success) {
            // Get the current resource log details
            $resource_log = getResourceLog($api_url, $api_key);

            if (!$resource_log || !isset($resource_log['lockVersion'])) {
                echo 'Error: Unable to retrieve lockVersion.';
                exit;
            }

            $lock_version = $resource_log['lockVersion'];

            // Prepare data for updating the resource log
            $data = [
                'notes' => $new_notes,
                'lockVersion' => $lock_version
            ];

            // Attempt to update the resource log
            $response_data = updateResourceLog($api_url, $api_key, $data);

            if ($response_data && !isset($response_data['error'])) {
                $success = true;
                echo 'Resource log updated successfully.';
                print_r($response_data);
            } else {
                $retry_count++;
                if ($retry_count < $max_retries) {
                    echo "Retrying... ($retry_count/$max_retries)\n";
                    sleep(1); // Optional: wait for a second before retrying
                } else {
                    echo 'Failed to update resource log after multiple attempts.';
                    if (isset($response_data['error'])) {
                        print_r($response_data['error']);
                    }
                }
            }
        }
    }


 ?>

By following these steps, you will be able to seamlessly integrate UniFi Protect with Fabman, allowing for efficient monitoring and management of machine usage in your makerspace.

2 Likes