Files
shaggy-solar/LVX6048/bin/lvx-resolve-links

99 lines
3.1 KiB
Plaintext
Raw Normal View History

2026-04-24 16:34:10 -04:00
#!/usr/bin/env python3
"""lvx-resolve-links — create /dev/lvx6048-N symlinks keyed to PI18 serial.
Globs /dev/hidraw*, sends PI18 `ID` to each, and creates
/dev/lvx6048-1 -> /dev/hidrawX where X's serial matches SERIAL_UNIT_1
/dev/lvx6048-2 -> /dev/hidrawX where X's serial matches SERIAL_UNIT_2
Must run as root. Intended as a systemd oneshot before powermon*.service.
Runs a single discovery pass with exclusive access — unlike powermon's native
resolve_path which each service performs independently at startup, causing
collisions when a sibling service is already holding the target hidraw.
Edit SERIAL_UNIT_1 / SERIAL_UNIT_2 when a unit is replaced.
"""
from __future__ import annotations
import asyncio
import glob
import os
import sys
SERIAL_UNIT_1 = "1496142109100037000000"
SERIAL_UNIT_2 = "1496142408100255000000"
LINK_FOR_SERIAL = {
SERIAL_UNIT_1: "/dev/lvx6048-1",
SERIAL_UNIT_2: "/dev/lvx6048-2",
}
sys.path.insert(0, "/home/noise/.local/share/uv/tools/powermon/lib/python3.11/site-packages")
from powermon.protocols import get_protocol_definition # noqa: E402
from powermon.ports.usbport import USBPort # noqa: E402
async def probe_serial(path: str) -> str | None:
proto = get_protocol_definition(protocol="PI18")
port = USBPort(path=path, protocol=proto)
port.path = path
try:
await port.connect()
if not port.is_connected():
return None
cmd = proto.get_id_command()
res = await port.send_and_receive(command=cmd)
await port.disconnect()
except Exception as e:
print(f" probe {path}: {e.__class__.__name__}: {e}", file=sys.stderr)
return None
if res is None or not getattr(res, "is_valid", False) or not res.readings:
return None
return str(res.readings[0].data_value)
def _relink(link: str, target: str) -> None:
target_basename = os.path.basename(target)
try:
if os.path.islink(link) or os.path.exists(link):
os.unlink(link)
except FileNotFoundError:
pass
os.symlink(target_basename, link)
async def main() -> int:
candidates = sorted(glob.glob("/dev/hidraw*"))
if not candidates:
print("no /dev/hidraw* devices present", file=sys.stderr)
return 1
sn_to_path: dict[str, str] = {}
for p in candidates:
sn = await probe_serial(p)
if sn:
print(f"{p}: serial {sn}")
sn_to_path[sn] = p
else:
print(f"{p}: no PI18 response (probably not an LVX6048)")
missing = []
for sn, link in LINK_FOR_SERIAL.items():
if sn in sn_to_path:
_relink(link, sn_to_path[sn])
print(f"symlink {link} -> {os.path.basename(sn_to_path[sn])}")
else:
missing.append((link, sn))
try:
if os.path.islink(link):
os.unlink(link)
except FileNotFoundError:
pass
print(f"WARNING: {link} serial {sn} not found on any /dev/hidraw*")
return 0 if not missing else 2
if __name__ == "__main__":
sys.exit(asyncio.run(main()))