99 lines
3.1 KiB
Python
Executable File
99 lines
3.1 KiB
Python
Executable File
#!/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()))
|