10. Vendor-Specific Extensions

10.1 Logitech Extensions

Logitech-specific camera properties and control functions for PTZ, LED, and audio features. Available on supported Logitech camera models (C922, C930, C925, etc.).


LogitechProperty enum

10 vendor properties specific to Logitech cameras. Integer enum values mapped to hardware controls.

Properties:

Property

Value

Purpose

LED_Mode

0

LED indicator on/off/auto

Brightness_Enhancement

1

Automatic brightness adjustment

Face_Detection

2

Enable/disable face detection

Low_Light_Compensation

3

Low-light mode

Automatic_Exposure

4

Auto-exposure control

Automatic_Whitebalance

5

Auto white balance control

Automatic_Lowlight_Compensation

6

Auto low-light adjustment

Pan_Speed

7

PTZ pan movement speed

Tilt_Speed

8

PTZ tilt movement speed

Zoom_Speed

9

PTZ zoom movement speed

Usage:

import duvc_ctl as duvc

device = duvc.devices()[^0]
result = duvc.get_logitech_property(device, duvc.LogitechProperty.LED_Mode)
if result.is_ok():
    print(f"LED Mode: {result.value()}")

get_logitech_property() - read Logitech property

Get Logitech-specific property value. Returns Result type with error on unsupported models.

def get_logitech_property(
    device: Device,
    prop: LogitechProperty
) -> Okuint32 | Erruint32:
    """
    Get Logitech property value.
    
    Returns:
        Okuint32 with value, or Erruint32 if unsupported/failed
    """

Usage:

import duvc_ctl as duvc

device = duvc.devices()[^0]

# Read LED mode
result = duvc.get_logitech_property(device, duvc.LogitechProperty.LED_Mode)
if result.is_ok():
    print(f"LED: {result.value()}")
else:
    print(f"Error: {result.error().description()}")

set_logitech_property() - write Logitech property

Set Logitech-specific property value. Range varies by property.

def set_logitech_property(
    device: Device,
    prop: LogitechProperty,
    value: int
) -> Okvoid | Errvoid:
    """
    Set Logitech property value.
    
    Returns:
        Okvoid on success, Errvoid on error
    """

Usage:

import duvc_ctl as duvc

device = duvc.devices()[^0]

# Set LED to auto (value = 2)
result = duvc.set_logitech_property(
    device,
    duvc.LogitechProperty.LED_Mode,
    2
)
if result.is_ok():
    print("LED set to auto")
else:
    print(f"Failed: {result.error().description()}")

# Set pan speed (0-100)
duvc.set_logitech_property(device, duvc.LogitechProperty.Pan_Speed, 75)

supports_logitech_properties() - capability check

Query if device supports Logitech extensions. Some models lack vendor features.

def supports_logitech_properties(device: Device) -> bool:
    """Check if device supports Logitech extensions."""

Usage:

import duvc_ctl as duvc

device = duvc.devices()[^0]

if duvc.supports_logitech_properties(device):
    print(f"{device.name} supports Logitech extensions")
    
    # Safe to use Logitech-specific functions
    result = duvc.get_logitech_property(
        device,
        duvc.LogitechProperty.LED_Mode
    )
else:
    print(f"{device.name} does not support Logitech extensions")

Capability detection:

import duvc_ctl as duvc

for device in duvc.devices():
    has_logitech = duvc.supports_logitech_properties(device)
    marker = "✓" if has_logitech else "✗"
    print(f"{marker} {device.name}")

10.2 GUID & Vendor Properties

Low-level GUID handling for Windows COM interfaces and generic vendor property access. All GUID functions are Windows-only.


GUID wrapper type (Windows-only)

GUID object encapsulates Windows GUID structure. Immutable wrapper for interface identification and vendor-specific control.

class GUID:
    """Windows GUID wrapper (128-bit UUID)."""
    def __str__(self) -> str: ...
    def __bytes__(self) -> bytes: ...

guid_from_uuid() - UUID to GUID conversion (Windows-only)

Convert Python UUID to Windows GUID format.

def guid_from_uuid(uuid_obj: uuid.UUID) -> GUID:
    """Convert Python UUID to GUID."""

Usage:

import duvc_ctl as duvc
import uuid

standard_uuid = uuid.UUID("6994ad05-93f7-406d-611d-4222d440c8a0")
guid = duvc.guid_from_uuid(standard_uuid)
print(guid)

guid_from_pyobj() - flexible GUID creation (Windows-only)

Create GUID from string, bytes, or UUID object.

def guid_from_pyobj(obj: str | bytes | uuid.UUID) -> GUID:
    """Create GUID from string, bytes, or UUID."""

String formats:

  • {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} (with braces)

  • XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX (no braces)

  • XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (hex only)

Usage:

import duvc_ctl as duvc

# From string
guid1 = duvc.guid_from_pyobj("{6994ad05-93f7-406d-611d-4222d440c8a0}")

# From bytes (16 bytes, little-endian GUID)
guid2 = duvc.guid_from_pyobj(b'\x05\xad\x94\x69\xf7\x93\x6d\x40...')

# From UUID
import uuid
guid3 = duvc.guid_from_pyobj(uuid.UUID("6994ad05-93f7-406d-611d-4222d440c8a0"))

VendorProperty type

Structured vendor property descriptor with metadata. Contains GUID, property ID, and access mode.

class VendorProperty:
    """Vendor-specific property descriptor."""
    vendor_guid: GUID
    property_id: int
    access_mode: str  # "read", "write", "read_write"
    data_type: str    # "int", "bytes", "string"

DeviceConnection type

Device connection context for vendor property operations. Wraps open camera handle.

class DeviceConnection:
    """Device connection for vendor operations."""
    device: Device
    is_open: bool
    error: str | None

read_vendor_property() - get vendor property (Windows-only)

Read vendor-specific property using GUID and property ID.

def read_vendor_property(
    device: Device,
    vendor_guid: GUID,
    property_id: int,
    data_type: str = "int"
) -> Result[bytes | int]:
    """Read vendor property."""

Data types: "int" → int, "bytes" → bytes, "string" → str

Usage:

import duvc_ctl as duvc

device = duvc.devices()[0]
guid = duvc.guid_from_pyobj("{6994ad05-93f7-406d-611d-4222d440c8a0}")

result = duvc.read_vendor_property(device, guid, prop_id=0, data_type="int")
if result.is_ok():
    value = result.value()
    print(f"Property value: {value}")
else:
    print(f"Error: {result.error().description()}")

write_vendor_property() - set vendor property (Windows-only)

Write vendor-specific property value.

def write_vendor_property(
    device: Device,
    vendor_guid: GUID,
    property_id: int,
    value: bytes | int | str,
    data_type: str = "int"
) -> Result[None]:
    """Write vendor property."""

Usage:

import duvc_ctl as duvc

device = duvc.devices()[0]
guid = duvc.guid_from_pyobj("{6994ad05-93f7-406d-611d-4222d440c8a0}")

result = duvc.write_vendor_property(
    device, guid, prop_id=0, value=100, data_type="int"
)
if result.is_ok():
    print("Property set successfully")
else:
    print(f"Error: {result.error().description()}")

get_vendor_property() - wrapper with error handling

High-level wrapper for read_vendor_property(). Raises exception on error.

def get_vendor_property(
    device: Device,
    vendor_guid: GUID,
    property_id: int,
    data_type: str = "int"
) -> bytes | int | str:
    """Read vendor property (exception on error)."""

Usage:

import duvc_ctl as duvc

device = duvc.devices()[0]
guid = duvc.guid_from_pyobj("{6994ad05-93f7-406d-611d-4222d440c8a0}")

try:
    value = duvc.get_vendor_property(device, guid, 0, "int")
    print(f"Property: {value}")
except duvc.DuvcError as e:
    print(f"Error: {e}")

set_vendor_property() - wrapper with error handling

High-level wrapper for write_vendor_property(). Raises exception on error.

def set_vendor_property(
    device: Device,
    vendor_guid: GUID,
    property_id: int,
    value: bytes | int | str,
    data_type: str = "int"
) -> None:
    """Write vendor property (exception on error)."""

Usage:

import duvc_ctl as duvc

device = duvc.devices()[0]
guid = duvc.guid_from_pyobj("{6994ad05-93f7-406d-611d-4222d440c8a0}")

try:
    duvc.set_vendor_property(device, guid, 0, 100, "int")
    print("Property set")
except duvc.DuvcError as e:
    print(f"Error: {e}")

10.3 Abstract Interfaces for Extension

Plugin-style extensibility through abstract interface implementation. Python subclasses can override C++ interface methods using pybind11 trampolines.


IPlatformInterface abstract (Windows-only)

Abstract base for platform-specific operations. Defines hooks for device enumeration, property access, and system queries. Not used directly; subclass via Python or use built-in implementation.

class IPlatformInterface:
    """Platform implementation contract."""
    def enumerate_devices(self) -> list[Device]: ...
    def query_device_capabilities(self, device: Device) -> DeviceCapabilities: ...
    def get_property(self, device: Device, prop_id: int) -> int: ...
    def set_property(self, device: Device, prop_id: int, value: int) -> bool: ...

IDeviceConnection abstract (Windows-only)

Abstract connection context for vendor operations. Manages device lifecycle and low-level I/O. Override for custom transport layers or simulation.

class IDeviceConnection:
    """Device connection contract."""
    def open(self, device: Device) -> bool: ...
    def close(self) -> None: ...
    def is_connected(self) -> bool: ...
    def read_property(self, prop_id: int) -> bytes: ...
    def write_property(self, prop_id: int, data: bytes) -> bool: ...

PyIPlatformInterface - Python subclassing trampoline

Pybind11 trampoline enabling Python classes to override interface methods. Automatic GIL management for C++→Python calls.

Usage:

import duvc_ctl as duvc

class CustomPlatform(duvc.IPlatformInterface):
    def enumerate_devices(self):
        print("Custom enumeration")
        devices = []
        # Custom device discovery logic
        return devices
    
    def query_device_capabilities(self, device):
        # Custom capability query
        return duvc.DeviceCapabilities()
    
    def get_property(self, device, prop_id):
        # Custom property read
        return 0
    
    def set_property(self, device, prop_id, value):
        # Custom property write
        return True

# Register with library
platform = CustomPlatform()
duvc.create_platform_interface(platform)

PyIDeviceConnection - Python subclassing trampoline

Trampoline for device connection override. Custom implementations handle non-standard transports or mock devices.

Usage:

import duvc_ctl as duvc

class MockConnection(duvc.IDeviceConnection):
    def open(self, device):
        print(f"Mock open: {device.name}")
        return True
    
    def close(self):
        print("Mock close")
    
    def is_connected(self):
        return True
    
    def read_property(self, prop_id):
        # Return mock data
        return b'\x00\x01\x02\x03'
    
    def write_property(self, prop_id, data):
        print(f"Mock write: {len(data)} bytes")
        return True

conn = MockConnection()
# Pass to device operations

PYBIND11_OVERRIDE_PURE macro pattern

Macros used internally to forward Python method calls to C++. Not directly called by users, but understanding helps with debugging.

Invocation (internal):

// C++ side (pybind11 binding code)
class PyIPlatformInterface : public IPlatformInterface {
    std::vector<Device> enumerate_devices() override {
        PYBIND11_OVERRIDE_PURE(
            std::vector<Device>,
            IPlatformInterface,
            enumerate_devices
        );
    }
};

When Python calls enumerate_devices(), pybind11 routes it through this macro, acquiring GIL and calling the Python implementation.


create_platform_interface() - factory registration

Register custom platform implementation. Library uses it for all subsequent operations.

def create_platform_interface(impl: IPlatformInterface) -> None:
    """Register custom platform implementation."""

Usage:

import duvc_ctl as duvc

class CustomPlatform(duvc.IPlatformInterface):
    # ... implementation ...
    pass

duvc.create_platform_interface(CustomPlatform())

# All library operations now use custom implementation
devices = duvc.devices()  # Uses CustomPlatform.enumerate_devices()

Python custom implementation patterns

Pattern 1: Simulation/Testing

import duvc_ctl as duvc

class SimulatedPlatform(duvc.IPlatformInterface):
    def __init__(self):
        self.device_list = []
    
    def enumerate_devices(self):
        # Return mock devices
        return self.device_list
    
    def query_device_capabilities(self, device):
        caps = duvc.DeviceCapabilities()
        caps.supported_camera_properties = [duvc.CamProp.Pan, duvc.CamProp.Tilt]
        return caps
    
    def get_property(self, device, prop_id):
        return 50  # Mock value
    
    def set_property(self, device, prop_id, value):
        return True  # Always succeed

# Use simulated environment
duvc.create_platform_interface(SimulatedPlatform())

Pattern 2: Logging/Debugging wrapper

import duvc_ctl as duvc

class LoggingPlatform(duvc.IPlatformInterface):
    def __init__(self, real_impl):
        self.real = real_impl
    
    def enumerate_devices(self):
        print("TRACE: enumerate_devices called")
        result = self.real.enumerate_devices()
        print(f"TRACE: found {len(result)} devices")
        return result
    
    def get_property(self, device, prop_id):
        print(f"TRACE: get_property device={device.name} prop={prop_id}")
        value = self.real.get_property(device, prop_id)
        print(f"TRACE: result={value}")
        return value
    
    def set_property(self, device, prop_id, value):
        print(f"TRACE: set_property device={device.name} prop={prop_id} value={value}")
        success = self.real.set_property(device, prop_id, value)
        print(f"TRACE: success={success}")
        return success

Pattern 3: Custom device transport

import duvc_ctl as duvc

class NetworkConnection(duvc.IDeviceConnection):
    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.socket = None
    
    def open(self, device):
        # Connect to remote device
        import socket
        self.socket = socket.socket()
        self.socket.connect((self.host, self.port))
        return True
    
    def is_connected(self):
        return self.socket is not None
    
    def read_property(self, prop_id):
        # Send read request over network
        self.socket.send(f"READ {prop_id}".encode())
        return self.socket.recv(256)
    
    def write_property(self, prop_id, data):
        # Send write request over network
        self.socket.send(f"WRITE {prop_id} {len(data)}".encode())
        self.socket.send(data)
        return True
    
    def close(self):
        if self.socket:
            self.socket.close()

Important Notes:

  • Custom implementations must implement all abstract methods

  • GIL is held during Python method calls; avoid blocking

  • Exceptions in Python methods are converted to C++ exceptions

  • Return types must match interface contract exactly