Skip to content

Instantly share code, notes, and snippets.

@j4gd33p
Created June 10, 2025 04:42
Show Gist options
  • Select an option

  • Save j4gd33p/ae18011c0e5ad979dbb2d3fc0a6c6f43 to your computer and use it in GitHub Desktop.

Select an option

Save j4gd33p/ae18011c0e5ad979dbb2d3fc0a6c6f43 to your computer and use it in GitHub Desktop.

UVC PTZ Agent Deployment Guide

This document provides a comprehensive, step-by-step walkthrough for installing, configuring, and running a Node.js–based UVC PTZ (pan/tilt/zoom) agent on remote machines. Once deployed, the agent exposes a secure HTTP API to control any UVC-compliant USB camera attached to the host.


Table of Contents

  1. Overview
  2. Prerequisites
    • Node.js & npm
    • Build Tools
    • Network Requirements
  3. Project Setup
    • Creating the Directory
    • Initializing npm
    • Installing Dependencies
  4. Agent Implementation
    • File Structure
    • index.js Explained
      • Express Server
      • Authentication Middleware
      • UVCControl Integration
      • API Endpoints
  5. Configuration & Environment
    • Environment Variables
    • Securing Credentials
  6. Running & Testing
    • Local Launch
    • Healthcheck & Capabilities Endpoint
    • Making Control Requests
  7. Daemonizing the Agent
    • Linux (systemd)
    • Windows (NSSM)
    • Cross-Platform (PM2)
  8. Network Security
    • TLS/HTTPS with Reverse Proxy
    • VPN or SSH Tunnel
  9. Central Server Integration
    • Example Axios Client
    • Error Handling
  10. Troubleshooting & Tips
    • Common Build Errors
    • USB Permission Issues
    • Handling Multiple Cameras
  11. Appendix
    • Useful Commands
    • References

1. Overview

The UVC PTZ Agent is a lightweight Node.js service that wraps the uvc-control library to expose camera pan/tilt/zoom controls over HTTP. Deploy one agent per machine where a UVC camera is attached. Your central server can then remotely pan, tilt, or zoom the camera by sending authenticated HTTP requests.

2. Prerequisites

2.1 Node.js & npm

  • Version: Node.js v14 or newer.
  • Installation:
    • Windows/Mac: Download and install from nodejs.org.
    • Linux (Debian/Ubuntu):
      curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
      sudo apt-get install -y nodejs

2.2 Build Tools

Native addons require C/C++ build tools and libusb headers:

  • Windows:

    1. Install Visual Studio Build Tools with the “Desktop development with C++” workload.
  • Linux:

    sudo apt-get update
    sudo apt-get install -y build-essential libusb-1.0-0-dev pkg-config
  • macOS:

    xcode-select --install

2.3 Network Requirements

  • Port Access: Decide on an HTTP port (default 3000). Ensure inbound TCP on that port is allowed by firewall or VPN.
  • Security: Plan to secure the agent endpoint via Basic Auth, HTTPS, or VPN.

3. Project Setup

3.1 Creating the Directory

On the remote host:

mkdir ~/uvc-agent && cd ~/uvc-agent

3.2 Initializing npm

npm init -y

This creates package.json with defaults.

3.3 Installing Dependencies

npm install express uvc-control basic-auth
  • express: HTTP server framework
  • uvc-control: UVC PTZ bindings
  • basic-auth: Minimal Basic Auth parser

4. Agent Implementation

4.1 File Structure

uvc-agent/
├── index.js
├── package.json
└── .env           # optional for environment variables

4.2 index.js Explained

Below is the full code for index.js with inline comments.

#!/usr/bin/env node

const express    = require('express');       // Web server
const basicAuth  = require('basic-auth');     // HTTP Basic Auth helper
const UVCControl = require('uvc-control');    // Camera PTZ library

const app = express();
app.use(express.json());  // parse JSON bodies

// ---- CONFIGURATION ----
const PORT      = process.env.PORT || 3000;
const AUTH_USER = process.env.AUTH_USER || 'admin';
const AUTH_PASS = process.env.AUTH_PASS || 'secret';

// ---- AUTHENTICATION MIDDLEWARE ----
app.use((req, res, next) => {
  const creds = basicAuth(req);
  if (!creds || creds.name !== AUTH_USER || creds.pass !== AUTH_PASS) {
    res.set('WWW-Authenticate', 'Basic realm="PTZ Agent"');
    return res.status(401).send('Authentication required');
  }
  next();
});

// ---- INITIALIZE CAMERA ----
const cam = new UVCControl();

// ---- HEALTHCHECK ----
app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

