Skip to content

Commit 6f54864

Browse files
committed
all changes made for load plate
1 parent 7f96c8f commit 6f54864

File tree

3 files changed

+134
-50
lines changed

3 files changed

+134
-50
lines changed

src/liconic_interface/inventory_handler.py

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ def add_plate(
125125
stack: int,
126126
slot: int,
127127
plate_id: Optional[str] = None,
128-
conveyor_child_resource: Optional[Resource] = None,
128+
plate_resource: Resource = None,
129+
current_liconic_resource: Resource = None,
129130
) -> None:
130131
"""
131132
Updates the liconic inventory file when a new plate is placed into the incubator.
@@ -146,29 +147,30 @@ def add_plate(
146147
# Validations if function is called directly
147148
if not self.is_valid_plate_type(plate_type): # SHOULD STILL WORK
148149
raise ValueError(f"Unsupported plate type: {plate_type}")
149-
if stack not in self.find_valid_stack(plate_type):
150+
if stack not in self.find_valid_stack(
151+
plate_type=plate_type,
152+
resource=current_liconic_resource
153+
):
150154
raise ValueError(f"Invalid stack {stack} for plate type {plate_type}")
151-
if not self.is_valid_stack_slot(stack, slot):
155+
if not self.is_valid_stack_slot(
156+
stack=stack,
157+
slot=slot,
158+
current_liconic_resource=current_liconic_resource
159+
):
152160
raise ValueError(
153161
f"Invalid stack/slot combination: stack {stack}, slot {slot}"
154162
)
155-
if self.is_location_occupied(stack, slot):
163+
if self.is_location_occupied(
164+
stack=stack,
165+
slot=slot,
166+
current_liconic_resource=current_liconic_resource,
167+
):
156168
raise Exception("Location already occupied")
157169

158-
# Add the plate to the inventory file
159-
self.inventory[stack][slot] = Slot(
160-
occupied=True,
161-
plate_id=plate_id,
162-
time_added=str(datetime.datetime.now()),
163-
)
164-
self.update_inventory_file()
165-
166-
# Handle Resource Client calls
167-
# Collect conveyor plate resource, if available.
168-
conveyor_child_resource = self.resource_client.query_resource(resource_name=f"{self.node_name}_conveyor.nest").child
169170
# Push plate resource onto stack/slot nest resource
170-
stack_slot_resource = self.resource_client.query_resource(resource_name=f"{self.node_name}_stack{stack}_slot{slot}.nest")
171-
self.resource_client.push(resource=stack_slot_resource, child=conveyor_child_resource)
171+
stack_resource = current_liconic_resource.children[str(stack)]
172+
slot_resource = stack_resource.children[str(slot)]
173+
self.resource_client.push(resource=slot_resource, child=plate_resource)
172174

173175

174176
def remove_plate(
@@ -216,22 +218,51 @@ def remove_plate(
216218
# Push plate resource onto conveyor resource
217219
self.resource_client.push(resource=conveyor_resource, child=stack_slot_resource_child)
218220

219-
def find_plate(self, plate_id: str) -> tuple[int, int]:
221+
def find_plate(
222+
self,
223+
plate_id: str,
224+
current_liconic_resource: Resource,
225+
) -> tuple[int, int]:
220226
"""
221227
Returns the stack and slot a plate is located on, given the plate id
222228
223229
Args:
224230
plate_type (str): name of the plate that matches existing plate definition
231+
current_liconic_resource (Resource): MADSci Resource object representing the current state of the incubator.
225232
226233
Returns:
227234
tuple[int, int]: Tuple with the (stack, slot) location of the plate with the specified plate_id
228235
"""
229-
for stack_key, stack in self.inventory.stacks.items():
230-
for slot_key, slot in stack.slots.items():
231-
if slot.plate_id == plate_id:
232-
return stack_key, slot_key
236+
# TODO: TEST THIS ONce A PLATE WITH A PLATE ID IS ADDED
237+
located_stack = None
238+
located_slot = None
239+
for stack in current_liconic_resource.children:
240+
stack_resource = current_liconic_resource.children[stack]
241+
for slot in stack_resource.children:
242+
slot_resource = stack_resource.children[slot]
243+
# TESTING
244+
print(stack)
245+
print(f"\t{slot}")
246+
if len(slot_resource.children) == 1:
247+
print("slot occupied")
248+
if slot_resource.children[0].attributes["liconic_plate_id"] == plate_id:
249+
print("MATCHING PLATE ID FOUND!")
250+
located_stack = int(stack)
251+
located_slot = int(slot)
252+
else:
253+
print("slot empty")
254+
if located_stack and located_slot:
255+
return (located_stack, located_slot)
233256
raise ValueError("Plate not found")
234257

258+
259+
260+
# for stack_key, stack in self.inventory.stacks.items():
261+
# for slot_key, slot in stack.slots.items():
262+
# if slot.plate_id == plate_id:
263+
# return stack_key, slot_key
264+
# raise ValueError("Plate not found")
265+
235266
def get_next_free_slot(
236267
self,
237268
plate_type: str,
@@ -291,19 +322,27 @@ def get_next_free_slot(
291322

292323
raise Exception(f"No valid stacks found for plate type '{plate_type}'")
293324

294-
def is_location_occupied(self, stack: int, slot: int) -> bool:
325+
def is_location_occupied(
326+
self,
327+
stack: int,
328+
slot: int,
329+
current_liconic_resource: Resource
330+
) -> bool:
295331
"""
296332
Given a stack and slot, determine if the location is occupied
297333
298334
Args:
299335
stack (int): stack number in the incubator
300336
slot (int): slot number in the stack
337+
current_liconic_resource: Resource object for the current incubator state
301338
302339
Returns:
303340
bool: True if location occupied, False otherwise
304341
305342
"""
306-
return self.inventory[int(stack)][int(slot)].occupied
343+
stack_resource = current_liconic_resource.children[str(stack)]
344+
slot_resource = stack_resource.children[str(slot)]
345+
return len(slot_resource.children) == 1
307346

308347
def get_plate_id(self, stack: int, slot: int) -> str:
309348
"""
@@ -363,7 +402,12 @@ def is_valid_plate_type(self, plate_type: str) -> bool:
363402
"""
364403
return plate_type in self.labware_definitions
365404

366-
def is_valid_stack_slot(self, stack: int, slot: int) -> bool:
405+
def is_valid_stack_slot(
406+
self,
407+
stack: int,
408+
slot: int,
409+
current_liconic_resource: Resource,
410+
) -> bool:
367411
"""
368412
Checks if the given stack and slot are valid for the current configuration
369413
@@ -375,10 +419,13 @@ def is_valid_stack_slot(self, stack: int, slot: int) -> bool:
375419
bool: True if stack/slot combination is valid for the current configuration, False otherwise
376420
"""
377421
valid = True
378-
if stack not in self.inventory.stacks:
422+
if str(stack) not in current_liconic_resource.children:
379423
valid = False
380-
if slot not in self.inventory[stack].slots:
424+
if str(slot) not in current_liconic_resource.children[str(stack)].children:
381425
valid = False
426+
427+
# TESTING
428+
print(f"STACK/SLOT COMBO IS VALID?: {valid}")
382429
return valid
383430

384431

src/liconic_interface/pydantic_models.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from pydantic import BaseModel, field_validator, model_validator
66

7+
from madsci.common.types.resource_types import Container
8+
79

810
class LoadPlateModel(BaseModel):
911
"""Model for loading a plate into the LiCONiC incubator"""
@@ -13,6 +15,7 @@ class LoadPlateModel(BaseModel):
1315
stack: Optional[int] = None
1416
slot: Optional[int] = None
1517
resource_tracker: Any = None
18+
current_liconic_resource: Container = None
1619

1720
def __init__(self, **data: Any) -> None:
1821
"""Initializes the load plate model"""
@@ -43,27 +46,42 @@ def validate_with_resource_tracker(self) -> "LoadPlateModel":
4346
if self.stack and self.slot:
4447
# 3a. Check that the stack is valid for the specified plate type
4548
valid_stacks = self.resource_tracker.find_valid_stack(
46-
plate_type=self.plate_type
49+
plate_type=self.plate_type,
50+
resource = self.current_liconic_resource,
4751
)
4852
if self.stack not in valid_stacks:
4953
raise ValueError(
5054
f"Stack {self.stack} not valid for plate type {self.plate_type}"
5155
)
5256
# 3b. Check that the stack/slot combination is valid
53-
if not self.resource_tracker.is_valid_stack_slot(self.stack, self.slot):
57+
if not self.resource_tracker.is_valid_stack_slot(
58+
self.stack,
59+
self.slot,
60+
self.current_liconic_resource,
61+
):
5462
raise ValueError(
5563
f"Invalid stack/slot combination: stack {self.stack}, slot {self.slot}"
5664
)
5765
# 3c. Check that the location is not already occupied
58-
if self.resource_tracker.is_location_occupied(self.stack, self.slot):
66+
print("CHECKING LOCATION OCCUPIED")
67+
if self.resource_tracker.is_location_occupied(
68+
self.stack,
69+
self.slot,
70+
self.current_liconic_resource
71+
):
5972
raise ValueError(
6073
f"Location stack {self.stack}, slot {self.slot} already occupied."
6174
)
6275

6376
# 4. Prevent duplicate plate ids
77+
print("PREVENTING DUPLICATE PLATE IDS")
78+
# TODO: TEST THIS WHEN A PLATE IS LOADED THAT HAS A PLATE ID
6479
if self.plate_id and str(self.plate_id).lower() != "none":
6580
try:
66-
self.resource_tracker.find_plate(self.plate_id)
81+
self.resource_tracker.find_plate(
82+
self.plate_id,
83+
self.current_liconic_resource,
84+
)
6785
except ValueError:
6886
# if we get a ValueError, then no existing plate with the same ID was found
6987
return self

src/liconic_rest_node.py

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ def init_resource_templates(self) -> None:
9595
tags=["PlateNest", "ANSI/SLAS"],
9696
)
9797

98+
# TODO: microplate resource template?
99+
100+
# TODO: deep well plate template?
101+
98102
def create_resources(self) -> None:
99103
"""Create resources for the node module."""
100104

@@ -421,24 +425,24 @@ def load_plate(
421425
) -> None:
422426
"""Load a plate into the incubator"""
423427

428+
# Collect current liconic container resource
429+
liconic_resource = self.resource_client.get_resource(self.liconic_container_resource_id)
430+
424431
# Validate arguments with pydantic model
425432
try:
426-
# TODO: convert pydantic model checks to use rezource handler, not inventory handler!
427433
LoadPlateModel(
428434
plate_type=plate_type,
429435
plate_id=plate_id,
430436
stack=stack,
431437
slot=slot,
438+
current_liconic_resource = liconic_resource,
432439
resource_tracker=self.inventory_handler,
433440
)
434441
except Exception as err:
435442
# Don't put device into error state if argument validation fails, just fail action
436443
self.logger.log_error(f"Error validating load_plate arguments: {err}")
437444
return ActionFailed(errors=str(err))
438445

439-
# Collect current liconic container resource
440-
liconic_resource = self.resource_client.get_resource(self.liconic_container_resource_id)
441-
442446
# Find next free slot if none specified
443447
if stack is None and slot is None:
444448
stack, slot = self.inventory_handler.get_next_free_slot(
@@ -451,30 +455,45 @@ def load_plate(
451455
self.logger.log_error(
452456
"Load_plate command cannot be completed, no plate in transfer station."
453457
)
454-
# return ActionFailed(
455-
# "Load_plate command cannot be completed, no plate in transfer station"
456-
# )
457-
else: # there is a plate in the transfer location
458+
return ActionFailed(
459+
"Load_plate command cannot be completed, no plate in transfer station"
460+
)
461+
# There is a plate in the transfer location. Collect the plate resource object.
462+
try:
458463
plate_resource = self.resource_client.get_resource(self.plate_carrier).child
464+
except Exception as e:
465+
self.logger.log_error("There is no plate resource on the conveyor belt to load.")
466+
return ActionFailed("There is no plate resource on the conveyor belt to load.")
467+
468+
# we can assume a plate resource will be there (plate_id might not be listed as an attribute yet)
469+
# TODO: add liconic_plate_id attribute to plate resource
470+
if plate_id:
471+
try:
472+
existing_liconic_plate_id = plate_resource.attributes["liconic_plate_id"]
473+
474+
# compare to entered plate id
475+
if plate_id != existing_liconic_plate_id:
476+
self.logger.log_warning(f"Existing liconic_plate_id attribute {existing_liconic_plate_id} does not match user entered plate id {plate_id}.")
477+
except Exception as e:
478+
self.logger.log_info("plate resource has no existing liconic_plate_id attribute.")
479+
# set the plate id attribute
480+
plate_resource.attributes["liconic_plate_id"] = plate_id
481+
self.resource_client.add_or_update_resource(plate_resource)
459482

460483
# # Load the plate
461484
# self.liconic_interface.load_plate(stack=stack, slot=slot)
462485
# while self.liconic_interface.is_busy:
463486
# time.sleep(1)
464487

465488
# Set plate resource as child to slot resource
466-
# TODO: START HERE TOMORROW - add this method to the inventory handler
467-
self.inventory_handler.load_plate_into_slot(
468-
plate_resource = plate_resource,
469-
stack = stack,
470-
slot = slot,
489+
self.inventory_handler.add_plate(
490+
plate_id=plate_id,
491+
stack=stack,
492+
slot=slot,
493+
plate_type=plate_type,
494+
plate_resource=plate_resource,
495+
current_liconic_resource = liconic_resource,
471496
)
472-
# self.inventory_handler.add_plate(
473-
# plate_id=plate_id,
474-
# stack=stack,
475-
# slot=slot,
476-
# plate_type=plate_type,
477-
# )
478497
self.logger.log_info(f"Plate loaded into LiCONiC stack {stack}, slot {slot}")
479498
return None
480499

0 commit comments

Comments
 (0)