Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

ONVIF Camera Control GUI

Python Platform Dependencies License

A lightweight desktop application for controlling ONVIF Profile S IP cameras — pan, tilt, zoom, focus, and wiper. Built with Python and tkinter; no third-party packages required.


Features

  • Camera presets — Store multiple cameras (name, IP, port, credentials, HTTP/HTTPS); one-click selection buttons on the main window
  • Virtual joystick — Drag in any direction to pan/tilt; distance from centre maps linearly to speed
  • Mouse wheel zoom — Scroll anywhere on the window to zoom in/out
  • Keyboard shortcuts — Arrow keys for pan/tilt, Z/X for zoom, W for wiper
  • PTZ presets — 3 saved-position slots per camera; one-click Goto and Set buttons recall or overwrite positions stored on the camera
  • Zoom — Continuous zoom in/out with hold-to-move, auto-stop on release
  • Focus — Manual near/far adjustment; Auto/Manual mode toggle
  • Wiper — Auxiliary command trigger
  • Camera Settings dialog — Speed sliders (PTZ, zoom, focus) and media profile selector in a dedicated dialog
  • HTTPS support — Optional TLS encryption via checkbox (requires Python with ssl)
  • Persistent connections — TCP/TLS connection reused across SOAP calls; no per-call handshake overhead
  • Auto-reconnect — Heartbeat detects dropped connections and reconnects automatically; status shows "Reconnecting…" in orange
  • Settings persistence — Camera presets saved to ~/.onvif_gui.ini
  • Service discovery — Detects correct service URLs automatically via GetCapabilities
  • WS-Security — PasswordDigest authentication (SOAP 1.2)
  • Threaded I/O — All camera calls run off the UI thread; GUI stays responsive
  • Dependency preflight — Checks Python version and tkinter at startup with install instructions if missing

Requirements

  • Python 3.8+ with tkinter included
    • Full installer from python.org (recommended)
    • Anaconda / Miniconda also works
    • The pythoncore minimal package does not include tkinter
  • No pip packages — standard library only
  • ssl module for HTTPS (included in all standard Python distributions)

Installation

git clone https://github.com/MRC757/onvif-camera-gui.git
cd onvif-camera-gui
python onvif_camera_gui.py

No virtual environment or pip install needed.


Usage

  1. Click Manage Cameras
  2. Click Add and fill in the camera's name, IP, port, username, password, and HTTP/HTTPS
  3. Click Save — a button labelled with the camera name appears on the main window
  4. Click the camera button to connect; it turns blue when connected
  5. Add as many cameras as needed and switch between them with a single click

Once connected

  • Click Camera Settings to adjust PTZ, zoom, and focus speeds, or to select a media profile
  • Drag the virtual joystick to pan/tilt — farther from centre = faster movement
  • Scroll the mouse wheel anywhere on the window to zoom in/out
  • Use the zoom, focus, and wiper controls as needed
  • Use Goto 1/2/3 to move the camera to a saved position; use Set 1/2/3 to overwrite that slot with the current position
  • If the camera reboots or the network drops, the status will show Reconnecting… and the app will reconnect automatically

Keyboard Shortcuts

Key Action
Tilt up
Tilt down
Pan left
Pan right
Z Zoom in
X Zoom out
W Activate wiper
Mouse wheel Zoom in / out
  • Diagonal movement works — hold + to pan-right and tilt-up simultaneously
  • Speed is scaled by the PTZ / Zoom speed sliders in Camera Settings
  • Shortcuts are suppressed while a text field has keyboard focus

Camera Setup

Create an ONVIF user

Most cameras maintain a separate ONVIF user list from the regular web interface accounts. Consult your camera's manual for the exact menu path — it is typically found under a section labelled ONVIF, Security, or Users.

  1. Open the camera web interface: http://<camera-ip>
  2. Navigate to the ONVIF user management section
  3. Create a user with Administrator or Operator role
  4. Use that username and password when adding the camera in Manage Cameras

Clock synchronisation

WS-Security authentication requires the PC clock and camera clock to agree within 5 minutes. If you see Sender not authorized errors after entering correct credentials, sync the clocks:

  • Camera: Enable NTP in the camera's date/time settings
  • Windows: Settings → Time & Language → Date & Time → Sync now

Troubleshooting

Error Cause Fix
Connection error Wrong IP/port, or camera unreachable Verify network connectivity and camera address
env:VersionMismatch Camera requires SOAP 1.2 but received SOAP 1.1 Already fixed in this version
ter:NotAuthorized Wrong credentials or clock skew > 5 min Check ONVIF user credentials; sync clocks
HTTP 404 on media/PTZ Camera uses non-default service URL Already fixed via GetCapabilities discovery
SSL handshake failed: plain HTTP HTTPS checked but camera endpoint is plain HTTP Uncheck Use HTTPS or change port to the camera's HTTPS port (often 443)
GUI doesn't open (silent exit) pythoncore package lacks tkinter Use full Python from python.org or Anaconda
[WARNING] ssl module not available Python built without OpenSSL Reinstall full Python; HTTPS checkbox will be disabled automatically
Status stays "Reconnecting…" Camera offline or credentials changed Verify camera is reachable; check credentials in Manage Cameras

Architecture

Module structure
onvif_camera_gui.py
│
├── _check_dependencies()       # Preflight: Python version, tkinter, ssl
│
├── CameraPreset                # Data class: name, ip, port, username, password, use_https
├── load_camera_presets()       # Read [camera_N] sections from ~/.onvif_gui.ini
├── save_camera_presets()       # Write [camera_N] sections, preserve other sections
│
├── ONVIFClient                 # Low-level SOAP 1.2 transport
│   ├── _create_security_header()    # WS-Security PasswordDigest
│   ├── _create_soap_envelope()      # SOAP 1.2 envelope builder
│   ├── _get_conn()                  # Return / create cached http.client connection
│   ├── _drop_conn()                 # Close and discard connection on error
│   ├── _send_request()              # POST over persistent connection; retry-once on drop
│   ├── close_connections()          # Close all persistent connections (on disconnect)
│   ├── get_device_information()     # Verify connectivity / heartbeat ping
│   ├── get_capabilities()           # Discover service URLs
│   ├── get_profiles()               # Media profile tokens
│   ├── get_video_sources()          # Video source tokens
│   ├── continuous_move()            # PTZ ContinuousMove
│   ├── stop()                       # PTZ Stop
│   ├── focus_continuous()           # Imaging Move
│   ├── focus_stop()                 # Imaging Stop
│   ├── set_focus_mode()             # Imaging SetImagingSettings
│   ├── send_auxiliary_command()     # PTZ SendAuxiliaryCommand (wiper)
│   ├── get_presets()                # PTZ GetPresets
│   ├── goto_preset()                # PTZ GotoPreset
│   └── set_preset()                 # PTZ SetPreset
│
├── ONVIFCameraController       # High-level connection & command logic
│   ├── connect()               # Auth + capability discovery + profile init
│   ├── disconnect()            # Stop movement + close_connections() + clean up
│   ├── move_continuous_full()  # Combined pan/tilt/zoom in one SOAP call
│   ├── goto_ptz_preset()       # Move camera to stored preset slot
│   ├── save_ptz_preset()       # Save current position to preset slot
│   └── get_presets()           # Fetch preset list from camera
│
├── CameraSettingsDialog        # Non-modal dialog: media profile selector + speed sliders
│   ├── show()                  # Open or raise existing dialog
│   ├── update_profiles()       # Called on connect — populate profile combo
│   └── clear_profiles()        # Called on disconnect — reset profile combo
│
├── ConnectionDialog            # Internal connection handler (used by presets)
│   ├── _load_settings()        # Read ~/.onvif_gui.ini on open
│   └── _save_settings()        # Write ~/.onvif_gui.ini on successful connect
│
├── CameraEditDialog            # Modal dialog: add / edit a single camera preset
├── CameraManagerDialog         # Non-modal dialog: list + Add/Edit/Delete/↑/↓
│
├── Joystick (tk.Canvas)        # Circular virtual joystick widget
│   ├── set_enabled()           # Blue handle = connected; grey = disconnected
│   ├── _on_press/drag/release  # Mouse event handlers
│   ├── _process()              # Maps (x,y) → normalised (pan, tilt) in [-1, 1]
│   └── _poll()                 # Fires on_move_cb every 80 ms while held
│
└── CameraControlGUI            # Main window + all UI panels
    ├── setup_cameras_frame()        # Camera preset button panel (top of window)
    ├── _rebuild_camera_buttons()    # Recreate tk.Button per preset; re-match active
    ├── _update_camera_button_styles() # Blue/sunken = active; default = inactive
    ├── _select_camera(preset)       # Thread: disconnect → connect to preset
    ├── _bind_keyboard()             # Attach KeyPress / KeyRelease / MouseWheel to root
    ├── _send_key_motion()           # Translate held-key set → single SOAP command
    ├── _on_mousewheel()             # Scroll-wheel zoom with debounced stop (200 ms)
    ├── _joystick_move()             # pan/tilt × speed → move_continuous_full() in a thread
    ├── _joystick_stop()             # Increments move-gen counter; sends stop + backup stop
    ├── _backup_stop()               # Safety-net stop 200 ms after release
    ├── _schedule_heartbeat()        # Ping camera every 10 s to detect drops
    ├── _heartbeat_tick()            # Runs GetDeviceInformation off UI thread
    ├── _on_connection_lost()        # Triggered by failed heartbeat; starts reconnect loop
    └── _reconnect_tick()            # Retry connect every 5 s until success

SOAP / HTTP Implementation Notes

Protocol details

This tool implements SOAP 1.2 (required by most modern ONVIF cameras):

Property Value
Envelope namespace http://www.w3.org/2003/05/soap-envelope
Content-Type application/soap+xml; charset=utf-8; action="<uri>"
Authentication WS-Security UsernameToken PasswordDigest
Service discovery GetCapabilities on device service
HTTP transport http.client.HTTPConnection / HTTPSConnection
Connection strategy Persistent (keep-alive); one connection per host:port, reused across calls
Retry policy Retry once on network/connection errors; drop and recreate socket

SOAP 1.1 (text/xml + SOAPAction header) will be rejected by cameras that require SOAP 1.2, returning env:VersionMismatch.

Why persistent connections matter for HTTPS: Each new TCP+TLS connection costs 2–4 round trips for the handshake. Persistent http.client connections pay this cost once; subsequent SOAP calls cost only 1 RTT, matching HTTP performance.


Contributing

Bug reports and pull requests are welcome. For significant changes please open an issue first to discuss the approach.

The project is intentionally kept as a single Python file with no external dependencies — please keep it that way.


License

This project is licensed under the MIT License.

About

Camera GUI that will control ONVIF compatible PTZ cameras.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages