Source code for pyGigEVision.genicam

"""GenICam XML descriptor download helper.

Every GigE Vision camera stores its register description XML in on-board
memory.  The location URL is at bootstrap register 0x0200
(``REG_FIRST_URL``) and follows the format::

    Local:cameralib.xml;0xADDR;0xSIZE

This module reads the URL, downloads the bytes, and decompresses the
descriptor if the camera stores it as a ZIP archive (common for larger
XML files).

Notes
-----
Parsing the XML itself is left to the vendor driver.  Register naming
conventions differ enough across vendors that a generic parser would add
more abstraction than value.  Vendor drivers receive the raw XML bytes
from :func:`fetch_genicam_xml` and interpret them using their own
register maps.
"""

from __future__ import annotations

import io
import logging
import zipfile

from .standard import REG_FIRST_URL

logger = logging.getLogger(__name__)


[docs] def parse_first_url(url_bytes: bytes) -> tuple[str, int, int]: """Parse the bytes read from REG_FIRST_URL into (filename, addr, size). Parameters ---------- url_bytes : bytes Raw bytes from ``client.read_mem(REG_FIRST_URL, 512)``. The string is null-terminated; everything after the first null byte is ignored. Non-ASCII trailing bytes are replaced with the Unicode replacement character and then discarded by the null split. Returns ------- tuple of (str, int, int) ``(filename, addr, size)`` where *filename* is the XML or ZIP entry name (e.g. ``"cameralib.xml"``), *addr* is the start address of the descriptor in camera memory, and *size* is the byte length. Numeric fields accept both ``0x``-prefixed hex (``0x10000``) and bare hex without a prefix (``ff000``, emitted by some cameras), as well as plain decimal integers. Raises ------ ValueError If the URL string cannot be split into at least three semicolon-separated parts (malformed descriptor). Examples -------- >>> url = b"Local:cameralib.xml;0x10000;0x4000\\x00" >>> filename, addr, size = parse_first_url(url) >>> filename 'cameralib.xml' >>> hex(addr) '0x10000' >>> size 16384 """ def _parse_int(field: str) -> int: field = field.strip() try: return int(field, 0) # 0x-prefixed hex or decimal except ValueError: return int(field, 16) # bare hex without a prefix, e.g. "ff000" url = url_bytes.split(b"\x00", 1)[0].decode("ascii", errors="replace") parts = url.split(";") if len(parts) < 3: raise ValueError(f"Malformed FIRST_URL: {url!r}") filename = parts[0].split(":")[-1] addr = _parse_int(parts[1]) size = _parse_int(parts[2]) return filename, addr, size
[docs] def fetch_genicam_xml(client: object) -> tuple[bytes, str]: """Download the GenICam XML descriptor from a connected camera. Reads the ``REG_FIRST_URL`` register to locate the descriptor, fetches the raw bytes, and decompresses them if the camera stored the XML as a ZIP archive. Parameters ---------- client : ~pyGigEVision.gvcp.GVCPClient An open :class:`~pyGigEVision.gvcp.GVCPClient` with control privilege. Must expose a ``read_mem(addr, size)`` method that returns ``bytes``. Returns ------- tuple of (bytes, str) ``(xml_bytes, filename)`` where *xml_bytes* is the raw (and, if necessary, decompressed) GenICam XML, and *filename* is the name of the XML entry (from the zip archive if the descriptor was compressed, otherwise the filename from ``REG_FIRST_URL``). Examples -------- Typical usage with a connected client:: from pyGigEVision import GVCPClient from pyGigEVision.genicam import fetch_genicam_xml with GVCPClient("169.254.1.10") as client: xml_bytes, filename = fetch_genicam_xml(client) print(f"Received {len(xml_bytes)} bytes from '{filename}'") """ url_bytes = client.read_mem(REG_FIRST_URL, 512) filename, addr, size = parse_first_url(url_bytes) logger.info("Fetching GenICam descriptor: %s (addr=0x%X, %d bytes)", filename, addr, size) data = client.read_mem(addr, size) if filename.lower().endswith(".zip"): with zipfile.ZipFile(io.BytesIO(data)) as zf: xml_name = next(n for n in zf.namelist() if n.lower().endswith(".xml")) return zf.read(xml_name), xml_name return data, filename