// ---- GET CAMERA CAPABILITIES ----
app.get('/caps', async (req, res) => {
  try {
    const caps = await cam.range();   // list of control ranges
    res.json(caps);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// ---- SET A CONTROL ----
app.post('/control', async (req, res) => {
  const { control, value } = req.body;
  if (!control || typeof value !== 'number') {
    return res.status(400).json({ error: 'Provide {control: string, value: number}' });
  }
  try {
    await cam.set(control, value);
    res.json({ control, value, result: 'ok' });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// ---- START SERVER ----
app.listen(PORT, () => {
  console.log(`🛠️  UVC Agent listening on port ${PORT}`);
});

Key Points:

  • Basic Auth secures every endpoint.
  • /health lets you monitor uptime.
  • /caps returns supported PTZ ranges (min/max values).
  • /control sets any UVC control by name.

5. Configuration & Environment

5.1 Environment Variables

Use a .env file or your shell to set:

AUTH_USER=agent01
AUTH_PASS=veryStrongPassword!
PORT=4000

Then load via your shell or a tool like dotenv if desired.

5.2 Securing Credentials

  • Never commit passwords to Git.
  • Use OS keyrings or container secrets for production.

6. Running & Testing

6.1 Local Launch

npm install   # ensure deps
node index.js # runs agent

6.2 Healthcheck & Capabilities

curl -u agent01:veryStrongPassword! http://localhost:4000/health
# → { "status":"ok" }

curl -u agent01:veryStrongPassword! http://localhost:4000/caps
# → JSON listing pan, tilt, zoom ranges

6.3 Sending PTZ Commands

curl -u agent01:veryStrongPassword!   -X POST http://localhost:4000/control   -H "Content-Type: application/json"   -d '{"control":"zoom_absolute","value":150}'

Expect {"control":"zoom_absolute","value":150,"result":"ok"} and camera movement.

7. Daemonizing the Agent

7.1 Linux (systemd)

  1. Create /etc/systemd/system/uvc-agent.service:
    [Unit]
    Description=UVC PTZ Agent
    After=network.target
    
    [Service]
    Type=simple
    User=YOUR_USER
    WorkingDirectory=/home/YOUR_USER/uvc-agent
    ExecStart=/usr/bin/node index.js
    Environment=AUTH_USER=agent01
    Environment=AUTH_PASS=veryStrongPassword!
    Environment=PORT=4000
    Restart=always
    RestartSec=5
    
    [Install]
    WantedBy=multi-user.target
  2. Reload & start:
    sudo systemctl daemon-reload
    sudo systemctl enable uvc-agent
    sudo systemctl start uvc-agent

7.2 Windows (NSSM)

  1. Download NSSM and unzip.
  2. Install service:
    .\nssm.exe install UvcAgent
    • Application Path: C:\Program Files\nodejs\node.exe
    • Arguments: C:\path\to\uvc-agent\index.js
    • Env variables in NSSM GUI (AUTH_USER, AUTH_PASS, PORT).
  3. Start the UvcAgent service via Services.msc.

7.3 Cross-Platform (PM2)

npm install -g pm2
pm2 start index.js --name uvc-agent \
  --env AUTH_USER=agent01 --env AUTH_PASS=... --env PORT=4000
pm2 save
pm2 startup           # follow printed instructions

8. Network Security

8.1 TLS/HTTPS with Reverse Proxy

  • Terminate TLS with Nginx or Caddy:
    server {
      listen 443 ssl;
      server_name agent.example.com;
      ssl_certificate /etc/letsencrypt/live/agent.example.com/fullchain.pem;
      ssl_certificate_key /etc/letsencrypt/live/agent.example.com/privkey.pem;
    
      location / {
        proxy_pass http://localhost:4000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
      }
    }

8.2 VPN or SSH Tunnel

  • SSH reverse tunnel from agent to central:
    ssh -N -R 5001:localhost:4000 central-user@central.example.com
  • Central then calls http://localhost:5001/control.

9. Central Server Integration

Example using Axios in Node.js:

const axios = require('axios');

async function panTiltZoom(agentHost, ctrl, val) {
  try {
    const res = await axios.post(
      `https://${agentHost}/control`,
      { control: ctrl, value: val },
      { auth: { username: 'agent01', password: 'veryStrongPassword!' } }
    );
    console.log('PTZ result:', res.data);
  } catch (err) {
    console.error('Error controlling camera:', err.message);
  }
}

// Usage:
panTiltZoom('agent-A.example.com', 'absolute_pan_tilt', 100);

Error Handling:

  • Network errors: retry with exponential backoff.
  • Authentication failures: verify credentials and time sync.
  • Unsupported controls: check /caps first.

10. Troubleshooting & Tips

  • Build fails on Windows: ensure Visual C++ Build Tools installed, run npm install --global --production windows-build-tools.
  • Permission denied on Linux: add your user to the video group:
    sudo usermod -aG video $USER
    newgrp video
  • Multiple cameras: pass a device index to UVCControl:
    const cam = new UVCControl('/dev/video1');
  • Control names: run cam.range() to list exactly supported names.

11. Appendix

11.1 Useful Commands

  • List UVC controls on Linux:
    v4l2-ctl --list-ctrls
  • Test HTTP endpoint:
    http --auth agent01:pw --json POST http://localhost:4000/control control=zoom_absolute value:=150

11.2 References


End of Document.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment