"""Logitech Rally Camera V-R0010 + Dante room mic (speaker identity).""" from __future__ import annotations import logging import re from pathlib import Path from discovery import Camera from usb_ids import ( RALLY_IDENTITY, is_rally_pid, looks_like_rally_name, usb_vid_pid_of_video_node, ) log = logging.getLogger("cameras.rally") _CARD_RE = re.compile(r"\s*(\d+)\s+\[([A-Za-z0-9_]+)\s*\]:\s*(.*)") def dante_capture_card() -> int | None: """ALSA card index for the Audinate Dante USB I/O module, if present.""" path = Path("/proc/asound/cards") if not path.is_file(): return None for line in path.read_text().splitlines(): m = _CARD_RE.match(line) if not m: continue rest = (m.group(3) or "").lower() name = (m.group(2) or "").lower() if "dante" in rest or "dante" in name: return int(m.group(1)) return None def rally_video_node() -> str | None: pinned = Path("/dev/rally") if pinned.exists(): return str(pinned) sysfs = Path("/sys/class/video4linux") if not sysfs.is_dir(): return None for p in sorted(sysfs.glob("video*"), key=lambda x: int(x.name[5:] or 0)): name = (p / "name").read_text().strip() if (p / "name").is_file() else "" idx = (p / "index").read_text().strip() if (p / "index").is_file() else "" node = f"/dev/{p.name}" if idx != "0": continue if looks_like_rally_name(name): return node ids = usb_vid_pid_of_video_node(node) if ids is not None and is_rally_pid(ids[1]): return node return None def _serial_of_video(node: str) -> str: link = Path(f"/sys/class/video4linux/{Path(node).name}/device") try: p = link.resolve() except OSError: return "" for _ in range(12): cand = p / "serial" if cand.is_file(): return cand.read_text().strip() if str(p).startswith("/sys/devices"): p = p.parent else: break return "" def discover_rally(*, width: int = 1920, height: int = 1080) -> Camera | None: """Speaker camera: Rally UVC + Dante capture. None if the head is missing.""" video = rally_video_node() if not video: return None card = dante_capture_card() serial = _serial_of_video(video) cam = Camera( index=20, identity=RALLY_IDENTITY, video_device=video, audio_card=card, serial=serial, width=width, height=height, ) log.info( "rally video=%s audio=%s serial=%s %dx%d", video, f"hw:{card},0" if card is not None else "none", serial or "-", width, height, ) return cam