Skip to content

Commit 62108ee

Browse files
rickwierengaclaude
andcommitted
Port Hamilton Vantage to new Device/Driver/Backend architecture
Decomposes the legacy monolithic VantageBackend (~5,334 lines) into the same layered architecture established by the STAR port: - VantageDriver(HamiltonLiquidHandler) — USB I/O, firmware protocol, setup - VantagePIPBackend(PIPBackend) — independent channel operations - VantageHead96Backend(Head96Backend) — 96-head operations - IPGBackend(OrientableGripperArmBackend) — plate gripper - VantageXArm, VantageLoadingCover — auxiliary subsystems - VantageChatterboxDriver — mock driver for testing - Vantage(Device) — user-facing device wiring capabilities All firmware commands verified parameter-by-parameter against the legacy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 475dbab commit 62108ee

14 files changed

Lines changed: 2933 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .chatterbox import VantageChatterboxDriver
2+
from .driver import VantageDriver
3+
from .vantage import Vantage
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""VantageChatterboxDriver: mock driver that prints commands instead of sending to hardware."""
2+
3+
from __future__ import annotations
4+
5+
import logging
6+
from typing import Any, List, Optional
7+
8+
from .driver import VantageDriver
9+
10+
logger = logging.getLogger("pylabrobot")
11+
12+
13+
class VantageChatterboxDriver(VantageDriver):
14+
"""A VantageDriver that prints firmware commands instead of communicating with hardware.
15+
16+
Useful for testing, debugging, and development without a physical Vantage.
17+
"""
18+
19+
def __init__(self):
20+
super().__init__()
21+
22+
async def setup(
23+
self,
24+
skip_loading_cover: bool = False,
25+
skip_core96: bool = False,
26+
skip_ipg: bool = False,
27+
):
28+
# Skip USB and hardware discovery entirely.
29+
# Import backends here to avoid circular imports.
30+
from .head96_backend import VantageHead96Backend
31+
from .ipg import IPGBackend
32+
from .loading_cover import VantageLoadingCover
33+
from .pip_backend import VantagePIPBackend
34+
from .x_arm import VantageXArm
35+
36+
self.id_ = 0
37+
self._num_channels = 8
38+
39+
self.pip = VantagePIPBackend(self)
40+
self.head96 = VantageHead96Backend(self) if not skip_core96 else None
41+
self.ipg = IPGBackend(driver=self) if not skip_ipg else None
42+
if self.ipg is not None:
43+
self.ipg._parked = True
44+
self.x_arm = VantageXArm(driver=self)
45+
self.loading_cover = VantageLoadingCover(driver=self)
46+
47+
# Initialize subsystems.
48+
for sub in self._subsystems:
49+
await sub._on_setup()
50+
51+
async def stop(self):
52+
# Stop subsystems (no-ops for chatterbox, but follows the pattern).
53+
for sub in reversed(self._subsystems):
54+
await sub._on_stop()
55+
# Clear state (skip super().stop() since there is no USB to close).
56+
self._num_channels = None
57+
self._tth2tti.clear()
58+
self.head96 = None
59+
self.ipg = None
60+
self.x_arm = None
61+
self.loading_cover = None
62+
63+
async def send_command(
64+
self,
65+
module: str,
66+
command: str,
67+
auto_id: bool = True,
68+
tip_pattern: Optional[List[bool]] = None,
69+
write_timeout: Optional[int] = None,
70+
read_timeout: Optional[int] = None,
71+
wait: bool = True,
72+
fmt: Optional[Any] = None,
73+
**kwargs,
74+
):
75+
cmd, _ = self._assemble_command(
76+
module=module,
77+
command=command,
78+
tip_pattern=tip_pattern,
79+
auto_id=auto_id,
80+
**kwargs,
81+
)
82+
logger.info("Chatterbox: %s", cmd)
83+
return None
84+
85+
async def send_raw_command(
86+
self,
87+
command: str,
88+
write_timeout: Optional[int] = None,
89+
read_timeout: Optional[int] = None,
90+
wait: bool = True,
91+
) -> Optional[str]:
92+
logger.info("Chatterbox raw: %s", command)
93+
return None

0 commit comments

Comments
 (0)