Sample storage#
The HighRes Biosolutions AmbiStore, SteriStore, and TundraStore use the same TCP command protocol and share a PyLabRobot interface. Choose the concrete class for the model you are configuring; the product name reported by firmware is retained as version information and does not override that choice.
Models#
Model |
Environment |
Verification |
|---|---|---|
Ambient storage |
Work in progress; not hardware-verified |
|
Heating, active cooling, humidity, CO2, and optional O2 |
Hardware-verified |
|
Refrigeration and temperature-dependent humidity control |
Work in progress; not hardware-verified |
The published temperature ranges and environmental options come from the current HighRes sample-storage page and an archived HighRes sample-storage brochure.
Network connection#
The remote-control server listens on TCP port 1000. The normal factory address is
192.168.127.60; HighRes devices also expose the service at 10.253.253.253. Give the dedicated
host Ethernet interface an address on both subnets so either address remains reachable:
sudo ip address replace 192.168.127.50/24 dev <interface>
sudo ip address replace 10.253.253.250/24 dev <interface>
These ip address changes are temporary and disappear when the USB Ethernet adapter is unplugged
or the host restarts. On Linux systems managed by NetworkManager, create a persistent connection
profile instead:
sudo nmcli connection add \
type ethernet \
ifname <interface> \
con-name highres-sample-storage \
ipv4.method manual \
ipv4.addresses "192.168.127.50/24,10.253.253.250/24" \
ipv4.never-default yes \
ipv6.method disabled
sudo nmcli connection up highres-sample-storage
Use ip -brief link to find <interface>. Verify the isolated link with
ping 10.253.253.253; do not add a gateway or default route to this connection.
Setup#
Pass the storage racks as a mapping from one-based physical stacker numbers to their PLR resources, then instantiate the appropriate model. Stacker numbers may be sparse. For example:
from pylabrobot.high_res.sample_storage import SteriStore
racks = {1: rack_1, 3: rack_3}
store = SteriStore(host="192.168.127.60", name="steristore", racks=racks)
await store.setup()
Each mapping key is the rack’s one-based device stacker number. Within a rack, the zero-based
carrier spot maps to the one-based physical slot: spot 0 is device slot 1, spot 1 is slot 2, and
so on. Mapping insertion order does not affect this relationship.
Alternatively, omit racks to select device-discovery mode. During setup(), the driver reads the
configured zero offset, slot height, and slot count with the read-only getstackerdimensions
command and creates empty stacker resources. Passing racks={} explicitly represents a store with
no configured racks and does not enable discovery.
During setup, the device-reported transfer nests become store.nests. Their locations relative to
the store are left undefined because they depend on the surrounding robot installation. Setup does
not invent plate resources for occupied nests; assign any already-present plates to the matching
nest after setup.
Plate transfers#
Fetch a known plate from its stacker slot to a transfer nest:
plate = await store.fetch_plate_to_loading_tray("plate_1", tray_index=0)
Move the plate on a transfer nest back into storage, choosing either a specific PlateHolder, the
smallest available site, or a random available site:
await store.take_in_plate(tray_index=0, site="smallest")
Both operations update the PLR resource tree only after successful hardware motion. See Sample-storage events for their structured execution events.
Move a plate directly between two transfer nests using their zero-based tray indices:
plate = await store.transfer_plate_between_nests(
source_tray_index=1,
destination_tray_index=0,
)
The driver verifies every transfer-nest endpoint with its live presence sensor before motion. The
stacker itself has no non-destructive per-slot presence query: fetching detects an empty source only
during the pick, while storing relies on the PLR resource tree to determine that the destination
slot is free. Keep the modeled stacker inventory synchronized with the physical store; barcode
EMPTY is not a plate-presence result.
Barcode scans#
Barcode scans require every transfer nest to be clear. The driver checks this before starting the scan because firmware 3.0.0.119 otherwise waits for an automation door and eventually times out.
barcodes = await store.request_stacker_barcodes(2)
barcode = await store.request_stacker_barcodes(2, slot=1)
A returned value of EMPTY means that the scanner did not read a barcode. It does not prove the
physical slot is empty; use resource bookkeeping or a physical plate-presence workflow for that.
Recovery#
request_is_parked() verifies that the device is homed and that both the spatula slide and lift
axes are retracted. recover() refuses to move when the spatula sensor reports a plate, because
the plate’s physical support must be inspected before a safe recovery path can be chosen.
If a transfer response is interrupted or otherwise ambiguous, the driver keeps the last confirmed
resource assignment in place, records store.unresolved_transfer, and blocks further plate moves,
homing, and barcode scans. Inspect the machine, recover it when necessary, then reconcile the
observed result:
await store.resolve_unresolved_transfer("source")
Use "destination" when the plate completed the move or "unassigned" when it is at neither
modeled endpoint. If the incomplete response invalidated the TCP stream, reconciliation reconnects
automatically before reading the spatula and nest sensors.