Zoneminder Monitors

This role automates the addition of camera monitors to ZoneMinder via the REST API.

ARA Ansible Bash HTTPS JSON REST API SSL VLAN

Zoneminder Monitors Role

Overview

This role automates the addition of camera monitors to ZoneMinder via the REST API. It authenticates with the ZoneMinder API, retrieves the list of existing monitors to prevent duplicates, and creates new camera monitors with customizable settings including name, RTSP path, resolution, recording function, video encoding parameters, and audio recording options. The role is idempotent and will only add monitors that don’t already exist.

Purpose

  • Camera Automation: Add multiple cameras via code instead of UI
  • Idempotent: Safe to run multiple times, prevents duplicates
  • API-Based: Uses ZoneMinder REST API for reliable automation
  • Flexible Configuration: Define camera settings in YAML
  • Batch Addition: Add multiple cameras in one run
  • Code as Configuration: Version control camera definitions

Requirements

  • Ansible 2.9 or higher
  • ZoneMinder installed and running (see zoneminder_install role)
  • ZoneMinder API enabled
  • API user credentials (typically admin account)
  • Network connectivity to ZoneMinder
  • IP cameras with RTSP streams
  • Camera credentials (username/password for RTSP URLs)

What This Role Does

Camera addition workflow:

  1. Authenticate: Login to ZoneMinder API and get access token
  2. Check existing: Retrieve list of currently configured monitors
  3. Compare: Identify which cameras need to be added
  4. Add new: Create monitors that don’t already exist
  5. Configure: Set all camera parameters (resolution, function, encoding)

Idempotency: If monitor with same name exists, skip creation.

Role Variables

Required Variables

VariableRequiredDescription
zoneminder_monitors_api_userYesZoneMinder API username
zoneminder_monitors_api_passwordYesAPI password (from vault)
zoneminder_monitors_listYesList of cameras to add

Optional Variables

VariableDefaultDescription
zoneminder_monitors_api_urlhttps://<host>/zm/apiZoneMinder API URL
zoneminder_monitors_validate_certstrueValidate SSL certificates
zoneminder_monitors_defaultsSee defaultsDefault camera settings

Variable Details

zoneminder_monitors_api_url

Base URL for ZoneMinder API.

Default: "https://{{ hostvars['zoneminder']['ansible_host'] }}/zm/api"

Override for custom host:

zoneminder_monitors_api_url: "https://192.168.x.x/zm/api"

zoneminder_monitors_api_user

ZoneMinder username for API authentication.

Default: admin

Custom user:

zoneminder_monitors_api_user: api_user

Note: User must have permission to add monitors.

zoneminder_monitors_api_password

Password for API authentication.

IMPORTANT: Store in Ansible Vault

# In vault.yml
vault_zoneminder_api_password: "admin_password_here"

# In playbook
zoneminder_monitors_api_password: "{{ vault_zoneminder_api_password }}"

zoneminder_monitors_validate_certs

Whether to validate SSL certificates.

Default: true

Disable for self-signed certs:

zoneminder_monitors_validate_certs: false

zoneminder_monitors_defaults

Default settings for all cameras (overridable per camera).

Default values:

zoneminder_monitors_defaults:
  type: "Ffmpeg"
  method: "rtpRtsp"
  colours: 4
  save_jpegs: 0
  video_writer: 2
  record_audio: 1
  encoder_parameters: "# Lines beginning with # are a comment \r\n# For changing quality, use the crf option\r\n# 1 is best, 51 is worst quality\r\ncrf=23\r\nmovflags=faststart"

Field descriptions:

FieldDescription
typeMonitor type (usually “Ffmpeg” for IP cameras)
methodRTSP method (“rtpRtsp” for most cameras)
coloursColor depth (4 = 32-bit color)
save_jpegsSave JPEG snapshots (0=no, 1=yes)
video_writerVideo encoding (0=disabled, 1=encode, 2=passthrough)
record_audioRecord audio stream (0=no, 1=yes)
encoder_parametersFFmpeg encoding options

zoneminder_monitors_list

List of cameras to add.

Required fields per camera:

  • name: Display name
  • path: Full RTSP URL with credentials
  • width: Video width in pixels
  • height: Video height in pixels
  • function: Recording mode

Optional fields (use defaults if not specified):

  • enabled: Enable/disable monitor
  • type: Monitor type
  • method: RTSP method
  • colours: Color depth
  • save_jpegs: Save JPEG snapshots
  • video_writer: Video encoder mode
  • record_audio: Record audio
  • encoder_parameters: FFmpeg options

Example:

zoneminder_monitors_list:
  - name: "Front Door"
    path: "rtsp://username:password@192.168.x.x:554/live/ch0"
    width: 1920
    height: 1080
    function: "Record"

  - name: "Backyard"
    path: "rtsp://username:password@192.168.x.x:554/live/ch0"
    width: 2304
    height: 1296
    function: "Modect"
    save_jpegs: 1  # Override default to save JPEGs

  - name: "Garage"
    path: "rtsp://admin:pass123@192.168.x.x:554/stream1"
    width: 1280
    height: 720
    function: "Mocord"
    record_audio: 0  # No audio for this camera

Dependencies

Requires:

  • zoneminder_install: ZoneMinder must be installed first
  • ZoneMinder API enabled (enabled by default)
  • Camera RTSP URLs and credentials

Example Playbook

Basic Usage

---
- name: Add Cameras to ZoneMinder
  hosts: localhost
  become: false

  vars:
    zoneminder_monitors_list:
      - name: "Front Door"
        path: "rtsp://admin:password@192.168.x.x:554/live/ch0"
        width: 1920
        height: 1080
        function: "Modect"

      - name: "Backyard"
        path: "rtsp://admin:password@192.168.x.x:554/live/ch0"
        width: 1920
        height: 1080
        function: "Record"

  roles:
    - zoneminder_monitors

Multiple Cameras with Custom Settings

---
- name: Add Security Cameras
  hosts: localhost
  become: false

  vars:
    zoneminder_monitors_list:
      # High-quality front camera with motion detection
      - name: "Front Entrance"
        path: "rtsp://admin:{{ vault_camera_password }}@192.168.x.x:554/live/ch0"
        width: 2304
        height: 1296
        function: "Modect"
        save_jpegs: 1  # Save snapshots
        video_writer: 1  # Re-encode for quality

      # Continuous recording for critical area
      - name: "Server Room"
        path: "rtsp://admin:{{ vault_camera_password }}@192.168.x.x:554/stream1"
        width: 1920
        height: 1080
        function: "Record"
        video_writer: 2  # Passthrough (no re-encoding)

      # Low-res camera for parking lot
      - name: "Parking Lot"
        path: "rtsp://admin:{{ vault_camera_password }}@192.168.x.x:554/stream2"
        width: 1280
        height: 720
        function: "Mocord"
        record_audio: 0  # No audio needed

  roles:
    - zoneminder_monitors

Self-Signed SSL Certificate

---
- name: Add Cameras (Self-Signed Cert)
  hosts: localhost
  become: false

  vars:
    zoneminder_monitors_validate_certs: false

    zoneminder_monitors_list:
      - name: "Camera 1"
        path: "rtsp://admin:password@192.168.x.x:554/live/ch0"
        width: 1920
        height: 1080
        function: "Modect"

  roles:
    - zoneminder_monitors

ZoneMinder Monitor Functions

Recording modes:

FunctionDescriptionUse Case
MonitorNo recording, live view onlyTesting camera connection
ModectMotion detection onlySave disk space, event-driven
RecordContinuous recordingCritical areas, 24/7 coverage
MocordContinuous + enhanced motion eventsBest of both (uses more disk)
NodectContinuous, no motion analysisReduce CPU, simple recording

Recommendations:

  • Front doors: Modect (motion-triggered)
  • Server rooms: Record (continuous)
  • Public areas: Mocord (continuous + events)
  • Testing: Monitor (no recording)

RTSP URL Format

Standard RTSP URL:

rtsp://username:password@ip_address:port/path

Examples by manufacturer:

Hikvision:

rtsp://admin:password@192.168.x.x:554/Streaming/Channels/101

Dahua:

rtsp://admin:password@192.168.x.x:554/cam/realmonitor?channel=1&subtype=0

Amcrest:

rtsp://admin:password@192.168.x.x:554/cam/realmonitor?channel=1&subtype=0

Reolink:

rtsp://admin:password@192.168.x.x:554/h264Preview_01_main

Generic ONVIF:

rtsp://admin:password@192.168.x.x:554/stream1

Find RTSP path:

  1. Check camera manual/datasheet
  2. Use ONVIF Device Manager (Windows tool)
  3. Check manufacturer’s website
  4. Try common paths: /stream1, /live/ch0, /h264

Video Encoding Options

video_writer

Controls how ZoneMinder handles video encoding.

Values:

ValueModeDescription
0DisabledNo video encoding (JPEG only)
1EncodeRe-encode video (high CPU, better quality)
2PassthroughCopy stream (low CPU, preserves quality)

Recommendations:

  • Passthrough (2): Default, best for most use cases
  • Encode (1): If camera codec incompatible or quality issues
  • Disabled (0): Only if using JPEG snapshots only

encoder_parameters

FFmpeg encoding parameters for video_writer=1.

Default:

crf=23
movflags=faststart

Parameters:

