A single unified Camera device type for Ignition 8.3 that brings live video, PTZ control, and camera health straight into the platform — no separate VMS window, no vendor lock-in.
SCADA is supervisory control and data acquisition — and in today's world, no matter where SCADA is used, cameras are a key insight into what is going on. Yet camera video traditionally lives in a separate VMS window, disconnected from the SCADA screens operators actually watch. This module makes any commodity IP camera a first-class Ignition device: live video and PTZ control on the same Perspective page as the process data it relates to, and camera health, status, and capabilities as OPC-UA tags that alarm and historise like any other device. Standard protocols only (ONVIF, RTSP, MJPEG, snapshot URLs) — no vendor lock-in, no cloud, no separate VMS licence.
The full purpose, definition of done, and permanent won't-do list live in docs/PROJECT_CHARTER.md — the charter drives every release decision.
Every camera pictured below is a synthetic RTSP test source stood up for this
README (see How to use it) — DemoLoadingDock and DemoYardCam. No physical
camera is required to reproduce any of it.
Camera device detail — the Connection Browser's device list, expanded. It
shows exactly what the module discovers per device: DemoLoadingDock's and
DemoYardCam's RTSP media profiles (H.264), each with Snapshot/Stream/Copy
URI actions. (ONVIF discovery — manufacturer/model/firmware/serial and
multi-profile enumeration — is a real capability of the module against ONVIF
hardware; it isn't pictured here because it needs physical ONVIF hardware to
demonstrate honestly.)
Gateway Live View grid — the Connection Browser's built-in multi-camera
grid, actually streaming: the left tile is DemoLoadingDock's synthetic feed
(colour bars, a moving marker, and a live timestamp overlay proving it isn't
a static image); the right tile is DemoYardCam, a second synthetic source
with a visibly different test pattern (SMPTE bars) so the two tiles are
clearly distinct.
Perspective CameraViewer and CameraGrid components — the same video,
now embedded in a Perspective page next to whatever process data it belongs
with. The top pane is a single CameraViewer bound to DemoLoadingDock; the
bottom pane is a CameraGrid showing both synthetic devices side by side,
proving the grid component streams multiple devices at once.
Designer-scope authoring — CameraViewer and CameraGrid are ordinary
Perspective components in the Designer's own component palette (Camera Driver
category), draggable onto any view. The property editor shown here is a
CameraViewer dropped on a scratch view, exposing its real bindable
properties — deviceName, mode, snapshotInterval, showOverlay,
objectFit, showSaveButton, showPtzControls — the same as any built-in
component, with no special-casing needed to use them.
| Method | Protocol | Capabilities |
|---|---|---|
| RTSP | RTSP | Direct video streaming, browser playback via bundled go2rtc + ffmpeg |
| MJPEG | HTTP MJPEG | Direct MJPEG video streaming |
| Snapshot | HTTP | JPEG snapshot capture from a still-image URL |
| ONVIF | ONVIF SOAP | Device discovery, media profile enumeration, PTZ control, and auto-detection of stream/snapshot URLs (Profile S/T) |
A single Camera device can use any combination of the above, enabled per device. URLs for RTSP, MJPEG, and snapshot can be entered directly or auto-detected via ONVIF when enabled.
- Single unified Camera device type with per-device connection methods
- Multi-protocol camera connectivity (RTSP, MJPEG, snapshot URLs, ONVIF)
- Sub-second live video via WebRTC (v3.1.0+), with automatic MSE → snapshot fallback
- PTZ control from Perspective and the gateway Connection Browser
- Authenticated HTTP endpoints (session auth, Basic Auth, API key, Perspective session tokens)
- Per-IP rate limiting (DoS protection)
- Bundled go2rtc and ffmpeg for RTSP-to-browser streaming — no external media server
- Hierarchical OPC-UA address space with camera data
- Connection Browser UI in Gateway Config (dashboard, live grid, diagnostics)
- Perspective
CameraViewerandCameraGridcomponents - Configurable SSL/TLS validation modes
- 550+ automated tests with 100% pass rate (gateway/common JUnit + web-ui vitest)
Gateway web UI → Config → System → Modules → Install or Upgrade a Module →
upload the signed .modl. No gateway restart is required — the module
hot-loads.
WebRTC note: the bundled go2rtc listens for WebRTC media on port 8555
(TCP+UDP). Host-network deployments need nothing. Docker bridge-network
deployments must publish -p 8555:8555 -p 8555:8555/udp and list the
host-reachable IP in data/camera-driver/go2rtc/webrtc-candidates.txt (one
host:port per line). If WebRTC cannot connect, playback silently falls back
to MSE.
- Gateway → Config → OPC UA → Device Connections
- Create new Device → Select "Camera"
- Enter the camera IP address, username, and password
- Enable the connection methods the camera supports (RTSP, MJPEG, Snapshot, ONVIF)
- For RTSP/MJPEG/snapshot, either enter URLs directly or enable ONVIF to auto-detect them
- Save and view tags in OPC Browser, or open the Connection Browser
(
/data/camera-driver/connection-browser)
Don't have a camera handy? The screenshots above were captured against synthetic RTSP sources — no hardware required to try the module:
scripts/demo-cameras.sh up # two synthetic cameras
scripts/demo-cameras.sh status # running? is anything actually watching?
scripts/demo-cameras.sh down # finished — leaves nothing behindThat publishes rtsp://127.0.0.1:8556/cam1 (test pattern) and
rtsp://127.0.0.1:8556/cam2 (colour bars, so a two-tile CameraGrid is
obviously showing two cameras). Port 8556 rather than 8554 because the
gateway's own bundled go2rtc owns 8554 when the gateway is host-networked, and
the collision shows up as a stream that never opens rather than a port error.
Then create a Camera device as above with RTSP enabled, Snapshot/MJPEG/ONVIF
disabled, and RTSP URL Override set to rtsp://127.0.0.1:8556/cam1 (and,
for a second device, rtsp://127.0.0.1:8556/cam2).
One container holds the whole demo, and encoding runs only while something is
reading a stream. Run down when you are done — an earlier version of this
section was three commands to start and none to stop, and left an encoder
running at 25 fps for 24 hours after the demo it was for had finished.
Drag a CameraViewer (single camera) or CameraGrid (multi-camera) component
onto a view and set deviceName (or cameras: [] for the grid) to the device
name(s) created above. See docs/USAGE.md for the full
HTTP endpoint and component reference, and
docs/TESTING.md for complete testing instructions.
All fields belong to the single Camera device type.
General: Device Name, Enabled
Connection: IP Address, Port, Username, Password, Use HTTPS, Connection Timeout, SSL Validation Mode
Streams (connection methods): RTSP Stream, MJPEG Stream, Snapshot, ONVIF / PTZ, plus optional RTSP URL / Snapshot URL / MJPEG URL overrides
Advanced: Poll Interval (ONVIF only)
All endpoints are at /data/camera-driver/* and require authentication.
| Endpoint | Description |
|---|---|
/data/camera-driver/snapshot?device=X&profile=Y |
JPEG snapshot |
/data/camera-driver/stream?device=X&profile=Y&fps=Z |
Live stream (fMP4/MJPEG fallback chain) |
POST /data/camera-driver/webrtc?device=X |
WebRTC SDP signaling (offer in, answer out) |
POST /data/camera-driver/ptz/move?device=X&pan=&tilt=&zoom= |
PTZ continuous move (hold) |
POST /data/camera-driver/ptz/stop?device=X |
PTZ stop (release) |
/data/camera-driver/ptz/status?device=X |
Current PTZ position |
/data/camera-driver/devices |
List all devices |
/data/camera-driver/device/:name/status |
Device status |
/data/camera-driver/connection-browser |
Connection Browser UI |
/data/camera-driver/health |
Health check |
/data/camera-driver/metrics |
Per-camera resource metrics |
Version: 3.3.0 | Status: Production Ready
ignition-module-camera-driver/
├── build.gradle.kts # Root build configuration
├── settings.gradle.kts # Subprojects
├── common/ # Shared code
├── designer/ # Designer-side hook
├── gateway/ # Gateway-side implementation
│ └── src/main/java/com/gaskony/camera/gateway/
│ ├── CameraModuleHook.java # Module entry point
│ ├── device/
│ │ ├── CameraExtensionPoint.java # Unified "Camera" device type
│ │ ├── CameraDevice.java # Device lifecycle
│ │ ├── CameraConfig.java # Device configuration record
│ │ ├── LegacyCameraExtensionPoint.java # Hidden pre-v3.0.0 alias (back-compat)
│ │ ├── ONVIFPoller.java # ONVIF polling mechanism
│ │ ├── AddressSpaceBuilder.java # OPC-UA node builder
│ │ └── generic/ # URL-based stream/snapshot helpers
│ ├── onvif/ # ONVIF protocol layer
│ │ ├── ONVIFClient.java # SOAP client
│ │ ├── ONVIFAuth.java # WS-UsernameToken auth
│ │ └── ... # Data models
│ ├── servlet/
│ │ └── CameraRoutes.java # HTTP endpoints
│ ├── stream/
│ │ └── Go2RtcManager.java # RTSP-to-browser streaming (go2rtc + ffmpeg)
│ └── auth/
│ └── AuthenticationManager.java # HTTP auth
└── web-ui/ # Connection Browser React component + Perspective components
| Document | Description |
|---|---|
| CHANGELOG.md | Complete version history |
| docs/PROJECT_CHARTER.md | Purpose, definition of done, won't-do list |
| /modules/.claude/skills/ | Shared skills across all modules |
| docs/USAGE.md | HTTP endpoint usage guide |
| SECURITY.md | Security architecture |
| docs/TESTING.md | Testing guide |
| docs/CAMERA_COMPATIBILITY.md | Camera compatibility notes |
| docs/IMPLEMENTATION_STATUS.md | Feature tracking |
| CLAUDE.md | AI assistant context |
Read the "Critical Bugs to Avoid" section of CLAUDE.md — it documents hard-won lessons (display names showing as "?...?", resource bundle registration, properties file locations) that took significant time to debug.
- go2rtc: https://github.com/AlexxIT/go2rtc (bundled for RTSP-to-browser streaming)
- ONVIF Official: https://www.onvif.org/
- ONVIF Specifications: https://www.onvif.org/profiles/
./gradlew clean build # Build module
./gradlew test # Run tests
./gradlew clean build test # Build and test
# Output: build/CameraDriver-3.3.0.modl- Local Path:
/modules/ignition-module-camera-driver/ - Remote: https://github.com/Gaskony-Ignition/ignition-module-camera-driver.git
MIT License - see LICENSE for details.
Copyright (c) 2025 Nigel Gwork
Nigel Gwork - @nigelgwork
- GitHub: https://github.com/Gaskony-Ignition/ignition-module-camera-driver
- Issues: https://github.com/Gaskony-Ignition/ignition-module-camera-driver/issues
- Security Policy: See SECURITY.md



