mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-05-25 13:29:59 +00:00
fix(bioyond): port shared peptide station infrastructure to sirna
- update Bioyond reset RPCs and add take_out helper - use scheduler status for Bioyond connection health checks - align debug call log defaults - add optional warehouse y-flip support - register Bioyond deck resources for PLR deserialization
This commit is contained in:
@@ -415,21 +415,24 @@ class BioyondV1RPC(BaseRequest):
|
||||
return {}
|
||||
return response.get("data", {})
|
||||
|
||||
def reset_location(self, location_id: str) -> int:
|
||||
def reset_location(self, location_id: Optional[str] = None) -> int:
|
||||
"""复位库位
|
||||
|
||||
现场实测 ``POST /api/lims/storage/reset-location`` 不传 ``data`` 即可成功
|
||||
因此默认无 ``data`` 字段;保留 ``location_id`` 仅为兼容旧调用,传入会被忽略。
|
||||
|
||||
参数:
|
||||
location_id: 库位ID
|
||||
location_id: 兼容入参,已被忽略;新逻辑不再以 location 为粒度复位。
|
||||
|
||||
返回值:
|
||||
int: 成功返回1,失败返回0
|
||||
"""
|
||||
del location_id
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/storage/reset-location',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": location_id,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return 0
|
||||
@@ -779,6 +782,49 @@ class BioyondV1RPC(BaseRequest):
|
||||
|
||||
return response.get("data", {})
|
||||
|
||||
def take_out(
|
||||
self,
|
||||
order_id: str,
|
||||
preintake_ids: list[str] | None = None,
|
||||
material_ids: list[str] | None = None,
|
||||
) -> dict:
|
||||
"""取出订单关联通量/物料
|
||||
|
||||
参数:
|
||||
order_id: 订单ID
|
||||
preintake_ids: 通量ID列表,可为空
|
||||
material_ids: 物料ID列表,可为空
|
||||
|
||||
返回值:
|
||||
dict: 服务端响应包,失败返回空字典
|
||||
"""
|
||||
if not order_id:
|
||||
self._logger.error("取出订单关联通量/物料错误: 缺少订单ID")
|
||||
return {}
|
||||
|
||||
params = {
|
||||
"orderId": order_id,
|
||||
"preintakeIds": list(preintake_ids or []),
|
||||
"materialIds": list(material_ids or []),
|
||||
}
|
||||
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/order/take-out',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": params,
|
||||
})
|
||||
|
||||
if not response:
|
||||
return {}
|
||||
|
||||
if response['code'] != 1:
|
||||
self._logger.error(f"取出订单关联通量/物料错误: {response.get('message', '')}")
|
||||
return response
|
||||
|
||||
return response
|
||||
|
||||
def cancel_order(self, json_str: str) -> bool:
|
||||
"""取消指定任务
|
||||
|
||||
@@ -886,21 +932,24 @@ class BioyondV1RPC(BaseRequest):
|
||||
return {}
|
||||
return response.get("data", {})
|
||||
|
||||
def reset_order_status(self, order_id: str) -> int:
|
||||
def reset_order_status(self, order_id: Optional[str] = None) -> int:
|
||||
"""复位订单状态
|
||||
|
||||
现场实测 ``POST /api/lims/order/reset-order-status`` 不传 ``data`` 即可成功
|
||||
因此默认无 ``data`` 字段;保留 ``order_id`` 仅为兼容旧调用,传入会被忽略。
|
||||
|
||||
参数:
|
||||
order_id: 订单ID
|
||||
order_id: 兼容入参,已被忽略;新逻辑不再以单订单为粒度复位。
|
||||
|
||||
返回值:
|
||||
int: 成功返回1,失败返回0
|
||||
"""
|
||||
del order_id
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/order/reset-order-status',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": order_id,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return 0
|
||||
|
||||
@@ -8,7 +8,7 @@ returned. Outside of an active session the wrapped method delegates to the
|
||||
original (unwrapped) implementation, leaving non-debug behavior intact.
|
||||
|
||||
The session writes a Markdown file under ``out_dir`` mirroring the format of
|
||||
``temp_benyao/peptide/_logs/2026-04-30_160316_day3_samplefile_only_raw_calls.md``
|
||||
``bioyond_debug_records/2026-04-30_160316_day3_samplefile_only_raw_calls.md``
|
||||
minus the "Raw Payload Argument" section.
|
||||
|
||||
This module has no dependency on ``BioyondV1RPC`` itself; the only contract is
|
||||
|
||||
@@ -56,13 +56,17 @@ class ConnectionMonitor:
|
||||
def _monitor_loop(self):
|
||||
while self._running:
|
||||
try:
|
||||
# 使用 lightweight API 检查连接
|
||||
# query_matial_type_list 是比较快的查询
|
||||
start_time = time.time()
|
||||
result = self.workstation.hardware_interface.material_type_list()
|
||||
# 使用轻量级调度状态接口检查连接,避免启动时打印完整物料类型列表。
|
||||
result = self.workstation.hardware_interface.scheduler_status()
|
||||
|
||||
status = "online" if result else "offline"
|
||||
msg = "Connection established" if status == "online" else "Failed to get material type list"
|
||||
if status == "online":
|
||||
msg = (
|
||||
f"Scheduler status={result.get('status')}, "
|
||||
f"hasTask={result.get('hasTask')}"
|
||||
)
|
||||
else:
|
||||
msg = "Failed to get scheduler status"
|
||||
|
||||
if status != self._last_status:
|
||||
logger.info(f"Bioyond连接状态变更: {self._last_status} -> {status}")
|
||||
@@ -730,7 +734,7 @@ class BioyondWorkstation(WorkstationBase):
|
||||
"""解析 ``debug_log_dir`` 为绝对路径。"""
|
||||
configured = (getattr(self, "bioyond_config", {}) or {}).get("debug_log_dir")
|
||||
default_dir = getattr(self, "_DEBUG_LOG_DEFAULT_DIR", None)
|
||||
candidate = configured or default_dir or "temp_benyao/_logs/bioyond_debug"
|
||||
candidate = configured or default_dir or "bioyond_debug_records"
|
||||
path = Path(candidate)
|
||||
if not path.is_absolute():
|
||||
repo_root = Path(__file__).resolve().parents[4]
|
||||
|
||||
@@ -1 +1,9 @@
|
||||
from . import sirna_materials # noqa: F401 ensure @resource classes are importable for PLR deserialize
|
||||
try:
|
||||
from . import peptide_materials # noqa: F401 ensure @resource classes are importable for PLR deserialize
|
||||
except Exception: # pragma: no cover - 允许轻量环境导入非资源辅助函数
|
||||
peptide_materials = None # type: ignore[assignment]
|
||||
|
||||
try:
|
||||
from . import sirna_materials # noqa: F401 ensure @resource classes are importable for PLR deserialize
|
||||
except Exception: # pragma: no cover - 允许轻量环境导入非资源辅助函数
|
||||
sirna_materials = None # type: ignore[assignment]
|
||||
|
||||
@@ -25,6 +25,7 @@ def bioyond_warehouse_numeric_stack(
|
||||
columns: int = 17,
|
||||
bioyond_axis: str = "xy_row_col",
|
||||
bioyond_key_axis: str = "row_col",
|
||||
frontend_y_flip: bool = False,
|
||||
) -> WareHouse:
|
||||
"""创建 Bioyond 数字库位堆栈,库位名使用服务端返回的 行-列 格式。
|
||||
|
||||
@@ -46,17 +47,22 @@ def bioyond_warehouse_numeric_stack(
|
||||
item_dx = 147.0
|
||||
item_dy = 106.0
|
||||
item_dz = 130.0
|
||||
locations = [
|
||||
Coordinate(dx + col * item_dx, dy + row * item_dy, dz)
|
||||
for row in range(num_items_y)
|
||||
for col in range(num_items_x)
|
||||
]
|
||||
resource_size_x = 127.0
|
||||
resource_size_y = 86.0
|
||||
resource_size_z = 25.0
|
||||
size_y = dy + item_dy * num_items_y
|
||||
locations = []
|
||||
for row in range(num_items_y):
|
||||
display_y = dy + row * item_dy
|
||||
y = size_y - display_y - resource_size_y if frontend_y_flip else display_y
|
||||
for col in range(num_items_x):
|
||||
locations.append(Coordinate(dx + col * item_dx, y, dz))
|
||||
holders = create_homogeneous_resources(
|
||||
klass=ResourceHolder,
|
||||
locations=locations,
|
||||
resource_size_x=127.0,
|
||||
resource_size_y=86.0,
|
||||
resource_size_z=25.0,
|
||||
resource_size_x=resource_size_x,
|
||||
resource_size_y=resource_size_y,
|
||||
resource_size_z=resource_size_z,
|
||||
name_prefix=name,
|
||||
)
|
||||
if bioyond_key_axis == "row_col":
|
||||
@@ -76,7 +82,7 @@ def bioyond_warehouse_numeric_stack(
|
||||
warehouse = BioyondWareHouse(
|
||||
name=name,
|
||||
size_x=dx + item_dx * num_items_x,
|
||||
size_y=dy + item_dy * num_items_y,
|
||||
size_y=size_y,
|
||||
size_z=dz + item_dz * num_items_z,
|
||||
num_items_x=num_items_x,
|
||||
num_items_y=num_items_y,
|
||||
@@ -97,6 +103,7 @@ def bioyond_warehouse_live_grid(
|
||||
slot_keys: list[str] | None = None,
|
||||
bioyond_axis: str = "xy_col_row",
|
||||
bioyond_key_axis: str = "row_col",
|
||||
frontend_y_flip: bool = False,
|
||||
) -> WareHouse:
|
||||
"""创建 Bioyond 实测库位网格,按服务端 code 保存位点标签。
|
||||
|
||||
@@ -112,17 +119,22 @@ def bioyond_warehouse_live_grid(
|
||||
item_dx = 147.0
|
||||
item_dy = 106.0
|
||||
item_dz = 130.0
|
||||
locations = [
|
||||
Coordinate(dx + col * item_dx, dy + row * item_dy, dz)
|
||||
for row in range(num_items_y)
|
||||
for col in range(num_items_x)
|
||||
]
|
||||
resource_size_x = 127.0
|
||||
resource_size_y = 86.0
|
||||
resource_size_z = 25.0
|
||||
size_y = dy + item_dy * num_items_y
|
||||
locations = []
|
||||
for row in range(num_items_y):
|
||||
display_y = dy + row * item_dy
|
||||
y = size_y - display_y - resource_size_y if frontend_y_flip else display_y
|
||||
for col in range(num_items_x):
|
||||
locations.append(Coordinate(dx + col * item_dx, y, dz))
|
||||
holders = create_homogeneous_resources(
|
||||
klass=ResourceHolder,
|
||||
locations=locations,
|
||||
resource_size_x=127.0,
|
||||
resource_size_y=86.0,
|
||||
resource_size_z=25.0,
|
||||
resource_size_x=resource_size_x,
|
||||
resource_size_y=resource_size_y,
|
||||
resource_size_z=resource_size_z,
|
||||
name_prefix=name,
|
||||
)
|
||||
keys = slot_keys or [str(index + 1) for index in range(num_items_x * num_items_y)]
|
||||
@@ -139,7 +151,7 @@ def bioyond_warehouse_live_grid(
|
||||
return BioyondWareHouse(
|
||||
name=name,
|
||||
size_x=dx + item_dx * num_items_x,
|
||||
size_y=dy + item_dy * num_items_y,
|
||||
size_y=size_y,
|
||||
size_z=dz + item_dz * num_items_z,
|
||||
num_items_x=num_items_x,
|
||||
num_items_y=num_items_y,
|
||||
|
||||
@@ -18,3 +18,7 @@ def register():
|
||||
from unilabos.devices.liquid_handling.rviz_backend import UniLiquidHandlerRvizBackend
|
||||
from unilabos.devices.liquid_handling.laiyu.backend.laiyu_v_backend import UniLiquidHandlerLaiyuBackend
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from unilabos.resources.bioyond.decks import BIOYOND_SirnaStation_Deck
|
||||
# noinspection PyUnresolvedReferences
|
||||
from unilabos.resources.bioyond.decks import BIOYOND_PeptideStation_Deck
|
||||
|
||||
Reference in New Issue
Block a user