ParameterDescriptionValues
crfQuality (1-51)18=high quality, 23=balanced, 28=low quality
movflagsMP4 optimizationfaststart for web streaming
presetEncoding speedultrafast, fast, medium, slow

High quality:

encoder_parameters: "crf=18\r\npreset=slow\r\nmovflags=faststart"

Low CPU:

encoder_parameters: "crf=28\r\npreset=ultrafast\r\nmovflags=faststart"

Resolution Settings

Common resolutions:

ResolutionWidthHeightName
4K38402160Ultra HD
2K23041296Quad HD
1080p19201080Full HD
720p1280720HD
480p640480SD

Recommendations:

  • Critical areas: 1080p or higher
  • General monitoring: 720p (balanced quality/storage)
  • Low-priority: 480p (saves disk space)

Storage impact:

  • 1080p uses ~2x disk space vs 720p
  • 4K uses ~4x disk space vs 1080p

API Authentication

Authentication Flow

1. POST /zm/api/host/login.json
   Body: user=admin&pass=password
   Response: { "access_token": "xxx", "refresh_token": "yyy" }

2. Use access_token for subsequent requests
   URL: /zm/api/monitors.json?token=xxx

Token expiration: Typically 1 hour

This role: Authenticates once at start of run

What This Role Does (Detailed)

1. Authenticate with ZoneMinder API

POST https://zoneminder/zm/api/host/login.json
Body: user=admin&pass=password

Response:

{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
  "refresh_token": "...",
  "access_token_expires": 3600
}

Extracts: access_token for use in subsequent requests

2. Get Existing Monitors

GET https://zoneminder/zm/api/monitors.json?token=<token>

Response:

{
  "monitors": [
    {
      "Monitor": {
        "Id": "1",
        "Name": "Front Door",
        "Type": "Ffmpeg",
        ...
      }
    }
  ]
}

Extracts: List of existing monitor names

3. Add New Monitors

For each camera in zoneminder_monitors_list:

If name not in existing monitors:

POST https://zoneminder/zm/api/monitors.json
Body (form-encoded):
  token=<token>
  Monitor[Name]=Front Door
  Monitor[Enabled]=1
  Monitor[Type]=Ffmpeg
  Monitor[Function]=Modect
  Monitor[Method]=rtpRtsp
  Monitor[Path]=rtsp://...
  Monitor[Width]=1920
  Monitor[Height]=1080
  Monitor[Colours]=4
  Monitor[SaveJPEGs]=0
  Monitor[VideoWriter]=2
  Monitor[RecordAudio]=1
  Monitor[EncoderParameters]=crf=23...

Response:

{
  "message": "Monitor saved",
  "id": 2
}

If name already exists: Skip (idempotent)

Security Considerations

  • API Password: Store in Ansible Vault
  • Camera Credentials: Included in RTSP URLs (consider vault for these too)
  • SSL Validation: Enable in production
  • Network Segmentation: Keep cameras on isolated VLAN
  • Strong Passwords: Use complex passwords for cameras
  • API User Permissions: Use dedicated API user (not admin if possible)
  • RTSP Encryption: Consider RTSPS if camera supports it

Vault example:

# vault.yml
vault_zoneminder_api_password: "admin_secure_password"
vault_camera_username: "admin"
vault_camera_password: "camera_secure_password"

# Usage in monitors list
path: "rtsp://{{ vault_camera_username }}:{{ vault_camera_password }}@192.168.x.x:554/live/ch0"

Tags

This role does not define any tags. Use playbook-level tags if needed:

- hosts: localhost
  roles:
    - zoneminder_monitors
  tags:
    - zoneminder
    - cameras
    - monitors
    - nvr

Notes

  • Role runs on localhost (API calls to ZoneMinder)
  • become: false (no root needed for API calls)
  • Idempotent: Checks existing monitors before adding
  • Passwords logged to console are hidden with no_log: true
  • API authentication token obtained fresh each run
  • Only adds monitors that don’t already exist

Troubleshooting

Authentication failed

Symptom: “Authentication failed” or 401 error

Causes:

  • Wrong username/password
  • ZoneMinder API authentication disabled
  • User doesn’t have API permissions

Check:

# Test API manually
curl -X POST https://zoneminder/zm/api/host/login.json \
  -d "user=admin&pass=password"

# Should return access_token

Fix:

  • Verify credentials in vault
  • Enable API auth in ZoneMinder: Options → System → OPT_USE_API
  • Check user permissions: Options → Users

Monitor not added

Symptom: Task shows “skipped” or no change

Causes:

  • Monitor with same name already exists
  • API returned error (check output)

Check existing monitors:

curl "https://zoneminder/zm/api/monitors.json?token=<token>" | jq '.monitors[].Monitor.Name'

Fix:

  • Use different monitor name
  • Delete existing monitor via UI
  • Check API error messages in Ansible output

Camera shows “Red” status in ZoneMinder

Symptom: Monitor added but shows red/offline

Causes:

  • RTSP URL incorrect
  • Camera not reachable
  • Wrong credentials
  • Wrong RTSP path

Test RTSP:

# Test with ffmpeg
ffmpeg -i "rtsp://user:pass@ip:554/path" -frames:v 1 test.jpg

# Test with VLC
vlc rtsp://user:pass@ip:554/path

Fix:

  • Verify RTSP URL and credentials
  • Check network connectivity
  • Try different RTSP path (consult camera manual)
  • Check firewall rules (RTSP uses port 554)

SSL certificate verification failed

Symptom: “SSL certificate problem” error

Cause: Self-signed certificate on ZoneMinder

Fix:

zoneminder_monitors_validate_certs: false

Or install CA certificate on Ansible control node

Monitor shows “Orange” status

Symptom: Monitor shows orange/yellow status

Meaning: ZoneMinder connected but having issues

Causes:

  • Intermittent network issues
  • Camera performance problems
  • ZoneMinder overloaded (too many cameras/high resolution)

Check ZoneMinder logs:

tail -f /var/log/zm/zmc-m<monitor_id>.log

Fix:

  • Reduce FPS or resolution
  • Check camera performance
  • Increase ZoneMinder system resources

Testing the Role

Verify Authentication

# Test API login
curl -X POST https://zoneminder/zm/api/host/login.json \
  -d "user=admin&pass=password" \
  -k  # Skip cert verification if self-signed

# Should return JSON with access_token

List Existing Monitors

# Get token first (from curl above)
TOKEN="eyJ0eXAiOiJKV1QiLCJhbGc..."

# List monitors
curl "https://zoneminder/zm/api/monitors.json?token=$TOKEN" -k | jq

Verify Monitor Added

Via API:

curl "https://zoneminder/zm/api/monitors.json?token=$TOKEN" -k | \
  jq '.monitors[].Monitor | {Id, Name, Path}'

Via Web UI:

  1. Log into ZoneMinder
  2. Console tab
  3. Should see new monitors listed

Test Camera Stream

In ZoneMinder:

  1. Click monitor name
  2. Should see live video feed
  3. Status should be green

Via ffmpeg (if issues):

ffmpeg -i "rtsp://user:pass@camera-ip:554/path" \
  -frames:v 1 -y test.jpg

# If succeeds: Camera works, issue is with ZoneMinder config
# If fails: Camera/network issue

Best Practices

  1. Use vault for credentials: Never commit passwords to git
  2. Test RTSP URLs first: Verify with VLC or ffmpeg before adding
  3. Start with lower resolution: Test connectivity before going to 4K
  4. Use passthrough encoding: Default video_writer=2 for best performance
  5. Name cameras descriptively: “Front Door” not “Camera 1”
  6. Document RTSP paths: Keep record of each camera’s RTSP URL format
  7. Network segmentation: Put cameras on dedicated VLAN
  8. Monitor disk space: High-res recording uses lots of space
  9. Regular testing: Verify cameras recording after adding
  10. Idempotent playbooks: Safe to run multiple times

Common Camera Issues

Wrong RTSP Path

Symptom: Can ping camera but ZoneMinder shows red

Solutions:

  • Check camera manual for correct RTSP path
  • Try common paths: /stream1, /live/ch0, /h264Preview_01_main
  • Use ONVIF Device Manager to discover RTSP URLs

Authentication Issues

Symptom: “401 Unauthorized” from camera

Solutions:

  • Verify camera username/password
  • URL-encode special characters in password
  • Some cameras: admin account disabled, create new user

Network Issues

Symptom: Intermittent red/orange status

Solutions:

  • Check network congestion
  • Verify camera on same subnet or routed correctly
  • Check multicast vs unicast RTSP (prefer unicast)

This role is often used with:

  • zoneminder_install: Install ZoneMinder first
  • SSL certificate roles: Deploy SSL certificates
  • Camera setup roles: Configure camera settings
  • Network roles: Configure VLANs for camera isolation

Advanced Configuration

Custom FFmpeg Options

Hardware acceleration (if supported):

encoder_parameters: "hwaccel=vaapi\r\nvaapi_device=/dev/dri/renderD128\r\ncrf=23"

H.265 encoding:

encoder_parameters: "vcodec=libx265\r\ncrf=28\r\npreset=fast"

Motion Detection Tuning

After adding camera, configure in ZoneMinder UI:

  1. Click camera → Zones
  2. Edit zones for motion detection areas
  3. Adjust sensitivity: Options → Images
  4. Set alarm thresholds

License

MIT

Author

Created for homelab infrastructure management.